ddd-ts-kit 0.14.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 (369) hide show
  1. package/README.md +28 -1
  2. package/dist/check/explain/tenancy-docs.d.ts.map +1 -1
  3. package/dist/check/explain/tenancy-docs.js +3 -1
  4. package/dist/check/explain/tenancy-docs.js.map +1 -1
  5. package/dist/check/fixtures/tenancy-fixtures.d.ts.map +1 -1
  6. package/dist/check/fixtures/tenancy-fixtures.js +28 -0
  7. package/dist/check/fixtures/tenancy-fixtures.js.map +1 -1
  8. package/dist/check/tenancy/knex-unscoped.d.ts +6 -1
  9. package/dist/check/tenancy/knex-unscoped.d.ts.map +1 -1
  10. package/dist/check/tenancy/knex-unscoped.js +19 -6
  11. package/dist/check/tenancy/knex-unscoped.js.map +1 -1
  12. package/dist/check/tenancy/raw-reaches-rows.d.ts +8 -0
  13. package/dist/check/tenancy/raw-reaches-rows.d.ts.map +1 -0
  14. package/dist/check/tenancy/raw-reaches-rows.js +91 -0
  15. package/dist/check/tenancy/raw-reaches-rows.js.map +1 -0
  16. package/dist/check/tenancy/raw-sql-text.d.ts +9 -0
  17. package/dist/check/tenancy/raw-sql-text.d.ts.map +1 -0
  18. package/dist/check/tenancy/raw-sql-text.js +29 -0
  19. package/dist/check/tenancy/raw-sql-text.js.map +1 -0
  20. package/dist/commands/generate.js +2 -0
  21. package/dist/commands/generate.js.map +1 -1
  22. package/dist/commands/init.d.ts.map +1 -1
  23. package/dist/commands/init.js +11 -1
  24. package/dist/commands/init.js.map +1 -1
  25. package/dist/declared/injection/laid-out-calls.d.ts +7 -0
  26. package/dist/declared/injection/laid-out-calls.d.ts.map +1 -0
  27. package/dist/declared/injection/laid-out-calls.js +14 -0
  28. package/dist/declared/injection/laid-out-calls.js.map +1 -0
  29. package/dist/declared/render-barrel.d.ts.map +1 -1
  30. package/dist/declared/render-barrel.js +2 -1
  31. package/dist/declared/render-barrel.js.map +1 -1
  32. package/dist/declared/render-dependencies-file.d.ts.map +1 -1
  33. package/dist/declared/render-dependencies-file.js +2 -1
  34. package/dist/declared/render-dependencies-file.js.map +1 -1
  35. package/dist/generate/kind.d.ts +2 -0
  36. package/dist/generate/kind.d.ts.map +1 -1
  37. package/dist/generate/kinds/context-kind.d.ts.map +1 -1
  38. package/dist/generate/kinds/context-kind.js +13 -6
  39. package/dist/generate/kinds/context-kind.js.map +1 -1
  40. package/dist/generate/kinds/next-adr-number.d.ts +4 -0
  41. package/dist/generate/kinds/next-adr-number.d.ts.map +1 -0
  42. package/dist/generate/kinds/next-adr-number.js +7 -0
  43. package/dist/generate/kinds/next-adr-number.js.map +1 -0
  44. package/dist/generate/kinds/write-adr.js +3 -6
  45. package/dist/generate/kinds/write-adr.js.map +1 -1
  46. package/dist/help/usage.d.ts +1 -1
  47. package/dist/help/usage.d.ts.map +1 -1
  48. package/dist/help/usage.js +1 -1
  49. package/dist/init/manifest/versions.js +24 -24
  50. package/dist/init/run-init.d.ts +2 -0
  51. package/dist/init/run-init.d.ts.map +1 -1
  52. package/dist/init/run-init.js +15 -1
  53. package/dist/init/run-init.js.map +1 -1
  54. package/dist/presets/apply-preset.d.ts +15 -0
  55. package/dist/presets/apply-preset.d.ts.map +1 -0
  56. package/dist/presets/apply-preset.js +94 -0
  57. package/dist/presets/apply-preset.js.map +1 -0
  58. package/dist/presets/grant-to-signed-in.d.ts +9 -0
  59. package/dist/presets/grant-to-signed-in.d.ts.map +1 -0
  60. package/dist/presets/grant-to-signed-in.js +61 -0
  61. package/dist/presets/grant-to-signed-in.js.map +1 -0
  62. package/dist/presets/identity-preset.d.ts +9 -0
  63. package/dist/presets/identity-preset.d.ts.map +1 -0
  64. package/dist/presets/identity-preset.js +44 -0
  65. package/dist/presets/identity-preset.js.map +1 -0
  66. package/dist/presets/identity-refusal.d.ts +5 -0
  67. package/dist/presets/identity-refusal.d.ts.map +1 -0
  68. package/dist/presets/identity-refusal.js +29 -0
  69. package/dist/presets/identity-refusal.js.map +1 -0
  70. package/dist/presets/identity-terms.d.ts +4 -0
  71. package/dist/presets/identity-terms.d.ts.map +1 -0
  72. package/dist/presets/identity-terms.js +57 -0
  73. package/dist/presets/identity-terms.js.map +1 -0
  74. package/dist/presets/no-flags.d.ts +4 -0
  75. package/dist/presets/no-flags.d.ts.map +1 -0
  76. package/dist/presets/no-flags.js +18 -0
  77. package/dist/presets/no-flags.js.map +1 -0
  78. package/dist/presets/organisations-as-tenants.d.ts +9 -0
  79. package/dist/presets/organisations-as-tenants.d.ts.map +1 -0
  80. package/dist/presets/organisations-as-tenants.js +43 -0
  81. package/dist/presets/organisations-as-tenants.js.map +1 -0
  82. package/dist/presets/preset-decision.d.ts +8 -0
  83. package/dist/presets/preset-decision.d.ts.map +1 -0
  84. package/dist/presets/preset-decision.js +19 -0
  85. package/dist/presets/preset-decision.js.map +1 -0
  86. package/dist/presets/preset-file.d.ts +13 -0
  87. package/dist/presets/preset-file.d.ts.map +1 -0
  88. package/dist/presets/preset-file.js +2 -0
  89. package/dist/presets/preset-file.js.map +1 -0
  90. package/dist/presets/preset-files.d.ts +9 -0
  91. package/dist/presets/preset-files.d.ts.map +1 -0
  92. package/dist/presets/preset-files.js +34 -0
  93. package/dist/presets/preset-files.js.map +1 -0
  94. package/dist/presets/preset-named.d.ts +3 -0
  95. package/dist/presets/preset-named.d.ts.map +1 -0
  96. package/dist/presets/preset-named.js +12 -0
  97. package/dist/presets/preset-named.js.map +1 -0
  98. package/dist/presets/preset-refusal.d.ts +6 -0
  99. package/dist/presets/preset-refusal.d.ts.map +1 -0
  100. package/dist/presets/preset-refusal.js +2 -0
  101. package/dist/presets/preset-refusal.js.map +1 -0
  102. package/dist/presets/preset-step.d.ts +8 -0
  103. package/dist/presets/preset-step.d.ts.map +1 -0
  104. package/dist/presets/preset-step.js +2 -0
  105. package/dist/presets/preset-step.js.map +1 -0
  106. package/dist/presets/preset.d.ts +36 -0
  107. package/dist/presets/preset.d.ts.map +1 -0
  108. package/dist/presets/preset.js +2 -0
  109. package/dist/presets/preset.js.map +1 -0
  110. package/dist/presets/with-authorizer-import.d.ts +6 -0
  111. package/dist/presets/with-authorizer-import.d.ts.map +1 -0
  112. package/dist/presets/with-authorizer-import.js +27 -0
  113. package/dist/presets/with-authorizer-import.js.map +1 -0
  114. package/dist/version.d.ts +1 -1
  115. package/dist/version.js +1 -1
  116. package/dist/workspace/workspace.d.ts +2 -0
  117. package/dist/workspace/workspace.d.ts.map +1 -1
  118. package/dist/workspace/workspace.js +4 -0
  119. package/dist/workspace/workspace.js.map +1 -1
  120. package/package.json +2 -2
  121. package/templates/pipeline/package-scripts.json +1 -1
  122. package/templates/presets/identity/context/application/authorization/__context__.permissions.ts.tpl +23 -0
  123. package/templates/presets/identity/context/application/authorization/change-member-roles.policy.ts.tpl +17 -0
  124. package/templates/presets/identity/context/application/authorization/grant-delegations.policy.ts.tpl +17 -0
  125. package/templates/presets/identity/context/application/authorization/invite-members.policy.ts.tpl +17 -0
  126. package/templates/presets/identity/context/application/authorization/read-members.policy.ts.tpl +17 -0
  127. package/templates/presets/identity/context/application/authorization/remove-members.policy.ts.tpl +17 -0
  128. package/templates/presets/identity/context/application/authorization/revoke-delegations.policy.ts.tpl +17 -0
  129. package/templates/presets/identity/context/application/authorization/revoke-invitations.policy.ts.tpl +17 -0
  130. package/templates/presets/identity/context/application/commands/accept-invitation/accept-invitation.command.ts.tpl +14 -0
  131. package/templates/presets/identity/context/application/commands/accept-invitation/accept-invitation.handler.ts.tpl +56 -0
  132. package/templates/presets/identity/context/application/commands/change-member-role/change-member-role.command.ts.tpl +17 -0
  133. package/templates/presets/identity/context/application/commands/change-member-role/change-member-role.handler.ts.tpl +82 -0
  134. package/templates/presets/identity/context/application/commands/deactivate-account/deactivate-account.command.ts.tpl +7 -0
  135. package/templates/presets/identity/context/application/commands/deactivate-account/deactivate-account.handler.ts.tpl +69 -0
  136. package/templates/presets/identity/context/application/commands/decline-invitation/decline-invitation.command.ts.tpl +14 -0
  137. package/templates/presets/identity/context/application/commands/decline-invitation/decline-invitation.handler.ts.tpl +36 -0
  138. package/templates/presets/identity/context/application/commands/grant-delegation/grant-delegation.command.ts.tpl +21 -0
  139. package/templates/presets/identity/context/application/commands/grant-delegation/grant-delegation.handler.ts.tpl +43 -0
  140. package/templates/presets/identity/context/application/commands/invite-member/invite-member.command.ts.tpl +16 -0
  141. package/templates/presets/identity/context/application/commands/invite-member/invite-member.handler.ts.tpl +53 -0
  142. package/templates/presets/identity/context/application/commands/open-organisation/open-organisation.command.ts.tpl +14 -0
  143. package/templates/presets/identity/context/application/commands/open-organisation/open-organisation.handler.ts.tpl +50 -0
  144. package/templates/presets/identity/context/application/commands/provision-account/provision-account.command.ts.tpl +14 -0
  145. package/templates/presets/identity/context/application/commands/provision-account/provision-account.handler.ts.tpl +41 -0
  146. package/templates/presets/identity/context/application/commands/remove-member/remove-member.command.ts.tpl +15 -0
  147. package/templates/presets/identity/context/application/commands/remove-member/remove-member.handler.ts.tpl +79 -0
  148. package/templates/presets/identity/context/application/commands/revoke-delegation/revoke-delegation.command.ts.tpl +15 -0
  149. package/templates/presets/identity/context/application/commands/revoke-delegation/revoke-delegation.handler.ts.tpl +39 -0
  150. package/templates/presets/identity/context/application/commands/revoke-invitation/revoke-invitation.command.ts.tpl +15 -0
  151. package/templates/presets/identity/context/application/commands/revoke-invitation/revoke-invitation.handler.ts.tpl +42 -0
  152. package/templates/presets/identity/context/application/integration/handlers/account-deactivated.integration-handler.ts.tpl +19 -0
  153. package/templates/presets/identity/context/application/integration/handlers/member-joined.integration-handler.ts.tpl +33 -0
  154. package/templates/presets/identity/context/application/integration/handlers/member-removed.integration-handler.ts.tpl +30 -0
  155. package/templates/presets/identity/context/application/integration/handlers/organisation-opened.integration-handler.ts.tpl +19 -0
  156. package/templates/presets/identity/context/application/integration/publishers/account-deactivated.publisher.ts.tpl +24 -0
  157. package/templates/presets/identity/context/application/integration/publishers/delegation-granted.publisher.ts.tpl +36 -0
  158. package/templates/presets/identity/context/application/integration/publishers/delegation-revoked.publisher.ts.tpl +33 -0
  159. package/templates/presets/identity/context/application/integration/publishers/member-joined.publisher.ts.tpl +33 -0
  160. package/templates/presets/identity/context/application/integration/publishers/member-removed.publisher.ts.tpl +33 -0
  161. package/templates/presets/identity/context/application/integration/publishers/member-role-changed.publisher.ts.tpl +35 -0
  162. package/templates/presets/identity/context/application/integration/publishers/organisation-opened.publisher.ts.tpl +32 -0
  163. package/templates/presets/identity/context/application/ports/identity-provider-admin.port.ts.tpl +17 -0
  164. package/templates/presets/identity/context/application/ports/identity-provider-connection.port.ts.tpl +14 -0
  165. package/templates/presets/identity/context/application/ports/invitation-tokens.port.ts.tpl +7 -0
  166. package/templates/presets/identity/context/application/ports/search-members-reader.port.ts.tpl +10 -0
  167. package/templates/presets/identity/context/application/queries/search-members/search-members.handler.ts.tpl +33 -0
  168. package/templates/presets/identity/context/application/queries/search-members/search-members.query.ts.tpl +19 -0
  169. package/templates/presets/identity/context/application/queries/search-members/search-members.result.ts.tpl +17 -0
  170. package/templates/presets/identity/context/domain/aggregates/account.aggregate.ts.tpl +92 -0
  171. package/templates/presets/identity/context/domain/aggregates/delegation.aggregate.ts.tpl +117 -0
  172. package/templates/presets/identity/context/domain/aggregates/invitation.aggregate.ts.tpl +128 -0
  173. package/templates/presets/identity/context/domain/aggregates/member.aggregate.ts.tpl +149 -0
  174. package/templates/presets/identity/context/domain/aggregates/organisation.aggregate.ts.tpl +52 -0
  175. package/templates/presets/identity/context/domain/errors/account.error.ts.tpl +40 -0
  176. package/templates/presets/identity/context/domain/errors/delegation.error.ts.tpl +45 -0
  177. package/templates/presets/identity/context/domain/errors/invitation.error.ts.tpl +30 -0
  178. package/templates/presets/identity/context/domain/errors/member.error.ts.tpl +50 -0
  179. package/templates/presets/identity/context/domain/errors/organisation.error.ts.tpl +32 -0
  180. package/templates/presets/identity/context/domain/events/account-deactivated.event.ts.tpl +13 -0
  181. package/templates/presets/identity/context/domain/events/account-provisioned.event.ts.tpl +17 -0
  182. package/templates/presets/identity/context/domain/events/delegation-granted.event.ts.tpl +22 -0
  183. package/templates/presets/identity/context/domain/events/delegation-revoked.event.ts.tpl +18 -0
  184. package/templates/presets/identity/context/domain/events/invitation-accepted.event.ts.tpl +17 -0
  185. package/templates/presets/identity/context/domain/events/invitation-declined.event.ts.tpl +16 -0
  186. package/templates/presets/identity/context/domain/events/invitation-issued.event.ts.tpl +22 -0
  187. package/templates/presets/identity/context/domain/events/invitation-revoked.event.ts.tpl +16 -0
  188. package/templates/presets/identity/context/domain/events/member-joined.event.ts.tpl +19 -0
  189. package/templates/presets/identity/context/domain/events/member-removed.event.ts.tpl +19 -0
  190. package/templates/presets/identity/context/domain/events/member-role-changed.event.ts.tpl +22 -0
  191. package/templates/presets/identity/context/domain/events/organisation-opened.event.ts.tpl +17 -0
  192. package/templates/presets/identity/context/domain/repositories/account.repository.ts.tpl +5 -0
  193. package/templates/presets/identity/context/domain/repositories/delegation.repository.ts.tpl +10 -0
  194. package/templates/presets/identity/context/domain/repositories/invitation.repository.ts.tpl +7 -0
  195. package/templates/presets/identity/context/domain/repositories/member.repository.ts.tpl +13 -0
  196. package/templates/presets/identity/context/domain/repositories/organisation.repository.ts.tpl +5 -0
  197. package/templates/presets/identity/context/domain/services/role-rights.service.ts.tpl +59 -0
  198. package/templates/presets/identity/context/domain/value-objects/delegation-period.vo.ts.tpl +42 -0
  199. package/templates/presets/identity/context/domain/value-objects/display-name.vo.ts.tpl +29 -0
  200. package/templates/presets/identity/context/domain/value-objects/email-address.vo.ts.tpl +31 -0
  201. package/templates/presets/identity/context/domain/value-objects/organisation-name.vo.ts.tpl +29 -0
  202. package/templates/presets/identity/context/domain/value-objects/organisation-rights.vo.ts.tpl +43 -0
  203. package/templates/presets/identity/context/domain/value-objects/organisation-slug.vo.ts.tpl +30 -0
  204. package/templates/presets/identity/context/domain/vocabularies/account-status.vocabulary.ts.tpl +7 -0
  205. package/templates/presets/identity/context/domain/vocabularies/invitation-status.vocabulary.ts.tpl +9 -0
  206. package/templates/presets/identity/context/domain/vocabularies/member-right.vocabulary.ts.tpl +9 -0
  207. package/templates/presets/identity/context/domain/vocabularies/member-role.vocabulary.ts.tpl +8 -0
  208. package/templates/presets/identity/context/infrastructure/external/crypto-invitation-tokens.adapter.ts.tpl +20 -0
  209. package/templates/presets/identity/context/infrastructure/external/environment-identity-provider-connection.adapter.ts.tpl +37 -0
  210. package/templates/presets/identity/context/infrastructure/external/keycloak-identity-provider-admin.adapter.ts.tpl +149 -0
  211. package/templates/presets/identity/context/infrastructure/persistence/__context__.schema.drizzle.ts.tpl +102 -0
  212. package/templates/presets/identity/context/infrastructure/persistence/account-row.schema.knex.ts.tpl +15 -0
  213. package/templates/presets/identity/context/infrastructure/persistence/account.mapper.drizzle.ts.tpl +38 -0
  214. package/templates/presets/identity/context/infrastructure/persistence/account.mapper.knex.ts.tpl +33 -0
  215. package/templates/presets/identity/context/infrastructure/persistence/create-__context__-tables.schema.knex.ts.tpl +107 -0
  216. package/templates/presets/identity/context/infrastructure/persistence/delegation-row.schema.knex.ts.tpl +17 -0
  217. package/templates/presets/identity/context/infrastructure/persistence/delegation.mapper.drizzle.ts.tpl +50 -0
  218. package/templates/presets/identity/context/infrastructure/persistence/delegation.mapper.knex.ts.tpl +45 -0
  219. package/templates/presets/identity/context/infrastructure/persistence/drizzle-account.repository.drizzle.ts.tpl +59 -0
  220. package/templates/presets/identity/context/infrastructure/persistence/drizzle-delegation.repository.drizzle.ts.tpl +77 -0
  221. package/templates/presets/identity/context/infrastructure/persistence/drizzle-invitation.repository.drizzle.ts.tpl +65 -0
  222. package/templates/presets/identity/context/infrastructure/persistence/drizzle-member.repository.drizzle.ts.tpl +84 -0
  223. package/templates/presets/identity/context/infrastructure/persistence/drizzle-organisation.repository.drizzle.ts.tpl +59 -0
  224. package/templates/presets/identity/context/infrastructure/persistence/drizzle-search-members-reader.repository.drizzle.ts.tpl +53 -0
  225. package/templates/presets/identity/context/infrastructure/persistence/invitation-row.schema.knex.ts.tpl +20 -0
  226. package/templates/presets/identity/context/infrastructure/persistence/invitation.mapper.drizzle.ts.tpl +45 -0
  227. package/templates/presets/identity/context/infrastructure/persistence/invitation.mapper.knex.ts.tpl +40 -0
  228. package/templates/presets/identity/context/infrastructure/persistence/knex-account.repository.knex.ts.tpl +61 -0
  229. package/templates/presets/identity/context/infrastructure/persistence/knex-delegation.repository.knex.ts.tpl +80 -0
  230. package/templates/presets/identity/context/infrastructure/persistence/knex-invitation.repository.knex.ts.tpl +67 -0
  231. package/templates/presets/identity/context/infrastructure/persistence/knex-member.repository.knex.ts.tpl +82 -0
  232. package/templates/presets/identity/context/infrastructure/persistence/knex-organisation.repository.knex.ts.tpl +61 -0
  233. package/templates/presets/identity/context/infrastructure/persistence/knex-search-members-reader.repository.knex.ts.tpl +77 -0
  234. package/templates/presets/identity/context/infrastructure/persistence/member-row.schema.knex.ts.tpl +15 -0
  235. package/templates/presets/identity/context/infrastructure/persistence/member.mapper.drizzle.ts.tpl +36 -0
  236. package/templates/presets/identity/context/infrastructure/persistence/member.mapper.knex.ts.tpl +31 -0
  237. package/templates/presets/identity/context/infrastructure/persistence/organisation-row.schema.knex.ts.tpl +12 -0
  238. package/templates/presets/identity/context/infrastructure/persistence/organisation.mapper.drizzle.ts.tpl +28 -0
  239. package/templates/presets/identity/context/infrastructure/persistence/organisation.mapper.knex.ts.tpl +26 -0
  240. package/templates/presets/identity/context/presentation/http/accounts/deactivate-account/deactivate-account.accepted.class-validator.per-route.ts.tpl +6 -0
  241. package/templates/presets/identity/context/presentation/http/accounts/deactivate-account/deactivate-account.controller.ts.tpl +71 -0
  242. package/templates/presets/identity/context/presentation/http/accounts/provision-account/provision-account.accepted.class-validator.per-route.ts.tpl +6 -0
  243. package/templates/presets/identity/context/presentation/http/accounts/provision-account/provision-account.body.class-validator.ts.tpl +9 -0
  244. package/templates/presets/identity/context/presentation/http/accounts/provision-account/provision-account.controller.ts.tpl +85 -0
  245. package/templates/presets/identity/context/presentation/http/accounts.routes.ts.tpl +5 -0
  246. package/templates/presets/identity/context/presentation/http/invitations/accept-invitation/accept-invitation.accepted.class-validator.per-route.ts.tpl +6 -0
  247. package/templates/presets/identity/context/presentation/http/invitations/accept-invitation/accept-invitation.body.class-validator.per-route.ts.tpl +8 -0
  248. package/templates/presets/identity/context/presentation/http/invitations/accept-invitation/accept-invitation.controller.ts.tpl +100 -0
  249. package/templates/presets/identity/context/presentation/http/invitations/decline-invitation/decline-invitation.accepted.class-validator.per-route.ts.tpl +6 -0
  250. package/templates/presets/identity/context/presentation/http/invitations/decline-invitation/decline-invitation.body.class-validator.per-route.ts.tpl +8 -0
  251. package/templates/presets/identity/context/presentation/http/invitations/decline-invitation/decline-invitation.controller.ts.tpl +98 -0
  252. package/templates/presets/identity/context/presentation/http/invitations.routes.ts.tpl +5 -0
  253. package/templates/presets/identity/context/presentation/http/organisations/change-member-role/change-member-role.accepted.class-validator.per-route.ts.tpl +6 -0
  254. package/templates/presets/identity/context/presentation/http/organisations/change-member-role/change-member-role.body.class-validator.ts.tpl +7 -0
  255. package/templates/presets/identity/context/presentation/http/organisations/change-member-role/change-member-role.controller.ts.tpl +83 -0
  256. package/templates/presets/identity/context/presentation/http/organisations/change-member-role/change-member-role.params.class-validator.ts.tpl +11 -0
  257. package/templates/presets/identity/context/presentation/http/organisations/grant-delegation/grant-delegation.accepted.class-validator.per-route.ts.tpl +6 -0
  258. package/templates/presets/identity/context/presentation/http/organisations/grant-delegation/grant-delegation.body.class-validator.ts.tpl +18 -0
  259. package/templates/presets/identity/context/presentation/http/organisations/grant-delegation/grant-delegation.controller.ts.tpl +103 -0
  260. package/templates/presets/identity/context/presentation/http/organisations/grant-delegation/grant-delegation.params.class-validator.ts.tpl +7 -0
  261. package/templates/presets/identity/context/presentation/http/organisations/invite-member/invite-member.accepted.class-validator.ts.tpl +10 -0
  262. package/templates/presets/identity/context/presentation/http/organisations/invite-member/invite-member.body.class-validator.ts.tpl +10 -0
  263. package/templates/presets/identity/context/presentation/http/organisations/invite-member/invite-member.controller.ts.tpl +65 -0
  264. package/templates/presets/identity/context/presentation/http/organisations/invite-member/invite-member.params.class-validator.ts.tpl +7 -0
  265. package/templates/presets/identity/context/presentation/http/organisations/open-organisation/open-organisation.accepted.class-validator.per-route.ts.tpl +6 -0
  266. package/templates/presets/identity/context/presentation/http/organisations/open-organisation/open-organisation.body.class-validator.ts.tpl +12 -0
  267. package/templates/presets/identity/context/presentation/http/organisations/open-organisation/open-organisation.controller.ts.tpl +79 -0
  268. package/templates/presets/identity/context/presentation/http/organisations/remove-member/remove-member.accepted.class-validator.per-route.ts.tpl +6 -0
  269. package/templates/presets/identity/context/presentation/http/organisations/remove-member/remove-member.controller.ts.tpl +73 -0
  270. package/templates/presets/identity/context/presentation/http/organisations/remove-member/remove-member.params.class-validator.ts.tpl +11 -0
  271. package/templates/presets/identity/context/presentation/http/organisations/revoke-delegation/revoke-delegation.accepted.class-validator.per-route.ts.tpl +6 -0
  272. package/templates/presets/identity/context/presentation/http/organisations/revoke-delegation/revoke-delegation.controller.ts.tpl +73 -0
  273. package/templates/presets/identity/context/presentation/http/organisations/revoke-delegation/revoke-delegation.params.class-validator.ts.tpl +11 -0
  274. package/templates/presets/identity/context/presentation/http/organisations/revoke-invitation/revoke-invitation.accepted.class-validator.per-route.ts.tpl +6 -0
  275. package/templates/presets/identity/context/presentation/http/organisations/revoke-invitation/revoke-invitation.controller.ts.tpl +73 -0
  276. package/templates/presets/identity/context/presentation/http/organisations/revoke-invitation/revoke-invitation.params.class-validator.ts.tpl +11 -0
  277. package/templates/presets/identity/context/presentation/http/organisations/search-members/search-members.controller.ts.tpl +86 -0
  278. package/templates/presets/identity/context/presentation/http/organisations/search-members/search-members.line.class-validator.ts.tpl +22 -0
  279. package/templates/presets/identity/context/presentation/http/organisations/search-members/search-members.params.class-validator.ts.tpl +7 -0
  280. package/templates/presets/identity/context/presentation/http/organisations.routes.ts.tpl +5 -0
  281. package/templates/presets/identity/context/presentation/http/shared/invitation-token.schema.class-validator.shared.ts.tpl +9 -0
  282. package/templates/presets/identity/context/presentation/http/shared/invitation-token.schema.zod.shared.ts.tpl +9 -0
  283. package/templates/presets/identity/context/published-language/account-deactivated.v1.integration-event.ts.tpl +33 -0
  284. package/templates/presets/identity/context/published-language/account-deactivated.v1.payload.class-validator.ts.tpl +7 -0
  285. package/templates/presets/identity/context/published-language/delegation-granted.v1.integration-event.ts.tpl +39 -0
  286. package/templates/presets/identity/context/published-language/delegation-granted.v1.payload.class-validator.ts.tpl +28 -0
  287. package/templates/presets/identity/context/published-language/delegation-revoked.v1.integration-event.ts.tpl +36 -0
  288. package/templates/presets/identity/context/published-language/delegation-revoked.v1.payload.class-validator.ts.tpl +16 -0
  289. package/templates/presets/identity/context/published-language/member-joined.v1.integration-event.ts.tpl +36 -0
  290. package/templates/presets/identity/context/published-language/member-joined.v1.payload.class-validator.ts.tpl +16 -0
  291. package/templates/presets/identity/context/published-language/member-removed.v1.integration-event.ts.tpl +36 -0
  292. package/templates/presets/identity/context/published-language/member-removed.v1.payload.class-validator.ts.tpl +18 -0
  293. package/templates/presets/identity/context/published-language/member-role-changed.v1.integration-event.ts.tpl +38 -0
  294. package/templates/presets/identity/context/published-language/member-role-changed.v1.payload.class-validator.ts.tpl +24 -0
  295. package/templates/presets/identity/context/published-language/organisation-opened.v1.integration-event.ts.tpl +35 -0
  296. package/templates/presets/identity/context/published-language/organisation-opened.v1.payload.class-validator.ts.tpl +13 -0
  297. package/templates/presets/identity/context/test/builders/account.builder.ts.tpl +59 -0
  298. package/templates/presets/identity/context/test/builders/delegation.builder.ts.tpl +67 -0
  299. package/templates/presets/identity/context/test/builders/invitation.builder.ts.tpl +60 -0
  300. package/templates/presets/identity/context/test/builders/member.builder.ts.tpl +59 -0
  301. package/templates/presets/identity/context/test/builders/organisation.builder.ts.tpl +44 -0
  302. package/templates/presets/identity/context/test/fakes/account.repository.fake.ts.tpl +10 -0
  303. package/templates/presets/identity/context/test/fakes/delegation.repository.fake.ts.tpl +26 -0
  304. package/templates/presets/identity/context/test/fakes/identity-provider-admin.fake.ts.tpl +41 -0
  305. package/templates/presets/identity/context/test/fakes/invitation-tokens.fake.ts.tpl +18 -0
  306. package/templates/presets/identity/context/test/fakes/invitation.repository.fake.ts.tpl +14 -0
  307. package/templates/presets/identity/context/test/fakes/member.repository.fake.ts.tpl +29 -0
  308. package/templates/presets/identity/context/test/fakes/organisation.repository.fake.ts.tpl +10 -0
  309. package/templates/presets/identity/context/test/fakes/search-members-reader.fake.ts.tpl +25 -0
  310. package/templates/presets/identity/context/test/integration/account.repository.contract.spec.ts.tpl +48 -0
  311. package/templates/presets/identity/context/test/integration/delegation.repository.contract.spec.ts.tpl +84 -0
  312. package/templates/presets/identity/context/test/integration/invitation.repository.contract.spec.ts.tpl +76 -0
  313. package/templates/presets/identity/context/test/integration/keycloak-identity-provider-admin.adapter.spec.ts.tpl +123 -0
  314. package/templates/presets/identity/context/test/integration/member.repository.contract.spec.ts.tpl +92 -0
  315. package/templates/presets/identity/context/test/integration/organisation.repository.contract.spec.ts.tpl +46 -0
  316. package/templates/presets/identity/context/test/integration/search-members-reader.spec.ts.tpl +86 -0
  317. package/templates/presets/identity/context/test/unit/accept-invitation.controller.spec.ts.tpl +57 -0
  318. package/templates/presets/identity/context/test/unit/accept-invitation.handler.spec.ts.tpl +69 -0
  319. package/templates/presets/identity/context/test/unit/account-deactivated.integration-handler.spec.ts.tpl +24 -0
  320. package/templates/presets/identity/context/test/unit/account-deactivated.publisher.spec.ts.tpl +25 -0
  321. package/templates/presets/identity/context/test/unit/account.aggregate.spec.ts.tpl +72 -0
  322. package/templates/presets/identity/context/test/unit/change-member-role.controller.spec.ts.tpl +57 -0
  323. package/templates/presets/identity/context/test/unit/change-member-role.handler.spec.ts.tpl +135 -0
  324. package/templates/presets/identity/context/test/unit/crypto-invitation-tokens.adapter.spec.ts.tpl +15 -0
  325. package/templates/presets/identity/context/test/unit/deactivate-account.controller.spec.ts.tpl +44 -0
  326. package/templates/presets/identity/context/test/unit/deactivate-account.handler.spec.ts.tpl +106 -0
  327. package/templates/presets/identity/context/test/unit/decline-invitation.controller.spec.ts.tpl +57 -0
  328. package/templates/presets/identity/context/test/unit/decline-invitation.handler.spec.ts.tpl +40 -0
  329. package/templates/presets/identity/context/test/unit/delegation-granted.publisher.spec.ts.tpl +36 -0
  330. package/templates/presets/identity/context/test/unit/delegation-period.vo.spec.ts.tpl +32 -0
  331. package/templates/presets/identity/context/test/unit/delegation-revoked.publisher.spec.ts.tpl +35 -0
  332. package/templates/presets/identity/context/test/unit/delegation.aggregate.spec.ts.tpl +111 -0
  333. package/templates/presets/identity/context/test/unit/display-name.vo.spec.ts.tpl +13 -0
  334. package/templates/presets/identity/context/test/unit/email-address.vo.spec.ts.tpl +24 -0
  335. package/templates/presets/identity/context/test/unit/environment-identity-provider-connection.adapter.spec.ts.tpl +41 -0
  336. package/templates/presets/identity/context/test/unit/grant-delegation.controller.spec.ts.tpl +57 -0
  337. package/templates/presets/identity/context/test/unit/grant-delegation.handler.spec.ts.tpl +66 -0
  338. package/templates/presets/identity/context/test/unit/identity-application.ts.tpl +79 -0
  339. package/templates/presets/identity/context/test/unit/invitation.aggregate.spec.ts.tpl +118 -0
  340. package/templates/presets/identity/context/test/unit/invite-member.controller.spec.ts.tpl +57 -0
  341. package/templates/presets/identity/context/test/unit/invite-member.handler.spec.ts.tpl +110 -0
  342. package/templates/presets/identity/context/test/unit/member-joined.integration-handler.spec.ts.tpl +38 -0
  343. package/templates/presets/identity/context/test/unit/member-joined.publisher.spec.ts.tpl +36 -0
  344. package/templates/presets/identity/context/test/unit/member-removed.integration-handler.spec.ts.tpl +45 -0
  345. package/templates/presets/identity/context/test/unit/member-removed.publisher.spec.ts.tpl +35 -0
  346. package/templates/presets/identity/context/test/unit/member-role-changed.publisher.spec.ts.tpl +36 -0
  347. package/templates/presets/identity/context/test/unit/member.aggregate.spec.ts.tpl +142 -0
  348. package/templates/presets/identity/context/test/unit/open-organisation.controller.spec.ts.tpl +57 -0
  349. package/templates/presets/identity/context/test/unit/open-organisation.handler.spec.ts.tpl +52 -0
  350. package/templates/presets/identity/context/test/unit/organisation-name.vo.spec.ts.tpl +13 -0
  351. package/templates/presets/identity/context/test/unit/organisation-opened.integration-handler.spec.ts.tpl +24 -0
  352. package/templates/presets/identity/context/test/unit/organisation-opened.publisher.spec.ts.tpl +35 -0
  353. package/templates/presets/identity/context/test/unit/organisation-rights.vo.spec.ts.tpl +23 -0
  354. package/templates/presets/identity/context/test/unit/organisation-slug.vo.spec.ts.tpl +17 -0
  355. package/templates/presets/identity/context/test/unit/organisation.aggregate.spec.ts.tpl +33 -0
  356. package/templates/presets/identity/context/test/unit/provision-account.controller.spec.ts.tpl +73 -0
  357. package/templates/presets/identity/context/test/unit/provision-account.handler.spec.ts.tpl +65 -0
  358. package/templates/presets/identity/context/test/unit/remove-member.controller.spec.ts.tpl +44 -0
  359. package/templates/presets/identity/context/test/unit/remove-member.handler.spec.ts.tpl +125 -0
  360. package/templates/presets/identity/context/test/unit/revoke-delegation.controller.spec.ts.tpl +44 -0
  361. package/templates/presets/identity/context/test/unit/revoke-delegation.handler.spec.ts.tpl +51 -0
  362. package/templates/presets/identity/context/test/unit/revoke-invitation.controller.spec.ts.tpl +44 -0
  363. package/templates/presets/identity/context/test/unit/revoke-invitation.handler.spec.ts.tpl +44 -0
  364. package/templates/presets/identity/context/test/unit/role-rights.service.spec.ts.tpl +54 -0
  365. package/templates/presets/identity/context/test/unit/search-members.controller.spec.ts.tpl +43 -0
  366. package/templates/presets/identity/context/test/unit/search-members.handler.spec.ts.tpl +38 -0
  367. package/templates/presets/identity/decision.md.tpl +37 -0
  368. package/templates/presets/identity/design.md.tpl +71 -0
  369. package/templates/presets/signed-in-grants.ts.tpl +12 -0
