ddd-ts-kit 0.14.0 → 0.16.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 (500) hide show
  1. package/README.md +31 -2
  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/explain/domain-docs.d.ts +3 -0
  6. package/dist/check/explain/domain-docs.d.ts.map +1 -0
  7. package/dist/check/explain/domain-docs.js +30 -0
  8. package/dist/check/explain/domain-docs.js.map +1 -0
  9. package/dist/check/explain/rule-docs.d.ts.map +1 -1
  10. package/dist/check/explain/rule-docs.js +2 -0
  11. package/dist/check/explain/rule-docs.js.map +1 -1
  12. package/dist/check/explain/tenancy-docs.d.ts.map +1 -1
  13. package/dist/check/explain/tenancy-docs.js +3 -1
  14. package/dist/check/explain/tenancy-docs.js.map +1 -1
  15. package/dist/check/fixtures/fixtures.d.ts.map +1 -1
  16. package/dist/check/fixtures/fixtures.js +4 -0
  17. package/dist/check/fixtures/fixtures.js.map +1 -1
  18. package/dist/check/fixtures/model-function-fixtures.d.ts +4 -0
  19. package/dist/check/fixtures/model-function-fixtures.d.ts.map +1 -0
  20. package/dist/check/fixtures/model-function-fixtures.js +52 -0
  21. package/dist/check/fixtures/model-function-fixtures.js.map +1 -0
  22. package/dist/check/fixtures/tenancy-fixtures.d.ts.map +1 -1
  23. package/dist/check/fixtures/tenancy-fixtures.js +28 -0
  24. package/dist/check/fixtures/tenancy-fixtures.js.map +1 -1
  25. package/dist/check/fixtures/value-object-file-fixtures.d.ts +4 -0
  26. package/dist/check/fixtures/value-object-file-fixtures.d.ts.map +1 -0
  27. package/dist/check/fixtures/value-object-file-fixtures.js +54 -0
  28. package/dist/check/fixtures/value-object-file-fixtures.js.map +1 -0
  29. package/dist/check/fixtures/value-object-files.d.ts +34 -0
  30. package/dist/check/fixtures/value-object-files.d.ts.map +1 -0
  31. package/dist/check/fixtures/value-object-files.js +46 -0
  32. package/dist/check/fixtures/value-object-files.js.map +1 -0
  33. package/dist/check/rules/domain-value-object-class.d.ts +3 -0
  34. package/dist/check/rules/domain-value-object-class.d.ts.map +1 -0
  35. package/dist/check/rules/domain-value-object-class.js +21 -0
  36. package/dist/check/rules/domain-value-object-class.js.map +1 -0
  37. package/dist/check/structure/class-body-brace.d.ts +6 -0
  38. package/dist/check/structure/class-body-brace.d.ts.map +1 -0
  39. package/dist/check/structure/class-body-brace.js +22 -0
  40. package/dist/check/structure/class-body-brace.js.map +1 -0
  41. package/dist/check/structure/read-structure.d.ts.map +1 -1
  42. package/dist/check/structure/read-structure.js +3 -1
  43. package/dist/check/structure/read-structure.js.map +1 -1
  44. package/dist/check/tenancy/knex-unscoped.d.ts +6 -1
  45. package/dist/check/tenancy/knex-unscoped.d.ts.map +1 -1
  46. package/dist/check/tenancy/knex-unscoped.js +19 -6
  47. package/dist/check/tenancy/knex-unscoped.js.map +1 -1
  48. package/dist/check/tenancy/raw-reaches-rows.d.ts +8 -0
  49. package/dist/check/tenancy/raw-reaches-rows.d.ts.map +1 -0
  50. package/dist/check/tenancy/raw-reaches-rows.js +91 -0
  51. package/dist/check/tenancy/raw-reaches-rows.js.map +1 -0
  52. package/dist/check/tenancy/raw-sql-text.d.ts +9 -0
  53. package/dist/check/tenancy/raw-sql-text.d.ts.map +1 -0
  54. package/dist/check/tenancy/raw-sql-text.js +29 -0
  55. package/dist/check/tenancy/raw-sql-text.js.map +1 -0
  56. package/dist/check/values/answered-literals.d.ts +8 -0
  57. package/dist/check/values/answered-literals.d.ts.map +1 -0
  58. package/dist/check/values/answered-literals.js +20 -0
  59. package/dist/check/values/answered-literals.js.map +1 -0
  60. package/dist/check/values/arrow-body.d.ts +4 -0
  61. package/dist/check/values/arrow-body.d.ts.map +1 -0
  62. package/dist/check/values/arrow-body.js +52 -0
  63. package/dist/check/values/arrow-body.js.map +1 -0
  64. package/dist/check/values/entry-role.d.ts +10 -0
  65. package/dist/check/values/entry-role.d.ts.map +1 -0
  66. package/dist/check/values/entry-role.js +40 -0
  67. package/dist/check/values/entry-role.js.map +1 -0
  68. package/dist/check/values/exported-function-body.d.ts +9 -0
  69. package/dist/check/values/exported-function-body.d.ts.map +1 -0
  70. package/dist/check/values/exported-function-body.js +29 -0
  71. package/dist/check/values/exported-function-body.js.map +1 -0
  72. package/dist/check/values/exported-values.d.ts +9 -0
  73. package/dist/check/values/exported-values.d.ts.map +1 -0
  74. package/dist/check/values/exported-values.js +48 -0
  75. package/dist/check/values/exported-values.js.map +1 -0
  76. package/dist/check/values/extends-value-object.d.ts +9 -0
  77. package/dist/check/values/extends-value-object.d.ts.map +1 -0
  78. package/dist/check/values/extends-value-object.js +25 -0
  79. package/dist/check/values/extends-value-object.js.map +1 -0
  80. package/dist/check/values/function-body.d.ts +10 -0
  81. package/dist/check/values/function-body.d.ts.map +1 -0
  82. package/dist/check/values/function-body.js +2 -0
  83. package/dist/check/values/function-body.js.map +1 -0
  84. package/dist/check/values/generate-hint.d.ts +4 -0
  85. package/dist/check/values/generate-hint.d.ts.map +1 -0
  86. package/dist/check/values/generate-hint.js +8 -0
  87. package/dist/check/values/generate-hint.js.map +1 -0
  88. package/dist/check/values/initializer-of.d.ts +3 -0
  89. package/dist/check/values/initializer-of.d.ts.map +1 -0
  90. package/dist/check/values/initializer-of.js +15 -0
  91. package/dist/check/values/initializer-of.js.map +1 -0
  92. package/dist/check/values/is-model-file.d.ts +8 -0
  93. package/dist/check/values/is-model-file.d.ts.map +1 -0
  94. package/dist/check/values/is-model-file.js +15 -0
  95. package/dist/check/values/is-model-file.js.map +1 -0
  96. package/dist/check/values/is-value-object-file.d.ts +7 -0
  97. package/dist/check/values/is-value-object-file.d.ts.map +1 -0
  98. package/dist/check/values/is-value-object-file.js +14 -0
  99. package/dist/check/values/is-value-object-file.js.map +1 -0
  100. package/dist/check/values/javascript-globals.d.ts +3 -0
  101. package/dist/check/values/javascript-globals.d.ts.map +1 -0
  102. package/dist/check/values/javascript-globals.js +22 -0
  103. package/dist/check/values/javascript-globals.js.map +1 -0
  104. package/dist/check/values/literal-at.d.ts +3 -0
  105. package/dist/check/values/literal-at.d.ts.map +1 -0
  106. package/dist/check/values/literal-at.js +7 -0
  107. package/dist/check/values/literal-at.js.map +1 -0
  108. package/dist/check/values/local-declaration.d.ts +8 -0
  109. package/dist/check/values/local-declaration.d.ts.map +1 -0
  110. package/dist/check/values/local-declaration.js +31 -0
  111. package/dist/check/values/local-declaration.js.map +1 -0
  112. package/dist/check/values/nested-bodies.d.ts +8 -0
  113. package/dist/check/values/nested-bodies.d.ts.map +1 -0
  114. package/dist/check/values/nested-bodies.js +28 -0
  115. package/dist/check/values/nested-bodies.js.map +1 -0
  116. package/dist/check/values/plain-value-functions.d.ts +10 -0
  117. package/dist/check/values/plain-value-functions.d.ts.map +1 -0
  118. package/dist/check/values/plain-value-functions.js +33 -0
  119. package/dist/check/values/plain-value-functions.js.map +1 -0
  120. package/dist/check/values/returns-package-type.d.ts +9 -0
  121. package/dist/check/values/returns-package-type.d.ts.map +1 -0
  122. package/dist/check/values/returns-package-type.js +12 -0
  123. package/dist/check/values/returns-package-type.js.map +1 -0
  124. package/dist/check/values/stands-for-a-value.d.ts +9 -0
  125. package/dist/check/values/stands-for-a-value.d.ts.map +1 -0
  126. package/dist/check/values/stands-for-a-value.js +28 -0
  127. package/dist/check/values/stands-for-a-value.js.map +1 -0
  128. package/dist/check/values/value-object-file-findings.d.ts +5 -0
  129. package/dist/check/values/value-object-file-findings.d.ts.map +1 -0
  130. package/dist/check/values/value-object-file-findings.js +96 -0
  131. package/dist/check/values/value-object-file-findings.js.map +1 -0
  132. package/dist/commands/generate.js +2 -0
  133. package/dist/commands/generate.js.map +1 -1
  134. package/dist/commands/init.d.ts.map +1 -1
  135. package/dist/commands/init.js +12 -1
  136. package/dist/commands/init.js.map +1 -1
  137. package/dist/config/index.d.ts +1 -1
  138. package/dist/config/index.d.ts.map +1 -1
  139. package/dist/config/index.js +1 -1
  140. package/dist/config/index.js.map +1 -1
  141. package/dist/config/parse-stack.d.ts.map +1 -1
  142. package/dist/config/parse-stack.js +2 -1
  143. package/dist/config/parse-stack.js.map +1 -1
  144. package/dist/config/stack.d.ts +8 -0
  145. package/dist/config/stack.d.ts.map +1 -1
  146. package/dist/config/stack.js +1 -0
  147. package/dist/config/stack.js.map +1 -1
  148. package/dist/declared/injection/laid-out-calls.d.ts +7 -0
  149. package/dist/declared/injection/laid-out-calls.d.ts.map +1 -0
  150. package/dist/declared/injection/laid-out-calls.js +14 -0
  151. package/dist/declared/injection/laid-out-calls.js.map +1 -0
  152. package/dist/declared/render-barrel.d.ts.map +1 -1
  153. package/dist/declared/render-barrel.js +2 -1
  154. package/dist/declared/render-barrel.js.map +1 -1
  155. package/dist/declared/render-dependencies-file.d.ts.map +1 -1
  156. package/dist/declared/render-dependencies-file.js +2 -1
  157. package/dist/declared/render-dependencies-file.js.map +1 -1
  158. package/dist/generate/kind.d.ts +2 -0
  159. package/dist/generate/kind.d.ts.map +1 -1
  160. package/dist/generate/kinds/context-kind.d.ts.map +1 -1
  161. package/dist/generate/kinds/context-kind.js +13 -6
  162. package/dist/generate/kinds/context-kind.js.map +1 -1
  163. package/dist/generate/kinds/next-adr-number.d.ts +4 -0
  164. package/dist/generate/kinds/next-adr-number.d.ts.map +1 -0
  165. package/dist/generate/kinds/next-adr-number.js +7 -0
  166. package/dist/generate/kinds/next-adr-number.js.map +1 -0
  167. package/dist/generate/kinds/write-adr.js +3 -6
  168. package/dist/generate/kinds/write-adr.js.map +1 -1
  169. package/dist/help/usage.d.ts +1 -1
  170. package/dist/help/usage.d.ts.map +1 -1
  171. package/dist/help/usage.js +2 -2
  172. package/dist/init/manifest/versions.js +24 -24
  173. package/dist/init/run-init.d.ts +2 -0
  174. package/dist/init/run-init.d.ts.map +1 -1
  175. package/dist/init/run-init.js +15 -1
  176. package/dist/init/run-init.js.map +1 -1
  177. package/dist/init/stack/resolve-stack.d.ts.map +1 -1
  178. package/dist/init/stack/resolve-stack.js +3 -1
  179. package/dist/init/stack/resolve-stack.js.map +1 -1
  180. package/dist/init/stack/stack-options.d.ts +2 -0
  181. package/dist/init/stack/stack-options.d.ts.map +1 -1
  182. package/dist/presets/apply-preset.d.ts +15 -0
  183. package/dist/presets/apply-preset.d.ts.map +1 -0
  184. package/dist/presets/apply-preset.js +94 -0
  185. package/dist/presets/apply-preset.js.map +1 -0
  186. package/dist/presets/grant-to-signed-in.d.ts +9 -0
  187. package/dist/presets/grant-to-signed-in.d.ts.map +1 -0
  188. package/dist/presets/grant-to-signed-in.js +61 -0
  189. package/dist/presets/grant-to-signed-in.js.map +1 -0
  190. package/dist/presets/identity-preset.d.ts +9 -0
  191. package/dist/presets/identity-preset.d.ts.map +1 -0
  192. package/dist/presets/identity-preset.js +44 -0
  193. package/dist/presets/identity-preset.js.map +1 -0
  194. package/dist/presets/identity-refusal.d.ts +5 -0
  195. package/dist/presets/identity-refusal.d.ts.map +1 -0
  196. package/dist/presets/identity-refusal.js +29 -0
  197. package/dist/presets/identity-refusal.js.map +1 -0
  198. package/dist/presets/identity-terms.d.ts +4 -0
  199. package/dist/presets/identity-terms.d.ts.map +1 -0
  200. package/dist/presets/identity-terms.js +57 -0
  201. package/dist/presets/identity-terms.js.map +1 -0
  202. package/dist/presets/no-flags.d.ts +4 -0
  203. package/dist/presets/no-flags.d.ts.map +1 -0
  204. package/dist/presets/no-flags.js +18 -0
  205. package/dist/presets/no-flags.js.map +1 -0
  206. package/dist/presets/organisations-as-tenants.d.ts +9 -0
  207. package/dist/presets/organisations-as-tenants.d.ts.map +1 -0
  208. package/dist/presets/organisations-as-tenants.js +43 -0
  209. package/dist/presets/organisations-as-tenants.js.map +1 -0
  210. package/dist/presets/preset-decision.d.ts +8 -0
  211. package/dist/presets/preset-decision.d.ts.map +1 -0
  212. package/dist/presets/preset-decision.js +19 -0
  213. package/dist/presets/preset-decision.js.map +1 -0
  214. package/dist/presets/preset-file.d.ts +13 -0
  215. package/dist/presets/preset-file.d.ts.map +1 -0
  216. package/dist/presets/preset-file.js +2 -0
  217. package/dist/presets/preset-file.js.map +1 -0
  218. package/dist/presets/preset-files.d.ts +9 -0
  219. package/dist/presets/preset-files.d.ts.map +1 -0
  220. package/dist/presets/preset-files.js +34 -0
  221. package/dist/presets/preset-files.js.map +1 -0
  222. package/dist/presets/preset-named.d.ts +3 -0
  223. package/dist/presets/preset-named.d.ts.map +1 -0
  224. package/dist/presets/preset-named.js +12 -0
  225. package/dist/presets/preset-named.js.map +1 -0
  226. package/dist/presets/preset-refusal.d.ts +6 -0
  227. package/dist/presets/preset-refusal.d.ts.map +1 -0
  228. package/dist/presets/preset-refusal.js +2 -0
  229. package/dist/presets/preset-refusal.js.map +1 -0
  230. package/dist/presets/preset-step.d.ts +8 -0
  231. package/dist/presets/preset-step.d.ts.map +1 -0
  232. package/dist/presets/preset-step.js +2 -0
  233. package/dist/presets/preset-step.js.map +1 -0
  234. package/dist/presets/preset.d.ts +36 -0
  235. package/dist/presets/preset.d.ts.map +1 -0
  236. package/dist/presets/preset.js +2 -0
  237. package/dist/presets/preset.js.map +1 -0
  238. package/dist/presets/with-authorizer-import.d.ts +6 -0
  239. package/dist/presets/with-authorizer-import.d.ts.map +1 -0
  240. package/dist/presets/with-authorizer-import.js +27 -0
  241. package/dist/presets/with-authorizer-import.js.map +1 -0
  242. package/dist/stack/stack-vars.js +2 -1
  243. package/dist/stack/stack-vars.js.map +1 -1
  244. package/dist/version.d.ts +1 -1
  245. package/dist/version.js +1 -1
  246. package/dist/workspace/workspace.d.ts +2 -0
  247. package/dist/workspace/workspace.d.ts.map +1 -1
  248. package/dist/workspace/workspace.js +4 -0
  249. package/dist/workspace/workspace.js.map +1 -1
  250. package/package.json +2 -2
  251. package/templates/init/rules/domain.md.tpl +1 -1
  252. package/templates/pipeline/package-scripts.json +1 -1
  253. package/templates/presets/identity/context/application/authorization/__context__.permissions.ts.tpl +23 -0
  254. package/templates/presets/identity/context/application/authorization/change-member-roles.policy.ts.tpl +17 -0
  255. package/templates/presets/identity/context/application/authorization/grant-delegations.policy.ts.tpl +17 -0
  256. package/templates/presets/identity/context/application/authorization/invite-members.policy.ts.tpl +17 -0
  257. package/templates/presets/identity/context/application/authorization/read-members.policy.ts.tpl +17 -0
  258. package/templates/presets/identity/context/application/authorization/remove-members.policy.ts.tpl +17 -0
  259. package/templates/presets/identity/context/application/authorization/revoke-delegations.policy.ts.tpl +17 -0
  260. package/templates/presets/identity/context/application/authorization/revoke-invitations.policy.ts.tpl +17 -0
  261. package/templates/presets/identity/context/application/commands/accept-invitation/accept-invitation.command.ts.tpl +14 -0
  262. package/templates/presets/identity/context/application/commands/accept-invitation/accept-invitation.handler.ts.tpl +56 -0
  263. package/templates/presets/identity/context/application/commands/change-member-role/change-member-role.command.ts.tpl +17 -0
  264. package/templates/presets/identity/context/application/commands/change-member-role/change-member-role.handler.ts.tpl +82 -0
  265. package/templates/presets/identity/context/application/commands/deactivate-account/deactivate-account.command.ts.tpl +7 -0
  266. package/templates/presets/identity/context/application/commands/deactivate-account/deactivate-account.handler.ts.tpl +69 -0
  267. package/templates/presets/identity/context/application/commands/decline-invitation/decline-invitation.command.ts.tpl +14 -0
  268. package/templates/presets/identity/context/application/commands/decline-invitation/decline-invitation.handler.ts.tpl +36 -0
  269. package/templates/presets/identity/context/application/commands/grant-delegation/grant-delegation.command.ts.tpl +21 -0
  270. package/templates/presets/identity/context/application/commands/grant-delegation/grant-delegation.handler.ts.tpl +43 -0
  271. package/templates/presets/identity/context/application/commands/invite-member/invite-member.command.ts.tpl +16 -0
  272. package/templates/presets/identity/context/application/commands/invite-member/invite-member.handler.ts.tpl +53 -0
  273. package/templates/presets/identity/context/application/commands/open-organisation/open-organisation.command.ts.tpl +14 -0
  274. package/templates/presets/identity/context/application/commands/open-organisation/open-organisation.handler.ts.tpl +50 -0
  275. package/templates/presets/identity/context/application/commands/provision-account/provision-account.command.ts.tpl +14 -0
  276. package/templates/presets/identity/context/application/commands/provision-account/provision-account.handler.ts.tpl +41 -0
  277. package/templates/presets/identity/context/application/commands/remove-member/remove-member.command.ts.tpl +15 -0
  278. package/templates/presets/identity/context/application/commands/remove-member/remove-member.handler.ts.tpl +79 -0
  279. package/templates/presets/identity/context/application/commands/revoke-delegation/revoke-delegation.command.ts.tpl +15 -0
  280. package/templates/presets/identity/context/application/commands/revoke-delegation/revoke-delegation.handler.ts.tpl +39 -0
  281. package/templates/presets/identity/context/application/commands/revoke-invitation/revoke-invitation.command.ts.tpl +15 -0
  282. package/templates/presets/identity/context/application/commands/revoke-invitation/revoke-invitation.handler.ts.tpl +42 -0
  283. package/templates/presets/identity/context/application/integration/handlers/account-deactivated.integration-handler.ts.tpl +19 -0
  284. package/templates/presets/identity/context/application/integration/handlers/member-joined.integration-handler.ts.tpl +33 -0
  285. package/templates/presets/identity/context/application/integration/handlers/member-removed.integration-handler.ts.tpl +30 -0
  286. package/templates/presets/identity/context/application/integration/handlers/organisation-opened.integration-handler.ts.tpl +19 -0
  287. package/templates/presets/identity/context/application/integration/publishers/account-deactivated.publisher.ts.tpl +24 -0
  288. package/templates/presets/identity/context/application/integration/publishers/delegation-granted.publisher.ts.tpl +36 -0
  289. package/templates/presets/identity/context/application/integration/publishers/delegation-revoked.publisher.ts.tpl +33 -0
  290. package/templates/presets/identity/context/application/integration/publishers/member-joined.publisher.ts.tpl +33 -0
  291. package/templates/presets/identity/context/application/integration/publishers/member-removed.publisher.ts.tpl +33 -0
  292. package/templates/presets/identity/context/application/integration/publishers/member-role-changed.publisher.ts.tpl +35 -0
  293. package/templates/presets/identity/context/application/integration/publishers/organisation-opened.publisher.ts.tpl +32 -0
  294. package/templates/presets/identity/context/application/ports/identity-provider-admin.port.ts.tpl +17 -0
  295. package/templates/presets/identity/context/application/ports/identity-provider-connection.port.ts.tpl +14 -0
  296. package/templates/presets/identity/context/application/ports/invitation-tokens.port.ts.tpl +7 -0
  297. package/templates/presets/identity/context/application/ports/search-members-reader.port.ts.tpl +10 -0
  298. package/templates/presets/identity/context/application/queries/search-members/search-members.handler.ts.tpl +33 -0
  299. package/templates/presets/identity/context/application/queries/search-members/search-members.query.ts.tpl +19 -0
  300. package/templates/presets/identity/context/application/queries/search-members/search-members.result.ts.tpl +17 -0
  301. package/templates/presets/identity/context/domain/aggregates/account.aggregate.ts.tpl +92 -0
  302. package/templates/presets/identity/context/domain/aggregates/delegation.aggregate.ts.tpl +117 -0
  303. package/templates/presets/identity/context/domain/aggregates/invitation.aggregate.ts.tpl +128 -0
  304. package/templates/presets/identity/context/domain/aggregates/member.aggregate.ts.tpl +149 -0
  305. package/templates/presets/identity/context/domain/aggregates/organisation.aggregate.ts.tpl +52 -0
  306. package/templates/presets/identity/context/domain/errors/account.error.ts.tpl +40 -0
  307. package/templates/presets/identity/context/domain/errors/delegation.error.ts.tpl +45 -0
  308. package/templates/presets/identity/context/domain/errors/invitation.error.ts.tpl +30 -0
  309. package/templates/presets/identity/context/domain/errors/member.error.ts.tpl +50 -0
  310. package/templates/presets/identity/context/domain/errors/organisation.error.ts.tpl +32 -0
  311. package/templates/presets/identity/context/domain/events/account-deactivated.event.ts.tpl +13 -0
  312. package/templates/presets/identity/context/domain/events/account-provisioned.event.ts.tpl +17 -0
  313. package/templates/presets/identity/context/domain/events/delegation-granted.event.ts.tpl +22 -0
  314. package/templates/presets/identity/context/domain/events/delegation-revoked.event.ts.tpl +18 -0
  315. package/templates/presets/identity/context/domain/events/invitation-accepted.event.ts.tpl +17 -0
  316. package/templates/presets/identity/context/domain/events/invitation-declined.event.ts.tpl +16 -0
  317. package/templates/presets/identity/context/domain/events/invitation-issued.event.ts.tpl +22 -0
  318. package/templates/presets/identity/context/domain/events/invitation-revoked.event.ts.tpl +16 -0
  319. package/templates/presets/identity/context/domain/events/member-joined.event.ts.tpl +19 -0
  320. package/templates/presets/identity/context/domain/events/member-removed.event.ts.tpl +19 -0
  321. package/templates/presets/identity/context/domain/events/member-role-changed.event.ts.tpl +22 -0
  322. package/templates/presets/identity/context/domain/events/organisation-opened.event.ts.tpl +17 -0
  323. package/templates/presets/identity/context/domain/repositories/account.repository.ts.tpl +5 -0
  324. package/templates/presets/identity/context/domain/repositories/delegation.repository.ts.tpl +10 -0
  325. package/templates/presets/identity/context/domain/repositories/invitation.repository.ts.tpl +7 -0
  326. package/templates/presets/identity/context/domain/repositories/member.repository.ts.tpl +13 -0
  327. package/templates/presets/identity/context/domain/repositories/organisation.repository.ts.tpl +5 -0
  328. package/templates/presets/identity/context/domain/services/role-rights.service.ts.tpl +59 -0
  329. package/templates/presets/identity/context/domain/value-objects/delegation-period.vo.ts.tpl +42 -0
  330. package/templates/presets/identity/context/domain/value-objects/display-name.vo.ts.tpl +29 -0
  331. package/templates/presets/identity/context/domain/value-objects/email-address.vo.ts.tpl +31 -0
  332. package/templates/presets/identity/context/domain/value-objects/organisation-name.vo.ts.tpl +29 -0
  333. package/templates/presets/identity/context/domain/value-objects/organisation-rights.vo.ts.tpl +43 -0
  334. package/templates/presets/identity/context/domain/value-objects/organisation-slug.vo.ts.tpl +30 -0
  335. package/templates/presets/identity/context/domain/vocabularies/account-status.vocabulary.ts.tpl +7 -0
  336. package/templates/presets/identity/context/domain/vocabularies/invitation-status.vocabulary.ts.tpl +9 -0
  337. package/templates/presets/identity/context/domain/vocabularies/member-right.vocabulary.ts.tpl +9 -0
  338. package/templates/presets/identity/context/domain/vocabularies/member-role.vocabulary.ts.tpl +8 -0
  339. package/templates/presets/identity/context/infrastructure/external/crypto-invitation-tokens.adapter.ts.tpl +20 -0
  340. package/templates/presets/identity/context/infrastructure/external/environment-identity-provider-connection.adapter.ts.tpl +37 -0
  341. package/templates/presets/identity/context/infrastructure/external/keycloak-identity-provider-admin.adapter.ts.tpl +149 -0
  342. package/templates/presets/identity/context/infrastructure/persistence/__context__.schema.drizzle.ts.tpl +102 -0
  343. package/templates/presets/identity/context/infrastructure/persistence/account-row.schema.knex.ts.tpl +15 -0
  344. package/templates/presets/identity/context/infrastructure/persistence/account.mapper.drizzle.ts.tpl +38 -0
  345. package/templates/presets/identity/context/infrastructure/persistence/account.mapper.knex.ts.tpl +33 -0
  346. package/templates/presets/identity/context/infrastructure/persistence/create-__context__-tables.schema.knex.ts.tpl +107 -0
  347. package/templates/presets/identity/context/infrastructure/persistence/delegation-row.schema.knex.ts.tpl +17 -0
  348. package/templates/presets/identity/context/infrastructure/persistence/delegation.mapper.drizzle.ts.tpl +50 -0
  349. package/templates/presets/identity/context/infrastructure/persistence/delegation.mapper.knex.ts.tpl +45 -0
  350. package/templates/presets/identity/context/infrastructure/persistence/drizzle-account.repository.drizzle.ts.tpl +59 -0
  351. package/templates/presets/identity/context/infrastructure/persistence/drizzle-delegation.repository.drizzle.ts.tpl +77 -0
  352. package/templates/presets/identity/context/infrastructure/persistence/drizzle-invitation.repository.drizzle.ts.tpl +65 -0
  353. package/templates/presets/identity/context/infrastructure/persistence/drizzle-member.repository.drizzle.ts.tpl +84 -0
  354. package/templates/presets/identity/context/infrastructure/persistence/drizzle-organisation.repository.drizzle.ts.tpl +59 -0
  355. package/templates/presets/identity/context/infrastructure/persistence/drizzle-search-members-reader.repository.drizzle.ts.tpl +53 -0
  356. package/templates/presets/identity/context/infrastructure/persistence/invitation-row.schema.knex.ts.tpl +20 -0
  357. package/templates/presets/identity/context/infrastructure/persistence/invitation.mapper.drizzle.ts.tpl +45 -0
  358. package/templates/presets/identity/context/infrastructure/persistence/invitation.mapper.knex.ts.tpl +40 -0
  359. package/templates/presets/identity/context/infrastructure/persistence/knex-account.repository.knex.ts.tpl +61 -0
  360. package/templates/presets/identity/context/infrastructure/persistence/knex-delegation.repository.knex.ts.tpl +80 -0
  361. package/templates/presets/identity/context/infrastructure/persistence/knex-invitation.repository.knex.ts.tpl +67 -0
  362. package/templates/presets/identity/context/infrastructure/persistence/knex-member.repository.knex.ts.tpl +82 -0
  363. package/templates/presets/identity/context/infrastructure/persistence/knex-organisation.repository.knex.ts.tpl +61 -0
  364. package/templates/presets/identity/context/infrastructure/persistence/knex-search-members-reader.repository.knex.ts.tpl +77 -0
  365. package/templates/presets/identity/context/infrastructure/persistence/member-row.schema.knex.ts.tpl +15 -0
  366. package/templates/presets/identity/context/infrastructure/persistence/member.mapper.drizzle.ts.tpl +36 -0
  367. package/templates/presets/identity/context/infrastructure/persistence/member.mapper.knex.ts.tpl +31 -0
  368. package/templates/presets/identity/context/infrastructure/persistence/organisation-row.schema.knex.ts.tpl +12 -0
  369. package/templates/presets/identity/context/infrastructure/persistence/organisation.mapper.drizzle.ts.tpl +28 -0
  370. package/templates/presets/identity/context/infrastructure/persistence/organisation.mapper.knex.ts.tpl +26 -0
  371. package/templates/presets/identity/context/presentation/http/accounts/deactivate-account/deactivate-account.accepted.class-validator.per-route.ts.tpl +6 -0
  372. package/templates/presets/identity/context/presentation/http/accounts/deactivate-account/deactivate-account.controller.ts.tpl +71 -0
  373. package/templates/presets/identity/context/presentation/http/accounts/provision-account/provision-account.accepted.class-validator.per-route.ts.tpl +6 -0
  374. package/templates/presets/identity/context/presentation/http/accounts/provision-account/provision-account.body.class-validator.ts.tpl +9 -0
  375. package/templates/presets/identity/context/presentation/http/accounts/provision-account/provision-account.controller.ts.tpl +85 -0
  376. package/templates/presets/identity/context/presentation/http/accounts.routes.ts.tpl +5 -0
  377. package/templates/presets/identity/context/presentation/http/invitations/accept-invitation/accept-invitation.accepted.class-validator.per-route.ts.tpl +6 -0
  378. package/templates/presets/identity/context/presentation/http/invitations/accept-invitation/accept-invitation.body.class-validator.per-route.ts.tpl +8 -0
  379. package/templates/presets/identity/context/presentation/http/invitations/accept-invitation/accept-invitation.controller.ts.tpl +100 -0
  380. package/templates/presets/identity/context/presentation/http/invitations/decline-invitation/decline-invitation.accepted.class-validator.per-route.ts.tpl +6 -0
  381. package/templates/presets/identity/context/presentation/http/invitations/decline-invitation/decline-invitation.body.class-validator.per-route.ts.tpl +8 -0
  382. package/templates/presets/identity/context/presentation/http/invitations/decline-invitation/decline-invitation.controller.ts.tpl +98 -0
  383. package/templates/presets/identity/context/presentation/http/invitations.routes.ts.tpl +5 -0
  384. package/templates/presets/identity/context/presentation/http/organisations/change-member-role/change-member-role.accepted.class-validator.per-route.ts.tpl +6 -0
  385. package/templates/presets/identity/context/presentation/http/organisations/change-member-role/change-member-role.body.class-validator.ts.tpl +7 -0
  386. package/templates/presets/identity/context/presentation/http/organisations/change-member-role/change-member-role.controller.ts.tpl +83 -0
  387. package/templates/presets/identity/context/presentation/http/organisations/change-member-role/change-member-role.params.class-validator.ts.tpl +11 -0
  388. package/templates/presets/identity/context/presentation/http/organisations/grant-delegation/grant-delegation.accepted.class-validator.per-route.ts.tpl +6 -0
  389. package/templates/presets/identity/context/presentation/http/organisations/grant-delegation/grant-delegation.body.class-validator.ts.tpl +18 -0
  390. package/templates/presets/identity/context/presentation/http/organisations/grant-delegation/grant-delegation.controller.ts.tpl +103 -0
  391. package/templates/presets/identity/context/presentation/http/organisations/grant-delegation/grant-delegation.params.class-validator.ts.tpl +7 -0
  392. package/templates/presets/identity/context/presentation/http/organisations/invite-member/invite-member.accepted.class-validator.ts.tpl +10 -0
  393. package/templates/presets/identity/context/presentation/http/organisations/invite-member/invite-member.body.class-validator.ts.tpl +10 -0
  394. package/templates/presets/identity/context/presentation/http/organisations/invite-member/invite-member.controller.ts.tpl +65 -0
  395. package/templates/presets/identity/context/presentation/http/organisations/invite-member/invite-member.params.class-validator.ts.tpl +7 -0
  396. package/templates/presets/identity/context/presentation/http/organisations/open-organisation/open-organisation.accepted.class-validator.per-route.ts.tpl +6 -0
  397. package/templates/presets/identity/context/presentation/http/organisations/open-organisation/open-organisation.body.class-validator.ts.tpl +12 -0
  398. package/templates/presets/identity/context/presentation/http/organisations/open-organisation/open-organisation.controller.ts.tpl +79 -0
  399. package/templates/presets/identity/context/presentation/http/organisations/remove-member/remove-member.accepted.class-validator.per-route.ts.tpl +6 -0
  400. package/templates/presets/identity/context/presentation/http/organisations/remove-member/remove-member.controller.ts.tpl +73 -0
  401. package/templates/presets/identity/context/presentation/http/organisations/remove-member/remove-member.params.class-validator.ts.tpl +11 -0
  402. package/templates/presets/identity/context/presentation/http/organisations/revoke-delegation/revoke-delegation.accepted.class-validator.per-route.ts.tpl +6 -0
  403. package/templates/presets/identity/context/presentation/http/organisations/revoke-delegation/revoke-delegation.controller.ts.tpl +73 -0
  404. package/templates/presets/identity/context/presentation/http/organisations/revoke-delegation/revoke-delegation.params.class-validator.ts.tpl +11 -0
  405. package/templates/presets/identity/context/presentation/http/organisations/revoke-invitation/revoke-invitation.accepted.class-validator.per-route.ts.tpl +6 -0
  406. package/templates/presets/identity/context/presentation/http/organisations/revoke-invitation/revoke-invitation.controller.ts.tpl +73 -0
  407. package/templates/presets/identity/context/presentation/http/organisations/revoke-invitation/revoke-invitation.params.class-validator.ts.tpl +11 -0
  408. package/templates/presets/identity/context/presentation/http/organisations/search-members/search-members.controller.ts.tpl +86 -0
  409. package/templates/presets/identity/context/presentation/http/organisations/search-members/search-members.line.class-validator.ts.tpl +22 -0
  410. package/templates/presets/identity/context/presentation/http/organisations/search-members/search-members.params.class-validator.ts.tpl +7 -0
  411. package/templates/presets/identity/context/presentation/http/organisations.routes.ts.tpl +5 -0
  412. package/templates/presets/identity/context/presentation/http/shared/invitation-token.schema.class-validator.shared.ts.tpl +9 -0
  413. package/templates/presets/identity/context/presentation/http/shared/invitation-token.schema.zod.shared.ts.tpl +9 -0
  414. package/templates/presets/identity/context/published-language/account-deactivated.v1.integration-event.ts.tpl +33 -0
  415. package/templates/presets/identity/context/published-language/account-deactivated.v1.payload.class-validator.ts.tpl +7 -0
  416. package/templates/presets/identity/context/published-language/delegation-granted.v1.integration-event.ts.tpl +39 -0
  417. package/templates/presets/identity/context/published-language/delegation-granted.v1.payload.class-validator.ts.tpl +28 -0
  418. package/templates/presets/identity/context/published-language/delegation-revoked.v1.integration-event.ts.tpl +36 -0
  419. package/templates/presets/identity/context/published-language/delegation-revoked.v1.payload.class-validator.ts.tpl +16 -0
  420. package/templates/presets/identity/context/published-language/member-joined.v1.integration-event.ts.tpl +36 -0
  421. package/templates/presets/identity/context/published-language/member-joined.v1.payload.class-validator.ts.tpl +16 -0
  422. package/templates/presets/identity/context/published-language/member-removed.v1.integration-event.ts.tpl +36 -0
  423. package/templates/presets/identity/context/published-language/member-removed.v1.payload.class-validator.ts.tpl +18 -0
  424. package/templates/presets/identity/context/published-language/member-role-changed.v1.integration-event.ts.tpl +38 -0
  425. package/templates/presets/identity/context/published-language/member-role-changed.v1.payload.class-validator.ts.tpl +24 -0
  426. package/templates/presets/identity/context/published-language/organisation-opened.v1.integration-event.ts.tpl +35 -0
  427. package/templates/presets/identity/context/published-language/organisation-opened.v1.payload.class-validator.ts.tpl +13 -0
  428. package/templates/presets/identity/context/test/builders/account.builder.ts.tpl +59 -0
  429. package/templates/presets/identity/context/test/builders/delegation.builder.ts.tpl +67 -0
  430. package/templates/presets/identity/context/test/builders/invitation.builder.ts.tpl +60 -0
  431. package/templates/presets/identity/context/test/builders/member.builder.ts.tpl +59 -0
  432. package/templates/presets/identity/context/test/builders/organisation.builder.ts.tpl +44 -0
  433. package/templates/presets/identity/context/test/fakes/account.repository.fake.ts.tpl +10 -0
  434. package/templates/presets/identity/context/test/fakes/delegation.repository.fake.ts.tpl +26 -0
  435. package/templates/presets/identity/context/test/fakes/identity-provider-admin.fake.ts.tpl +41 -0
  436. package/templates/presets/identity/context/test/fakes/invitation-tokens.fake.ts.tpl +18 -0
  437. package/templates/presets/identity/context/test/fakes/invitation.repository.fake.ts.tpl +14 -0
  438. package/templates/presets/identity/context/test/fakes/member.repository.fake.ts.tpl +29 -0
  439. package/templates/presets/identity/context/test/fakes/organisation.repository.fake.ts.tpl +10 -0
  440. package/templates/presets/identity/context/test/fakes/search-members-reader.fake.ts.tpl +25 -0
  441. package/templates/presets/identity/context/test/integration/account.repository.contract.spec.ts.tpl +48 -0
  442. package/templates/presets/identity/context/test/integration/delegation.repository.contract.spec.ts.tpl +84 -0
  443. package/templates/presets/identity/context/test/integration/invitation.repository.contract.spec.ts.tpl +76 -0
  444. package/templates/presets/identity/context/test/integration/keycloak-identity-provider-admin.adapter.spec.ts.tpl +123 -0
  445. package/templates/presets/identity/context/test/integration/member.repository.contract.spec.ts.tpl +92 -0
  446. package/templates/presets/identity/context/test/integration/organisation.repository.contract.spec.ts.tpl +46 -0
  447. package/templates/presets/identity/context/test/integration/search-members-reader.spec.ts.tpl +86 -0
  448. package/templates/presets/identity/context/test/unit/accept-invitation.controller.spec.ts.tpl +57 -0
  449. package/templates/presets/identity/context/test/unit/accept-invitation.handler.spec.ts.tpl +69 -0
  450. package/templates/presets/identity/context/test/unit/account-deactivated.integration-handler.spec.ts.tpl +24 -0
  451. package/templates/presets/identity/context/test/unit/account-deactivated.publisher.spec.ts.tpl +25 -0
  452. package/templates/presets/identity/context/test/unit/account.aggregate.spec.ts.tpl +72 -0
  453. package/templates/presets/identity/context/test/unit/change-member-role.controller.spec.ts.tpl +57 -0
  454. package/templates/presets/identity/context/test/unit/change-member-role.handler.spec.ts.tpl +135 -0
  455. package/templates/presets/identity/context/test/unit/crypto-invitation-tokens.adapter.spec.ts.tpl +15 -0
  456. package/templates/presets/identity/context/test/unit/deactivate-account.controller.spec.ts.tpl +44 -0
  457. package/templates/presets/identity/context/test/unit/deactivate-account.handler.spec.ts.tpl +106 -0
  458. package/templates/presets/identity/context/test/unit/decline-invitation.controller.spec.ts.tpl +57 -0
  459. package/templates/presets/identity/context/test/unit/decline-invitation.handler.spec.ts.tpl +40 -0
  460. package/templates/presets/identity/context/test/unit/delegation-granted.publisher.spec.ts.tpl +36 -0
  461. package/templates/presets/identity/context/test/unit/delegation-period.vo.spec.ts.tpl +32 -0
  462. package/templates/presets/identity/context/test/unit/delegation-revoked.publisher.spec.ts.tpl +35 -0
  463. package/templates/presets/identity/context/test/unit/delegation.aggregate.spec.ts.tpl +111 -0
  464. package/templates/presets/identity/context/test/unit/display-name.vo.spec.ts.tpl +13 -0
  465. package/templates/presets/identity/context/test/unit/email-address.vo.spec.ts.tpl +24 -0
  466. package/templates/presets/identity/context/test/unit/environment-identity-provider-connection.adapter.spec.ts.tpl +41 -0
  467. package/templates/presets/identity/context/test/unit/grant-delegation.controller.spec.ts.tpl +57 -0
  468. package/templates/presets/identity/context/test/unit/grant-delegation.handler.spec.ts.tpl +66 -0
  469. package/templates/presets/identity/context/test/unit/identity-application.ts.tpl +79 -0
  470. package/templates/presets/identity/context/test/unit/invitation.aggregate.spec.ts.tpl +118 -0
  471. package/templates/presets/identity/context/test/unit/invite-member.controller.spec.ts.tpl +57 -0
  472. package/templates/presets/identity/context/test/unit/invite-member.handler.spec.ts.tpl +110 -0
  473. package/templates/presets/identity/context/test/unit/member-joined.integration-handler.spec.ts.tpl +38 -0
  474. package/templates/presets/identity/context/test/unit/member-joined.publisher.spec.ts.tpl +36 -0
  475. package/templates/presets/identity/context/test/unit/member-removed.integration-handler.spec.ts.tpl +45 -0
  476. package/templates/presets/identity/context/test/unit/member-removed.publisher.spec.ts.tpl +35 -0
  477. package/templates/presets/identity/context/test/unit/member-role-changed.publisher.spec.ts.tpl +36 -0
  478. package/templates/presets/identity/context/test/unit/member.aggregate.spec.ts.tpl +142 -0
  479. package/templates/presets/identity/context/test/unit/open-organisation.controller.spec.ts.tpl +57 -0
  480. package/templates/presets/identity/context/test/unit/open-organisation.handler.spec.ts.tpl +52 -0
  481. package/templates/presets/identity/context/test/unit/organisation-name.vo.spec.ts.tpl +13 -0
  482. package/templates/presets/identity/context/test/unit/organisation-opened.integration-handler.spec.ts.tpl +24 -0
  483. package/templates/presets/identity/context/test/unit/organisation-opened.publisher.spec.ts.tpl +35 -0
  484. package/templates/presets/identity/context/test/unit/organisation-rights.vo.spec.ts.tpl +23 -0
  485. package/templates/presets/identity/context/test/unit/organisation-slug.vo.spec.ts.tpl +17 -0
  486. package/templates/presets/identity/context/test/unit/organisation.aggregate.spec.ts.tpl +33 -0
  487. package/templates/presets/identity/context/test/unit/provision-account.controller.spec.ts.tpl +73 -0
  488. package/templates/presets/identity/context/test/unit/provision-account.handler.spec.ts.tpl +65 -0
  489. package/templates/presets/identity/context/test/unit/remove-member.controller.spec.ts.tpl +44 -0
  490. package/templates/presets/identity/context/test/unit/remove-member.handler.spec.ts.tpl +125 -0
  491. package/templates/presets/identity/context/test/unit/revoke-delegation.controller.spec.ts.tpl +44 -0
  492. package/templates/presets/identity/context/test/unit/revoke-delegation.handler.spec.ts.tpl +51 -0
  493. package/templates/presets/identity/context/test/unit/revoke-invitation.controller.spec.ts.tpl +44 -0
  494. package/templates/presets/identity/context/test/unit/revoke-invitation.handler.spec.ts.tpl +44 -0
  495. package/templates/presets/identity/context/test/unit/role-rights.service.spec.ts.tpl +54 -0
  496. package/templates/presets/identity/context/test/unit/search-members.controller.spec.ts.tpl +43 -0
  497. package/templates/presets/identity/context/test/unit/search-members.handler.spec.ts.tpl +38 -0
  498. package/templates/presets/identity/decision.md.tpl +37 -0
  499. package/templates/presets/identity/design.md.tpl +71 -0
  500. package/templates/presets/signed-in-grants.ts.tpl +12 -0
