@flowdular/sdk 0.2.4 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (930) hide show
  1. package/.ai/agents/sandbox/business-manager.md +5 -1
  2. package/.ai/blueprints/author-spec/README.md +1 -1
  3. package/.ai/blueprints/author-spec/spec-requirements.yaml +44 -0
  4. package/.ai/blueprints/author-spec/steps.yaml +5 -5
  5. package/.ai/blueprints/author-spec/templates/module.yaml +99 -3
  6. package/.ai/blueprints/edit-module/gates.yaml +4 -0
  7. package/.ai/blueprints/edit-module/required-files.yaml +9 -0
  8. package/.ai/blueprints/new-module/gates.yaml +4 -0
  9. package/.ai/blueprints/new-module/spec-requirements.yaml +2 -2
  10. package/.ai/blueprints/release/gates.yaml +4 -0
  11. package/.ai/platform-capabilities.md +128 -0
  12. package/.ai/policies/capabilities.yaml +130 -3
  13. package/.ai/policies/path-ownership.yaml +5 -2
  14. package/.ai/policies/task-budgets.yaml +5 -3
  15. package/.ai/references/catalog/module.json +4 -4
  16. package/.ai/references/catalog/package.json +2 -2
  17. package/.ai/references/catalog/spec/module.yaml +5 -3
  18. package/.ai/references/catalog/src/platform.ts +2 -0
  19. package/.ai/references/catalog/src/services/catalog-service.ts +89 -1
  20. package/.ai/references/catalog/src/services/data-classes.ts +47 -0
  21. package/.ai/references/catalog/src/services/database-repository.ts +98 -1
  22. package/.ai/references/catalog/src/services/repository.ts +22 -1
  23. package/.ai/references/catalog/tests/data-classes.test.ts +157 -0
  24. package/.ai/references/catalog.provenance.json +12 -10
  25. package/.ai/skills/README.md +10 -0
  26. package/.ai/skills/agent-tool-design/SKILL.md +1 -2
  27. package/.ai/skills/auth-security-review/SKILL.md +1 -1
  28. package/.ai/skills/business-agent-design/SKILL.md +0 -1
  29. package/.ai/skills/cli-extension/SKILL.md +1 -1
  30. package/.ai/skills/deploy-operate/SKILL.md +119 -0
  31. package/.ai/skills/module-new/SKILL.md +29 -3
  32. package/.ai/skills/module-update/SKILL.md +9 -3
  33. package/.ai/skills/perf-audit/SKILL.md +0 -1
  34. package/.ai/skills/release-eject-pr/SKILL.md +0 -1
  35. package/.ai/skills/spec-interview/SKILL.md +120 -0
  36. package/.ai/skills/test-hardening/SKILL.md +1 -0
  37. package/.ai/skills/ux-design/SKILL.md +34 -3
  38. package/.ai/skills/variables/SKILL.md +0 -2
  39. package/.ai/skills/workflow-development/SKILL.md +0 -1
  40. package/AGENTS.md +4 -0
  41. package/docs/agent-contract.md +2 -2
  42. package/docs/design-system.md +185 -31
  43. package/modules/access/migrations/0001_access_core.down.sql +1 -0
  44. package/modules/access/migrations/0001_access_core.up.sql +28 -0
  45. package/modules/access/migrations/README.md +3 -0
  46. package/modules/access/module.json +33 -0
  47. package/modules/access/package.json +43 -0
  48. package/modules/access/spec/module.yaml +225 -0
  49. package/modules/access/src/acl/permissions.ts +6 -0
  50. package/modules/access/src/api/endpoints.ts +244 -0
  51. package/modules/access/src/client/ActivityView.tsrx +245 -0
  52. package/modules/access/src/client/AttestationsView.tsrx +176 -0
  53. package/modules/access/src/client/ReviewView.tsrx +586 -0
  54. package/modules/access/src/client/api.ts +132 -0
  55. package/modules/access/src/client/contribution.tsrx +41 -0
  56. package/modules/access/src/client/index.ts +41 -0
  57. package/modules/access/src/client/navigation.ts +57 -0
  58. package/modules/access/src/client/presentation.ts +69 -0
  59. package/modules/access/src/client/state.ts +72 -0
  60. package/modules/access/src/domain/types.ts +189 -0
  61. package/modules/access/src/index.ts +38 -0
  62. package/modules/access/src/platform.ts +60 -0
  63. package/modules/access/src/server/index.ts +42 -0
  64. package/modules/access/src/server/runtime.ts +105 -0
  65. package/modules/access/src/services/access-service.ts +298 -0
  66. package/modules/access/src/services/audit-window.ts +78 -0
  67. package/modules/access/src/services/auth-directory.ts +72 -0
  68. package/modules/access/src/services/changes.ts +91 -0
  69. package/modules/access/src/services/data-classes.ts +28 -0
  70. package/modules/access/src/services/database-repository.ts +185 -0
  71. package/modules/access/src/services/directory.ts +102 -0
  72. package/modules/access/src/services/index.ts +33 -0
  73. package/modules/access/src/services/list-exports.ts +150 -0
  74. package/modules/access/src/services/migration.ts +54 -0
  75. package/modules/access/src/services/repository.ts +30 -0
  76. package/modules/access/src/services/review.ts +178 -0
  77. package/modules/access/translations/en.json +155 -0
  78. package/modules/access/translations/pl.json +155 -0
  79. package/modules/agents/migrations/0022_credential_rotation_inventory.down.sql +2 -0
  80. package/modules/agents/migrations/0022_credential_rotation_inventory.up.sql +7 -0
  81. package/modules/agents/migrations/0023_agents_retention_indexes.down.sql +2 -0
  82. package/modules/agents/migrations/0023_agents_retention_indexes.up.sql +9 -0
  83. package/modules/agents/migrations/0024_agent_meter_refusals.down.sql +3 -0
  84. package/modules/agents/migrations/0024_agent_meter_refusals.up.sql +22 -0
  85. package/modules/agents/migrations/0025_agent_list_indexes.down.sql +3 -0
  86. package/modules/agents/migrations/0025_agent_list_indexes.up.sql +11 -0
  87. package/modules/agents/module.json +30 -4
  88. package/modules/agents/package.json +2 -1
  89. package/modules/agents/spec/module.yaml +63 -5
  90. package/modules/agents/src/api/endpoints.ts +291 -33
  91. package/modules/agents/src/cli/commands.json +11 -0
  92. package/modules/agents/src/cli/index.ts +45 -0
  93. package/modules/agents/src/client/AgentDashboardWidget.tsrx +19 -11
  94. package/modules/agents/src/client/AgentDefinitionList.tsrx +53 -10
  95. package/modules/agents/src/client/AgentPlaygroundView.tsrx +4 -4
  96. package/modules/agents/src/client/AgentProceduresView.tsrx +2 -2
  97. package/modules/agents/src/client/AgentRunsView.tsrx +143 -40
  98. package/modules/agents/src/client/AgentsView.tsrx +109 -33
  99. package/modules/agents/src/client/api.ts +99 -21
  100. package/modules/agents/src/client/state.ts +97 -0
  101. package/modules/agents/src/domain/types.ts +44 -4
  102. package/modules/agents/src/platform.ts +42 -0
  103. package/modules/agents/src/server/action-execution.ts +201 -106
  104. package/modules/agents/src/server/runtime.ts +32 -0
  105. package/modules/agents/src/services/agent-service.ts +158 -30
  106. package/modules/agents/src/services/credential-rotation.ts +185 -0
  107. package/modules/agents/src/services/credential-vault.ts +71 -46
  108. package/modules/agents/src/services/data-classes.ts +176 -0
  109. package/modules/agents/src/services/database-repository.ts +393 -101
  110. package/modules/agents/src/services/metering.ts +111 -0
  111. package/modules/agents/src/services/migration.ts +109 -0
  112. package/modules/agents/src/services/module-agent-preflight.ts +17 -9
  113. package/modules/agents/src/services/notifications.ts +98 -0
  114. package/modules/agents/src/services/provider-repository.ts +9 -18
  115. package/modules/agents/src/services/provider-service.ts +1 -9
  116. package/modules/agents/src/services/reports.ts +107 -0
  117. package/modules/agents/src/services/repository.ts +86 -6
  118. package/modules/agents/src/services/usage-service.ts +2 -0
  119. package/modules/agents/src/services/worker.ts +114 -4
  120. package/modules/agents/src/settings.ts +14 -3
  121. package/modules/agents/translations/en.json +14 -4
  122. package/modules/agents/translations/pl.json +14 -4
  123. package/modules/approvals/migrations/0001_approvals_core.down.sql +3 -0
  124. package/modules/approvals/migrations/0001_approvals_core.up.sql +78 -0
  125. package/modules/approvals/migrations/0002_approvals_expiry_routing_role.down.sql +2 -0
  126. package/modules/approvals/migrations/0002_approvals_expiry_routing_role.up.sql +19 -0
  127. package/modules/approvals/migrations/0003_approvals_retention_indexes.down.sql +6 -0
  128. package/modules/approvals/migrations/0003_approvals_retention_indexes.up.sql +14 -0
  129. package/modules/approvals/migrations/0004_approvals_inbox_keyset_indexes.down.sql +5 -0
  130. package/modules/approvals/migrations/0004_approvals_inbox_keyset_indexes.up.sql +12 -0
  131. package/modules/approvals/migrations/0005_approvals_grant_audit.down.sql +4 -0
  132. package/modules/approvals/migrations/0005_approvals_grant_audit.up.sql +20 -0
  133. package/modules/approvals/migrations/README.md +3 -0
  134. package/modules/approvals/module.json +34 -0
  135. package/modules/approvals/package.json +43 -0
  136. package/modules/approvals/spec/module.yaml +327 -0
  137. package/modules/approvals/src/acl/permissions.ts +7 -0
  138. package/modules/approvals/src/api/endpoints.ts +457 -0
  139. package/modules/approvals/src/client/ApprovalRequestDrawer.tsrx +138 -0
  140. package/modules/approvals/src/client/ApprovalsView.tsrx +562 -0
  141. package/modules/approvals/src/client/PendingCountWidget.tsrx +52 -0
  142. package/modules/approvals/src/client/api.ts +203 -0
  143. package/modules/approvals/src/client/approvals.css +51 -0
  144. package/modules/approvals/src/client/contribution.tsrx +44 -0
  145. package/modules/approvals/src/client/index.ts +26 -0
  146. package/modules/approvals/src/client/navigation.ts +39 -0
  147. package/modules/approvals/src/client/pending-count.ts +26 -0
  148. package/modules/approvals/src/client/presentation.ts +52 -0
  149. package/modules/approvals/src/client/state.ts +141 -0
  150. package/modules/approvals/src/domain/capability.ts +107 -0
  151. package/modules/approvals/src/domain/grant.ts +69 -0
  152. package/modules/approvals/src/domain/types.ts +168 -0
  153. package/modules/approvals/src/index.ts +61 -0
  154. package/modules/approvals/src/platform.ts +136 -0
  155. package/modules/approvals/src/server/index.ts +42 -0
  156. package/modules/approvals/src/server/runtime.ts +185 -0
  157. package/modules/approvals/src/services/approvals-service.ts +637 -0
  158. package/modules/approvals/src/services/callbacks.ts +75 -0
  159. package/modules/approvals/src/services/data-classes.ts +167 -0
  160. package/modules/approvals/src/services/database-repository.ts +783 -0
  161. package/modules/approvals/src/services/eligibility.ts +121 -0
  162. package/modules/approvals/src/services/expiry-runner.ts +81 -0
  163. package/modules/approvals/src/services/index.ts +31 -0
  164. package/modules/approvals/src/services/migration.ts +256 -0
  165. package/modules/approvals/src/services/notifications.ts +95 -0
  166. package/modules/approvals/src/services/repository.ts +169 -0
  167. package/modules/approvals/src/services/service-error.ts +70 -0
  168. package/modules/approvals/src/settings.ts +102 -0
  169. package/modules/approvals/translations/en.json +104 -0
  170. package/modules/approvals/translations/pl.json +104 -0
  171. package/modules/audit/migrations/0001_audit_core.down.sql +8 -0
  172. package/modules/audit/migrations/0001_audit_core.up.sql +107 -0
  173. package/modules/audit/migrations/0002_audit_sweep_routing_role.down.sql +2 -0
  174. package/modules/audit/migrations/0002_audit_sweep_routing_role.up.sql +21 -0
  175. package/modules/audit/migrations/0003_audit_export_request.down.sql +12 -0
  176. package/modules/audit/migrations/0003_audit_export_request.up.sql +40 -0
  177. package/modules/audit/migrations/0004_audit_hold_seal_erasure.down.sql +17 -0
  178. package/modules/audit/migrations/0004_audit_hold_seal_erasure.up.sql +192 -0
  179. package/modules/audit/migrations/0005_audit_event_format.down.sql +17 -0
  180. package/modules/audit/migrations/0005_audit_event_format.up.sql +60 -0
  181. package/modules/audit/migrations/0006_audit_own_class_walks.down.sql +3 -0
  182. package/modules/audit/migrations/0006_audit_own_class_walks.up.sql +14 -0
  183. package/modules/audit/migrations/README.md +3 -0
  184. package/modules/audit/module.json +32 -0
  185. package/modules/audit/package.json +49 -0
  186. package/modules/audit/spec/module.yaml +809 -0
  187. package/modules/audit/src/acl/permissions.ts +7 -0
  188. package/modules/audit/src/api/endpoints.ts +315 -0
  189. package/modules/audit/src/cli/commands.json +95 -0
  190. package/modules/audit/src/cli/index.ts +658 -0
  191. package/modules/audit/src/client/DataClassesView.tsrx +420 -0
  192. package/modules/audit/src/client/ExportsView.tsrx +256 -0
  193. package/modules/audit/src/client/HoldsView.tsrx +604 -0
  194. package/modules/audit/src/client/SweepsView.tsrx +251 -0
  195. package/modules/audit/src/client/api.ts +185 -0
  196. package/modules/audit/src/client/contribution.tsrx +52 -0
  197. package/modules/audit/src/client/index.ts +23 -0
  198. package/modules/audit/src/client/navigation.ts +74 -0
  199. package/modules/audit/src/client/presentation.ts +105 -0
  200. package/modules/audit/src/client/state.ts +86 -0
  201. package/modules/audit/src/domain/data-classes.ts +16 -0
  202. package/modules/audit/src/domain/types.ts +339 -0
  203. package/modules/audit/src/index.ts +76 -0
  204. package/modules/audit/src/platform.ts +44 -0
  205. package/modules/audit/src/server/index.ts +177 -0
  206. package/modules/audit/src/server/runtime.ts +303 -0
  207. package/modules/audit/src/services/anchor-key.ts +143 -0
  208. package/modules/audit/src/services/anchor-rotation.ts +147 -0
  209. package/modules/audit/src/services/audit-runners.ts +278 -0
  210. package/modules/audit/src/services/backup-guard.ts +95 -0
  211. package/modules/audit/src/services/database-repository.ts +1946 -0
  212. package/modules/audit/src/services/declared-classes.ts +64 -0
  213. package/modules/audit/src/services/erasure-port.ts +126 -0
  214. package/modules/audit/src/services/erasure-service.ts +770 -0
  215. package/modules/audit/src/services/export-directory.ts +106 -0
  216. package/modules/audit/src/services/export-service.ts +714 -0
  217. package/modules/audit/src/services/hold-service.ts +295 -0
  218. package/modules/audit/src/services/index.ts +17 -0
  219. package/modules/audit/src/services/migration.ts +658 -0
  220. package/modules/audit/src/services/own-classes.ts +318 -0
  221. package/modules/audit/src/services/platform-version.ts +23 -0
  222. package/modules/audit/src/services/repository.ts +495 -0
  223. package/modules/audit/src/services/retention-service.ts +191 -0
  224. package/modules/audit/src/services/seal-service.ts +769 -0
  225. package/modules/audit/src/services/service-error.ts +61 -0
  226. package/modules/audit/src/services/subject-keys.ts +90 -0
  227. package/modules/audit/src/services/sweep-service.ts +298 -0
  228. package/modules/audit/src/services/zip.ts +217 -0
  229. package/modules/audit/src/settings.ts +69 -0
  230. package/modules/audit/translations/en.json +210 -0
  231. package/modules/audit/translations/pl.json +210 -0
  232. package/modules/auth/README.md +77 -29
  233. package/modules/auth/migrations/0016_external_identities.up.sql +16 -0
  234. package/modules/auth/migrations/0017_mfa_key_id.up.sql +8 -0
  235. package/modules/auth/migrations/0018_retire_bundled_module_scopes.up.sql +29 -0
  236. package/modules/auth/migrations/0019_notifications_member_scopes.up.sql +44 -0
  237. package/modules/auth/migrations/0020_identity_providers.up.sql +61 -0
  238. package/modules/auth/migrations/0021_membership_status.up.sql +19 -0
  239. package/modules/auth/migrations/0022_auth_provider_scopes.up.sql +45 -0
  240. package/modules/auth/migrations/0023_enterprise_module_scopes.down.sql +19 -0
  241. package/modules/auth/migrations/0023_enterprise_module_scopes.up.sql +71 -0
  242. package/modules/auth/migrations/0024_approvals_member_scopes.down.sql +22 -0
  243. package/modules/auth/migrations/0024_approvals_member_scopes.up.sql +48 -0
  244. package/modules/auth/migrations/0025_audit_holds_owner_scope.down.sql +21 -0
  245. package/modules/auth/migrations/0025_audit_holds_owner_scope.up.sql +47 -0
  246. package/modules/auth/migrations/0026_auth_export_keyset_indexes.down.sql +6 -0
  247. package/modules/auth/migrations/0026_auth_export_keyset_indexes.up.sql +15 -0
  248. package/modules/auth/migrations/0027_workflow_automation_profile_scopes.down.sql +34 -0
  249. package/modules/auth/migrations/0027_workflow_automation_profile_scopes.up.sql +72 -0
  250. package/modules/auth/migrations/0028_auth_member_search_prefix_indexes.down.sql +6 -0
  251. package/modules/auth/migrations/0028_auth_member_search_prefix_indexes.up.sql +21 -0
  252. package/modules/auth/migrations/0029_reports_exports_access_scopes.down.sql +22 -0
  253. package/modules/auth/migrations/0029_reports_exports_access_scopes.up.sql +56 -0
  254. package/modules/auth/migrations/0030_auth_membership_keyset_index.down.sql +4 -0
  255. package/modules/auth/migrations/0030_auth_membership_keyset_index.up.sql +10 -0
  256. package/modules/auth/migrations/0031_membership_scope_backfills_under_rls.down.sql +4 -0
  257. package/modules/auth/migrations/0031_membership_scope_backfills_under_rls.up.sql +31 -0
  258. package/modules/auth/migrations/0032_audit_actor_service.down.sql +9 -0
  259. package/modules/auth/migrations/0032_audit_actor_service.up.sql +10 -0
  260. package/modules/auth/migrations/0033_auth_account_keyset_indexes.down.sql +5 -0
  261. package/modules/auth/migrations/0033_auth_account_keyset_indexes.up.sql +23 -0
  262. package/modules/auth/module.json +3 -3
  263. package/modules/auth/package.json +2 -1
  264. package/modules/auth/spec/module.yaml +372 -3
  265. package/modules/auth/src/acl/scopes.ts +50 -6
  266. package/modules/auth/src/cli/commands.json +11 -0
  267. package/modules/auth/src/cli/greenfield.ts +2 -2
  268. package/modules/auth/src/cli/index.ts +14 -0
  269. package/modules/auth/src/cli/secrets.ts +114 -0
  270. package/modules/auth/src/client/AuthenticationCore.tsrx +61 -8
  271. package/modules/auth/src/client/api.ts +72 -6
  272. package/modules/auth/src/client/contribution.tsrx +23 -0
  273. package/modules/auth/src/client/index.ts +6 -0
  274. package/modules/auth/src/client/providers/IdentityProviderForm.tsrx +191 -0
  275. package/modules/auth/src/client/providers/IdentityProvidersView.tsrx +358 -0
  276. package/modules/auth/src/client/providers/api.ts +128 -0
  277. package/modules/auth/src/client/providers/provider-columns.tsrx +60 -0
  278. package/modules/auth/src/client/providers/state.ts +35 -0
  279. package/modules/auth/src/client/security/SecurityView.tsrx +9 -1
  280. package/modules/auth/src/client/state.ts +15 -1
  281. package/modules/auth/src/domain/types.ts +55 -0
  282. package/modules/auth/src/index.ts +11 -1
  283. package/modules/auth/src/middleware/authentication.ts +4 -0
  284. package/modules/auth/src/server/endpoints.ts +467 -171
  285. package/modules/auth/src/server/http.ts +3 -2
  286. package/modules/auth/src/server/index.ts +22 -1
  287. package/modules/auth/src/server/membership-endpoints.ts +62 -0
  288. package/modules/auth/src/server/mfa-enforcement.ts +156 -0
  289. package/modules/auth/src/server/oidc.ts +389 -0
  290. package/modules/auth/src/server/provider-endpoints.ts +246 -0
  291. package/modules/auth/src/server/provider-host.ts +77 -0
  292. package/modules/auth/src/server/runtime.ts +231 -55
  293. package/modules/auth/src/services/auth-service-error.ts +7 -2
  294. package/modules/auth/src/services/auth-service.ts +948 -51
  295. package/modules/auth/src/services/data-classes.ts +236 -0
  296. package/modules/auth/src/services/database-repository.ts +853 -71
  297. package/modules/auth/src/services/identity-provider-service.ts +571 -0
  298. package/modules/auth/src/services/mail-delivery.ts +16 -16
  299. package/modules/auth/src/services/mail-message.ts +54 -0
  300. package/modules/auth/src/services/mail-port.ts +24 -0
  301. package/modules/auth/src/services/mail-smtp.ts +85 -0
  302. package/modules/auth/src/services/mfa-rotation.ts +144 -0
  303. package/modules/auth/src/services/migration.ts +810 -0
  304. package/modules/auth/src/services/password.ts +14 -0
  305. package/modules/auth/src/services/provider-secret-rotation.ts +169 -0
  306. package/modules/auth/src/services/provider-secrets.ts +136 -0
  307. package/modules/auth/src/services/repository.ts +321 -5
  308. package/modules/auth/src/services/session-sweep-runner.ts +73 -0
  309. package/modules/auth/src/services/settings-store.ts +10 -83
  310. package/modules/auth/src/services/totp.ts +88 -62
  311. package/modules/auth/src/services/validation.ts +194 -7
  312. package/modules/auth/src/settings.ts +18 -0
  313. package/modules/auth/translations/en.json +84 -1
  314. package/modules/auth/translations/pl.json +84 -1
  315. package/modules/automations/migrations/0005_secret_rotation_inventory.down.sql +1 -0
  316. package/modules/automations/migrations/0005_secret_rotation_inventory.up.sql +6 -0
  317. package/modules/automations/migrations/0006_automations_list_sort_indexes.down.sql +4 -0
  318. package/modules/automations/migrations/0006_automations_list_sort_indexes.up.sql +11 -0
  319. package/modules/automations/module.json +24 -6
  320. package/modules/automations/package.json +4 -1
  321. package/modules/automations/spec/module.yaml +81 -7
  322. package/modules/automations/src/api/endpoints.ts +202 -28
  323. package/modules/automations/src/cli/commands.json +17 -0
  324. package/modules/automations/src/cli/index.ts +114 -0
  325. package/modules/automations/src/client/AutomationScheduleForm.tsrx +77 -19
  326. package/modules/automations/src/client/AutomationSchedulesView.tsrx +144 -40
  327. package/modules/automations/src/client/AutomationTriggersView.tsrx +131 -32
  328. package/modules/automations/src/client/api.ts +62 -19
  329. package/modules/automations/src/client/presentation.ts +20 -2
  330. package/modules/automations/src/client/state.ts +84 -0
  331. package/modules/automations/src/domain/cadence.ts +99 -3
  332. package/modules/automations/src/domain/cron.ts +366 -0
  333. package/modules/automations/src/domain/time-zone.ts +61 -0
  334. package/modules/automations/src/platform.ts +38 -4
  335. package/modules/automations/src/server/index.ts +11 -0
  336. package/modules/automations/src/server/runtime.ts +46 -22
  337. package/modules/{automations-workflows-integration/src/server/adapter.ts → automations/src/server/workflow-target.ts} +13 -14
  338. package/modules/{automations-workflows-integration/src/server/tools.ts → automations/src/server/workflow-tools.ts} +0 -0
  339. package/modules/automations/src/services/data-classes.ts +62 -0
  340. package/modules/automations/src/services/database-repository.ts +152 -38
  341. package/modules/automations/src/services/migration.ts +119 -9
  342. package/modules/automations/src/services/repository.ts +52 -1
  343. package/modules/automations/src/services/schedule-runner.ts +84 -0
  344. package/modules/automations/src/services/schedule-service.ts +152 -26
  345. package/modules/automations/src/services/secret-rotation.ts +176 -0
  346. package/modules/automations/src/services/secret-vault.ts +68 -41
  347. package/modules/automations/src/services/trigger-service.ts +13 -10
  348. package/modules/automations/translations/en.json +15 -2
  349. package/modules/automations/translations/pl.json +15 -2
  350. package/modules/connectors/migrations/0001_connectors_core.down.sql +3 -0
  351. package/modules/connectors/migrations/0001_connectors_core.up.sql +72 -0
  352. package/modules/connectors/migrations/0002_connectors_call_keys.down.sql +3 -0
  353. package/modules/connectors/migrations/0002_connectors_call_keys.up.sql +18 -0
  354. package/modules/connectors/migrations/0003_connectors_rotation_inventory.down.sql +2 -0
  355. package/modules/connectors/migrations/0003_connectors_rotation_inventory.up.sql +19 -0
  356. package/modules/connectors/migrations/README.md +3 -0
  357. package/modules/connectors/module.json +32 -0
  358. package/modules/{automations-workflows-integration → connectors}/package.json +28 -26
  359. package/modules/connectors/spec/module.yaml +448 -0
  360. package/modules/connectors/src/acl/permissions.ts +6 -0
  361. package/modules/connectors/src/agent/tools.ts +180 -0
  362. package/modules/connectors/src/api/endpoints.ts +572 -0
  363. package/modules/connectors/src/cli/commands.json +17 -0
  364. package/modules/connectors/src/cli/index.ts +126 -0
  365. package/modules/connectors/src/client/ConnectorCallsView.tsrx +358 -0
  366. package/modules/connectors/src/client/ConnectorForm.tsrx +424 -0
  367. package/modules/connectors/src/client/ConnectorsView.tsrx +711 -0
  368. package/modules/connectors/src/client/api.ts +281 -0
  369. package/modules/connectors/src/client/consent.ts +48 -0
  370. package/modules/connectors/src/client/contribution.tsrx +37 -0
  371. package/modules/connectors/src/client/index.ts +22 -0
  372. package/modules/connectors/src/client/navigation.ts +42 -0
  373. package/modules/connectors/src/client/presentation.ts +38 -0
  374. package/modules/connectors/src/client/state.ts +175 -0
  375. package/modules/connectors/src/domain/calls.ts +70 -0
  376. package/modules/connectors/src/domain/definitions.ts +190 -0
  377. package/modules/connectors/src/domain/http-json.ts +68 -0
  378. package/modules/connectors/src/domain/types.ts +185 -0
  379. package/modules/connectors/src/index.ts +64 -0
  380. package/modules/connectors/src/platform.ts +56 -0
  381. package/modules/connectors/src/server/index.ts +66 -0
  382. package/modules/connectors/src/server/runtime.ts +163 -0
  383. package/modules/connectors/src/services/call-service.ts +903 -0
  384. package/modules/connectors/src/services/connectors-service.ts +653 -0
  385. package/modules/connectors/src/services/credential-rotation.ts +221 -0
  386. package/modules/connectors/src/services/credential-vault.ts +194 -0
  387. package/modules/connectors/src/services/data-classes.ts +158 -0
  388. package/modules/connectors/src/services/database-repository.ts +897 -0
  389. package/modules/connectors/src/services/egress.ts +261 -0
  390. package/modules/connectors/src/services/index.ts +19 -0
  391. package/modules/connectors/src/services/migration.ts +202 -0
  392. package/modules/connectors/src/services/repository.ts +175 -0
  393. package/modules/connectors/src/services/service-error.ts +46 -0
  394. package/modules/connectors/src/settings.ts +69 -0
  395. package/modules/connectors/translations/en.json +191 -0
  396. package/modules/connectors/translations/pl.json +191 -0
  397. package/modules/directory/migrations/0001_directory_core.down.sql +5 -0
  398. package/modules/directory/migrations/0001_directory_core.up.sql +111 -0
  399. package/modules/directory/migrations/README.md +3 -0
  400. package/modules/directory/module.json +27 -0
  401. package/modules/directory/package.json +46 -0
  402. package/modules/directory/spec/module.yaml +351 -0
  403. package/modules/directory/src/acl/permissions.ts +7 -0
  404. package/modules/directory/src/api/endpoints.ts +478 -0
  405. package/modules/directory/src/api/scim-endpoints.ts +516 -0
  406. package/modules/directory/src/client/GroupMappingsView.tsrx +430 -0
  407. package/modules/directory/src/client/ProvisioningLogView.tsrx +248 -0
  408. package/modules/directory/src/client/ScimTokensView.tsrx +587 -0
  409. package/modules/directory/src/client/api.ts +222 -0
  410. package/modules/directory/src/client/contribution.tsrx +46 -0
  411. package/modules/directory/src/client/index.ts +22 -0
  412. package/modules/directory/src/client/navigation.ts +57 -0
  413. package/modules/directory/src/client/presentation.ts +143 -0
  414. package/modules/directory/src/client/state.ts +173 -0
  415. package/modules/directory/src/domain/scim.ts +346 -0
  416. package/modules/directory/src/domain/types.ts +166 -0
  417. package/modules/directory/src/index.ts +44 -0
  418. package/modules/directory/src/platform.ts +43 -0
  419. package/modules/directory/src/server/index.ts +17 -0
  420. package/modules/directory/src/server/runtime.ts +124 -0
  421. package/modules/directory/src/services/auth-port.ts +127 -0
  422. package/modules/directory/src/services/data-classes.ts +41 -0
  423. package/modules/directory/src/services/database-repository.ts +1067 -0
  424. package/modules/directory/src/services/directory-service.ts +311 -0
  425. package/modules/directory/src/services/index.ts +43 -0
  426. package/modules/directory/src/services/migration.ts +146 -0
  427. package/modules/directory/src/services/provisioning-service.ts +1420 -0
  428. package/modules/directory/src/services/rate-limiter.ts +103 -0
  429. package/modules/directory/src/services/repository.ts +208 -0
  430. package/modules/directory/src/services/service-error.ts +42 -0
  431. package/modules/directory/src/services/token-service.ts +215 -0
  432. package/modules/directory/src/settings.ts +86 -0
  433. package/modules/directory/translations/en.json +185 -0
  434. package/modules/directory/translations/pl.json +185 -0
  435. package/modules/documents/migrations/0001_documents_core.down.sql +1 -0
  436. package/modules/documents/migrations/0001_documents_core.up.sql +35 -0
  437. package/modules/documents/migrations/0002_documents_files_page.down.sql +1 -0
  438. package/modules/documents/migrations/0002_documents_files_page.up.sql +6 -0
  439. package/modules/documents/migrations/0003_documents_rotation_inventory.down.sql +2 -0
  440. package/modules/documents/migrations/0003_documents_rotation_inventory.up.sql +18 -0
  441. package/modules/documents/migrations/README.md +3 -0
  442. package/modules/documents/module.json +32 -0
  443. package/modules/documents/package.json +50 -0
  444. package/modules/documents/spec/module.yaml +248 -0
  445. package/modules/documents/src/acl/permissions.ts +6 -0
  446. package/modules/documents/src/api/endpoints.ts +401 -0
  447. package/modules/documents/src/cli/commands.json +17 -0
  448. package/modules/documents/src/cli/index.ts +145 -0
  449. package/modules/documents/src/client/DocumentUploader.tsrx +148 -0
  450. package/modules/documents/src/client/DocumentsView.tsrx +540 -0
  451. package/modules/documents/src/client/api.ts +209 -0
  452. package/modules/documents/src/client/contribution.tsrx +29 -0
  453. package/modules/documents/src/client/download.ts +70 -0
  454. package/modules/documents/src/client/index.ts +22 -0
  455. package/modules/documents/src/client/navigation.ts +25 -0
  456. package/modules/documents/src/client/presentation.ts +110 -0
  457. package/modules/documents/src/client/state.ts +117 -0
  458. package/modules/documents/src/domain/attachments.ts +84 -0
  459. package/modules/documents/src/domain/types.ts +80 -0
  460. package/modules/documents/src/index.ts +46 -0
  461. package/modules/documents/src/platform.ts +61 -0
  462. package/modules/documents/src/server/index.ts +30 -0
  463. package/modules/documents/src/server/runtime.ts +107 -0
  464. package/modules/documents/src/services/attachments.ts +39 -0
  465. package/modules/documents/src/services/data-classes.ts +25 -0
  466. package/modules/documents/src/services/database-repository.ts +270 -0
  467. package/modules/documents/src/services/documents-service.ts +590 -0
  468. package/modules/documents/src/services/index.ts +18 -0
  469. package/modules/documents/src/services/migration.ts +117 -0
  470. package/modules/documents/src/services/repository.ts +58 -0
  471. package/modules/documents/src/services/storage-rotation.ts +157 -0
  472. package/modules/documents/src/settings.ts +107 -0
  473. package/modules/documents/translations/en.json +111 -0
  474. package/modules/documents/translations/pl.json +111 -0
  475. package/modules/exports/migrations/0001_exports_core.down.sql +2 -0
  476. package/modules/exports/migrations/0001_exports_core.up.sql +28 -0
  477. package/modules/exports/migrations/0002_exports_job_routing_role.down.sql +4 -0
  478. package/modules/exports/migrations/0002_exports_job_routing_role.up.sql +21 -0
  479. package/modules/exports/migrations/0003_exports_rotation_inventory.down.sql +1 -0
  480. package/modules/exports/migrations/0003_exports_rotation_inventory.up.sql +9 -0
  481. package/modules/exports/migrations/README.md +3 -0
  482. package/modules/exports/module.json +32 -0
  483. package/modules/exports/package.json +45 -0
  484. package/modules/exports/spec/module.yaml +230 -0
  485. package/modules/exports/src/acl/permissions.ts +6 -0
  486. package/modules/exports/src/api/endpoints.ts +211 -0
  487. package/modules/exports/src/cli/commands.json +17 -0
  488. package/modules/exports/src/cli/index.ts +145 -0
  489. package/modules/exports/src/client/ExportsView.tsrx +327 -0
  490. package/modules/exports/src/client/api.ts +133 -0
  491. package/modules/exports/src/client/contribution.tsrx +31 -0
  492. package/modules/exports/src/client/index.ts +20 -0
  493. package/modules/exports/src/client/navigation.ts +25 -0
  494. package/modules/exports/src/client/presentation.ts +53 -0
  495. package/modules/exports/src/client/state.ts +56 -0
  496. package/modules/exports/src/domain/lists.ts +29 -0
  497. package/modules/exports/src/domain/types.ts +120 -0
  498. package/modules/exports/src/index.ts +41 -0
  499. package/modules/exports/src/platform.ts +80 -0
  500. package/modules/exports/src/server/index.ts +50 -0
  501. package/modules/exports/src/server/runtime.ts +160 -0
  502. package/modules/exports/src/services/data-classes.ts +59 -0
  503. package/modules/exports/src/services/database-repository.ts +403 -0
  504. package/modules/exports/src/services/export-runner.ts +123 -0
  505. package/modules/exports/src/services/export-service.ts +356 -0
  506. package/modules/exports/src/services/index.ts +6 -0
  507. package/modules/exports/src/services/list-registry.ts +105 -0
  508. package/modules/exports/src/services/migration.ts +116 -0
  509. package/modules/exports/src/services/repository.ts +96 -0
  510. package/modules/exports/src/services/storage-rotation.ts +138 -0
  511. package/modules/exports/src/settings.ts +70 -0
  512. package/modules/exports/translations/en.json +69 -0
  513. package/modules/exports/translations/pl.json +69 -0
  514. package/modules/import/migrations/0001_import_core.down.sql +6 -0
  515. package/modules/import/migrations/0001_import_core.up.sql +61 -0
  516. package/modules/import/migrations/0002_import_job_routing_role.down.sql +4 -0
  517. package/modules/import/migrations/0002_import_job_routing_role.up.sql +20 -0
  518. package/modules/import/migrations/0003_import_jobs_export_index.down.sql +1 -0
  519. package/modules/import/migrations/0003_import_jobs_export_index.up.sql +7 -0
  520. package/modules/import/migrations/0004_import_jobs_traceparent.down.sql +1 -0
  521. package/modules/import/migrations/0004_import_jobs_traceparent.up.sql +7 -0
  522. package/modules/import/migrations/README.md +3 -0
  523. package/modules/import/module.json +37 -0
  524. package/modules/import/package.json +50 -0
  525. package/modules/import/spec/module.yaml +335 -0
  526. package/modules/import/src/acl/permissions.ts +6 -0
  527. package/modules/import/src/api/endpoints.ts +400 -0
  528. package/modules/import/src/client/ImportJobDrawer.tsrx +229 -0
  529. package/modules/import/src/client/ImportsView.tsrx +455 -0
  530. package/modules/import/src/client/NewImportForm.tsrx +274 -0
  531. package/modules/import/src/client/api.ts +243 -0
  532. package/modules/import/src/client/contribution.tsrx +29 -0
  533. package/modules/import/src/client/index.ts +23 -0
  534. package/modules/import/src/client/navigation.ts +25 -0
  535. package/modules/import/src/client/presentation.ts +59 -0
  536. package/modules/import/src/client/state.ts +43 -0
  537. package/modules/import/src/domain/csv.ts +258 -0
  538. package/modules/import/src/domain/ports.ts +157 -0
  539. package/modules/import/src/domain/types.ts +179 -0
  540. package/modules/import/src/index.ts +58 -0
  541. package/modules/import/src/platform.ts +60 -0
  542. package/modules/import/src/server/index.ts +49 -0
  543. package/modules/import/src/server/runtime.ts +158 -0
  544. package/modules/import/src/services/csv-source.ts +121 -0
  545. package/modules/import/src/services/data-classes.ts +63 -0
  546. package/modules/import/src/services/database-repository.ts +780 -0
  547. package/modules/import/src/services/import-runner.ts +127 -0
  548. package/modules/import/src/services/import-service.ts +872 -0
  549. package/modules/import/src/services/index.ts +6 -0
  550. package/modules/import/src/services/migration.ts +156 -0
  551. package/modules/import/src/services/port-registry.ts +158 -0
  552. package/modules/import/src/services/repository.ts +147 -0
  553. package/modules/import/src/settings.ts +70 -0
  554. package/modules/import/translations/en.json +175 -0
  555. package/modules/import/translations/pl.json +175 -0
  556. package/modules/metering/migrations/0001_metering_core.down.sql +6 -0
  557. package/modules/metering/migrations/0001_metering_core.up.sql +119 -0
  558. package/modules/metering/migrations/0002_metering_bucket_export.down.sql +3 -0
  559. package/modules/metering/migrations/0002_metering_bucket_export.up.sql +5 -0
  560. package/modules/metering/migrations/README.md +3 -0
  561. package/modules/metering/module.json +46 -0
  562. package/modules/metering/package.json +50 -0
  563. package/modules/metering/spec/module.yaml +269 -0
  564. package/modules/metering/src/acl/permissions.ts +5 -0
  565. package/modules/metering/src/api/endpoints.ts +104 -0
  566. package/modules/metering/src/cli/commands.json +28 -0
  567. package/modules/metering/src/cli/index.ts +246 -0
  568. package/modules/metering/src/client/LimitsView.tsrx +170 -0
  569. package/modules/metering/src/client/UsageSummaryWidget.tsrx +63 -0
  570. package/modules/metering/src/client/UsageView.tsrx +205 -0
  571. package/modules/metering/src/client/api.ts +91 -0
  572. package/modules/metering/src/client/contribution.tsrx +42 -0
  573. package/modules/metering/src/client/index.ts +26 -0
  574. package/modules/metering/src/client/navigation.ts +54 -0
  575. package/modules/metering/src/client/presentation.ts +63 -0
  576. package/modules/metering/src/client/state.ts +31 -0
  577. package/modules/metering/src/domain/meters.ts +100 -0
  578. package/modules/metering/src/domain/types.ts +71 -0
  579. package/modules/metering/src/index.ts +51 -0
  580. package/modules/metering/src/platform.ts +84 -0
  581. package/modules/metering/src/server/index.ts +40 -0
  582. package/modules/metering/src/server/runtime.ts +165 -0
  583. package/modules/metering/src/services/data-classes.ts +34 -0
  584. package/modules/metering/src/services/database-repository.ts +574 -0
  585. package/modules/metering/src/services/index.ts +9 -0
  586. package/modules/metering/src/services/meter-registry.ts +145 -0
  587. package/modules/metering/src/services/metering-service.ts +441 -0
  588. package/modules/metering/src/services/migration.ts +183 -0
  589. package/modules/metering/src/services/notifications.ts +75 -0
  590. package/modules/metering/src/services/reports.ts +105 -0
  591. package/modules/metering/src/services/repository.ts +132 -0
  592. package/modules/metering/src/services/service-error.ts +46 -0
  593. package/modules/metering/src/settings.ts +47 -0
  594. package/modules/metering/translations/en.json +68 -0
  595. package/modules/metering/translations/pl.json +68 -0
  596. package/modules/notifications/migrations/0001_notifications_core.down.sql +1 -0
  597. package/modules/notifications/migrations/0001_notifications_core.up.sql +26 -0
  598. package/modules/notifications/migrations/0002_notifications_preferences.down.sql +1 -0
  599. package/modules/notifications/migrations/0002_notifications_preferences.up.sql +17 -0
  600. package/modules/notifications/migrations/0003_notifications_webhook_subscriptions.down.sql +1 -0
  601. package/modules/notifications/migrations/0003_notifications_webhook_subscriptions.up.sql +30 -0
  602. package/modules/notifications/migrations/0004_notifications_deliveries.down.sql +1 -0
  603. package/modules/notifications/migrations/0004_notifications_deliveries.up.sql +35 -0
  604. package/modules/notifications/migrations/0005_notifications_delivery_routing_role.down.sql +2 -0
  605. package/modules/notifications/migrations/0005_notifications_delivery_routing_role.up.sql +17 -0
  606. package/modules/notifications/migrations/0006_notifications_delivery_title.down.sql +1 -0
  607. package/modules/notifications/migrations/0006_notifications_delivery_title.up.sql +11 -0
  608. package/modules/notifications/migrations/0007_notifications_secret_rotation_inventory.down.sql +2 -0
  609. package/modules/notifications/migrations/0007_notifications_secret_rotation_inventory.up.sql +19 -0
  610. package/modules/notifications/migrations/0008_notifications_delivery_claim.down.sql +8 -0
  611. package/modules/notifications/migrations/0008_notifications_delivery_claim.up.sql +19 -0
  612. package/modules/notifications/migrations/0009_notifications_kind_approvals.down.sql +16 -0
  613. package/modules/notifications/migrations/0009_notifications_kind_approvals.up.sql +18 -0
  614. package/modules/notifications/migrations/0010_notifications_kind_meter_threshold.down.sql +16 -0
  615. package/modules/notifications/migrations/0010_notifications_kind_meter_threshold.up.sql +18 -0
  616. package/modules/notifications/migrations/0011_notifications_retention_keyset.down.sql +5 -0
  617. package/modules/notifications/migrations/0011_notifications_retention_keyset.up.sql +15 -0
  618. package/modules/notifications/migrations/0012_notifications_email_channel.down.sql +12 -0
  619. package/modules/notifications/migrations/0012_notifications_email_channel.up.sql +38 -0
  620. package/modules/notifications/migrations/0013_notifications_list_pages.down.sql +5 -0
  621. package/modules/notifications/migrations/0013_notifications_list_pages.up.sql +13 -0
  622. package/modules/notifications/migrations/README.md +3 -0
  623. package/modules/notifications/module.json +32 -0
  624. package/modules/notifications/package.json +49 -0
  625. package/modules/notifications/spec/module.yaml +718 -0
  626. package/modules/notifications/src/acl/permissions.ts +11 -0
  627. package/modules/notifications/src/api/endpoints.ts +814 -0
  628. package/modules/notifications/src/cli/commands.json +17 -0
  629. package/modules/notifications/src/cli/index.ts +126 -0
  630. package/modules/notifications/src/client/DeliveriesView.tsrx +483 -0
  631. package/modules/notifications/src/client/FactList.tsrx +24 -0
  632. package/modules/notifications/src/client/NotificationPreferencesView.tsrx +240 -0
  633. package/modules/notifications/src/client/NotificationsInboxView.tsrx +592 -0
  634. package/modules/notifications/src/client/UnreadCountWidget.tsrx +62 -0
  635. package/modules/notifications/src/client/WebhookForm.tsrx +297 -0
  636. package/modules/notifications/src/client/WebhooksView.tsrx +637 -0
  637. package/modules/notifications/src/client/api.ts +382 -0
  638. package/modules/notifications/src/client/contribution.tsrx +83 -0
  639. package/modules/notifications/src/client/inbox.ts +41 -0
  640. package/modules/notifications/src/client/index.ts +29 -0
  641. package/modules/notifications/src/client/navigation.ts +92 -0
  642. package/modules/notifications/src/client/notifications.css +70 -0
  643. package/modules/notifications/src/client/presentation.ts +81 -0
  644. package/modules/notifications/src/client/state.ts +246 -0
  645. package/modules/notifications/src/client/unread-count.ts +26 -0
  646. package/modules/notifications/src/domain/locale.ts +47 -0
  647. package/modules/notifications/src/domain/publish.ts +46 -0
  648. package/modules/notifications/src/domain/types.ts +186 -0
  649. package/modules/notifications/src/index.ts +62 -0
  650. package/modules/notifications/src/platform.ts +85 -0
  651. package/modules/notifications/src/server/index.ts +82 -0
  652. package/modules/notifications/src/server/runtime.ts +251 -0
  653. package/modules/notifications/src/services/data-classes.ts +139 -0
  654. package/modules/notifications/src/services/database-repository.ts +1334 -0
  655. package/modules/notifications/src/services/delivery-payload.ts +93 -0
  656. package/modules/notifications/src/services/delivery-runner.ts +170 -0
  657. package/modules/notifications/src/services/delivery-service.ts +811 -0
  658. package/modules/notifications/src/services/egress.ts +239 -0
  659. package/modules/notifications/src/services/email-channel.ts +93 -0
  660. package/modules/notifications/src/services/index.ts +23 -0
  661. package/modules/notifications/src/services/migration.ts +572 -0
  662. package/modules/notifications/src/services/notifications-service.ts +297 -0
  663. package/modules/notifications/src/services/paging.ts +60 -0
  664. package/modules/notifications/src/services/publish-service.ts +133 -0
  665. package/modules/notifications/src/services/repository.ts +342 -0
  666. package/modules/notifications/src/services/secret-rotation.ts +177 -0
  667. package/modules/notifications/src/services/secret-vault.ts +168 -0
  668. package/modules/notifications/src/services/service-error.ts +67 -0
  669. package/modules/notifications/src/services/signature.ts +81 -0
  670. package/modules/notifications/src/services/webhook-service.ts +307 -0
  671. package/modules/notifications/src/settings.ts +130 -0
  672. package/modules/notifications/translations/en.json +255 -0
  673. package/modules/notifications/translations/pl.json +255 -0
  674. package/modules/profile/module.json +3 -3
  675. package/modules/profile/package.json +1 -1
  676. package/modules/profile/spec/module.yaml +3 -3
  677. package/modules/profile/src/services/database-repository.ts +3 -13
  678. package/modules/reports/module.json +28 -0
  679. package/modules/reports/package.json +42 -0
  680. package/modules/reports/spec/module.yaml +231 -0
  681. package/modules/reports/src/acl/permissions.ts +5 -0
  682. package/modules/reports/src/api/endpoints.ts +72 -0
  683. package/modules/reports/src/client/ReportsSummaryWidget.tsrx +52 -0
  684. package/modules/reports/src/client/ReportsView.tsrx +246 -0
  685. package/modules/reports/src/client/api.ts +65 -0
  686. package/modules/reports/src/client/contribution.tsrx +36 -0
  687. package/modules/reports/src/client/index.ts +18 -0
  688. package/modules/reports/src/client/navigation.ts +39 -0
  689. package/modules/reports/src/client/presentation.ts +123 -0
  690. package/modules/reports/src/client/state.ts +28 -0
  691. package/modules/reports/src/domain/providers.ts +115 -0
  692. package/modules/reports/src/domain/types.ts +44 -0
  693. package/modules/reports/src/index.ts +47 -0
  694. package/modules/reports/src/platform.ts +37 -0
  695. package/modules/reports/src/server/index.ts +21 -0
  696. package/modules/reports/src/server/runtime.ts +40 -0
  697. package/modules/reports/src/services/index.ts +17 -0
  698. package/modules/reports/src/services/provider-registry.ts +144 -0
  699. package/modules/reports/src/services/range.ts +65 -0
  700. package/modules/reports/src/services/reports-service.ts +316 -0
  701. package/modules/reports/src/services/service-error.ts +26 -0
  702. package/modules/reports/src/settings.ts +49 -0
  703. package/modules/reports/translations/en.json +35 -0
  704. package/modules/reports/translations/pl.json +35 -0
  705. package/modules/sandbox/module.json +4 -4
  706. package/modules/sandbox/package.json +1 -1
  707. package/modules/sandbox/spec/module.yaml +3 -3
  708. package/modules/sandbox/src/services/database-repository.ts +14 -23
  709. package/modules/sandbox/src/services/directory.ts +16 -0
  710. package/modules/sandbox/src/services/sandbox-service.ts +13 -11
  711. package/modules/search/migrations/0001_search_core.down.sql +1 -0
  712. package/modules/search/migrations/0001_search_core.up.sql +22 -0
  713. package/modules/search/migrations/0002_search_recent_export.down.sql +3 -0
  714. package/modules/search/migrations/0002_search_recent_export.up.sql +5 -0
  715. package/modules/search/migrations/README.md +3 -0
  716. package/modules/search/module.json +28 -0
  717. package/modules/search/package.json +43 -0
  718. package/modules/search/spec/module.yaml +177 -0
  719. package/modules/search/src/acl/permissions.ts +5 -0
  720. package/modules/search/src/api/endpoints.ts +221 -0
  721. package/modules/search/src/client/SearchView.tsrx +346 -0
  722. package/modules/search/src/client/api.ts +157 -0
  723. package/modules/search/src/client/contribution.tsrx +31 -0
  724. package/modules/search/src/client/index.ts +17 -0
  725. package/modules/search/src/client/navigation.ts +97 -0
  726. package/modules/search/src/client/search.css +56 -0
  727. package/modules/search/src/client/state.ts +113 -0
  728. package/modules/search/src/domain/data-classes.ts +73 -0
  729. package/modules/search/src/domain/providers.ts +88 -0
  730. package/modules/search/src/domain/types.ts +56 -0
  731. package/modules/search/src/index.ts +46 -0
  732. package/modules/search/src/platform.ts +42 -0
  733. package/modules/search/src/server/index.ts +25 -0
  734. package/modules/search/src/server/runtime.ts +123 -0
  735. package/modules/search/src/services/database-repository.ts +163 -0
  736. package/modules/search/src/services/index.ts +21 -0
  737. package/modules/search/src/services/migration.ts +65 -0
  738. package/modules/search/src/services/provider-registry.ts +137 -0
  739. package/modules/search/src/services/repository.ts +43 -0
  740. package/modules/search/src/services/search-service.ts +442 -0
  741. package/modules/search/src/services/service-error.ts +35 -0
  742. package/modules/search/src/settings.ts +68 -0
  743. package/modules/search/translations/en.json +45 -0
  744. package/modules/search/translations/pl.json +45 -0
  745. package/modules/system/module.json +2 -2
  746. package/modules/system/package.json +1 -1
  747. package/modules/system/spec/module.yaml +30 -3
  748. package/modules/system/src/client/FlagsPanel.tsrx +127 -0
  749. package/modules/system/src/client/ModuleSettingRow.tsrx +17 -7
  750. package/modules/system/src/client/ModuleSettingsSection.tsrx +10 -71
  751. package/modules/system/src/client/ModulesView.tsrx +91 -65
  752. package/modules/system/src/client/flags.ts +35 -0
  753. package/modules/system/src/client/index.ts +8 -0
  754. package/modules/system/src/client/navigation.ts +8 -0
  755. package/modules/system/src/client/settings-state.ts +159 -0
  756. package/modules/system/src/client/state.ts +2 -0
  757. package/modules/system/src/domain/time-zone.ts +105 -0
  758. package/modules/system/src/index.ts +12 -0
  759. package/modules/system/src/platform.ts +5 -1
  760. package/modules/system/src/server/endpoints.ts +72 -21
  761. package/modules/system/src/settings.ts +28 -0
  762. package/modules/system/translations/en.json +17 -0
  763. package/modules/system/translations/pl.json +17 -0
  764. package/modules/users/module.json +30 -4
  765. package/modules/users/package.json +4 -1
  766. package/modules/users/spec/module.yaml +290 -7
  767. package/modules/users/src/api/endpoints.ts +228 -15
  768. package/modules/users/src/client/InviteMemberForm.tsrx +2 -2
  769. package/modules/users/src/client/MemberDrawer.tsrx +142 -26
  770. package/modules/users/src/client/UserAccountForm.tsrx +2 -2
  771. package/modules/users/src/client/UsersView.tsrx +432 -71
  772. package/modules/users/src/client/api.ts +106 -12
  773. package/modules/users/src/client/contribution.tsrx +5 -0
  774. package/modules/users/src/client/member-columns.tsrx +22 -9
  775. package/modules/users/src/client/state.ts +172 -7
  776. package/modules/users/src/domain/lists.ts +9 -0
  777. package/modules/users/src/index.ts +11 -0
  778. package/modules/users/src/platform.ts +50 -1
  779. package/modules/users/src/services/member-export.ts +51 -0
  780. package/modules/users/src/services/member-import.ts +249 -0
  781. package/modules/users/src/services/member-search.ts +145 -0
  782. package/modules/users/src/services/users-service.ts +117 -4
  783. package/modules/users/translations/en.json +57 -8
  784. package/modules/users/translations/pl.json +57 -8
  785. package/modules/workflows/migrations/0004_payload_rotation_inventory.up.sql +17 -0
  786. package/modules/workflows/migrations/0005_workflows_human_approval.up.sql +28 -0
  787. package/modules/workflows/migrations/0006_workflows_retention_indexes.up.sql +11 -0
  788. package/modules/workflows/migrations/0007_workflows_erasure_subject_indexes.up.sql +17 -0
  789. package/modules/workflows/migrations/0008_workflows_subject_account.up.sql +24 -0
  790. package/modules/workflows/migrations/0009_workflows_definition_list_indexes.up.sql +9 -0
  791. package/modules/workflows/module.json +34 -6
  792. package/modules/workflows/package.json +2 -1
  793. package/modules/workflows/spec/module.yaml +26 -9
  794. package/modules/workflows/src/api/endpoints.ts +131 -25
  795. package/modules/workflows/src/cli/commands.json +17 -0
  796. package/modules/workflows/src/cli/index.ts +114 -0
  797. package/modules/workflows/src/client/WorkflowExecutionHistory.tsrx +36 -27
  798. package/modules/workflows/src/client/WorkflowInspector.tsrx +116 -0
  799. package/modules/workflows/src/client/WorkflowNodePalette.tsrx +1 -0
  800. package/modules/workflows/src/client/WorkflowsView.tsrx +236 -99
  801. package/modules/workflows/src/client/api.ts +54 -34
  802. package/modules/workflows/src/client/canvas-model.ts +12 -0
  803. package/modules/workflows/src/client/state.ts +95 -0
  804. package/modules/workflows/src/domain/events.ts +25 -11
  805. package/modules/workflows/src/domain/graph.ts +135 -1
  806. package/modules/workflows/src/domain/types.ts +91 -4
  807. package/modules/workflows/src/platform.ts +12 -0
  808. package/modules/workflows/src/server/runtime.ts +78 -5
  809. package/modules/workflows/src/services/approvals.ts +62 -0
  810. package/modules/workflows/src/services/cursors.ts +37 -0
  811. package/modules/workflows/src/services/data-classes.ts +232 -0
  812. package/modules/workflows/src/services/database-repository.ts +660 -171
  813. package/modules/workflows/src/services/migration.ts +240 -0
  814. package/modules/workflows/src/services/notifications.ts +98 -0
  815. package/modules/workflows/src/services/payload-codec.ts +51 -28
  816. package/modules/workflows/src/services/payload-rotation.ts +171 -0
  817. package/modules/workflows/src/services/repository.ts +123 -8
  818. package/modules/workflows/src/services/simulation.ts +7 -0
  819. package/modules/workflows/src/services/worker.ts +446 -25
  820. package/modules/workflows/src/services/workflows-service.ts +264 -103
  821. package/modules/workflows/translations/en.json +12 -4
  822. package/modules/workflows/translations/pl.json +12 -4
  823. package/modules.json +48 -4
  824. package/package.json +64 -6
  825. package/packages/client/package.json +1 -0
  826. package/packages/client/src/ApplicationShell.tsrx +43 -0
  827. package/packages/client/src/contributions.ts +55 -0
  828. package/packages/client/src/i18n/locales/en.json +5 -0
  829. package/packages/client/src/i18n/locales/pl.json +5 -0
  830. package/packages/client/src/index.ts +13 -1
  831. package/packages/client/src/routing.ts +22 -0
  832. package/packages/client/src/shell/CommandPalette.tsrx +223 -11
  833. package/packages/client/src/shell/command-search.ts +126 -0
  834. package/packages/client/src/shell/command.ts +30 -0
  835. package/packages/client/src/shell/navigation.ts +12 -0
  836. package/packages/client/src/shell/palette-keys.ts +27 -0
  837. package/packages/client/src/shell/shell.css +7 -0
  838. package/packages/contracts/schemas/module-spec.schema.json +287 -1
  839. package/packages/contracts/schemas/module.schema.json +25 -0
  840. package/packages/contracts/src/index.ts +144 -1
  841. package/packages/database/src/backup.ts +180 -0
  842. package/packages/database/src/decoders.ts +25 -0
  843. package/packages/database/src/index.ts +17 -0
  844. package/packages/database/src/migrations.ts +7 -0
  845. package/packages/database/src/record-history.ts +5 -14
  846. package/packages/harness/src/index.ts +2 -0
  847. package/packages/harness/src/runtime.ts +378 -2
  848. package/packages/harness/src/tool-adapters.ts +20 -9
  849. package/packages/kernel/src/acl.ts +179 -0
  850. package/packages/kernel/src/approval-grant.ts +310 -0
  851. package/packages/kernel/src/capability-registry.ts +69 -2
  852. package/packages/kernel/src/data-class-registry.ts +354 -0
  853. package/packages/kernel/src/index.ts +85 -4
  854. package/packages/kernel/src/keyring.ts +208 -0
  855. package/packages/kernel/src/module-compatibility.ts +35 -4
  856. package/packages/kernel/src/module-registry.ts +49 -2
  857. package/packages/kernel/src/module-settings.ts +189 -27
  858. package/packages/server/package.json +2 -1
  859. package/packages/server/src/composition.ts +38 -0
  860. package/packages/server/src/endpoint.ts +104 -47
  861. package/packages/server/src/export/csv.ts +34 -0
  862. package/packages/server/src/export/definition.ts +346 -0
  863. package/packages/server/src/export/index.ts +61 -0
  864. package/packages/server/src/export/run.ts +116 -0
  865. package/packages/server/src/index.ts +155 -0
  866. package/packages/server/src/jobs/index.ts +93 -0
  867. package/packages/server/src/jobs/runner.ts +486 -0
  868. package/packages/server/src/log.ts +291 -0
  869. package/packages/server/src/mail/config.ts +184 -0
  870. package/packages/server/src/mail/contracts.ts +251 -0
  871. package/packages/server/src/mail/index.ts +31 -0
  872. package/packages/server/src/mail/port.ts +101 -0
  873. package/packages/server/src/mail/smtp.ts +191 -0
  874. package/packages/server/src/mail/template.ts +105 -0
  875. package/packages/server/src/metrics.ts +447 -0
  876. package/packages/server/src/pagination.ts +246 -0
  877. package/packages/server/src/trace/context.ts +103 -0
  878. package/packages/server/src/trace/egress.ts +66 -0
  879. package/packages/server/src/trace/error-sink.ts +296 -0
  880. package/packages/server/src/trace/exporter.ts +295 -0
  881. package/packages/server/src/trace/index.ts +60 -0
  882. package/packages/server/src/trace/job-sink.ts +172 -0
  883. package/packages/server/src/trace/tracer.ts +338 -0
  884. package/packages/server/src/web.ts +5 -1
  885. package/packages/storage/package.json +40 -0
  886. package/packages/storage/src/config.ts +189 -0
  887. package/packages/storage/src/content-type.ts +124 -0
  888. package/packages/storage/src/contracts.ts +112 -0
  889. package/packages/storage/src/envelope.ts +238 -0
  890. package/packages/storage/src/index.ts +49 -0
  891. package/packages/storage/src/local.ts +75 -0
  892. package/packages/storage/src/port.ts +262 -0
  893. package/packages/storage/src/read-token.ts +130 -0
  894. package/packages/storage/src/reseal.ts +128 -0
  895. package/packages/storage/src/s3.ts +144 -0
  896. package/packages/storage/src/scanner.ts +22 -0
  897. package/packages/storage/src/sigv4.ts +104 -0
  898. package/packages/storage/src/store.ts +13 -0
  899. package/packages/ui/package.json +1 -0
  900. package/packages/ui/src/components/Button.tsrx +3 -0
  901. package/packages/ui/src/components/ConfirmDialog.tsrx +13 -2
  902. package/packages/ui/src/components/DateField.tsrx +92 -0
  903. package/packages/ui/src/components/DatePicker.tsrx +525 -0
  904. package/packages/ui/src/components/DateRangeField.tsrx +93 -0
  905. package/packages/ui/src/components/Drawer.tsrx +10 -1
  906. package/packages/ui/src/components/FileUpload.tsrx +158 -0
  907. package/packages/ui/src/components/Pagination.tsrx +130 -0
  908. package/packages/ui/src/components/Select.tsrx +78 -0
  909. package/packages/ui/src/components/Table.tsrx +498 -81
  910. package/packages/ui/src/components/Tabs.tsrx +53 -0
  911. package/packages/ui/src/components/ToastHost.tsrx +48 -0
  912. package/packages/ui/src/components/calendar.ts +243 -0
  913. package/packages/ui/src/components/date-field.ts +62 -0
  914. package/packages/ui/src/components/file-upload.ts +49 -0
  915. package/packages/ui/src/components/focus-trap.ts +62 -0
  916. package/packages/ui/src/components/pagination.ts +68 -0
  917. package/packages/ui/src/components/table-sorting.ts +49 -0
  918. package/packages/ui/src/components/tabs.ts +61 -0
  919. package/packages/ui/src/components/toast-store.ts +116 -0
  920. package/packages/ui/src/icons/Icon.tsrx +5 -0
  921. package/packages/ui/src/index.ts +45 -0
  922. package/packages/ui/src/styles/components.css +598 -0
  923. package/modules/automations-workflows-integration/module.json +0 -27
  924. package/modules/automations-workflows-integration/spec/module.yaml +0 -93
  925. package/modules/automations-workflows-integration/src/index.ts +0 -14
  926. package/modules/automations-workflows-integration/src/platform.ts +0 -24
  927. package/modules/automations-workflows-integration/src/server/index.ts +0 -11
  928. package/modules/automations-workflows-integration/translations/en.json +0 -21
  929. package/modules/automations-workflows-integration/translations/pl.json +0 -21
  930. package/modules/workflows/src/services/cursor-codec.ts +0 -62