@@ -0,0 +1,45 @@
1
+ import type { AuditStamp } from '@ddd-ts-kit/kernel';
2
+ import { Invitation } from '{{alias.context}}/domain/aggregates/invitation.aggregate{{alias.ext}}';
3
+ import { EmailAddress } from '{{alias.context}}/domain/value-objects/email-address.vo{{alias.ext}}';
4
+ import type { invitations } from '{{alias.context}}/infrastructure/persistence/{{context.kebab}}.schema{{alias.ext}}';
5
+
6
+ type InvitationRow = typeof invitations.$inferSelect;
7
+
8
+ export type InvitationColumns = Omit<InvitationRow, 'version' | keyof AuditStamp>;
9
+
10
+ export const InvitationMapper = {
11
+ toDomain(row: InvitationRow): Invitation {
12
+ const loaded = Invitation.rehydrate(row.id, {
13
+ organisationId: row.organisationId,
14
+ email: EmailAddress.create(row.email),
15
+ role: row.role,
16
+ tokenHash: row.tokenHash,
17
+ invitedBy: row.invitedBy,
18
+ issuedAt: row.issuedAt,
19
+ expiresAt: row.expiresAt,
20
+ status: row.status,
21
+ answeredAt: row.answeredAt ?? undefined,
22
+ });
23
+
24
+ loaded.version = row.version;
25
+
26
+ return loaded;
27
+ },
28
+
29
+ toRow(saved: Invitation): InvitationColumns {
30
+ const state = saved.toState();
31
+
32
+ return {
33
+ id: saved.id,
34
+ organisationId: state.organisationId,
35
+ email: state.email.value,
36
+ role: state.role,
37
+ tokenHash: state.tokenHash,
38
+ invitedBy: state.invitedBy,
39
+ issuedAt: state.issuedAt,
40
+ expiresAt: state.expiresAt,
41
+ status: state.status,
42
+ answeredAt: state.answeredAt ?? null,
43
+ };
44
+ },
45
+ };
@@ -0,0 +1,40 @@
1
+ import { Invitation } from '{{alias.context}}/domain/aggregates/invitation.aggregate{{alias.ext}}';
2
+ import { EmailAddress } from '{{alias.context}}/domain/value-objects/email-address.vo{{alias.ext}}';
3
+ import type { InvitationColumns, InvitationRow } from '{{alias.context}}/infrastructure/persistence/invitation-row.schema{{alias.ext}}';
4
+
5
+ export const InvitationMapper = {
6
+ toDomain(row: InvitationRow): Invitation {
7
+ const loaded = Invitation.rehydrate(row.id, {
8
+ organisationId: row.organisation_id,
9
+ email: EmailAddress.create(row.email),
10
+ role: row.role,
11
+ tokenHash: row.token_hash,
12
+ invitedBy: row.invited_by,
13
+ issuedAt: row.issued_at,
14
+ expiresAt: row.expires_at,
15
+ status: row.status,
16
+ answeredAt: row.answered_at ?? undefined,
17
+ });
18
+
19
+ loaded.version = row.version;
20
+
21
+ return loaded;
22
+ },
23
+
24
+ toRow(saved: Invitation): InvitationColumns {
25
+ const state = saved.toState();
26
+
27
+ return {
28
+ id: saved.id,
29
+ organisation_id: state.organisationId,
30
+ email: state.email.value,
31
+ role: state.role,
32
+ token_hash: state.tokenHash,
33
+ invited_by: state.invitedBy,
34
+ issued_at: state.issuedAt,
35
+ expires_at: state.expiresAt,
36
+ status: state.status,
37
+ answered_at: state.answeredAt ?? null,
38
+ };
39
+ },
40
+ };
@@ -0,0 +1,61 @@
1
+ import type { Knex } from 'knex';
2
+ import { stampCreation, stampUpdate, type Clock, type ExecutionScope } from '@ddd-ts-kit/kernel';
3
+ import { Repository } from '@ddd-ts-kit/kernel/declarations';
4
+ import { type KnexContext, auditRow, insertIgnoringConflict, optimisticUpdate } from '@ddd-ts-kit/knex';
5
+ import type { Account } from '{{alias.context}}/domain/aggregates/account.aggregate{{alias.ext}}';
6
+ import { AccountRepository } from '{{alias.context}}/domain/repositories/account.repository{{alias.ext}}';
7
+ import { {{context.constant}}_SCHEMA } from '{{alias.context}}/infrastructure/persistence/{{context.kebab}}.schema{{alias.ext}}';
8
+ import { AccountMapper } from '{{alias.context}}/infrastructure/persistence/account.mapper{{alias.ext}}';
9
+ import type { AccountColumns, AccountRow } from '{{alias.context}}/infrastructure/persistence/account-row.schema{{alias.ext}}';
10
+
11
+ type VersionedAccount = AccountColumns & { readonly version: number };
12
+
13
+ @Repository(AccountRepository)
14
+ export class KnexAccountRepository implements AccountRepository {
15
+ constructor(
16
+ private readonly database: KnexContext,
17
+ private readonly scope: ExecutionScope,
18
+ private readonly clock: Clock,
19
+ ) {}
20
+
21
+ async findById(id: string): Promise<Account | undefined> {
22
+ const row = await tableOf(this.database).where({ id }).first();
23
+
24
+ return row === undefined ? undefined : AccountMapper.toDomain(row);
25
+ }
26
+
27
+ async save(saved: Account): Promise<void> {
28
+ await optimisticUpdate(saved, async ({ expected, next }) => {
29
+ const state = { ...AccountMapper.toRow(saved), version: next };
30
+
31
+ return expected === 0 ? this.insert(state) : this.update(state, expected);
32
+ });
33
+ }
34
+
35
+ private async insert(state: VersionedAccount): Promise<number> {
36
+ const row = { ...state, ...auditRow(stampCreation(this.scope, this.clock)) };
37
+
38
+ const inserted = await insertIgnoringConflict(this.database.executor(), tableOf(this.database), {
39
+ row,
40
+ conflict: 'id',
41
+ });
42
+
43
+ return inserted ? 1 : 0;
44
+ }
45
+
46
+ private async update(state: VersionedAccount, expected: number): Promise<number> {
47
+ return tableOf(this.database)
48
+ .where({ id: state.id, version: expected })
49
+ .update({ ...state, ...auditRow(stampUpdate(this.scope, this.clock)) });
50
+ }
51
+ }
52
+
53
+ /** A function and not a method: a query builder is thenable, and a method that answers one is read as asynchronous by whatever wraps the adapter (tracing), which then answers a promise. */
54
+ function tableOf(database: KnexContext): Knex.QueryBuilder<AccountRow, AccountRow[]> {
55
+ {{#if tenancy.any}}
56
+ // ddd-ts-kit-disable-next-line tenancy/scoped-statement -- {{preset.adr}}: the {{context.kebab}} context is the control plane of the tenants, and its rows belong to none of them.
57
+ {{/if}}
58
+ const table = database.executor()<AccountRow, AccountRow[]>('accounts');
59
+
60
+ return table.withSchema({{context.constant}}_SCHEMA);
61
+ }
@@ -0,0 +1,80 @@
1
+ import type { Knex } from 'knex';
2
+ import { stampCreation, stampUpdate, type Clock, type ExecutionScope } from '@ddd-ts-kit/kernel';
3
+ import { Repository } from '@ddd-ts-kit/kernel/declarations';
4
+ import { type KnexContext, auditRow, insertIgnoringConflict, optimisticUpdate } from '@ddd-ts-kit/knex';
5
+ import type { Delegation } from '{{alias.context}}/domain/aggregates/delegation.aggregate{{alias.ext}}';
6
+ import { DelegationRepository } from '{{alias.context}}/domain/repositories/delegation.repository{{alias.ext}}';
7
+ import { {{context.constant}}_SCHEMA } from '{{alias.context}}/infrastructure/persistence/{{context.kebab}}.schema{{alias.ext}}';
8
+ import { DelegationMapper } from '{{alias.context}}/infrastructure/persistence/delegation.mapper{{alias.ext}}';
9
+ import type { DelegationColumns, DelegationRow } from '{{alias.context}}/infrastructure/persistence/delegation-row.schema{{alias.ext}}';
10
+
11
+ type VersionedDelegation = DelegationColumns & { readonly version: number };
12
+
13
+ @Repository(DelegationRepository)
14
+ export class KnexDelegationRepository implements DelegationRepository {
15
+ constructor(
16
+ private readonly database: KnexContext,
17
+ private readonly scope: ExecutionScope,
18
+ private readonly clock: Clock,
19
+ ) {}
20
+
21
+ async findById(id: string): Promise<Delegation | undefined> {
22
+ const row = await tableOf(this.database).where({ id }).first();
23
+
24
+ return row === undefined ? undefined : DelegationMapper.toDomain(row);
25
+ }
26
+
27
+ async findUnendedTo(organisationId: string, delegateId: string): Promise<readonly Delegation[]> {
28
+ const lentTo = { organisation_id: organisationId, delegate_id: delegateId };
29
+ const rows = await tableOf(this.database).where(lentTo).whereNull('ended_at').orderBy('id');
30
+
31
+ return rows.map((row) => DelegationMapper.toDomain(row));
32
+ }
33
+
34
+ async findUnendedInvolving(organisationId: string, accountId: string): Promise<readonly Delegation[]> {
35
+ const rows = await tableOf(this.database)
36
+ .where({ organisation_id: organisationId })
37
+ .whereNull('ended_at')
38
+ .where((either) => {
39
+ void either.where({ delegator_id: accountId }).orWhere({ delegate_id: accountId });
40
+ })
41
+ .orderBy('id');
42
+
43
+ return rows.map((row) => DelegationMapper.toDomain(row));
44
+ }
45
+
46
+ async save(saved: Delegation): Promise<void> {
47
+ await optimisticUpdate(saved, async ({ expected, next }) => {
48
+ const state = { ...DelegationMapper.toRow(saved), version: next };
49
+
50
+ return expected === 0 ? this.insert(state) : this.update(state, expected);
51
+ });
52
+ }
53
+
54
+ private async insert(state: VersionedDelegation): Promise<number> {
55
+ const row = { ...state, ...auditRow(stampCreation(this.scope, this.clock)) };
56
+
57
+ const inserted = await insertIgnoringConflict(this.database.executor(), tableOf(this.database), {
58
+ row,
59
+ conflict: 'id',
60
+ });
61
+
62
+ return inserted ? 1 : 0;
63
+ }
64
+
65
+ private async update(state: VersionedDelegation, expected: number): Promise<number> {
66
+ return tableOf(this.database)
67
+ .where({ id: state.id, version: expected })
68
+ .update({ ...state, ...auditRow(stampUpdate(this.scope, this.clock)) });
69
+ }
70
+ }
71
+
72
+ /** A function and not a method: a query builder is thenable, and a method that answers one is read as asynchronous by whatever wraps the adapter (tracing), which then answers a promise. */
73
+ function tableOf(database: KnexContext): Knex.QueryBuilder<DelegationRow, DelegationRow[]> {
74
+ {{#if tenancy.any}}
75
+ // ddd-ts-kit-disable-next-line tenancy/scoped-statement -- {{preset.adr}}: the {{context.kebab}} context is the control plane of the tenants, and its rows belong to none of them.
76
+ {{/if}}
77
+ const table = database.executor()<DelegationRow, DelegationRow[]>('delegations');
78
+
79
+ return table.withSchema({{context.constant}}_SCHEMA);
80
+ }
@@ -0,0 +1,67 @@
1
+ import type { Knex } from 'knex';
2
+ import { stampCreation, stampUpdate, type Clock, type ExecutionScope } from '@ddd-ts-kit/kernel';
3
+ import { Repository } from '@ddd-ts-kit/kernel/declarations';
4
+ import { type KnexContext, auditRow, insertIgnoringConflict, optimisticUpdate } from '@ddd-ts-kit/knex';
5
+ import type { Invitation } from '{{alias.context}}/domain/aggregates/invitation.aggregate{{alias.ext}}';
6
+ import { InvitationRepository } from '{{alias.context}}/domain/repositories/invitation.repository{{alias.ext}}';
7
+ import { {{context.constant}}_SCHEMA } from '{{alias.context}}/infrastructure/persistence/{{context.kebab}}.schema{{alias.ext}}';
8
+ import { InvitationMapper } from '{{alias.context}}/infrastructure/persistence/invitation.mapper{{alias.ext}}';
9
+ import type { InvitationColumns, InvitationRow } from '{{alias.context}}/infrastructure/persistence/invitation-row.schema{{alias.ext}}';
10
+
11
+ type VersionedInvitation = InvitationColumns & { readonly version: number };
12
+
13
+ @Repository(InvitationRepository)
14
+ export class KnexInvitationRepository implements InvitationRepository {
15
+ constructor(
16
+ private readonly database: KnexContext,
17
+ private readonly scope: ExecutionScope,
18
+ private readonly clock: Clock,
19
+ ) {}
20
+
21
+ async findById(id: string): Promise<Invitation | undefined> {
22
+ const row = await tableOf(this.database).where({ id }).first();
23
+
24
+ return row === undefined ? undefined : InvitationMapper.toDomain(row);
25
+ }
26
+
27
+ async findByTokenHash(tokenHash: string): Promise<Invitation | undefined> {
28
+ const row = await tableOf(this.database).where({ token_hash: tokenHash }).first();
29
+
30
+ return row === undefined ? undefined : InvitationMapper.toDomain(row);
31
+ }
32
+
33
+ async save(saved: Invitation): Promise<void> {
34
+ await optimisticUpdate(saved, async ({ expected, next }) => {
35
+ const state = { ...InvitationMapper.toRow(saved), version: next };
36
+
37
+ return expected === 0 ? this.insert(state) : this.update(state, expected);
38
+ });
39
+ }
40
+
41
+ private async insert(state: VersionedInvitation): Promise<number> {
42
+ const row = { ...state, ...auditRow(stampCreation(this.scope, this.clock)) };
43
+
44
+ const inserted = await insertIgnoringConflict(this.database.executor(), tableOf(this.database), {
45
+ row,
46
+ conflict: 'id',
47
+ });
48
+
49
+ return inserted ? 1 : 0;
50
+ }
51
+
52
+ private async update(state: VersionedInvitation, expected: number): Promise<number> {
53
+ return tableOf(this.database)
54
+ .where({ id: state.id, version: expected })
55
+ .update({ ...state, ...auditRow(stampUpdate(this.scope, this.clock)) });
56
+ }
57
+ }
58
+
59
+ /** A function and not a method: a query builder is thenable, and a method that answers one is read as asynchronous by whatever wraps the adapter (tracing), which then answers a promise. */
60
+ function tableOf(database: KnexContext): Knex.QueryBuilder<InvitationRow, InvitationRow[]> {
61
+ {{#if tenancy.any}}
62
+ // ddd-ts-kit-disable-next-line tenancy/scoped-statement -- {{preset.adr}}: the {{context.kebab}} context is the control plane of the tenants, and its rows belong to none of them.
63
+ {{/if}}
64
+ const table = database.executor()<InvitationRow, InvitationRow[]>('invitations');
65
+
66
+ return table.withSchema({{context.constant}}_SCHEMA);
67
+ }
@@ -0,0 +1,82 @@
1
+ import type { Knex } from 'knex';
2
+ import { stampCreation, stampUpdate, type Clock, type ExecutionScope } from '@ddd-ts-kit/kernel';
3
+ import { Repository } from '@ddd-ts-kit/kernel/declarations';
4
+ import { type KnexContext, auditRow, insertIgnoringConflict, optimisticUpdate } from '@ddd-ts-kit/knex';
5
+ import type { Member } from '{{alias.context}}/domain/aggregates/member.aggregate{{alias.ext}}';
6
+ import { MemberRepository } from '{{alias.context}}/domain/repositories/member.repository{{alias.ext}}';
7
+ import { MemberRole } from '{{alias.context}}/domain/vocabularies/member-role.vocabulary{{alias.ext}}';
8
+ import { {{context.constant}}_SCHEMA } from '{{alias.context}}/infrastructure/persistence/{{context.kebab}}.schema{{alias.ext}}';
9
+ import { MemberMapper } from '{{alias.context}}/infrastructure/persistence/member.mapper{{alias.ext}}';
10
+ import type { MemberColumns, MemberRow } from '{{alias.context}}/infrastructure/persistence/member-row.schema{{alias.ext}}';
11
+
12
+ type VersionedMember = MemberColumns & { readonly version: number };
13
+
14
+ @Repository(MemberRepository)
15
+ export class KnexMemberRepository implements MemberRepository {
16
+ constructor(
17
+ private readonly database: KnexContext,
18
+ private readonly scope: ExecutionScope,
19
+ private readonly clock: Clock,
20
+ ) {}
21
+
22
+ async findById(id: string): Promise<Member | undefined> {
23
+ const row = await tableOf(this.database).where({ id }).first();
24
+
25
+ return row === undefined ? undefined : MemberMapper.toDomain(row);
26
+ }
27
+
28
+ async findOf(organisationId: string, accountId: string): Promise<Member | undefined> {
29
+ const current = { organisation_id: organisationId, account_id: accountId };
30
+ const row = await tableOf(this.database).where(current).whereNull('removed_at').first();
31
+
32
+ return row === undefined ? undefined : MemberMapper.toDomain(row);
33
+ }
34
+
35
+ async searchCurrentOf(accountId: string): Promise<readonly Member[]> {
36
+ const rows = await tableOf(this.database).where({ account_id: accountId }).whereNull('removed_at').orderBy('id');
37
+
38
+ return rows.map((row) => MemberMapper.toDomain(row));
39
+ }
40
+
41
+ async countOwners(organisationId: string): Promise<number> {
42
+ const owners = { organisation_id: organisationId, role: MemberRole.Owner };
43
+ const counted = await tableOf(this.database).where(owners).whereNull('removed_at').count({ total: '*' }).first();
44
+
45
+ return Number(counted?.total ?? 0);
46
+ }
47
+
48
+ async save(saved: Member): Promise<void> {
49
+ await optimisticUpdate(saved, async ({ expected, next }) => {
50
+ const state = { ...MemberMapper.toRow(saved), version: next };
51
+
52
+ return expected === 0 ? this.insert(state) : this.update(state, expected);
53
+ });
54
+ }
55
+
56
+ private async insert(state: VersionedMember): Promise<number> {
57
+ const row = { ...state, ...auditRow(stampCreation(this.scope, this.clock)) };
58
+
59
+ const inserted = await insertIgnoringConflict(this.database.executor(), tableOf(this.database), {
60
+ row,
61
+ conflict: 'id',
62
+ });
63
+
64
+ return inserted ? 1 : 0;
65
+ }
66
+
67
+ private async update(state: VersionedMember, expected: number): Promise<number> {
68
+ return tableOf(this.database)
69
+ .where({ id: state.id, version: expected })
70
+ .update({ ...state, ...auditRow(stampUpdate(this.scope, this.clock)) });
71
+ }
72
+ }
73
+
74
+ /** A function and not a method: a query builder is thenable, and a method that answers one is read as asynchronous by whatever wraps the adapter (tracing), which then answers a promise. */
75
+ function tableOf(database: KnexContext): Knex.QueryBuilder<MemberRow, MemberRow[]> {
76
+ {{#if tenancy.any}}
77
+ // ddd-ts-kit-disable-next-line tenancy/scoped-statement -- {{preset.adr}}: the {{context.kebab}} context is the control plane of the tenants, and its rows belong to none of them.
78
+ {{/if}}
79
+ const table = database.executor()<MemberRow, MemberRow[]>('members');
80
+
81
+ return table.withSchema({{context.constant}}_SCHEMA);
82
+ }
@@ -0,0 +1,61 @@
1
+ import type { Knex } from 'knex';
2
+ import { stampCreation, stampUpdate, type Clock, type ExecutionScope } from '@ddd-ts-kit/kernel';
3
+ import { Repository } from '@ddd-ts-kit/kernel/declarations';
4
+ import { type KnexContext, auditRow, insertIgnoringConflict, optimisticUpdate } from '@ddd-ts-kit/knex';
5
+ import type { Organisation } from '{{alias.context}}/domain/aggregates/organisation.aggregate{{alias.ext}}';
6
+ import { OrganisationRepository } from '{{alias.context}}/domain/repositories/organisation.repository{{alias.ext}}';
7
+ import { {{context.constant}}_SCHEMA } from '{{alias.context}}/infrastructure/persistence/{{context.kebab}}.schema{{alias.ext}}';
8
+ import { OrganisationMapper } from '{{alias.context}}/infrastructure/persistence/organisation.mapper{{alias.ext}}';
9
+ import type { OrganisationColumns, OrganisationRow } from '{{alias.context}}/infrastructure/persistence/organisation-row.schema{{alias.ext}}';
10
+
11
+ type VersionedOrganisation = OrganisationColumns & { readonly version: number };
12
+
13
+ @Repository(OrganisationRepository)
14
+ export class KnexOrganisationRepository implements OrganisationRepository {
15
+ constructor(
16
+ private readonly database: KnexContext,
17
+ private readonly scope: ExecutionScope,
18
+ private readonly clock: Clock,
19
+ ) {}
20
+
21
+ async findById(id: string): Promise<Organisation | undefined> {
22
+ const row = await tableOf(this.database).where({ id }).first();
23
+
24
+ return row === undefined ? undefined : OrganisationMapper.toDomain(row);
25
+ }
26
+
27
+ async save(saved: Organisation): Promise<void> {
28
+ await optimisticUpdate(saved, async ({ expected, next }) => {
29
+ const state = { ...OrganisationMapper.toRow(saved), version: next };
30
+
31
+ return expected === 0 ? this.insert(state) : this.update(state, expected);
32
+ });
33
+ }
34
+
35
+ private async insert(state: VersionedOrganisation): Promise<number> {
36
+ const row = { ...state, ...auditRow(stampCreation(this.scope, this.clock)) };
37
+
38
+ const inserted = await insertIgnoringConflict(this.database.executor(), tableOf(this.database), {
39
+ row,
40
+ conflict: 'id',
41
+ });
42
+
43
+ return inserted ? 1 : 0;
44
+ }
45
+
46
+ private async update(state: VersionedOrganisation, expected: number): Promise<number> {
47
+ return tableOf(this.database)
48
+ .where({ id: state.id, version: expected })
49
+ .update({ ...state, ...auditRow(stampUpdate(this.scope, this.clock)) });
50
+ }
51
+ }
52
+
53
+ /** A function and not a method: a query builder is thenable, and a method that answers one is read as asynchronous by whatever wraps the adapter (tracing), which then answers a promise. */
54
+ function tableOf(database: KnexContext): Knex.QueryBuilder<OrganisationRow, OrganisationRow[]> {
55
+ {{#if tenancy.any}}
56
+ // ddd-ts-kit-disable-next-line tenancy/scoped-statement -- {{preset.adr}}: the {{context.kebab}} context is the control plane of the tenants, and its rows belong to none of them.
57
+ {{/if}}
58
+ const table = database.executor()<OrganisationRow, OrganisationRow[]>('organisations');
59
+
60
+ return table.withSchema({{context.constant}}_SCHEMA);
61
+ }
@@ -0,0 +1,77 @@
1
+ import type { Knex } from 'knex';
2
+ import { InvalidPageRequestError, pageOf, type PageRequest } from '@ddd-ts-kit/kernel';
3
+ import { Repository } from '@ddd-ts-kit/kernel/declarations';
4
+ import type { KnexContext } from '@ddd-ts-kit/knex';
5
+ import { SearchMembersReader } from '{{alias.context}}/application/ports/search-members-reader.port{{alias.ext}}';
6
+ import type {
7
+ SearchMembersItem,
8
+ SearchMembersResult,
9
+ SearchMembersSort,
10
+ } from '{{alias.context}}/application/queries/search-members/search-members.result{{alias.ext}}';
11
+ import { {{context.constant}}_SCHEMA } from '{{alias.context}}/infrastructure/persistence/{{context.kebab}}.schema{{alias.ext}}';
12
+ import type { MemberRow } from '{{alias.context}}/infrastructure/persistence/member-row.schema{{alias.ext}}';
13
+
14
+ interface MemberLine extends Pick<MemberRow, 'id' | 'account_id' | 'role' | 'joined_at'> {
15
+ readonly display_name: string;
16
+ readonly email: string;
17
+ }
18
+
19
+ const COLUMNS = [
20
+ 'members.id',
21
+ 'members.account_id',
22
+ 'members.role',
23
+ 'members.joined_at',
24
+ 'accounts.display_name',
25
+ 'accounts.email',
26
+ ];
27
+
28
+ /** The current members of an organisation joined to the profile of their account: one statement, the page applied by the database. */
29
+ @Repository(SearchMembersReader)
30
+ export class KnexSearchMembersReader implements SearchMembersReader {
31
+ constructor(private readonly database: KnexContext) {}
32
+
33
+ async search(organisationId: string, page: PageRequest<SearchMembersSort>): Promise<SearchMembersResult> {
34
+ if (page.kind !== 'offset') {
35
+ throw new InvalidPageRequestError('The members of an organisation are read by offset');
36
+ }
37
+
38
+ const direction = page.sort[0]?.direction ?? 'asc';
39
+ const current = { 'members.organisation_id': organisationId, 'members.removed_at': null };
40
+
41
+ const rows = await membersOf(this.database)
42
+ .select<MemberLine[]>(COLUMNS)
43
+ .join('accounts', 'accounts.id', 'members.account_id')
44
+ .where(current)
45
+ .orderBy([
46
+ { column: 'members.joined_at', order: direction },
47
+ { column: 'members.id', order: direction },
48
+ ])
49
+ .limit(page.limit)
50
+ .offset(page.offset);
51
+
52
+ const counted = await membersOf(this.database).where(current).count<{ total: string | number }[]>({ total: '*' }).first();
53
+
54
+ return pageOf(rows.map(toItem), { total: Number(counted?.total ?? 0) });
55
+ }
56
+ }
57
+
58
+ /** A function and not a method: a query builder is thenable, and a method that answers one is read as asynchronous by whatever wraps the adapter (tracing), which then answers a promise. */
59
+ function membersOf(database: KnexContext): Knex.QueryBuilder<MemberLine, MemberLine[]> {
60
+ {{#if tenancy.any}}
61
+ // ddd-ts-kit-disable-next-line tenancy/scoped-statement -- {{preset.adr}}: the {{context.kebab}} context is the control plane of the tenants, and its rows belong to none of them.
62
+ {{/if}}
63
+ const members = database.executor()<MemberLine, MemberLine[]>('members');
64
+
65
+ return members.withSchema({{context.constant}}_SCHEMA);
66
+ }
67
+
68
+ function toItem(line: MemberLine): SearchMembersItem {
69
+ return {
70
+ memberId: line.id,
71
+ accountId: line.account_id,
72
+ displayName: line.display_name,
73
+ email: line.email,
74
+ role: line.role,
75
+ joinedAt: line.joined_at,
76
+ };
77
+ }
@@ -0,0 +1,15 @@
1
+ import type { AuditRow } from '@ddd-ts-kit/knex';
2
+ import type { MemberRole } from '{{alias.context}}/domain/vocabularies/member-role.vocabulary{{alias.ext}}';
3
+
4
+ export interface MemberColumns {
5
+ readonly id: string;
6
+ readonly organisation_id: string;
7
+ readonly account_id: string;
8
+ readonly role: MemberRole;
9
+ readonly joined_at: Date;
10
+ readonly removed_at: Date | null;
11
+ }
12
+
13
+ export interface MemberRow extends MemberColumns, AuditRow {
14
+ readonly version: number;
15
+ }
@@ -0,0 +1,36 @@
1
+ import type { AuditStamp } from '@ddd-ts-kit/kernel';
2
+ import { Member } from '{{alias.context}}/domain/aggregates/member.aggregate{{alias.ext}}';
3
+ import type { members } from '{{alias.context}}/infrastructure/persistence/{{context.kebab}}.schema{{alias.ext}}';
4
+
5
+ type MemberRow = typeof members.$inferSelect;
6
+
7
+ export type MemberColumns = Omit<MemberRow, 'version' | keyof AuditStamp>;
8
+
9
+ export const MemberMapper = {
10
+ toDomain(row: MemberRow): Member {
11
+ const loaded = Member.rehydrate(row.id, {
12
+ organisationId: row.organisationId,
13
+ accountId: row.accountId,
14
+ role: row.role,
15
+ joinedAt: row.joinedAt,
16
+ removedAt: row.removedAt ?? undefined,
17
+ });
18
+
19
+ loaded.version = row.version;
20
+
21
+ return loaded;
22
+ },
23
+
24
+ toRow(saved: Member): MemberColumns {
25
+ const state = saved.toState();
26
+
27
+ return {
28
+ id: saved.id,
29
+ organisationId: state.organisationId,
30
+ accountId: state.accountId,
31
+ role: state.role,
32
+ joinedAt: state.joinedAt,
33
+ removedAt: state.removedAt ?? null,
34
+ };
35
+ },
36
+ };
@@ -0,0 +1,31 @@
1
+ import { Member } from '{{alias.context}}/domain/aggregates/member.aggregate{{alias.ext}}';
2
+ import type { MemberColumns, MemberRow } from '{{alias.context}}/infrastructure/persistence/member-row.schema{{alias.ext}}';
3
+
4
+ export const MemberMapper = {
5
+ toDomain(row: MemberRow): Member {
6
+ const loaded = Member.rehydrate(row.id, {
7
+ organisationId: row.organisation_id,
8
+ accountId: row.account_id,
9
+ role: row.role,
10
+ joinedAt: row.joined_at,
11
+ removedAt: row.removed_at ?? undefined,
12
+ });
13
+
14
+ loaded.version = row.version;
15
+
16
+ return loaded;
17
+ },
18
+
19
+ toRow(saved: Member): MemberColumns {
20
+ const state = saved.toState();
21
+
22
+ return {
23
+ id: saved.id,
24
+ organisation_id: state.organisationId,
25
+ account_id: state.accountId,
26
+ role: state.role,
27
+ joined_at: state.joinedAt,
28
+ removed_at: state.removedAt ?? null,
29
+ };
30
+ },
31
+ };
@@ -0,0 +1,12 @@
1
+ import type { AuditRow } from '@ddd-ts-kit/knex';
2
+
3
+ export interface OrganisationColumns {
4
+ readonly id: string;
5
+ readonly name: string;
6
+ readonly opened_by: string;
7
+ readonly opened_at: Date;
8
+ }
9
+
10
+ export interface OrganisationRow extends OrganisationColumns, AuditRow {
11
+ readonly version: number;
12
+ }
@@ -0,0 +1,28 @@
1
+ import type { AuditStamp } from '@ddd-ts-kit/kernel';
2
+ import { Organisation } from '{{alias.context}}/domain/aggregates/organisation.aggregate{{alias.ext}}';
3
+ import { OrganisationName } from '{{alias.context}}/domain/value-objects/organisation-name.vo{{alias.ext}}';
4
+ import type { organisations } from '{{alias.context}}/infrastructure/persistence/{{context.kebab}}.schema{{alias.ext}}';
5
+
6
+ type OrganisationRow = typeof organisations.$inferSelect;
7
+
8
+ export type OrganisationColumns = Omit<OrganisationRow, 'version' | keyof AuditStamp>;
9
+
10
+ export const OrganisationMapper = {
11
+ toDomain(row: OrganisationRow): Organisation {
12
+ const loaded = Organisation.rehydrate(row.id, {
13
+ name: OrganisationName.create(row.name),
14
+ openedBy: row.openedBy,
15
+ openedAt: row.openedAt,
16
+ });
17
+
18
+ loaded.version = row.version;
19
+
20
+ return loaded;
21
+ },
22
+
23
+ toRow(saved: Organisation): OrganisationColumns {
24
+ const state = saved.toState();
25
+
26
+ return { id: saved.id, name: state.name.value, openedBy: state.openedBy, openedAt: state.openedAt };
27
+ },
28
+ };