@@ -0,0 +1,125 @@
1
+ import { describe, expect, it } from '{{tests.module}}';
2
+ import { MemberRight } from '{{alias.context}}/domain/vocabularies/member-right.vocabulary{{alias.ext}}';
3
+ import { MemberRole } from '{{alias.context}}/domain/vocabularies/member-role.vocabulary{{alias.ext}}';
4
+ import { RemoveMemberCommand } from '{{alias.context}}/application/commands/remove-member/remove-member.command{{alias.ext}}';
5
+ import { aDelegation } from '{{alias.context}}/test/builders/delegation.builder{{alias.ext}}';
6
+ import { aMember } from '{{alias.context}}/test/builders/member.builder{{alias.ext}}';
7
+ import { anOrganisation } from '{{alias.context}}/test/builders/organisation.builder{{alias.ext}}';
8
+ import { arrangeIdentity } from '{{alias.context}}/test/unit/identity-application{{alias.ext}}';
9
+
10
+ const DAY = 86_400_000;
11
+
12
+ describe('Remove member', () => {
13
+ it('removes the member, ends every delegation they gave or received, and tells the provider and the other contexts', async () => {
14
+ const { application, organisations, members, delegations, identityProvider, as } = arrangeIdentity();
15
+ const now = application.clock.now();
16
+
17
+ await organisations.save(anOrganisation().identifiedBy('acme').build());
18
+ await members.save(aMember().of('acme', 'subject-ada').as(MemberRole.Admin).build());
19
+ await members.save(aMember().of('acme', 'subject-alan').build());
20
+
21
+ await delegations.save(
22
+ aDelegation()
23
+ .identifiedBy('delegation-1')
24
+ .to('subject-alan')
25
+ .lending(MemberRight.InviteMembers)
26
+ .between(now, new Date(now.getTime() + DAY))
27
+ .build(),
28
+ );
29
+
30
+ // Started once the organisation is arranged: what the provider is asked from now on follows the removal alone.
31
+ await application.start();
32
+
33
+ const before = application.eventBus.published.length;
34
+
35
+ await application.commandBus.execute(new RemoveMemberCommand(as('subject-ada'), 'acme', 'subject-alan'));
36
+
37
+ expect(await members.findOf('acme', 'subject-alan')).toBeUndefined();
38
+ expect((await delegations.findById('delegation-1'))?.isActiveAt(now)).toBe(false);
39
+ expect(identityProvider.asked).toEqual(['remove subject-alan from acme']);
40
+
41
+ expect(application.eventBus.published.slice(before).map((event) => event.type)).toEqual([
42
+ '{{context.kebab}}.member.removed',
43
+ '{{context.kebab}}.delegation.revoked',
44
+ ]);
45
+ });
46
+
47
+ it('refuses a person who is not a member, and keeps the last owner', async () => {
48
+ const { application, organisations, members, as } = arrangeIdentity();
49
+
50
+ await organisations.save(anOrganisation().identifiedBy('acme').build());
51
+ await members.save(aMember().of('acme', 'subject-ada').as(MemberRole.Owner).build());
52
+
53
+ await expect(
54
+ application.commandBus.execute(new RemoveMemberCommand(as('subject-ada'), 'acme', 'subject-nobody')),
55
+ ).rejects.toMatchObject({ code: 'MEMBER_NOT_FOUND' });
56
+
57
+ await expect(
58
+ application.commandBus.execute(new RemoveMemberCommand(as('subject-ada'), 'acme', 'subject-ada')),
59
+ ).rejects.toMatchObject({ code: 'LAST_OWNER' });
60
+ });
61
+
62
+ it('is refused to a member who does not manage the members', async () => {
63
+ const { application, organisations, members, as } = arrangeIdentity();
64
+
65
+ await organisations.save(anOrganisation().identifiedBy('acme').build());
66
+ await members.save(aMember().of('acme', 'subject-alan').build());
67
+ await members.save(aMember().of('acme', 'subject-grace').build());
68
+
69
+ await expect(
70
+ application.commandBus.execute(new RemoveMemberCommand(as('subject-alan'), 'acme', 'subject-grace')),
71
+ ).rejects.toMatchObject({ code: 'FORBIDDEN' });
72
+ });
73
+ });
74
+
75
+ describe('Remove member under a delegation', () => {
76
+ it('removes within the role of the delegate, never above it, and names the delegation used', async () => {
77
+ const { application, organisations, members, delegations, as } = arrangeIdentity();
78
+ const now = application.clock.now();
79
+
80
+ await organisations.save(anOrganisation().identifiedBy('acme').build());
81
+ await members.save(aMember().of('acme', 'subject-ada').as(MemberRole.Owner).build());
82
+ await members.save(aMember().of('acme', 'subject-alan').build());
83
+ await members.save(aMember().of('acme', 'subject-irene').build());
84
+ await members.save(aMember().of('acme', 'subject-linus').as(MemberRole.Admin).build());
85
+
86
+ await delegations.save(
87
+ aDelegation()
88
+ .identifiedBy('delegation-1')
89
+ .from('subject-ada')
90
+ .to('subject-alan')
91
+ .lending(MemberRight.ManageMembers)
92
+ .between(now, new Date(now.getTime() + DAY))
93
+ .build(),
94
+ );
95
+
96
+ await expect(
97
+ application.commandBus.execute(new RemoveMemberCommand(as('subject-alan'), 'acme', 'subject-linus')),
98
+ ).rejects.toMatchObject({ code: 'ABOVE_OWN_ROLE' });
99
+
100
+ await application.commandBus.execute(new RemoveMemberCommand(as('subject-alan'), 'acme', 'subject-irene'));
101
+
102
+ expect(application.eventBus.published.at(-1)).toMatchObject({
103
+ type: '{{context.kebab}}.member.removed',
104
+ payload: { accountId: 'subject-irene', delegationId: 'delegation-1' },
105
+ });
106
+ });
107
+ });
108
+
109
+ describe('Two owners who remove each other at once', () => {
110
+ it('cannot both commit: the organisation keeps an owner, and the second change is refused as a conflict', async () => {
111
+ const { application, organisations, members, as } = arrangeIdentity();
112
+
113
+ await organisations.save(anOrganisation().identifiedBy('acme').build());
114
+ await members.save(aMember().of('acme', 'subject-ada').as(MemberRole.Owner).build());
115
+ await members.save(aMember().of('acme', 'subject-grace').as(MemberRole.Owner).build());
116
+
117
+ const outcomes = await Promise.allSettled([
118
+ application.commandBus.execute(new RemoveMemberCommand(as('subject-ada'), 'acme', 'subject-grace')),
119
+ application.commandBus.execute(new RemoveMemberCommand(as('subject-grace'), 'acme', 'subject-ada')),
120
+ ]);
121
+
122
+ expect(outcomes.map((outcome) => outcome.status).sort()).toEqual(['fulfilled', 'rejected']);
123
+ expect(await members.countOwners('acme')).toBe(1);
124
+ });
125
+ });
@@ -0,0 +1,44 @@
1
+ import { describe, expect, it } from '{{tests.module}}';
2
+ import { controllerRoute, executeRoute } from '@ddd-ts-kit/http';
3
+ import { InMemoryCommandBus, type Actor } from '@ddd-ts-kit/kernel';
4
+ import { RevokeDelegationCommand } from '{{alias.context}}/application/commands/revoke-delegation/revoke-delegation.command{{alias.ext}}';
5
+ import { RevokeDelegationController } from '{{alias.context}}/presentation/http/organisations/revoke-delegation/revoke-delegation.controller{{alias.ext}}';
6
+
7
+ /** Who calls, as the authenticator of the platform makes them from the token. */
8
+ const caller: Actor = {
9
+ id: 'subject-ada',
10
+ type: 'user',
11
+ roles: [],
12
+ attributes: { email: 'ada@example.test', username: 'ada' },
13
+ };
14
+
15
+ describe('DELETE /organisations/:organisationId/delegations/:delegationId', () => {
16
+ it('answers 200 with what the use case answered, and sends it what the request says', async () => {
17
+ const { route, sent } = arrange();
18
+
19
+ const response = await executeRoute(
20
+ route,
21
+ { params: { organisationId: 'acme', delegationId: 'delegation-1' }, query: {}, headers: {}, body: {}, raw: undefined },
22
+ { validateResponses: true, resolvePrincipal: () => caller },
23
+ );
24
+
25
+ expect(route.path).toBe('/organisations/:organisationId/delegations/:delegationId');
26
+ expect([response.status, response.body]).toEqual([200, { id: 'delegation-1' }]);
27
+ expect(sent).toMatchObject([{ organisationId: 'acme', delegationId: 'delegation-1' }]);
28
+ });
29
+ });
30
+
31
+ /** The route, on a bus that keeps what it was sent and answers as the use case would. */
32
+ function arrange() {
33
+ const sent: RevokeDelegationCommand[] = [];
34
+
35
+ const commands = new InMemoryCommandBus().register(RevokeDelegationCommand, {
36
+ execute: async (command) => {
37
+ sent.push(command);
38
+
39
+ return 'delegation-1';
40
+ },
41
+ });
42
+
43
+ return { route: controllerRoute(new RevokeDelegationController(commands)), sent };
44
+ }
@@ -0,0 +1,51 @@
1
+ import { describe, expect, it } from '{{tests.module}}';
2
+ import { MemberRole } from '{{alias.context}}/domain/vocabularies/member-role.vocabulary{{alias.ext}}';
3
+ import { RevokeDelegationCommand } from '{{alias.context}}/application/commands/revoke-delegation/revoke-delegation.command{{alias.ext}}';
4
+ import { aDelegation } from '{{alias.context}}/test/builders/delegation.builder{{alias.ext}}';
5
+ import { aMember } from '{{alias.context}}/test/builders/member.builder{{alias.ext}}';
6
+ import { arrangeIdentity } from '{{alias.context}}/test/unit/identity-application{{alias.ext}}';
7
+
8
+ describe('Revoke delegation', () => {
9
+ it('ends a delegation of the organisation while it holds, and tells the other contexts', async () => {
10
+ const { application, members, delegations, as } = arrangeIdentity();
11
+ const now = application.clock.now();
12
+
13
+ await members.save(aMember().of('acme', 'subject-ada').as(MemberRole.Owner).build());
14
+
15
+ await delegations.save(
16
+ aDelegation()
17
+ .identifiedBy('delegation-1')
18
+ .between(now, new Date(now.getTime() + 86_400_000))
19
+ .build(),
20
+ );
21
+
22
+ const before = application.eventBus.published.length;
23
+
24
+ await application.commandBus.execute(new RevokeDelegationCommand(as('subject-ada'), 'acme', 'delegation-1'));
25
+
26
+ expect((await delegations.findById('delegation-1'))?.isActiveAt(now)).toBe(false);
27
+ expect(application.eventBus.published.slice(before)).toMatchObject([{ type: '{{context.kebab}}.delegation.revoked' }]);
28
+ });
29
+
30
+ it('finds no delegation of another organisation', async () => {
31
+ const { application, members, delegations, as } = arrangeIdentity();
32
+
33
+ await members.save(aMember().of('acme', 'subject-ada').as(MemberRole.Owner).build());
34
+ await delegations.save(aDelegation().identifiedBy('delegation-1').in('globex').build());
35
+
36
+ await expect(
37
+ application.commandBus.execute(new RevokeDelegationCommand(as('subject-ada'), 'acme', 'delegation-1')),
38
+ ).rejects.toMatchObject({ code: 'DELEGATION_NOT_FOUND' });
39
+ });
40
+
41
+ it('is refused to a member whose role does not delegate', async () => {
42
+ const { application, members, delegations, as } = arrangeIdentity();
43
+
44
+ await members.save(aMember().of('acme', 'subject-alan').build());
45
+ await delegations.save(aDelegation().identifiedBy('delegation-1').build());
46
+
47
+ await expect(
48
+ application.commandBus.execute(new RevokeDelegationCommand(as('subject-alan'), 'acme', 'delegation-1')),
49
+ ).rejects.toMatchObject({ code: 'FORBIDDEN' });
50
+ });
51
+ });
@@ -0,0 +1,44 @@
1
+ import { describe, expect, it } from '{{tests.module}}';
2
+ import { controllerRoute, executeRoute } from '@ddd-ts-kit/http';
3
+ import { InMemoryCommandBus, type Actor } from '@ddd-ts-kit/kernel';
4
+ import { RevokeInvitationCommand } from '{{alias.context}}/application/commands/revoke-invitation/revoke-invitation.command{{alias.ext}}';
5
+ import { RevokeInvitationController } from '{{alias.context}}/presentation/http/organisations/revoke-invitation/revoke-invitation.controller{{alias.ext}}';
6
+
7
+ /** Who calls, as the authenticator of the platform makes them from the token. */
8
+ const caller: Actor = {
9
+ id: 'subject-ada',
10
+ type: 'user',
11
+ roles: [],
12
+ attributes: { email: 'ada@example.test', username: 'ada' },
13
+ };
14
+
15
+ describe('DELETE /organisations/:organisationId/invitations/:invitationId', () => {
16
+ it('answers 200 with what the use case answered, and sends it what the request says', async () => {
17
+ const { route, sent } = arrange();
18
+
19
+ const response = await executeRoute(
20
+ route,
21
+ { params: { organisationId: 'acme', invitationId: 'invitation-1' }, query: {}, headers: {}, body: {}, raw: undefined },
22
+ { validateResponses: true, resolvePrincipal: () => caller },
23
+ );
24
+
25
+ expect(route.path).toBe('/organisations/:organisationId/invitations/:invitationId');
26
+ expect([response.status, response.body]).toEqual([200, { id: 'invitation-1' }]);
27
+ expect(sent).toMatchObject([{ organisationId: 'acme', invitationId: 'invitation-1' }]);
28
+ });
29
+ });
30
+
31
+ /** The route, on a bus that keeps what it was sent and answers as the use case would. */
32
+ function arrange() {
33
+ const sent: RevokeInvitationCommand[] = [];
34
+
35
+ const commands = new InMemoryCommandBus().register(RevokeInvitationCommand, {
36
+ execute: async (command) => {
37
+ sent.push(command);
38
+
39
+ return 'invitation-1';
40
+ },
41
+ });
42
+
43
+ return { route: controllerRoute(new RevokeInvitationController(commands)), sent };
44
+ }
@@ -0,0 +1,44 @@
1
+ import { describe, expect, it } from '{{tests.module}}';
2
+ import { InvitationStatus } from '{{alias.context}}/domain/vocabularies/invitation-status.vocabulary{{alias.ext}}';
3
+ import { MemberRole } from '{{alias.context}}/domain/vocabularies/member-role.vocabulary{{alias.ext}}';
4
+ import { RevokeInvitationCommand } from '{{alias.context}}/application/commands/revoke-invitation/revoke-invitation.command{{alias.ext}}';
5
+ import { anInvitation } from '{{alias.context}}/test/builders/invitation.builder{{alias.ext}}';
6
+ import { aMember } from '{{alias.context}}/test/builders/member.builder{{alias.ext}}';
7
+ import { arrangeIdentity } from '{{alias.context}}/test/unit/identity-application{{alias.ext}}';
8
+
9
+ describe('Revoke invitation', () => {
10
+ it('takes back an invitation of the organisation', async () => {
11
+ const { application, members, invitations, as } = arrangeIdentity();
12
+
13
+ await members.save(aMember().of('acme', 'subject-ada').as(MemberRole.Admin).build());
14
+ await invitations.save(anInvitation().identifiedBy('invitation-1').to('acme').build());
15
+
16
+ await application.commandBus.execute(new RevokeInvitationCommand(as('subject-ada'), 'acme', 'invitation-1'));
17
+
18
+ expect((await invitations.findById('invitation-1'))?.status).toBe(InvitationStatus.Revoked);
19
+ });
20
+
21
+ it('finds no invitation of another organisation, and none that does not exist', async () => {
22
+ const { application, members, invitations, as } = arrangeIdentity();
23
+
24
+ await members.save(aMember().of('acme', 'subject-ada').as(MemberRole.Admin).build());
25
+ await invitations.save(anInvitation().identifiedBy('invitation-1').to('globex').build());
26
+
27
+ for (const invitationId of ['invitation-1', 'invitation-9']) {
28
+ await expect(
29
+ application.commandBus.execute(new RevokeInvitationCommand(as('subject-ada'), 'acme', invitationId)),
30
+ ).rejects.toMatchObject({ code: 'INVITATION_NOT_FOUND' });
31
+ }
32
+ });
33
+
34
+ it('is refused to a member whose role does not invite', async () => {
35
+ const { application, members, invitations, as } = arrangeIdentity();
36
+
37
+ await members.save(aMember().of('acme', 'subject-alan').build());
38
+ await invitations.save(anInvitation().identifiedBy('invitation-1').to('acme').build());
39
+
40
+ await expect(
41
+ application.commandBus.execute(new RevokeInvitationCommand(as('subject-alan'), 'acme', 'invitation-1')),
42
+ ).rejects.toMatchObject({ code: 'FORBIDDEN' });
43
+ });
44
+ });
@@ -0,0 +1,54 @@
1
+ import { describe, expect, it } from '{{tests.module}}';
2
+ import { RoleRightsService } from '{{alias.context}}/domain/services/role-rights.service{{alias.ext}}';
3
+ import { MemberRight } from '{{alias.context}}/domain/vocabularies/member-right.vocabulary{{alias.ext}}';
4
+ import { MemberRole } from '{{alias.context}}/domain/vocabularies/member-role.vocabulary{{alias.ext}}';
5
+ import { aDelegation } from '{{alias.context}}/test/builders/delegation.builder{{alias.ext}}';
6
+ import { aMember } from '{{alias.context}}/test/builders/member.builder{{alias.ext}}';
7
+
8
+ const now = new Date('2026-01-10T00:00:00.000Z');
9
+
10
+ describe('RoleRightsService', () => {
11
+ it('grants every right to an owner, all but delegation to an admin, the reading of members to a member', () => {
12
+ expect(new RoleRightsService().rightsOf(MemberRole.Owner)).toEqual(Object.values(MemberRight));
13
+ expect(new RoleRightsService().rightsOf(MemberRole.Admin)).not.toContain(MemberRight.GrantDelegations);
14
+ expect(new RoleRightsService().rightsOf(MemberRole.Member)).toEqual([MemberRight.ReadMembers]);
15
+ });
16
+
17
+ it('gives an account no right in an organisation it is not, or no longer, a member of', () => {
18
+ const removed = aMember().of('acme', 'account-ada').as(MemberRole.Owner).removedAt(now).build();
19
+
20
+ expect(
21
+ new RoleRightsService().rightsIn('acme', 'account-ada', { member: undefined, lent: [], now }).rights,
22
+ ).toEqual([]);
23
+
24
+ expect(new RoleRightsService().rightsIn('acme', 'account-ada', { member: removed, lent: [], now }).rights).toEqual(
25
+ [],
26
+ );
27
+ });
28
+
29
+ it('adds to the rights of the role those an active delegation lends, and nothing of one that ended', () => {
30
+ const member = aMember().of('acme', 'account-deputy').build();
31
+ const active = aDelegation().to('account-deputy').lending(MemberRight.InviteMembers).build();
32
+
33
+ const ended = aDelegation()
34
+ .to('account-deputy')
35
+ .lending(MemberRight.ManageMembers)
36
+ .between(new Date('2025-12-01T00:00:00.000Z'), new Date('2026-01-05T00:00:00.000Z'))
37
+ .build();
38
+
39
+ const rights = new RoleRightsService().rightsIn('acme', 'account-deputy', { member, lent: [active, ended], now });
40
+
41
+ expect(rights.rights).toEqual([MemberRight.InviteMembers, MemberRight.ReadMembers]);
42
+ });
43
+
44
+ it('names the delegation a right is used under when the role does not grant it, and none otherwise', () => {
45
+ const deputy = aMember().of('acme', 'account-deputy').build();
46
+ const admin = aMember().of('acme', 'account-admin').as(MemberRole.Admin).build();
47
+ const lent = [aDelegation().identifiedBy('delegation-1').lending(MemberRight.InviteMembers).build()];
48
+ const rights = new RoleRightsService();
49
+
50
+ expect(rights.delegationUsedFor(MemberRight.InviteMembers, { member: deputy, lent, now })).toBe('delegation-1');
51
+ expect(rights.delegationUsedFor(MemberRight.InviteMembers, { member: admin, lent, now })).toBeUndefined();
52
+ expect(rights.delegationUsedFor(MemberRight.ManageMembers, { member: deputy, lent, now })).toBeUndefined();
53
+ });
54
+ });
@@ -0,0 +1,43 @@
1
+ import { describe, expect, it } from '{{tests.module}}';
2
+ import { controllerRoute, executeRoute } from '@ddd-ts-kit/http';
3
+ import { InMemoryQueryBus, pageOf, type Actor } from '@ddd-ts-kit/kernel';
4
+ import { SearchMembersQuery } from '{{alias.context}}/application/queries/search-members/search-members.query{{alias.ext}}';
5
+ import { MemberRole } from '{{alias.context}}/domain/vocabularies/member-role.vocabulary{{alias.ext}}';
6
+ import { SearchMembersController } from '{{alias.context}}/presentation/http/organisations/search-members/search-members.controller{{alias.ext}}';
7
+
8
+ const caller: Actor = { id: 'subject-ada', type: 'user', roles: [], attributes: {} };
9
+
10
+ const ada = {
11
+ memberId: 'member-1',
12
+ accountId: 'subject-ada',
13
+ displayName: 'Ada Lovelace',
14
+ email: 'ada@example.test',
15
+ role: MemberRole.Owner,
16
+ joinedAt: new Date('2026-01-01T00:00:00.000Z'),
17
+ };
18
+
19
+ describe('GET /organisations/:organisationId/members', () => {
20
+ it('answers 200 with a page of the members, each instant written as ISO 8601', async () => {
21
+ const asked: SearchMembersQuery[] = [];
22
+
23
+ const queries = new InMemoryQueryBus().register(SearchMembersQuery, {
24
+ execute: async (query) => {
25
+ asked.push(query);
26
+
27
+ return pageOf([ada], { total: 1 });
28
+ },
29
+ });
30
+
31
+ const route = controllerRoute(new SearchMembersController(queries));
32
+
33
+ const response = await executeRoute(
34
+ route,
35
+ { params: { organisationId: 'acme' }, query: { limit: '10' }, headers: {}, body: {}, raw: undefined },
36
+ { validateResponses: true, resolvePrincipal: () => caller },
37
+ );
38
+
39
+ expect(response.status).toBe(200);
40
+ expect(response.body).toMatchObject({ items: [{ ...ada, joinedAt: '2026-01-01T00:00:00.000Z' }], total: 1 });
41
+ expect(asked).toMatchObject([{ organisationId: 'acme', page: { kind: 'offset', limit: 10 } }]);
42
+ });
43
+ });
@@ -0,0 +1,38 @@
1
+ import { describe, expect, it } from '{{tests.module}}';
2
+ import { offsetPage } from '@ddd-ts-kit/kernel';
3
+ import { MemberRole } from '{{alias.context}}/domain/vocabularies/member-role.vocabulary{{alias.ext}}';
4
+ import { SearchMembersQuery } from '{{alias.context}}/application/queries/search-members/search-members.query{{alias.ext}}';
5
+ import { aMember } from '{{alias.context}}/test/builders/member.builder{{alias.ext}}';
6
+ import { arrangeIdentity } from '{{alias.context}}/test/unit/identity-application{{alias.ext}}';
7
+
8
+ const ada = {
9
+ memberId: 'member-1',
10
+ accountId: 'subject-ada',
11
+ displayName: 'Ada Lovelace',
12
+ email: 'ada@example.test',
13
+ role: MemberRole.Owner,
14
+ joinedAt: new Date('2026-01-01T00:00:00.000Z'),
15
+ };
16
+
17
+ describe('Search members', () => {
18
+ it('answers a page of the members of the organisation to one of its members', async () => {
19
+ const { application, members, reader, as } = arrangeIdentity();
20
+
21
+ await members.save(aMember().of('acme', 'subject-ada').as(MemberRole.Owner).build());
22
+ reader.given('acme', ada);
23
+
24
+ const page = await application.queryBus.execute(new SearchMembersQuery(as('subject-ada'), 'acme', offsetPage({ limit: 20 })));
25
+
26
+ expect(page.items).toEqual([ada]);
27
+ });
28
+
29
+ it('answers nothing to a person who is not a member of the organisation', async () => {
30
+ const { application, reader, as } = arrangeIdentity();
31
+
32
+ reader.given('acme', ada);
33
+
34
+ await expect(
35
+ application.queryBus.execute(new SearchMembersQuery(as('subject-mallory'), 'acme', offsetPage({ limit: 20 }))),
36
+ ).rejects.toMatchObject({ code: 'FORBIDDEN' });
37
+ });
38
+ });
@@ -0,0 +1,37 @@
1
+ # ADR-{{adr.number}}: {{context.kebab}} is the control plane of the tenants
2
+
3
+ - Status: accepted
4
+ - Date: {{adr.date}}
5
+
6
+ ## Context
7
+
8
+ The project keeps the data of each organisation apart (`"stack.tenancy"`): every statement of a
9
+ repository is confined to the tenant of the work, and `tenancy/scoped-statement` reports one that
10
+ is not. The organisations of `{{context.kebab}}` are those tenants, and this context decides who
11
+ belongs to which: a person signs in once and lists their organisations, an invitation is answered
12
+ by a person who belongs to none yet, a membership is written for an organisation that is not the
13
+ one the request runs in.
14
+
15
+ ## Decision
16
+
17
+ The tables of `{{context.kebab}}` (accounts, organisations, members, invitations, delegations)
18
+ belong to no tenant: they hold no tenant column, and each statement of their adapters carries a
19
+ suppression of `tenancy/scoped-statement` that names this record. What one organisation may read
20
+ of another is decided per use case instead: every use case of an organisation authorizes the
21
+ actor on the rights of their membership in it (the policies of
22
+ `application/authorization/`), and every read names that organisation.
23
+
24
+ ## Consequences
25
+
26
+ - Easier: a person, their memberships and their invitations are one model, whatever the tenant
27
+ of the request; the provider is told of every change, so that tokens name the organisations.
28
+ - Harder: isolation between the organisations of this context rests on its policies and on the
29
+ organisation each statement names, not on the storage. A new statement names its organisation.
30
+ - The integration events of an organisation carry it as their tenant, so that the consumers of the
31
+ other contexts run in it.
32
+
33
+ ## Alternatives considered
34
+
35
+ - **A tenant column on these tables.** Refused: the membership of a person in an organisation is
36
+ written by work that does not run in that organisation (opening it, accepting an invitation),
37
+ and an elevation widens reads only, never writes.
@@ -0,0 +1,71 @@
1
+ ## Design
2
+
3
+ Written by the identity preset of ddd-ts-kit (`--preset identity`); from now on it is the code of
4
+ this project, to change like any other.
5
+
6
+ **The identity provider authenticates, this context authorizes.** An account is a person under
7
+ the subject their provider gives them (`actor.id`): it is provisioned at their first sign-in from
8
+ what their token says (`PUT /accounts/me`) and refreshed at each later one. No password, no secret
9
+ of the provider is stored here. The email address of an account is always the one of the token,
10
+ never one a client sends, and one the provider verified (`email_verified`): an invitation is
11
+ matched on it. A deactivated account leaves every organisation and ends every delegation in the
12
+ same unit of work, so that it holds no right anywhere while its last token lives; the last owner
13
+ of an organisation appoints another owner before deactivating.
14
+
15
+ **Organisations, members and roles.** An organisation is opened under its slug, which is its
16
+ identity for good, and its opener is its first owner. A member holds one role, `owner`, `admin`
17
+ or `member`; what a role grants is one table, `RoleRightsService`. Nobody changes their own role,
18
+ and nobody appoints, invites, changes or removes anyone above their own role (only an owner makes
19
+ an owner), whatever a delegation lends them. An organisation keeps one owner at least: every
20
+ change of a member saves the organisation with it, so that its version orders the changes of its
21
+ owners and two owners who remove each other at once cannot both commit.
22
+
23
+ **Invitations.** An invitation is issued to an email address, in a role, for a week. Its token
24
+ is answered to the inviter once (`POST /organisations/:organisationId/invitations`) and only its
25
+ SHA-256 digest is stored: send it to the person (a notification context, a link), who accepts or
26
+ declines it signed in, with the address it was sent to.
27
+
28
+ **Delegations.** A member lends some of the rights of their role to another member of the
29
+ organisation, for ninety days at most: the delegate acts in their own name, with those rights, and
30
+ the audit trail of the project records every use case either way; the facts a change leads to
31
+ name the delegation it was decided under (`delegationId`). A delegation lends nothing beyond the
32
+ role of its delegator, never what was lent to them, never the power to act above the delegate's
33
+ own role, and it ends when one of its two members leaves or when its delegator changes role.
34
+
35
+ **Who may do what.** Every use case requires a permission of `{{context.camel}}Permissions`, which
36
+ the platform grants to every signed-in person whatever its access model (roles, attribute
37
+ policies, relationships, OpenFGA or an adapter of the project): the grant `'{{context.kebab}}:*'`
38
+ of `signed-in-grants.ts`, which `createAuthorizer` answers before it asks the model
39
+ (`SignedInGrantsAuthorizer` of `@ddd-ts-kit/auth`). Systems, services and anonymous callers get
40
+ nothing from it.
41
+
42
+ Inside an organisation, the policies of `application/authorization/` decide on the rights the
43
+ actor holds there (`OrganisationRights`): their role, and the rights an active delegation lends
44
+ them. The integration events (`member.joined`, `member.role-changed`, `member.removed`,
45
+ `delegation.granted`, `delegation.revoked`) let another context write the relationships of its
46
+ own model, or the tuples of OpenFGA, from these facts.
47
+ {{#if tenancy.any}}
48
+
49
+ **Tenants.** Each organisation is a tenant: its slug is the tenant id, the integration events of
50
+ an organisation carry it as their tenant, and the consumers of the other contexts run in it. The
51
+ tables of this context belong to no tenant, since it decides who belongs to which ({{preset.adr}}).
52
+ The token names the organisations of the person: with Keycloak, the `organization` claim, which
53
+ `createAuthenticator` reads (`tenantClaim: 'organization'`). One organisation is the tenant of
54
+ every request; several are the `tenants` of the actor, and a request names the one it works in
55
+ (`x-tenant-id`), refused if the person is not a member of it.
56
+ {{/if}}
57
+
58
+ **The identity provider follows, at least once.** The consumers of `application/integration/handlers/`
59
+ read the facts the context publishes through its outbox and tell the provider
60
+ (`IdentityProviderAdmin`): it knows the organisation under its slug, adds or removes its members,
61
+ and disables a deactivated account. A provider that is down is asked again by whoever delivers the
62
+ fact (the outbox relay, the broker) until it answers, so a removal or a deactivation is never
63
+ lost; each consumer brings the provider in line with what the context holds now, so that a late
64
+ retry never gives a removed person their organisation back. `KeycloakIdentityProviderAdmin` is a
65
+ recipe for the administration API of Keycloak 26 or later (organizations enabled in the realm), as
66
+ a confidential client holding `manage-users` and `manage-realm` of `realm-management`; it reads
67
+ `KEYCLOAK_URL`, `KEYCLOAK_REALM`, `IDENTITY_PROVIDER_ADMIN_CLIENT_ID` and
68
+ `IDENTITY_PROVIDER_ADMIN_CLIENT_SECRET` when a call is made. Its spec runs it against a local
69
+ server that answers as Keycloak does: run it once against your realm before relying on it. Another
70
+ provider is another adapter of the same port.
71
+
@@ -0,0 +1,12 @@
1
+ import type { Grant } from '@ddd-ts-kit/auth';
2
+
3
+ /**
4
+ * What every signed-in person may do, whatever the access model of the platform decides for the
5
+ * rest: the permissions of a context that serves each person for themselves, such as their
6
+ * account and the organisations they belong to, whose own policies decide on the element.
7
+ * `createAuthorizer` grants them before it asks the model.
8
+ */
9
+ export const signedInGrants: readonly Grant[] = [
10
+ // ddd-ts-kit:signed-in-grants
11
+ // /ddd-ts-kit:signed-in-grants
12
+ ];