@@ -0,0 +1,1334 @@
1
+ import type {
2
+ DatabaseHandle,
3
+ DatabaseParameter,
4
+ DatabaseRow,
5
+ DatabaseStatement,
6
+ DatabaseTransaction,
7
+ } from '@flowdular/sdk/database';
8
+ import { integer, runDatabaseMigrations } from '@flowdular/sdk/database';
9
+ import { keysetWhere } from '@flowdular/sdk/server';
10
+ import type {
11
+ DeliveryAttempt,
12
+ DeliveryChannel,
13
+ DeliveryRouting,
14
+ DeliveryStatus,
15
+ MemberNotificationSettings,
16
+ NotificationKind,
17
+ NotificationPreference,
18
+ NotificationsInbox,
19
+ NotificationsInboxStatus,
20
+ WebhookSubscription,
21
+ WebhookSubscriptionStatus,
22
+ } from '../domain/types.ts';
23
+ import { databaseMigrations } from './migration.ts';
24
+ import type {
25
+ ClaimDeliveryInput,
26
+ CompleteDeliveryInput,
27
+ DeliveryFilters,
28
+ ExportCursor,
29
+ InboxFilters,
30
+ ListPage,
31
+ NotificationsRepository,
32
+ PagedRows,
33
+ PublishEventInput,
34
+ PublishEventResult,
35
+ StoredWebhookSubscription,
36
+ SubscriptionFilters,
37
+ } from './repository.ts';
38
+ import type { EncryptedSecret } from './secret-vault.ts';
39
+
40
+ interface InboxRow {
41
+ id: string;
42
+ tenant_id: string;
43
+ recipient_account_id: string;
44
+ kind: NotificationKind;
45
+ title: string;
46
+ body: string | null;
47
+ source_module: string;
48
+ source_ref: string;
49
+ status: NotificationsInboxStatus;
50
+ read_at: Date | string | null;
51
+ created_at: number | bigint | string;
52
+ }
53
+
54
+ interface PreferenceRow {
55
+ id: string;
56
+ tenant_id: string;
57
+ recipient_account_id: string;
58
+ kind: NotificationKind;
59
+ enabled: number | bigint | string;
60
+ created_at: number | bigint | string;
61
+ updated_at: number | bigint | string;
62
+ }
63
+
64
+ interface MemberPreferenceRow {
65
+ tenant_id: string;
66
+ recipient_account_id: string;
67
+ email_delivery: number | bigint | string;
68
+ created_at: number | bigint | string;
69
+ updated_at: number | bigint | string;
70
+ }
71
+
72
+ interface SubscriptionRow {
73
+ id: string;
74
+ tenant_id: string;
75
+ name: string;
76
+ url: string;
77
+ events_json: string;
78
+ secret_key_id: string;
79
+ secret_iv: string;
80
+ secret_tag: string;
81
+ secret_ciphertext: string;
82
+ secret_fingerprint: string;
83
+ secret_revision: number | bigint | string;
84
+ status: WebhookSubscriptionStatus;
85
+ description: string | null;
86
+ last_delivery_at: number | bigint | string | null;
87
+ created_at: number | bigint | string;
88
+ updated_at: number | bigint | string;
89
+ created_by: string;
90
+ }
91
+
92
+ /**
93
+ * The claim the poll loop takes before a request leaves. It is a lease on a
94
+ * pending attempt, not one of the four states the ledger publishes: it counts
95
+ * no attempt, records no outcome, and every read path answers `pending` for it.
96
+ */
97
+ type ClaimedDeliveryStatus = 'sending';
98
+
99
+ interface DeliveryRow {
100
+ id: string;
101
+ tenant_id: string;
102
+ channel: DeliveryChannel;
103
+ subscription_id: string | null;
104
+ recipient_account_id: string | null;
105
+ kind: NotificationKind;
106
+ source_module: string;
107
+ source_ref: string;
108
+ title: string;
109
+ sequence: number | bigint | string;
110
+ attempt_number: number | bigint | string;
111
+ status: DeliveryStatus | ClaimedDeliveryStatus;
112
+ scheduled_for: number | bigint | string;
113
+ claimed_at: number | bigint | string | null;
114
+ completed_at: number | bigint | string | null;
115
+ response_status: number | bigint | string | null;
116
+ error_class: string | null;
117
+ payload_digest: string;
118
+ payload_bytes: number | bigint | string;
119
+ occurred_at: number | bigint | string;
120
+ created_at: number | bigint | string;
121
+ }
122
+
123
+ const SUBSCRIPTION_COLUMNS = `id, tenant_id, name, url, events_json,
124
+ secret_key_id, secret_iv, secret_tag, secret_ciphertext, secret_fingerprint,
125
+ secret_revision, status, description, last_delivery_at, created_at,
126
+ updated_at, created_by`;
127
+
128
+ const DELIVERY_COLUMNS = `id, tenant_id, channel, subscription_id,
129
+ recipient_account_id, kind, source_module, source_ref, title, sequence,
130
+ attempt_number, status, scheduled_for, completed_at, response_status,
131
+ error_class, payload_digest, payload_bytes, occurred_at, created_at`;
132
+
133
+ /* The three list screens page by keyset. Each list has one filter predicate
134
+ and one page index, and the page read is composed from them at call time so
135
+ the direction, the keyset and the limit stay one decision. */
136
+ const INBOX_LIST = `tenant_id = $1 AND recipient_account_id = $2
137
+ AND (($3::text IS NULL AND status <> 'archived') OR status = $3)
138
+ AND ($4::text IS NULL OR kind = $4)`;
139
+
140
+ /* The subscription page orders by lower(name), which keysetWhere cannot name
141
+ as a column, so the source names it once and the row carries it back as the
142
+ key the next page resumes from. */
143
+ const SUBSCRIPTION_PAGE_SOURCE = `SELECT ${SUBSCRIPTION_COLUMNS}, name_key
144
+ FROM (SELECT ${SUBSCRIPTION_COLUMNS}, lower(name) AS name_key
145
+ FROM notifications_webhook_subscriptions) AS subscriptions`;
146
+ const SUBSCRIPTION_LIST = `tenant_id = $1
147
+ AND ($2::text IS NULL OR status = $2)
148
+ AND ($3::text IS NULL OR name ILIKE $3 OR url ILIKE $3)`;
149
+
150
+ const DELIVERY_LIST = `tenant_id = $1
151
+ AND ($2::text IS NULL OR status = $2)
152
+ AND ($3::text IS NULL OR subscription_id = $3)
153
+ AND ($4::text IS NULL OR source_ref ILIKE $4 OR source_module ILIKE $4)`;
154
+
155
+ /* Queries stay explicit. Values always travel in the adapter's parameter
156
+ channel; nothing from a request is concatenated into SQL. */
157
+ const SQL = {
158
+ inboxBySource: `SELECT id FROM notifications_inbox
159
+ WHERE tenant_id = $1 AND kind = $2 AND source_ref = $3
160
+ ORDER BY id`,
161
+ deliveriesBySource: `SELECT id FROM notifications_deliveries
162
+ WHERE tenant_id = $1 AND kind = $2 AND source_ref = $3
163
+ ORDER BY id`,
164
+ insertInbox: `INSERT INTO notifications_inbox
165
+ (id, tenant_id, recipient_account_id, kind, title, body, source_module,
166
+ source_ref, status, read_at, created_at)
167
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
168
+ ON CONFLICT DO NOTHING
169
+ RETURNING id`,
170
+ countUnread: `SELECT count(*) AS total FROM notifications_inbox
171
+ WHERE tenant_id = $1 AND recipient_account_id = $2 AND status = 'unread'`,
172
+ getInboxItem: `SELECT * FROM notifications_inbox
173
+ WHERE tenant_id = $1 AND recipient_account_id = $2 AND id = $3`,
174
+ /* The unique source index answers this; it is the key a publication is
175
+ idempotent on, so one member holds at most one item per event. */
176
+ findInboxItem: `SELECT * FROM notifications_inbox
177
+ WHERE tenant_id = $1 AND recipient_account_id = $2 AND kind = $3
178
+ AND source_ref = $4`,
179
+ setInboxStatus: `UPDATE notifications_inbox SET status = $4, read_at = $5
180
+ WHERE tenant_id = $1 AND recipient_account_id = $2 AND id = $3
181
+ RETURNING *`,
182
+ /* Both walks run on notifications_inbox_retention_idx, so a page is a range
183
+ read of the batch rather than a sort of the workspace's history. */
184
+ exportInbox: `SELECT * FROM notifications_inbox
185
+ WHERE tenant_id = $1
186
+ AND ($2::bigint IS NULL OR (created_at, id) > ($2::bigint, $3::text))
187
+ ORDER BY created_at, id
188
+ LIMIT $4`,
189
+ deleteInboxBefore: `DELETE FROM notifications_inbox
190
+ WHERE id IN (
191
+ SELECT id FROM notifications_inbox
192
+ WHERE tenant_id = $1 AND created_at < $2
193
+ ORDER BY created_at, id LIMIT $3
194
+ )`,
195
+
196
+ disabledRecipients: `SELECT recipient_account_id FROM notifications_preferences
197
+ WHERE tenant_id = $1 AND kind = $2 AND enabled = 0`,
198
+ listPreferences: `SELECT * FROM notifications_preferences
199
+ WHERE tenant_id = $1 AND recipient_account_id = $2
200
+ ORDER BY kind`,
201
+ savePreference: `INSERT INTO notifications_preferences
202
+ (id, tenant_id, recipient_account_id, kind, enabled, created_at, updated_at)
203
+ VALUES ($1, $2, $3, $4, $5, $6, $7)
204
+ ON CONFLICT (tenant_id, recipient_account_id, kind) DO UPDATE SET
205
+ enabled = EXCLUDED.enabled, updated_at = EXCLUDED.updated_at
206
+ RETURNING *`,
207
+
208
+ /* An absent row means the defaults, so only the members who asked for mail
209
+ are read back; the set bounds the e-mail rows one publication writes. */
210
+ emailRecipients: `SELECT recipient_account_id
211
+ FROM notifications_member_preferences
212
+ WHERE tenant_id = $1 AND email_delivery = 1`,
213
+ getMemberSettings: `SELECT * FROM notifications_member_preferences
214
+ WHERE tenant_id = $1 AND recipient_account_id = $2`,
215
+ saveMemberSettings: `INSERT INTO notifications_member_preferences
216
+ (tenant_id, recipient_account_id, email_delivery, created_at, updated_at)
217
+ VALUES ($1, $2, $3, $4, $5)
218
+ ON CONFLICT (tenant_id, recipient_account_id) DO UPDATE SET
219
+ email_delivery = EXCLUDED.email_delivery, updated_at = EXCLUDED.updated_at
220
+ RETURNING *`,
221
+
222
+ activeSubscriptionsForKind: `SELECT ${SUBSCRIPTION_COLUMNS}
223
+ FROM notifications_webhook_subscriptions
224
+ WHERE tenant_id = $1 AND status = 'active'
225
+ AND events_json::jsonb @> to_jsonb($2::text)
226
+ ORDER BY lower(name), id`,
227
+ getSubscription: `SELECT ${SUBSCRIPTION_COLUMNS}
228
+ FROM notifications_webhook_subscriptions
229
+ WHERE tenant_id = $1 AND id = $2`,
230
+ createSubscription: `INSERT INTO notifications_webhook_subscriptions
231
+ (${SUBSCRIPTION_COLUMNS})
232
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17)
233
+ ON CONFLICT DO NOTHING
234
+ RETURNING id`,
235
+ updateSubscription: `UPDATE notifications_webhook_subscriptions
236
+ SET name = $3, url = $4, events_json = $5, description = $6, updated_at = $7
237
+ WHERE tenant_id = $1 AND id = $2`,
238
+ setSubscriptionStatus: `UPDATE notifications_webhook_subscriptions
239
+ SET status = $3, updated_at = $4
240
+ WHERE tenant_id = $1 AND id = $2`,
241
+ disableSubscription: `UPDATE notifications_webhook_subscriptions
242
+ SET status = 'disabled', updated_at = $3
243
+ WHERE tenant_id = $1 AND id = $2`,
244
+ rotateSubscriptionSecret: `UPDATE notifications_webhook_subscriptions
245
+ SET secret_key_id = $3, secret_iv = $4, secret_tag = $5,
246
+ secret_ciphertext = $6, secret_fingerprint = $7,
247
+ secret_revision = secret_revision + 1, updated_at = $8
248
+ WHERE tenant_id = $1 AND id = $2`,
249
+ deleteSubscription: `DELETE FROM notifications_webhook_subscriptions
250
+ WHERE tenant_id = $1 AND id = $2`,
251
+ deletePendingDeliveries: `DELETE FROM notifications_deliveries
252
+ WHERE tenant_id = $1 AND subscription_id = $2 AND status = 'pending'`,
253
+ stampSubscriptionDelivery: `UPDATE notifications_webhook_subscriptions
254
+ SET last_delivery_at = $3 WHERE tenant_id = $1 AND id = $2`,
255
+
256
+ insertDelivery: `INSERT INTO notifications_deliveries (${DELIVERY_COLUMNS})
257
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20)
258
+ ON CONFLICT DO NOTHING
259
+ RETURNING id`,
260
+ getDelivery: `SELECT * FROM notifications_deliveries
261
+ WHERE tenant_id = $1 AND id = $2`,
262
+ /* One statement does the read back under the tenant, the due check and the
263
+ claim, so nothing can change between deciding to send and sending. */
264
+ claimDelivery: `UPDATE notifications_deliveries
265
+ SET status = 'sending', claimed_at = $3
266
+ WHERE tenant_id = $1 AND id = $2 AND scheduled_for <= $3
267
+ AND (status = 'pending' OR (status = 'sending' AND claimed_at <= $4))
268
+ RETURNING *`,
269
+ releaseDelivery: `UPDATE notifications_deliveries
270
+ SET status = 'pending', claimed_at = NULL, scheduled_for = $3
271
+ WHERE tenant_id = $1 AND id = $2 AND status = 'sending'`,
272
+ completeDelivery: `UPDATE notifications_deliveries
273
+ SET status = $3, completed_at = $4, response_status = $5, error_class = $6
274
+ WHERE tenant_id = $1 AND id = $2 AND status IN ('pending', 'sending')`,
275
+ latestSequence: `SELECT coalesce(max(sequence), 0) AS sequence
276
+ FROM notifications_deliveries
277
+ WHERE tenant_id = $1 AND channel = $2
278
+ AND coalesce(subscription_id, recipient_account_id) = $3 AND kind = $4
279
+ AND source_ref = $5`,
280
+ /* Read through the cross-tenant background lease; the attempt is read again
281
+ under the tenant this returned before anything leaves the process. */
282
+ listDueDeliveries: `SELECT tenant_id, id, scheduled_for, status
283
+ FROM notifications_deliveries
284
+ WHERE status = 'pending' AND scheduled_for <= $1
285
+ ORDER BY scheduled_for, id LIMIT $2`,
286
+ /* Kept apart from the due read so that one stays an ordered walk of the
287
+ routing index: a claim is a small partition, and folding it into the due
288
+ query with an OR would cost a sort of every due row instead. */
289
+ listStrandedDeliveries: `SELECT tenant_id, id, scheduled_for, status
290
+ FROM notifications_deliveries
291
+ WHERE status = 'sending' AND claimed_at <= $1
292
+ ORDER BY claimed_at, id LIMIT $2`,
293
+ listDeliveryTenants: `SELECT DISTINCT tenant_id FROM notifications_deliveries
294
+ WHERE tenant_id > $2 ORDER BY tenant_id LIMIT $1`,
295
+ exportDeliveries: `SELECT * FROM notifications_deliveries
296
+ WHERE tenant_id = $1
297
+ AND ($2::bigint IS NULL OR (created_at, id) > ($2::bigint, $3::text))
298
+ ORDER BY created_at, id
299
+ LIMIT $4`,
300
+ deleteCompletedBefore: `DELETE FROM notifications_deliveries
301
+ WHERE id IN (
302
+ SELECT id FROM notifications_deliveries
303
+ WHERE tenant_id = $1 AND status <> 'pending' AND completed_at IS NOT NULL
304
+ AND completed_at < $2
305
+ ORDER BY completed_at LIMIT $3
306
+ )`,
307
+ } as const;
308
+
309
+ /* 23505 is the SQLSTATE for a unique violation; the index name keeps a
310
+ different unique index on the table from being mistaken for the name. The
311
+ driver text is never surfaced, only the stable domain error. */
312
+ function isDuplicateName(error: unknown): boolean {
313
+ const cause = error as { code?: unknown; constraint?: unknown };
314
+ const text = String(error);
315
+ return (
316
+ (cause?.code === '23505' || text.includes('23505')) &&
317
+ (String(cause?.constraint ?? '').includes(
318
+ 'notifications_webhook_subscriptions_name_idx',
319
+ ) ||
320
+ text.includes('notifications_webhook_subscriptions_name_idx'))
321
+ );
322
+ }
323
+
324
+ function optionalInteger(
325
+ value: number | bigint | string | null,
326
+ field: string,
327
+ ): number | null {
328
+ return value === null ? null : integer(value, field);
329
+ }
330
+
331
+ /* The driver returns a timestamp as a Date; the domain keeps ISO text. */
332
+ function isoText(value: Date | string): string {
333
+ return value instanceof Date ? value.toISOString() : value;
334
+ }
335
+
336
+ /* The term is matched as a substring, so the three characters LIKE reads as
337
+ syntax are escaped with the backslash PostgreSQL takes as the default escape
338
+ character. The match runs after the tenant predicate, over that workspace's
339
+ rows alone. */
340
+ function likePattern(term: string | undefined): string | null {
341
+ if (term === undefined) return null;
342
+ return '%' + term.replace(/[\\%_]/g, (character) => '\\' + character) + '%';
343
+ }
344
+
345
+ /**
346
+ * One page read. The keyset and the ORDER BY name the same two columns in the
347
+ * same direction, which is the one order the list's page index carries either
348
+ * way round; the column names are constants here, never request input.
349
+ */
350
+ function pageStatement(
351
+ source: string,
352
+ filters: string,
353
+ parameters: readonly DatabaseParameter[],
354
+ columns: readonly [string, string],
355
+ page: ListPage<string | number>,
356
+ ): DatabaseStatement {
357
+ const keyset =
358
+ page.after === null
359
+ ? null
360
+ : keysetWhere(columns, [page.after.key, page.after.id], {
361
+ direction: page.direction,
362
+ parameterOffset: parameters.length,
363
+ });
364
+ const bound = [...parameters, ...(keyset?.parameters ?? [])];
365
+ const order = page.direction === 'asc' ? 'ASC' : 'DESC';
366
+ return {
367
+ text: `${source}
368
+ WHERE ${filters}${
369
+ keyset === null
370
+ ? ''
371
+ : `
372
+ AND ${keyset.text}`
373
+ }
374
+ ORDER BY ${columns[0]} ${order}, ${columns[1]} ${order}
375
+ LIMIT $${bound.length + 1}`,
376
+ parameters: [...bound, page.limit],
377
+ };
378
+ }
379
+
380
+ function paged<
381
+ Row extends { readonly id: string },
382
+ Key extends string | number,
383
+ >(
384
+ rows: readonly Row[],
385
+ limit: number,
386
+ key: (row: Row) => Key,
387
+ ): PagedRows<Row, Key> {
388
+ const last = rows.at(-1);
389
+ return {
390
+ rows,
391
+ next:
392
+ last !== undefined && rows.length === limit
393
+ ? { key: key(last), id: last.id }
394
+ : null,
395
+ };
396
+ }
397
+
398
+ function inboxFromRow(row: InboxRow): NotificationsInbox {
399
+ return {
400
+ id: row.id,
401
+ tenantId: row.tenant_id,
402
+ recipientAccountId: row.recipient_account_id,
403
+ kind: row.kind,
404
+ title: row.title,
405
+ body: row.body,
406
+ sourceModule: row.source_module,
407
+ sourceRef: row.source_ref,
408
+ status: row.status,
409
+ readAt: row.read_at === null ? null : isoText(row.read_at),
410
+ createdAt: integer(row.created_at, 'timestamp'),
411
+ };
412
+ }
413
+
414
+ function memberSettingsFromRow(
415
+ row: MemberPreferenceRow,
416
+ ): MemberNotificationSettings {
417
+ return {
418
+ tenantId: row.tenant_id,
419
+ recipientAccountId: row.recipient_account_id,
420
+ emailDelivery: integer(row.email_delivery, 'preference flag') === 1,
421
+ createdAt: integer(row.created_at, 'timestamp'),
422
+ updatedAt: integer(row.updated_at, 'timestamp'),
423
+ };
424
+ }
425
+
426
+ function preferenceFromRow(row: PreferenceRow): NotificationPreference {
427
+ return {
428
+ id: row.id,
429
+ tenantId: row.tenant_id,
430
+ recipientAccountId: row.recipient_account_id,
431
+ kind: row.kind,
432
+ enabled: integer(row.enabled, 'preference flag') === 1,
433
+ createdAt: integer(row.created_at, 'timestamp'),
434
+ updatedAt: integer(row.updated_at, 'timestamp'),
435
+ };
436
+ }
437
+
438
+ function subscriptionEvents(value: string): readonly NotificationKind[] {
439
+ try {
440
+ const parsed = JSON.parse(value) as unknown;
441
+ if (!Array.isArray(parsed)) return [];
442
+ return parsed.filter(
443
+ (entry): entry is NotificationKind => typeof entry === 'string',
444
+ );
445
+ } catch {
446
+ /* A row edited outside the module subscribes to nothing rather than to
447
+ everything; delivery fails closed. */
448
+ return [];
449
+ }
450
+ }
451
+
452
+ function subscriptionFromRow(row: SubscriptionRow): StoredWebhookSubscription {
453
+ return {
454
+ id: row.id,
455
+ tenantId: row.tenant_id,
456
+ name: row.name,
457
+ url: row.url,
458
+ events: subscriptionEvents(row.events_json),
459
+ secretFingerprint: row.secret_fingerprint,
460
+ secretRevision: integer(row.secret_revision, 'secret revision'),
461
+ status: row.status,
462
+ description: row.description,
463
+ lastDeliveryAt: optionalInteger(row.last_delivery_at, 'timestamp'),
464
+ createdAt: integer(row.created_at, 'timestamp'),
465
+ updatedAt: integer(row.updated_at, 'timestamp'),
466
+ createdBy: row.created_by,
467
+ secret: {
468
+ keyId: row.secret_key_id,
469
+ iv: row.secret_iv,
470
+ tag: row.secret_tag,
471
+ ciphertext: row.secret_ciphertext,
472
+ },
473
+ };
474
+ }
475
+
476
+ /* A claimed attempt has not completed, so the ledger keeps calling it pending:
477
+ the four states the specification defines are the only ones any read path,
478
+ screen or filter ever sees. */
479
+ function deliveryStatus(
480
+ status: DeliveryStatus | ClaimedDeliveryStatus,
481
+ ): DeliveryStatus {
482
+ return status === 'sending' ? 'pending' : status;
483
+ }
484
+
485
+ function deliveryFromRow(row: DeliveryRow): DeliveryAttempt {
486
+ return {
487
+ id: row.id,
488
+ tenantId: row.tenant_id,
489
+ channel: row.channel,
490
+ subscriptionId: row.subscription_id,
491
+ recipientAccountId: row.recipient_account_id,
492
+ kind: row.kind,
493
+ sourceModule: row.source_module,
494
+ sourceRef: row.source_ref,
495
+ title: row.title,
496
+ sequence: integer(row.sequence, 'sequence'),
497
+ attemptNumber: integer(row.attempt_number, 'attempt number'),
498
+ status: deliveryStatus(row.status),
499
+ scheduledFor: integer(row.scheduled_for, 'timestamp'),
500
+ completedAt: optionalInteger(row.completed_at, 'timestamp'),
501
+ responseStatus: optionalInteger(row.response_status, 'response status'),
502
+ errorClass: row.error_class,
503
+ payloadDigest: row.payload_digest,
504
+ payloadBytes: integer(row.payload_bytes, 'payload size'),
505
+ occurredAt: integer(row.occurred_at, 'timestamp'),
506
+ createdAt: integer(row.created_at, 'timestamp'),
507
+ };
508
+ }
509
+
510
+ function inboxParameters(
511
+ record: NotificationsInbox,
512
+ ): readonly DatabaseParameter[] {
513
+ return [
514
+ record.id,
515
+ record.tenantId,
516
+ record.recipientAccountId,
517
+ record.kind,
518
+ record.title,
519
+ record.body,
520
+ record.sourceModule,
521
+ record.sourceRef,
522
+ record.status,
523
+ record.readAt,
524
+ record.createdAt,
525
+ ];
526
+ }
527
+
528
+ function deliveryParameters(
529
+ record: DeliveryAttempt,
530
+ ): readonly DatabaseParameter[] {
531
+ return [
532
+ record.id,
533
+ record.tenantId,
534
+ record.channel,
535
+ record.subscriptionId,
536
+ record.recipientAccountId,
537
+ record.kind,
538
+ record.sourceModule,
539
+ record.sourceRef,
540
+ record.title,
541
+ record.sequence,
542
+ record.attemptNumber,
543
+ record.status,
544
+ record.scheduledFor,
545
+ record.completedAt,
546
+ record.responseStatus,
547
+ record.errorClass,
548
+ record.payloadDigest,
549
+ record.payloadBytes,
550
+ record.occurredAt,
551
+ record.createdAt,
552
+ ];
553
+ }
554
+
555
+ /** A publication that addresses nobody reads no preferences. */
556
+ const NO_RECIPIENTS: ReadonlySet<string> = new Set();
557
+
558
+ export interface NotificationsDatabaseHandles {
559
+ readonly runtime: DatabaseHandle;
560
+ /** Cross-tenant, read only, granted the delivery routing columns alone. */
561
+ readonly background: DatabaseHandle;
562
+ }
563
+
564
+ /** A repository over platform-owned PostgreSQL leases. */
565
+ export class DatabaseNotificationsRepository
566
+ implements NotificationsRepository
567
+ {
568
+ constructor(private readonly handles: NotificationsDatabaseHandles) {}
569
+
570
+ async #read<Row extends DatabaseRow>(
571
+ tenantId: string,
572
+ statement: DatabaseStatement,
573
+ ): Promise<readonly Row[]> {
574
+ const result = await this.handles.runtime.transaction(
575
+ (transaction) => transaction.query<Row>(statement),
576
+ { access: 'read', tenantId },
577
+ );
578
+ return result.rows;
579
+ }
580
+
581
+ async #write(
582
+ tenantId: string,
583
+ statement: DatabaseStatement,
584
+ ): Promise<number> {
585
+ const result = await this.handles.runtime.transaction(
586
+ (transaction) => transaction.execute(statement),
587
+ { access: 'write', tenantId },
588
+ );
589
+ return result.affectedRows;
590
+ }
591
+
592
+ async publish(input: PublishEventInput): Promise<PublishEventResult> {
593
+ return this.handles.runtime.transaction(
594
+ async (transaction) => {
595
+ const existing = await this.#existingEvent(transaction, input);
596
+ if (existing) return existing;
597
+ const disabled =
598
+ input.recipients.length === 0
599
+ ? NO_RECIPIENTS
600
+ : await this.#disabledRecipients(
601
+ transaction,
602
+ input.tenantId,
603
+ input.kind,
604
+ );
605
+ const mailed =
606
+ input.recipients.length === 0
607
+ ? NO_RECIPIENTS
608
+ : await this.#emailRecipients(transaction, input.tenantId);
609
+ const inboxItemIds: string[] = [];
610
+ const deliveryIds: string[] = [];
611
+ let addressed = 0;
612
+ let queued = 0;
613
+ for (const recipient of input.recipients) {
614
+ if (disabled.has(recipient)) continue;
615
+ addressed += 1;
616
+ const item = input.inboxItem(recipient);
617
+ const written = await transaction.query<{ id: string }>({
618
+ text: SQL.insertInbox,
619
+ parameters: [...inboxParameters(item)],
620
+ });
621
+ const id = written.rows[0]?.id;
622
+ if (id !== undefined) inboxItemIds.push(id);
623
+ /* The kind switch decided there is an item at all; this decides
624
+ whether the item that exists is also mailed. */
625
+ if (!mailed.has(recipient)) continue;
626
+ queued += 1;
627
+ const mail = await transaction.query<{ id: string }>({
628
+ text: SQL.insertDelivery,
629
+ parameters: [...deliveryParameters(input.emailDelivery(item))],
630
+ });
631
+ const mailId = mail.rows[0]?.id;
632
+ if (mailId !== undefined) deliveryIds.push(mailId);
633
+ }
634
+ const subscriptions = await transaction.query<SubscriptionRow>({
635
+ text: SQL.activeSubscriptionsForKind,
636
+ parameters: [input.tenantId, input.kind],
637
+ });
638
+ for (const row of subscriptions.rows) {
639
+ const written = await transaction.query<{ id: string }>({
640
+ text: SQL.insertDelivery,
641
+ parameters: [
642
+ ...deliveryParameters(input.delivery(subscriptionFromRow(row))),
643
+ ],
644
+ });
645
+ const id = written.rows[0]?.id;
646
+ if (id !== undefined) deliveryIds.push(id);
647
+ }
648
+ /* Fewer rows than the inserts carried means a concurrent publication
649
+ of the same event committed after the idempotency read took its
650
+ snapshot: its rows hold the unique indexes. They are this event's
651
+ rows, so they are read back rather than answered with the empty
652
+ result of an insert that conflicted. */
653
+ if (
654
+ inboxItemIds.length < addressed ||
655
+ deliveryIds.length < subscriptions.rows.length + queued
656
+ ) {
657
+ const written = await this.#existingEvent(transaction, input);
658
+ if (written) return written;
659
+ }
660
+ /* Sorted, because the repeat path reads them back ordered by id: a
661
+ caller comparing the two results must see the same arrays. */
662
+ return {
663
+ inboxItemIds: inboxItemIds.sort(),
664
+ deliveryIds: deliveryIds.sort(),
665
+ };
666
+ },
667
+ { access: 'write', tenantId: input.tenantId },
668
+ );
669
+ }
670
+
671
+ /* The idempotency read and every insert share one transaction, so a repeat
672
+ cannot interleave between the check and the writes it guards. */
673
+ async #existingEvent(
674
+ transaction: DatabaseTransaction,
675
+ input: PublishEventInput,
676
+ ): Promise<PublishEventResult | null> {
677
+ const parameters = [input.tenantId, input.kind, input.sourceRef];
678
+ const inbox = await transaction.query<{ id: string }>({
679
+ text: SQL.inboxBySource,
680
+ parameters,
681
+ });
682
+ const deliveries = await transaction.query<{ id: string }>({
683
+ text: SQL.deliveriesBySource,
684
+ parameters,
685
+ });
686
+ if (inbox.rows.length === 0 && deliveries.rows.length === 0) return null;
687
+ return {
688
+ inboxItemIds: inbox.rows.map((row) => row.id),
689
+ deliveryIds: deliveries.rows.map((row) => row.id),
690
+ };
691
+ }
692
+
693
+ /* An absent row means the member never asked for mail, so only the ones who
694
+ did are read back. Read once per publication, not once per recipient. */
695
+ async #emailRecipients(
696
+ transaction: DatabaseTransaction,
697
+ tenantId: string,
698
+ ): Promise<ReadonlySet<string>> {
699
+ const result = await transaction.query<{ recipient_account_id: string }>({
700
+ text: SQL.emailRecipients,
701
+ parameters: [tenantId],
702
+ });
703
+ return new Set(result.rows.map((row) => row.recipient_account_id));
704
+ }
705
+
706
+ /* An absent preference row means enabled, so only the members who switched
707
+ the kind off are read back. Every path that writes an inbox item crosses
708
+ this, a publication and a fan-out alike, so no caller can hand a member an
709
+ item of a kind they disabled. */
710
+ async #disabledRecipients(
711
+ transaction: DatabaseTransaction,
712
+ tenantId: string,
713
+ kind: NotificationKind,
714
+ ): Promise<ReadonlySet<string>> {
715
+ const result = await transaction.query<{ recipient_account_id: string }>({
716
+ text: SQL.disabledRecipients,
717
+ parameters: [tenantId, kind],
718
+ });
719
+ return new Set(result.rows.map((row) => row.recipient_account_id));
720
+ }
721
+
722
+ async listInbox(
723
+ tenantId: string,
724
+ recipientAccountId: string,
725
+ filters: InboxFilters,
726
+ page: ListPage<number>,
727
+ ): Promise<PagedRows<NotificationsInbox, number>> {
728
+ const rows = await this.#read<InboxRow>(
729
+ tenantId,
730
+ pageStatement(
731
+ 'SELECT * FROM notifications_inbox',
732
+ INBOX_LIST,
733
+ [
734
+ tenantId,
735
+ recipientAccountId,
736
+ filters.status ?? null,
737
+ filters.kind ?? null,
738
+ ],
739
+ ['created_at', 'id'],
740
+ page,
741
+ ),
742
+ );
743
+ return paged(rows.map(inboxFromRow), page.limit, (row) => row.createdAt);
744
+ }
745
+
746
+ async exportInboxPage(
747
+ tenantId: string,
748
+ after: ExportCursor | null,
749
+ limit: number,
750
+ ): Promise<readonly NotificationsInbox[]> {
751
+ const rows = await this.#read<InboxRow>(tenantId, {
752
+ text: SQL.exportInbox,
753
+ parameters: [
754
+ tenantId,
755
+ after?.createdAt ?? null,
756
+ after?.id ?? null,
757
+ limit,
758
+ ],
759
+ });
760
+ return rows.map(inboxFromRow);
761
+ }
762
+
763
+ async deleteInboxItemsBefore(
764
+ tenantId: string,
765
+ before: number,
766
+ limit: number,
767
+ ): Promise<number> {
768
+ return this.#write(tenantId, {
769
+ text: SQL.deleteInboxBefore,
770
+ parameters: [tenantId, before, limit],
771
+ });
772
+ }
773
+
774
+ async countUnreadInbox(
775
+ tenantId: string,
776
+ recipientAccountId: string,
777
+ ): Promise<number> {
778
+ const rows = await this.#read<{ total: number | bigint | string }>(
779
+ tenantId,
780
+ { text: SQL.countUnread, parameters: [tenantId, recipientAccountId] },
781
+ );
782
+ return integer(rows[0]?.total ?? 0, 'unread count');
783
+ }
784
+
785
+ async getInboxItem(
786
+ tenantId: string,
787
+ recipientAccountId: string,
788
+ id: string,
789
+ ): Promise<NotificationsInbox | null> {
790
+ const rows = await this.#read<InboxRow>(tenantId, {
791
+ text: SQL.getInboxItem,
792
+ parameters: [tenantId, recipientAccountId, id],
793
+ });
794
+ return rows[0] ? inboxFromRow(rows[0]) : null;
795
+ }
796
+
797
+ async setInboxStatus(
798
+ tenantId: string,
799
+ recipientAccountId: string,
800
+ id: string,
801
+ status: NotificationsInboxStatus,
802
+ readAt: string | null,
803
+ ): Promise<NotificationsInbox | null> {
804
+ const result = await this.handles.runtime.transaction(
805
+ (transaction) =>
806
+ transaction.query<InboxRow>({
807
+ text: SQL.setInboxStatus,
808
+ parameters: [tenantId, recipientAccountId, id, status, readAt],
809
+ }),
810
+ { access: 'write', tenantId },
811
+ );
812
+ const row = result.rows[0];
813
+ return row ? inboxFromRow(row) : null;
814
+ }
815
+
816
+ async appendInboxItems(
817
+ tenantId: string,
818
+ records: readonly NotificationsInbox[],
819
+ emailDelivery: (item: NotificationsInbox) => DeliveryAttempt,
820
+ ): Promise<readonly string[]> {
821
+ if (records.length === 0) return [];
822
+ return this.handles.runtime.transaction(
823
+ async (transaction) => {
824
+ const disabled = new Map<NotificationKind, ReadonlySet<string>>();
825
+ const mailed = await this.#emailRecipients(transaction, tenantId);
826
+ const written: string[] = [];
827
+ for (const record of records) {
828
+ let skipped = disabled.get(record.kind);
829
+ if (!skipped) {
830
+ skipped = await this.#disabledRecipients(
831
+ transaction,
832
+ tenantId,
833
+ record.kind,
834
+ );
835
+ disabled.set(record.kind, skipped);
836
+ }
837
+ if (skipped.has(record.recipientAccountId)) continue;
838
+ const result = await transaction.query<{ id: string }>({
839
+ text: SQL.insertInbox,
840
+ parameters: [...inboxParameters(record)],
841
+ });
842
+ const id = result.rows[0]?.id;
843
+ if (id === undefined) continue;
844
+ written.push(id);
845
+ /* Only a fresh item is mailed: a repeat wrote nothing, and the
846
+ member was already told. */
847
+ if (!mailed.has(record.recipientAccountId)) continue;
848
+ await transaction.query<{ id: string }>({
849
+ text: SQL.insertDelivery,
850
+ parameters: [...deliveryParameters(emailDelivery(record))],
851
+ });
852
+ }
853
+ return written;
854
+ },
855
+ { access: 'write', tenantId },
856
+ );
857
+ }
858
+
859
+ async findInboxItem(
860
+ tenantId: string,
861
+ recipientAccountId: string,
862
+ kind: NotificationKind,
863
+ sourceRef: string,
864
+ ): Promise<NotificationsInbox | null> {
865
+ const rows = await this.#read<InboxRow>(tenantId, {
866
+ text: SQL.findInboxItem,
867
+ parameters: [tenantId, recipientAccountId, kind, sourceRef],
868
+ });
869
+ return rows[0] ? inboxFromRow(rows[0]) : null;
870
+ }
871
+
872
+ async getMemberSettings(
873
+ tenantId: string,
874
+ recipientAccountId: string,
875
+ ): Promise<MemberNotificationSettings | null> {
876
+ const rows = await this.#read<MemberPreferenceRow>(tenantId, {
877
+ text: SQL.getMemberSettings,
878
+ parameters: [tenantId, recipientAccountId],
879
+ });
880
+ return rows[0] ? memberSettingsFromRow(rows[0]) : null;
881
+ }
882
+
883
+ async saveMemberSettings(
884
+ record: MemberNotificationSettings,
885
+ ): Promise<MemberNotificationSettings> {
886
+ const result = await this.handles.runtime.transaction(
887
+ (transaction) =>
888
+ transaction.query<MemberPreferenceRow>({
889
+ text: SQL.saveMemberSettings,
890
+ parameters: [
891
+ record.tenantId,
892
+ record.recipientAccountId,
893
+ record.emailDelivery ? 1 : 0,
894
+ record.createdAt,
895
+ record.updatedAt,
896
+ ],
897
+ }),
898
+ { access: 'write', tenantId: record.tenantId },
899
+ );
900
+ return memberSettingsFromRow(result.rows[0]!);
901
+ }
902
+
903
+ async listPreferences(
904
+ tenantId: string,
905
+ recipientAccountId: string,
906
+ ): Promise<readonly NotificationPreference[]> {
907
+ const rows = await this.#read<PreferenceRow>(tenantId, {
908
+ text: SQL.listPreferences,
909
+ parameters: [tenantId, recipientAccountId],
910
+ });
911
+ return rows.map(preferenceFromRow);
912
+ }
913
+
914
+ async savePreference(
915
+ record: NotificationPreference,
916
+ ): Promise<NotificationPreference> {
917
+ const result = await this.handles.runtime.transaction(
918
+ (transaction) =>
919
+ transaction.query<PreferenceRow>({
920
+ text: SQL.savePreference,
921
+ parameters: [
922
+ record.id,
923
+ record.tenantId,
924
+ record.recipientAccountId,
925
+ record.kind,
926
+ record.enabled ? 1 : 0,
927
+ record.createdAt,
928
+ record.updatedAt,
929
+ ],
930
+ }),
931
+ { access: 'write', tenantId: record.tenantId },
932
+ );
933
+ return preferenceFromRow(result.rows[0]!);
934
+ }
935
+
936
+ async listSubscriptions(
937
+ tenantId: string,
938
+ filters: SubscriptionFilters,
939
+ page: ListPage<string>,
940
+ ): Promise<PagedRows<StoredWebhookSubscription, string>> {
941
+ const rows = await this.#read<SubscriptionRow & { name_key: string }>(
942
+ tenantId,
943
+ pageStatement(
944
+ SUBSCRIPTION_PAGE_SOURCE,
945
+ SUBSCRIPTION_LIST,
946
+ [tenantId, filters.status ?? null, likePattern(filters.search)],
947
+ ['name_key', 'id'],
948
+ page,
949
+ ),
950
+ );
951
+ const keys = new Map(rows.map((row) => [row.id, row.name_key]));
952
+ return paged(
953
+ rows.map(subscriptionFromRow),
954
+ page.limit,
955
+ (row) => keys.get(row.id)!,
956
+ );
957
+ }
958
+
959
+ async getSubscription(
960
+ tenantId: string,
961
+ id: string,
962
+ ): Promise<StoredWebhookSubscription | null> {
963
+ const rows = await this.#read<SubscriptionRow>(tenantId, {
964
+ text: SQL.getSubscription,
965
+ parameters: [tenantId, id],
966
+ });
967
+ return rows[0] ? subscriptionFromRow(rows[0]) : null;
968
+ }
969
+
970
+ async createSubscription(
971
+ record: StoredWebhookSubscription,
972
+ ): Promise<StoredWebhookSubscription | null> {
973
+ const result = await this.handles.runtime.transaction(
974
+ (transaction) =>
975
+ transaction.query<{ id: string }>({
976
+ text: SQL.createSubscription,
977
+ parameters: [
978
+ record.id,
979
+ record.tenantId,
980
+ record.name,
981
+ record.url,
982
+ JSON.stringify(record.events),
983
+ record.secret.keyId,
984
+ record.secret.iv,
985
+ record.secret.tag,
986
+ record.secret.ciphertext,
987
+ record.secretFingerprint,
988
+ record.secretRevision,
989
+ record.status,
990
+ record.description,
991
+ record.lastDeliveryAt,
992
+ record.createdAt,
993
+ record.updatedAt,
994
+ record.createdBy,
995
+ ],
996
+ }),
997
+ { access: 'write', tenantId: record.tenantId },
998
+ );
999
+ return result.rows.length === 1 ? record : null;
1000
+ }
1001
+
1002
+ async updateSubscription(
1003
+ record: Pick<
1004
+ WebhookSubscription,
1005
+ | 'tenantId'
1006
+ | 'id'
1007
+ | 'name'
1008
+ | 'url'
1009
+ | 'events'
1010
+ | 'description'
1011
+ | 'updatedAt'
1012
+ >,
1013
+ ): Promise<StoredWebhookSubscription | null | 'conflict'> {
1014
+ let affected: number;
1015
+ try {
1016
+ affected = await this.#write(record.tenantId, {
1017
+ text: SQL.updateSubscription,
1018
+ parameters: [
1019
+ record.tenantId,
1020
+ record.id,
1021
+ record.name,
1022
+ record.url,
1023
+ JSON.stringify(record.events),
1024
+ record.description,
1025
+ record.updatedAt,
1026
+ ],
1027
+ });
1028
+ } catch (error) {
1029
+ if (isDuplicateName(error)) return 'conflict';
1030
+ throw error;
1031
+ }
1032
+ return affected === 1
1033
+ ? await this.getSubscription(record.tenantId, record.id)
1034
+ : null;
1035
+ }
1036
+
1037
+ async setSubscriptionStatus(
1038
+ tenantId: string,
1039
+ id: string,
1040
+ status: WebhookSubscriptionStatus,
1041
+ updatedAt: number,
1042
+ ): Promise<StoredWebhookSubscription | null> {
1043
+ const affected = await this.#write(tenantId, {
1044
+ text: SQL.setSubscriptionStatus,
1045
+ parameters: [tenantId, id, status, updatedAt],
1046
+ });
1047
+ return affected === 1 ? await this.getSubscription(tenantId, id) : null;
1048
+ }
1049
+
1050
+ /* The status change and the queue drop share one transaction, so the poll
1051
+ loop can never read a still-active row whose pending attempts are already
1052
+ gone, or send an attempt the operator has just dropped. */
1053
+ async disableSubscription(
1054
+ tenantId: string,
1055
+ id: string,
1056
+ updatedAt: number,
1057
+ ): Promise<StoredWebhookSubscription | null> {
1058
+ const disabled = await this.handles.runtime.transaction(
1059
+ async (transaction) => {
1060
+ const updated = await transaction.execute({
1061
+ text: SQL.disableSubscription,
1062
+ parameters: [tenantId, id, updatedAt],
1063
+ });
1064
+ if (updated.affectedRows !== 1) return false;
1065
+ await transaction.execute({
1066
+ text: SQL.deletePendingDeliveries,
1067
+ parameters: [tenantId, id],
1068
+ });
1069
+ return true;
1070
+ },
1071
+ { access: 'write', tenantId },
1072
+ );
1073
+ return disabled ? await this.getSubscription(tenantId, id) : null;
1074
+ }
1075
+
1076
+ async rotateSubscriptionSecret(
1077
+ tenantId: string,
1078
+ id: string,
1079
+ secret: EncryptedSecret,
1080
+ fingerprint: string,
1081
+ updatedAt: number,
1082
+ ): Promise<StoredWebhookSubscription | null> {
1083
+ const affected = await this.#write(tenantId, {
1084
+ text: SQL.rotateSubscriptionSecret,
1085
+ parameters: [
1086
+ tenantId,
1087
+ id,
1088
+ secret.keyId,
1089
+ secret.iv,
1090
+ secret.tag,
1091
+ secret.ciphertext,
1092
+ fingerprint,
1093
+ updatedAt,
1094
+ ],
1095
+ });
1096
+ return affected === 1 ? await this.getSubscription(tenantId, id) : null;
1097
+ }
1098
+
1099
+ async deleteSubscription(tenantId: string, id: string): Promise<boolean> {
1100
+ return this.handles.runtime.transaction(
1101
+ async (transaction) => {
1102
+ await transaction.execute({
1103
+ text: SQL.deletePendingDeliveries,
1104
+ parameters: [tenantId, id],
1105
+ });
1106
+ const removed = await transaction.execute({
1107
+ text: SQL.deleteSubscription,
1108
+ parameters: [tenantId, id],
1109
+ });
1110
+ return removed.affectedRows === 1;
1111
+ },
1112
+ { access: 'write', tenantId },
1113
+ );
1114
+ }
1115
+
1116
+ async stampSubscriptionDelivery(
1117
+ tenantId: string,
1118
+ id: string,
1119
+ at: number,
1120
+ ): Promise<void> {
1121
+ await this.#write(tenantId, {
1122
+ text: SQL.stampSubscriptionDelivery,
1123
+ parameters: [tenantId, id, at],
1124
+ });
1125
+ }
1126
+
1127
+ async listDeliveries(
1128
+ tenantId: string,
1129
+ filters: DeliveryFilters,
1130
+ page: ListPage<number>,
1131
+ ): Promise<PagedRows<DeliveryAttempt, number>> {
1132
+ const rows = await this.#read<DeliveryRow>(
1133
+ tenantId,
1134
+ pageStatement(
1135
+ 'SELECT * FROM notifications_deliveries',
1136
+ DELIVERY_LIST,
1137
+ [
1138
+ tenantId,
1139
+ filters.status ?? null,
1140
+ filters.subscriptionId ?? null,
1141
+ likePattern(filters.search),
1142
+ ],
1143
+ ['scheduled_for', 'id'],
1144
+ page,
1145
+ ),
1146
+ );
1147
+ return paged(
1148
+ rows.map(deliveryFromRow),
1149
+ page.limit,
1150
+ (row) => row.scheduledFor,
1151
+ );
1152
+ }
1153
+
1154
+ async getDelivery(
1155
+ tenantId: string,
1156
+ id: string,
1157
+ ): Promise<DeliveryAttempt | null> {
1158
+ const rows = await this.#read<DeliveryRow>(tenantId, {
1159
+ text: SQL.getDelivery,
1160
+ parameters: [tenantId, id],
1161
+ });
1162
+ return rows[0] ? deliveryFromRow(rows[0]) : null;
1163
+ }
1164
+
1165
+ async appendDelivery(
1166
+ record: DeliveryAttempt,
1167
+ ): Promise<DeliveryAttempt | null> {
1168
+ const result = await this.handles.runtime.transaction(
1169
+ (transaction) =>
1170
+ transaction.query<{ id: string }>({
1171
+ text: SQL.insertDelivery,
1172
+ parameters: [...deliveryParameters(record)],
1173
+ }),
1174
+ { access: 'write', tenantId: record.tenantId },
1175
+ );
1176
+ return result.rows.length === 1 ? record : null;
1177
+ }
1178
+
1179
+ async claimDelivery(
1180
+ input: ClaimDeliveryInput,
1181
+ ): Promise<DeliveryAttempt | null> {
1182
+ const result = await this.handles.runtime.transaction(
1183
+ (transaction) =>
1184
+ transaction.query<DeliveryRow>({
1185
+ text: SQL.claimDelivery,
1186
+ parameters: [
1187
+ input.tenantId,
1188
+ input.id,
1189
+ input.now,
1190
+ input.strandedBefore,
1191
+ ],
1192
+ }),
1193
+ { access: 'write', tenantId: input.tenantId },
1194
+ );
1195
+ const row = result.rows[0];
1196
+ return row ? deliveryFromRow(row) : null;
1197
+ }
1198
+
1199
+ async releaseDelivery(
1200
+ tenantId: string,
1201
+ id: string,
1202
+ scheduledFor: number,
1203
+ ): Promise<boolean> {
1204
+ const affected = await this.#write(tenantId, {
1205
+ text: SQL.releaseDelivery,
1206
+ parameters: [tenantId, id, scheduledFor],
1207
+ });
1208
+ return affected === 1;
1209
+ }
1210
+
1211
+ async completeDelivery(input: CompleteDeliveryInput): Promise<boolean> {
1212
+ const affected = await this.#write(input.tenantId, {
1213
+ text: SQL.completeDelivery,
1214
+ parameters: [
1215
+ input.tenantId,
1216
+ input.id,
1217
+ input.status,
1218
+ input.completedAt,
1219
+ input.responseStatus,
1220
+ input.errorClass,
1221
+ ],
1222
+ });
1223
+ return affected === 1;
1224
+ }
1225
+
1226
+ async latestDeliverySequence(
1227
+ attempt: Pick<
1228
+ DeliveryAttempt,
1229
+ | 'tenantId'
1230
+ | 'channel'
1231
+ | 'subscriptionId'
1232
+ | 'recipientAccountId'
1233
+ | 'kind'
1234
+ | 'sourceRef'
1235
+ >,
1236
+ ): Promise<number> {
1237
+ const rows = await this.#read<{ sequence: number | bigint | string }>(
1238
+ attempt.tenantId,
1239
+ {
1240
+ text: SQL.latestSequence,
1241
+ parameters: [
1242
+ attempt.tenantId,
1243
+ attempt.channel,
1244
+ attempt.subscriptionId ?? attempt.recipientAccountId,
1245
+ attempt.kind,
1246
+ attempt.sourceRef,
1247
+ ],
1248
+ },
1249
+ );
1250
+ return integer(rows[0]?.sequence ?? 0, 'sequence');
1251
+ }
1252
+
1253
+ async listDueDeliveries(
1254
+ now: number,
1255
+ limit: number,
1256
+ ): Promise<readonly DeliveryRouting[]> {
1257
+ return this.#route(SQL.listDueDeliveries, now, limit);
1258
+ }
1259
+
1260
+ async listStrandedDeliveries(
1261
+ before: number,
1262
+ limit: number,
1263
+ ): Promise<readonly DeliveryRouting[]> {
1264
+ return this.#route(SQL.listStrandedDeliveries, before, limit);
1265
+ }
1266
+
1267
+ async #route(
1268
+ text: string,
1269
+ bound: number,
1270
+ limit: number,
1271
+ ): Promise<readonly DeliveryRouting[]> {
1272
+ const result = await this.handles.background.query<{
1273
+ tenant_id: string;
1274
+ id: string;
1275
+ scheduled_for: number | bigint | string;
1276
+ status: DeliveryStatus | ClaimedDeliveryStatus;
1277
+ }>({ text, parameters: [bound, limit] });
1278
+ return result.rows.map((row) => ({
1279
+ tenantId: row.tenant_id,
1280
+ id: row.id,
1281
+ scheduledFor: integer(row.scheduled_for, 'timestamp'),
1282
+ status: deliveryStatus(row.status),
1283
+ }));
1284
+ }
1285
+
1286
+ async listDeliveryTenants(
1287
+ limit: number,
1288
+ after: string,
1289
+ ): Promise<readonly string[]> {
1290
+ const result = await this.handles.background.query<{ tenant_id: string }>({
1291
+ text: SQL.listDeliveryTenants,
1292
+ parameters: [limit, after],
1293
+ });
1294
+ return result.rows.map((row) => row.tenant_id);
1295
+ }
1296
+
1297
+ async deleteCompletedDeliveriesBefore(
1298
+ tenantId: string,
1299
+ before: number,
1300
+ limit: number,
1301
+ ): Promise<number> {
1302
+ return this.#write(tenantId, {
1303
+ text: SQL.deleteCompletedBefore,
1304
+ parameters: [tenantId, before, limit],
1305
+ });
1306
+ }
1307
+
1308
+ async exportDeliveriesPage(
1309
+ tenantId: string,
1310
+ after: ExportCursor | null,
1311
+ limit: number,
1312
+ ): Promise<readonly DeliveryAttempt[]> {
1313
+ const rows = await this.#read<DeliveryRow>(tenantId, {
1314
+ text: SQL.exportDeliveries,
1315
+ parameters: [
1316
+ tenantId,
1317
+ after?.createdAt ?? null,
1318
+ after?.id ?? null,
1319
+ limit,
1320
+ ],
1321
+ });
1322
+ return rows.map(deliveryFromRow);
1323
+ }
1324
+ }
1325
+
1326
+ export async function migrateNotificationsDatabase(
1327
+ database: DatabaseHandle,
1328
+ ): Promise<void> {
1329
+ await runDatabaseMigrations(
1330
+ database,
1331
+ 'notifications.core',
1332
+ databaseMigrations,
1333
+ );
1334
+ }