@open-mercato/core 0.6.5-develop.4384.1.ce2ec6eaaa → 0.6.5-develop.4393.1.de282b5dfd

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 (533) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/dist/generated/entities/channel_ingest_dead_letter/index.js +25 -0
  3. package/dist/generated/entities/channel_ingest_dead_letter/index.js.map +7 -0
  4. package/dist/generated/entities/channel_thread_mapping/index.js +25 -0
  5. package/dist/generated/entities/channel_thread_mapping/index.js.map +7 -0
  6. package/dist/generated/entities/channel_thread_token/index.js +17 -0
  7. package/dist/generated/entities/channel_thread_token/index.js.map +7 -0
  8. package/dist/generated/entities/communication_channel/index.js +43 -0
  9. package/dist/generated/entities/communication_channel/index.js.map +7 -0
  10. package/dist/generated/entities/customer_interaction/index.js +4 -0
  11. package/dist/generated/entities/customer_interaction/index.js.map +2 -2
  12. package/dist/generated/entities/external_conversation/index.js +25 -0
  13. package/dist/generated/entities/external_conversation/index.js.map +7 -0
  14. package/dist/generated/entities/external_message/index.js +25 -0
  15. package/dist/generated/entities/external_message/index.js.map +7 -0
  16. package/dist/generated/entities/integration_credentials/index.js +3 -1
  17. package/dist/generated/entities/integration_credentials/index.js.map +2 -2
  18. package/dist/generated/entities/message/index.js +2 -0
  19. package/dist/generated/entities/message/index.js.map +2 -2
  20. package/dist/generated/entities/message_channel_link/index.js +33 -0
  21. package/dist/generated/entities/message_channel_link/index.js.map +7 -0
  22. package/dist/generated/entities/message_reaction/index.js +25 -0
  23. package/dist/generated/entities/message_reaction/index.js.map +7 -0
  24. package/dist/generated/entities.ids.generated.js +11 -0
  25. package/dist/generated/entities.ids.generated.js.map +2 -2
  26. package/dist/generated/entity-fields-registry.js +117 -0
  27. package/dist/generated/entity-fields-registry.js.map +2 -2
  28. package/dist/helpers/integration/authFixtures.js +2 -1
  29. package/dist/helpers/integration/authFixtures.js.map +2 -2
  30. package/dist/helpers/integration/communicationChannelsFixtures.js +58 -0
  31. package/dist/helpers/integration/communicationChannelsFixtures.js.map +7 -0
  32. package/dist/modules/communication_channels/acl.js +47 -0
  33. package/dist/modules/communication_channels/acl.js.map +7 -0
  34. package/dist/modules/communication_channels/api/delete/channels/[id]/route.js +133 -0
  35. package/dist/modules/communication_channels/api/delete/channels/[id]/route.js.map +7 -0
  36. package/dist/modules/communication_channels/api/delete/messages/[messageId]/reactions/[reactionId]/route.js +113 -0
  37. package/dist/modules/communication_channels/api/delete/messages/[messageId]/reactions/[reactionId]/route.js.map +7 -0
  38. package/dist/modules/communication_channels/api/get/channels/[id]/health/route.js +138 -0
  39. package/dist/modules/communication_channels/api/get/channels/[id]/health/route.js.map +7 -0
  40. package/dist/modules/communication_channels/api/get/channels/[id]/route.js +93 -0
  41. package/dist/modules/communication_channels/api/get/channels/[id]/route.js.map +7 -0
  42. package/dist/modules/communication_channels/api/get/channels/route.js +96 -0
  43. package/dist/modules/communication_channels/api/get/channels/route.js.map +7 -0
  44. package/dist/modules/communication_channels/api/get/me/channels/route.js +82 -0
  45. package/dist/modules/communication_channels/api/get/me/channels/route.js.map +7 -0
  46. package/dist/modules/communication_channels/api/get/oauth/[provider]/callback/route.js +274 -0
  47. package/dist/modules/communication_channels/api/get/oauth/[provider]/callback/route.js.map +7 -0
  48. package/dist/modules/communication_channels/api/post/channels/[id]/import-history/route.js +168 -0
  49. package/dist/modules/communication_channels/api/post/channels/[id]/import-history/route.js.map +7 -0
  50. package/dist/modules/communication_channels/api/post/channels/[id]/poll-now/route.js +143 -0
  51. package/dist/modules/communication_channels/api/post/channels/[id]/poll-now/route.js.map +7 -0
  52. package/dist/modules/communication_channels/api/post/channels/[id]/push/register/route.js +127 -0
  53. package/dist/modules/communication_channels/api/post/channels/[id]/push/register/route.js.map +7 -0
  54. package/dist/modules/communication_channels/api/post/channels/[id]/set-primary/route.js +99 -0
  55. package/dist/modules/communication_channels/api/post/channels/[id]/set-primary/route.js.map +7 -0
  56. package/dist/modules/communication_channels/api/post/channels/[id]/test-send/route.js +197 -0
  57. package/dist/modules/communication_channels/api/post/channels/[id]/test-send/route.js.map +7 -0
  58. package/dist/modules/communication_channels/api/post/channels/connect/credentials/route.js +124 -0
  59. package/dist/modules/communication_channels/api/post/channels/connect/credentials/route.js.map +7 -0
  60. package/dist/modules/communication_channels/api/post/messages/[messageId]/reactions/route.js +120 -0
  61. package/dist/modules/communication_channels/api/post/messages/[messageId]/reactions/route.js.map +7 -0
  62. package/dist/modules/communication_channels/api/post/oauth/[provider]/initiate/route.js +157 -0
  63. package/dist/modules/communication_channels/api/post/oauth/[provider]/initiate/route.js.map +7 -0
  64. package/dist/modules/communication_channels/api/post/send-as-user/route.js +115 -0
  65. package/dist/modules/communication_channels/api/post/send-as-user/route.js.map +7 -0
  66. package/dist/modules/communication_channels/api/post/test-seed/route.js +217 -0
  67. package/dist/modules/communication_channels/api/post/test-seed/route.js.map +7 -0
  68. package/dist/modules/communication_channels/api/post/webhook/[provider]/route.js +175 -0
  69. package/dist/modules/communication_channels/api/post/webhook/[provider]/route.js.map +7 -0
  70. package/dist/modules/communication_channels/api/post/webhooks/gmail/route.js +123 -0
  71. package/dist/modules/communication_channels/api/post/webhooks/gmail/route.js.map +7 -0
  72. package/dist/modules/communication_channels/api/put/threads/[threadId]/assign/route.js +117 -0
  73. package/dist/modules/communication_channels/api/put/threads/[threadId]/assign/route.js.map +7 -0
  74. package/dist/modules/communication_channels/backend/communication_channels/channels/[id]/page.js +180 -0
  75. package/dist/modules/communication_channels/backend/communication_channels/channels/[id]/page.js.map +7 -0
  76. package/dist/modules/communication_channels/backend/communication_channels/channels/[id]/page.meta.js +36 -0
  77. package/dist/modules/communication_channels/backend/communication_channels/channels/[id]/page.meta.js.map +7 -0
  78. package/dist/modules/communication_channels/backend/communication_channels/channels/page.js +107 -0
  79. package/dist/modules/communication_channels/backend/communication_channels/channels/page.js.map +7 -0
  80. package/dist/modules/communication_channels/backend/communication_channels/channels/page.meta.js +38 -0
  81. package/dist/modules/communication_channels/backend/communication_channels/channels/page.meta.js.map +7 -0
  82. package/dist/modules/communication_channels/backend/profile/communication-channels/page.js +727 -0
  83. package/dist/modules/communication_channels/backend/profile/communication-channels/page.js.map +7 -0
  84. package/dist/modules/communication_channels/backend/profile/communication-channels/page.meta.js +38 -0
  85. package/dist/modules/communication_channels/backend/profile/communication-channels/page.meta.js.map +7 -0
  86. package/dist/modules/communication_channels/commands/connect-credential-channel.js +154 -0
  87. package/dist/modules/communication_channels/commands/connect-credential-channel.js.map +7 -0
  88. package/dist/modules/communication_channels/commands/delete-channel.js +137 -0
  89. package/dist/modules/communication_channels/commands/delete-channel.js.map +7 -0
  90. package/dist/modules/communication_channels/commands/deliver-outbound-message.js +400 -0
  91. package/dist/modules/communication_channels/commands/deliver-outbound-message.js.map +7 -0
  92. package/dist/modules/communication_channels/commands/disconnect-channel.js +163 -0
  93. package/dist/modules/communication_channels/commands/disconnect-channel.js.map +7 -0
  94. package/dist/modules/communication_channels/commands/ingest-inbound-message.js +413 -0
  95. package/dist/modules/communication_channels/commands/ingest-inbound-message.js.map +7 -0
  96. package/dist/modules/communication_channels/commands/interceptors.js +68 -0
  97. package/dist/modules/communication_channels/commands/interceptors.js.map +7 -0
  98. package/dist/modules/communication_channels/commands/process-inbound-reaction.js +198 -0
  99. package/dist/modules/communication_channels/commands/process-inbound-reaction.js.map +7 -0
  100. package/dist/modules/communication_channels/commands/push-register.js +146 -0
  101. package/dist/modules/communication_channels/commands/push-register.js.map +7 -0
  102. package/dist/modules/communication_channels/commands/push-renew.js +23 -0
  103. package/dist/modules/communication_channels/commands/push-renew.js.map +7 -0
  104. package/dist/modules/communication_channels/commands/push-unregister.js +108 -0
  105. package/dist/modules/communication_channels/commands/push-unregister.js.map +7 -0
  106. package/dist/modules/communication_channels/commands/queue-import-history.js +113 -0
  107. package/dist/modules/communication_channels/commands/queue-import-history.js.map +7 -0
  108. package/dist/modules/communication_channels/commands/reassign-conversation.js +193 -0
  109. package/dist/modules/communication_channels/commands/reassign-conversation.js.map +7 -0
  110. package/dist/modules/communication_channels/commands/set-primary-channel.js +114 -0
  111. package/dist/modules/communication_channels/commands/set-primary-channel.js.map +7 -0
  112. package/dist/modules/communication_channels/commands/toggle-outbound-reaction.js +260 -0
  113. package/dist/modules/communication_channels/commands/toggle-outbound-reaction.js.map +7 -0
  114. package/dist/modules/communication_channels/data/enrichers.js +286 -0
  115. package/dist/modules/communication_channels/data/enrichers.js.map +7 -0
  116. package/dist/modules/communication_channels/data/entities.js +447 -0
  117. package/dist/modules/communication_channels/data/entities.js.map +7 -0
  118. package/dist/modules/communication_channels/data/extensions.js +67 -0
  119. package/dist/modules/communication_channels/data/extensions.js.map +7 -0
  120. package/dist/modules/communication_channels/data/validators.js +123 -0
  121. package/dist/modules/communication_channels/data/validators.js.map +7 -0
  122. package/dist/modules/communication_channels/di.js +35 -0
  123. package/dist/modules/communication_channels/di.js.map +7 -0
  124. package/dist/modules/communication_channels/encryption.js +12 -0
  125. package/dist/modules/communication_channels/encryption.js.map +7 -0
  126. package/dist/modules/communication_channels/events.js +124 -0
  127. package/dist/modules/communication_channels/events.js.map +7 -0
  128. package/dist/modules/communication_channels/index.js +20 -0
  129. package/dist/modules/communication_channels/index.js.map +7 -0
  130. package/dist/modules/communication_channels/lib/access-control.js +43 -0
  131. package/dist/modules/communication_channels/lib/access-control.js.map +7 -0
  132. package/dist/modules/communication_channels/lib/adapter-compat.js +36 -0
  133. package/dist/modules/communication_channels/lib/adapter-compat.js.map +7 -0
  134. package/dist/modules/communication_channels/lib/adapter-registry-singleton.js +22 -0
  135. package/dist/modules/communication_channels/lib/adapter-registry-singleton.js.map +7 -0
  136. package/dist/modules/communication_channels/lib/adapter.js +1 -0
  137. package/dist/modules/communication_channels/lib/adapter.js.map +7 -0
  138. package/dist/modules/communication_channels/lib/connect-channel.js +95 -0
  139. package/dist/modules/communication_channels/lib/connect-channel.js.map +7 -0
  140. package/dist/modules/communication_channels/lib/contact-resolver.js +79 -0
  141. package/dist/modules/communication_channels/lib/contact-resolver.js.map +7 -0
  142. package/dist/modules/communication_channels/lib/credential-refresh.js +97 -0
  143. package/dist/modules/communication_channels/lib/credential-refresh.js.map +7 -0
  144. package/dist/modules/communication_channels/lib/dead-letter.js +62 -0
  145. package/dist/modules/communication_channels/lib/dead-letter.js.map +7 -0
  146. package/dist/modules/communication_channels/lib/email-capabilities.js +47 -0
  147. package/dist/modules/communication_channels/lib/email-capabilities.js.map +7 -0
  148. package/dist/modules/communication_channels/lib/email-contact.js +14 -0
  149. package/dist/modules/communication_channels/lib/email-contact.js.map +7 -0
  150. package/dist/modules/communication_channels/lib/email-mime.js +259 -0
  151. package/dist/modules/communication_channels/lib/email-mime.js.map +7 -0
  152. package/dist/modules/communication_channels/lib/error-classification.js +101 -0
  153. package/dist/modules/communication_channels/lib/error-classification.js.map +7 -0
  154. package/dist/modules/communication_channels/lib/gmail-pubsub-jwt.js +185 -0
  155. package/dist/modules/communication_channels/lib/gmail-pubsub-jwt.js.map +7 -0
  156. package/dist/modules/communication_channels/lib/mutation-guards.js +114 -0
  157. package/dist/modules/communication_channels/lib/mutation-guards.js.map +7 -0
  158. package/dist/modules/communication_channels/lib/oauth-client-config.js +32 -0
  159. package/dist/modules/communication_channels/lib/oauth-client-config.js.map +7 -0
  160. package/dist/modules/communication_channels/lib/oauth-state.js +128 -0
  161. package/dist/modules/communication_channels/lib/oauth-state.js.map +7 -0
  162. package/dist/modules/communication_channels/lib/oauth-token.js +45 -0
  163. package/dist/modules/communication_channels/lib/oauth-token.js.map +7 -0
  164. package/dist/modules/communication_channels/lib/pg-errors.js +11 -0
  165. package/dist/modules/communication_channels/lib/pg-errors.js.map +7 -0
  166. package/dist/modules/communication_channels/lib/provider-health.js +24 -0
  167. package/dist/modules/communication_channels/lib/provider-health.js.map +7 -0
  168. package/dist/modules/communication_channels/lib/push-state.js +19 -0
  169. package/dist/modules/communication_channels/lib/push-state.js.map +7 -0
  170. package/dist/modules/communication_channels/lib/queue.js +54 -0
  171. package/dist/modules/communication_channels/lib/queue.js.map +7 -0
  172. package/dist/modules/communication_channels/lib/reaction-processor-types.js +5 -0
  173. package/dist/modules/communication_channels/lib/reaction-processor-types.js.map +7 -0
  174. package/dist/modules/communication_channels/lib/reaction-semantics.js +11 -0
  175. package/dist/modules/communication_channels/lib/reaction-semantics.js.map +7 -0
  176. package/dist/modules/communication_channels/lib/registry.js +67 -0
  177. package/dist/modules/communication_channels/lib/registry.js.map +7 -0
  178. package/dist/modules/communication_channels/lib/route-mutation-guard.js +43 -0
  179. package/dist/modules/communication_channels/lib/route-mutation-guard.js.map +7 -0
  180. package/dist/modules/communication_channels/lib/sanitize-channel-html.js +96 -0
  181. package/dist/modules/communication_channels/lib/sanitize-channel-html.js.map +7 -0
  182. package/dist/modules/communication_channels/lib/send-as-user.js +194 -0
  183. package/dist/modules/communication_channels/lib/send-as-user.js.map +7 -0
  184. package/dist/modules/communication_channels/lib/system-user.js +22 -0
  185. package/dist/modules/communication_channels/lib/system-user.js.map +7 -0
  186. package/dist/modules/communication_channels/lib/test-seed.js +68 -0
  187. package/dist/modules/communication_channels/lib/test-seed.js.map +7 -0
  188. package/dist/modules/communication_channels/lib/thread-matcher.js +263 -0
  189. package/dist/modules/communication_channels/lib/thread-matcher.js.map +7 -0
  190. package/dist/modules/communication_channels/lib/thread-token.js +219 -0
  191. package/dist/modules/communication_channels/lib/thread-token.js.map +7 -0
  192. package/dist/modules/communication_channels/lib/use-connect-channel.js +61 -0
  193. package/dist/modules/communication_channels/lib/use-connect-channel.js.map +7 -0
  194. package/dist/modules/communication_channels/migrations/Migration20260526134719_communication_channels.js +50 -0
  195. package/dist/modules/communication_channels/migrations/Migration20260526134719_communication_channels.js.map +7 -0
  196. package/dist/modules/communication_channels/migrations/Migration20260527195446_communication_channels.js +19 -0
  197. package/dist/modules/communication_channels/migrations/Migration20260527195446_communication_channels.js.map +7 -0
  198. package/dist/modules/communication_channels/migrations/Migration20260529231848_communication_channels.js +13 -0
  199. package/dist/modules/communication_channels/migrations/Migration20260529231848_communication_channels.js.map +7 -0
  200. package/dist/modules/communication_channels/migrations/Migration20260531120000_communication_channels.js +17 -0
  201. package/dist/modules/communication_channels/migrations/Migration20260531120000_communication_channels.js.map +7 -0
  202. package/dist/modules/communication_channels/notifications.client.js +51 -0
  203. package/dist/modules/communication_channels/notifications.client.js.map +7 -0
  204. package/dist/modules/communication_channels/notifications.handlers.js +53 -0
  205. package/dist/modules/communication_channels/notifications.handlers.js.map +7 -0
  206. package/dist/modules/communication_channels/notifications.js +56 -0
  207. package/dist/modules/communication_channels/notifications.js.map +7 -0
  208. package/dist/modules/communication_channels/setup.js +105 -0
  209. package/dist/modules/communication_channels/setup.js.map +7 -0
  210. package/dist/modules/communication_channels/subscribers/channel-requires-reauth-notification.js +71 -0
  211. package/dist/modules/communication_channels/subscribers/channel-requires-reauth-notification.js.map +7 -0
  212. package/dist/modules/communication_channels/subscribers/outbound-bridge.js +103 -0
  213. package/dist/modules/communication_channels/subscribers/outbound-bridge.js.map +7 -0
  214. package/dist/modules/communication_channels/subscribers/user-deleted-cascade.js +51 -0
  215. package/dist/modules/communication_channels/subscribers/user-deleted-cascade.js.map +7 -0
  216. package/dist/modules/communication_channels/widgets/components.js +7 -0
  217. package/dist/modules/communication_channels/widgets/components.js.map +7 -0
  218. package/dist/modules/communication_channels/widgets/injection/channel-badge/widget.client.js +18 -0
  219. package/dist/modules/communication_channels/widgets/injection/channel-badge/widget.client.js.map +7 -0
  220. package/dist/modules/communication_channels/widgets/injection/channel-badge/widget.js +30 -0
  221. package/dist/modules/communication_channels/widgets/injection/channel-badge/widget.js.map +7 -0
  222. package/dist/modules/communication_channels/widgets/injection/channel-info-panel/widget.client.js +185 -0
  223. package/dist/modules/communication_channels/widgets/injection/channel-info-panel/widget.client.js.map +7 -0
  224. package/dist/modules/communication_channels/widgets/injection/channel-info-panel/widget.js +17 -0
  225. package/dist/modules/communication_channels/widgets/injection/channel-info-panel/widget.js.map +7 -0
  226. package/dist/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.client.js +44 -0
  227. package/dist/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.client.js.map +7 -0
  228. package/dist/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.js +17 -0
  229. package/dist/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.js.map +7 -0
  230. package/dist/modules/communication_channels/widgets/injection/profile-channels-menu/widget.js +23 -0
  231. package/dist/modules/communication_channels/widgets/injection/profile-channels-menu/widget.js.map +7 -0
  232. package/dist/modules/communication_channels/widgets/injection/reaction-bar/widget.client.js +141 -0
  233. package/dist/modules/communication_channels/widgets/injection/reaction-bar/widget.client.js.map +7 -0
  234. package/dist/modules/communication_channels/widgets/injection/reaction-bar/widget.js +17 -0
  235. package/dist/modules/communication_channels/widgets/injection/reaction-bar/widget.js.map +7 -0
  236. package/dist/modules/communication_channels/widgets/injection-table.js +38 -0
  237. package/dist/modules/communication_channels/widgets/injection-table.js.map +7 -0
  238. package/dist/modules/communication_channels/widgets/notifications/ChannelRequiresReauthRenderer.js +25 -0
  239. package/dist/modules/communication_channels/widgets/notifications/ChannelRequiresReauthRenderer.js.map +7 -0
  240. package/dist/modules/communication_channels/widgets/notifications/MessageReceivedRenderer.js +19 -0
  241. package/dist/modules/communication_channels/widgets/notifications/MessageReceivedRenderer.js.map +7 -0
  242. package/dist/modules/communication_channels/widgets/notifications/index.js +7 -0
  243. package/dist/modules/communication_channels/widgets/notifications/index.js.map +7 -0
  244. package/dist/modules/communication_channels/workers/channel-import-history.js +185 -0
  245. package/dist/modules/communication_channels/workers/channel-import-history.js.map +7 -0
  246. package/dist/modules/communication_channels/workers/gmail-history-sync.js +154 -0
  247. package/dist/modules/communication_channels/workers/gmail-history-sync.js.map +7 -0
  248. package/dist/modules/communication_channels/workers/gmail-renew-watch.js +95 -0
  249. package/dist/modules/communication_channels/workers/gmail-renew-watch.js.map +7 -0
  250. package/dist/modules/communication_channels/workers/inbound-processor.js +56 -0
  251. package/dist/modules/communication_channels/workers/inbound-processor.js.map +7 -0
  252. package/dist/modules/communication_channels/workers/outbound-delivery.js +85 -0
  253. package/dist/modules/communication_channels/workers/outbound-delivery.js.map +7 -0
  254. package/dist/modules/communication_channels/workers/poll-channel.js +240 -0
  255. package/dist/modules/communication_channels/workers/poll-channel.js.map +7 -0
  256. package/dist/modules/communication_channels/workers/poll-tick.js +132 -0
  257. package/dist/modules/communication_channels/workers/poll-tick.js.map +7 -0
  258. package/dist/modules/communication_channels/workers/reaction-processor.js +192 -0
  259. package/dist/modules/communication_channels/workers/reaction-processor.js.map +7 -0
  260. package/dist/modules/customers/acl.js +18 -0
  261. package/dist/modules/customers/acl.js.map +2 -2
  262. package/dist/modules/customers/api/activities/route.js +9 -0
  263. package/dist/modules/customers/api/activities/route.js.map +2 -2
  264. package/dist/modules/customers/api/companies/[id]/route.js +18 -7
  265. package/dist/modules/customers/api/companies/[id]/route.js.map +2 -2
  266. package/dist/modules/customers/api/interactions/[id]/visibility/route.js +151 -0
  267. package/dist/modules/customers/api/interactions/[id]/visibility/route.js.map +7 -0
  268. package/dist/modules/customers/api/interactions/counts/route.js +6 -0
  269. package/dist/modules/customers/api/interactions/counts/route.js.map +2 -2
  270. package/dist/modules/customers/api/interactions/route.js +26 -7
  271. package/dist/modules/customers/api/interactions/route.js.map +2 -2
  272. package/dist/modules/customers/api/people/[id]/email-threads/route.js +82 -0
  273. package/dist/modules/customers/api/people/[id]/email-threads/route.js.map +7 -0
  274. package/dist/modules/customers/api/people/[id]/emails/route.js +157 -0
  275. package/dist/modules/customers/api/people/[id]/emails/route.js.map +7 -0
  276. package/dist/modules/customers/api/people/[id]/route.js +12 -4
  277. package/dist/modules/customers/api/people/[id]/route.js.map +2 -2
  278. package/dist/modules/customers/backend/customers/people-v2/[id]/page.js +10 -0
  279. package/dist/modules/customers/backend/customers/people-v2/[id]/page.js.map +2 -2
  280. package/dist/modules/customers/commands/deals.js +46 -5
  281. package/dist/modules/customers/commands/deals.js.map +2 -2
  282. package/dist/modules/customers/commands/interactions.js +16 -0
  283. package/dist/modules/customers/commands/interactions.js.map +2 -2
  284. package/dist/modules/customers/components/detail/ActivityCard.js +32 -0
  285. package/dist/modules/customers/components/detail/ActivityCard.js.map +2 -2
  286. package/dist/modules/customers/components/detail/ComposeEmailDialog.js +242 -0
  287. package/dist/modules/customers/components/detail/ComposeEmailDialog.js.map +7 -0
  288. package/dist/modules/customers/components/detail/DealForm.js +2 -1
  289. package/dist/modules/customers/components/detail/DealForm.js.map +2 -2
  290. package/dist/modules/customers/components/detail/DealsSection.js +10 -0
  291. package/dist/modules/customers/components/detail/DealsSection.js.map +2 -2
  292. package/dist/modules/customers/components/detail/EmailCardActions.js +179 -0
  293. package/dist/modules/customers/components/detail/EmailCardActions.js.map +7 -0
  294. package/dist/modules/customers/components/detail/EmailReplyForwardActions.js +52 -0
  295. package/dist/modules/customers/components/detail/EmailReplyForwardActions.js.map +7 -0
  296. package/dist/modules/customers/components/detail/PersonDetailTabs.js +7 -1
  297. package/dist/modules/customers/components/detail/PersonDetailTabs.js.map +2 -2
  298. package/dist/modules/customers/components/detail/PersonEmailThreadsTab.js +366 -0
  299. package/dist/modules/customers/components/detail/PersonEmailThreadsTab.js.map +7 -0
  300. package/dist/modules/customers/data/enrichers.js +133 -2
  301. package/dist/modules/customers/data/enrichers.js.map +2 -2
  302. package/dist/modules/customers/data/entities.js +18 -0
  303. package/dist/modules/customers/data/entities.js.map +2 -2
  304. package/dist/modules/customers/data/extensions.js +16 -0
  305. package/dist/modules/customers/data/extensions.js.map +7 -0
  306. package/dist/modules/customers/encryption.js +11 -0
  307. package/dist/modules/customers/encryption.js.map +2 -2
  308. package/dist/modules/customers/events.js +4 -1
  309. package/dist/modules/customers/events.js.map +2 -2
  310. package/dist/modules/customers/lib/findPeopleByAddresses.js +64 -0
  311. package/dist/modules/customers/lib/findPeopleByAddresses.js.map +7 -0
  312. package/dist/modules/customers/lib/kysely.js.map +2 -2
  313. package/dist/modules/customers/lib/link-channel-message-handler.js +303 -0
  314. package/dist/modules/customers/lib/link-channel-message-handler.js.map +7 -0
  315. package/dist/modules/customers/lib/personEmailThreads.js +205 -0
  316. package/dist/modules/customers/lib/personEmailThreads.js.map +7 -0
  317. package/dist/modules/customers/lib/visibilityFilter.js +51 -0
  318. package/dist/modules/customers/lib/visibilityFilter.js.map +7 -0
  319. package/dist/modules/customers/migrations/Migration20260527012240_customers.js +20 -0
  320. package/dist/modules/customers/migrations/Migration20260527012240_customers.js.map +7 -0
  321. package/dist/modules/customers/setup.js +2 -1
  322. package/dist/modules/customers/setup.js.map +2 -2
  323. package/dist/modules/customers/subscribers/link-channel-message-received.js +12 -0
  324. package/dist/modules/customers/subscribers/link-channel-message-received.js.map +7 -0
  325. package/dist/modules/customers/subscribers/link-channel-message-sent.js +12 -0
  326. package/dist/modules/customers/subscribers/link-channel-message-sent.js.map +7 -0
  327. package/dist/modules/integrations/data/entities.js +8 -1
  328. package/dist/modules/integrations/data/entities.js.map +2 -2
  329. package/dist/modules/integrations/lib/credentials-service.js +29 -14
  330. package/dist/modules/integrations/lib/credentials-service.js.map +2 -2
  331. package/dist/modules/integrations/migrations/Migration20260526154136_integrations.js +15 -0
  332. package/dist/modules/integrations/migrations/Migration20260526154136_integrations.js.map +7 -0
  333. package/dist/modules/messages/commands/messages.js +70 -8
  334. package/dist/modules/messages/commands/messages.js.map +2 -2
  335. package/dist/modules/messages/components/ComposeMessagePageClient.js +24 -13
  336. package/dist/modules/messages/components/ComposeMessagePageClient.js.map +2 -2
  337. package/dist/modules/messages/components/MessageDetailPageClient.js +39 -2
  338. package/dist/modules/messages/components/MessageDetailPageClient.js.map +2 -2
  339. package/dist/modules/messages/components/MessagesInboxPageClient.js +1 -0
  340. package/dist/modules/messages/components/MessagesInboxPageClient.js.map +2 -2
  341. package/dist/modules/messages/data/entities.js +8 -1
  342. package/dist/modules/messages/data/entities.js.map +2 -2
  343. package/dist/modules/messages/migrations/Migration20260531130000.js +15 -0
  344. package/dist/modules/messages/migrations/Migration20260531130000.js.map +7 -0
  345. package/dist/modules/messages/widgets/injection-table.js +7 -0
  346. package/dist/modules/messages/widgets/injection-table.js.map +7 -0
  347. package/generated/entities/channel_ingest_dead_letter/index.ts +11 -0
  348. package/generated/entities/channel_thread_mapping/index.ts +11 -0
  349. package/generated/entities/channel_thread_token/index.ts +7 -0
  350. package/generated/entities/communication_channel/index.ts +20 -0
  351. package/generated/entities/customer_interaction/index.ts +2 -0
  352. package/generated/entities/external_conversation/index.ts +11 -0
  353. package/generated/entities/external_message/index.ts +11 -0
  354. package/generated/entities/integration_credentials/index.ts +1 -0
  355. package/generated/entities/message/index.ts +1 -0
  356. package/generated/entities/message_channel_link/index.ts +15 -0
  357. package/generated/entities/message_reaction/index.ts +11 -0
  358. package/generated/entities.ids.generated.ts +11 -0
  359. package/generated/entity-fields-registry.ts +117 -0
  360. package/package.json +9 -7
  361. package/src/helpers/integration/authFixtures.ts +4 -1
  362. package/src/helpers/integration/communicationChannelsFixtures.ts +124 -0
  363. package/src/modules/communication_channels/acl.ts +43 -0
  364. package/src/modules/communication_channels/api/delete/channels/[id]/route.ts +163 -0
  365. package/src/modules/communication_channels/api/delete/messages/[messageId]/reactions/[reactionId]/route.ts +143 -0
  366. package/src/modules/communication_channels/api/get/channels/[id]/health/route.ts +173 -0
  367. package/src/modules/communication_channels/api/get/channels/[id]/route.ts +111 -0
  368. package/src/modules/communication_channels/api/get/channels/route.ts +109 -0
  369. package/src/modules/communication_channels/api/get/me/channels/route.ts +100 -0
  370. package/src/modules/communication_channels/api/get/oauth/[provider]/callback/route.ts +355 -0
  371. package/src/modules/communication_channels/api/post/channels/[id]/import-history/route.ts +206 -0
  372. package/src/modules/communication_channels/api/post/channels/[id]/poll-now/route.ts +174 -0
  373. package/src/modules/communication_channels/api/post/channels/[id]/push/register/route.ts +158 -0
  374. package/src/modules/communication_channels/api/post/channels/[id]/set-primary/route.ts +114 -0
  375. package/src/modules/communication_channels/api/post/channels/[id]/test-send/route.ts +241 -0
  376. package/src/modules/communication_channels/api/post/channels/connect/credentials/route.ts +134 -0
  377. package/src/modules/communication_channels/api/post/messages/[messageId]/reactions/route.ts +143 -0
  378. package/src/modules/communication_channels/api/post/oauth/[provider]/initiate/route.ts +192 -0
  379. package/src/modules/communication_channels/api/post/send-as-user/route.ts +125 -0
  380. package/src/modules/communication_channels/api/post/test-seed/route.ts +267 -0
  381. package/src/modules/communication_channels/api/post/webhook/[provider]/route.ts +227 -0
  382. package/src/modules/communication_channels/api/post/webhooks/gmail/route.ts +161 -0
  383. package/src/modules/communication_channels/api/put/threads/[threadId]/assign/route.ts +132 -0
  384. package/src/modules/communication_channels/backend/communication_channels/channels/[id]/page.meta.ts +34 -0
  385. package/src/modules/communication_channels/backend/communication_channels/channels/[id]/page.tsx +250 -0
  386. package/src/modules/communication_channels/backend/communication_channels/channels/page.meta.ts +36 -0
  387. package/src/modules/communication_channels/backend/communication_channels/channels/page.tsx +137 -0
  388. package/src/modules/communication_channels/backend/profile/communication-channels/page.meta.ts +36 -0
  389. package/src/modules/communication_channels/backend/profile/communication-channels/page.tsx +907 -0
  390. package/src/modules/communication_channels/commands/connect-credential-channel.ts +243 -0
  391. package/src/modules/communication_channels/commands/delete-channel.ts +193 -0
  392. package/src/modules/communication_channels/commands/deliver-outbound-message.ts +579 -0
  393. package/src/modules/communication_channels/commands/disconnect-channel.ts +241 -0
  394. package/src/modules/communication_channels/commands/ingest-inbound-message.ts +602 -0
  395. package/src/modules/communication_channels/commands/interceptors.ts +104 -0
  396. package/src/modules/communication_channels/commands/process-inbound-reaction.ts +265 -0
  397. package/src/modules/communication_channels/commands/push-register.ts +203 -0
  398. package/src/modules/communication_channels/commands/push-renew.ts +49 -0
  399. package/src/modules/communication_channels/commands/push-unregister.ts +168 -0
  400. package/src/modules/communication_channels/commands/queue-import-history.ts +180 -0
  401. package/src/modules/communication_channels/commands/reassign-conversation.ts +273 -0
  402. package/src/modules/communication_channels/commands/set-primary-channel.ts +154 -0
  403. package/src/modules/communication_channels/commands/toggle-outbound-reaction.ts +347 -0
  404. package/src/modules/communication_channels/data/enrichers.ts +413 -0
  405. package/src/modules/communication_channels/data/entities.ts +546 -0
  406. package/src/modules/communication_channels/data/extensions.ts +76 -0
  407. package/src/modules/communication_channels/data/validators.ts +138 -0
  408. package/src/modules/communication_channels/di.ts +40 -0
  409. package/src/modules/communication_channels/encryption.ts +44 -0
  410. package/src/modules/communication_channels/events.ts +122 -0
  411. package/src/modules/communication_channels/i18n/de.json +138 -0
  412. package/src/modules/communication_channels/i18n/en.json +138 -0
  413. package/src/modules/communication_channels/i18n/es.json +138 -0
  414. package/src/modules/communication_channels/i18n/pl.json +138 -0
  415. package/src/modules/communication_channels/index.ts +19 -0
  416. package/src/modules/communication_channels/lib/access-control.ts +110 -0
  417. package/src/modules/communication_channels/lib/adapter-compat.ts +57 -0
  418. package/src/modules/communication_channels/lib/adapter-registry-singleton.ts +35 -0
  419. package/src/modules/communication_channels/lib/adapter.ts +605 -0
  420. package/src/modules/communication_channels/lib/connect-channel.ts +163 -0
  421. package/src/modules/communication_channels/lib/contact-resolver.ts +162 -0
  422. package/src/modules/communication_channels/lib/credential-refresh.ts +197 -0
  423. package/src/modules/communication_channels/lib/dead-letter.ts +87 -0
  424. package/src/modules/communication_channels/lib/email-capabilities.ts +60 -0
  425. package/src/modules/communication_channels/lib/email-contact.ts +17 -0
  426. package/src/modules/communication_channels/lib/email-mime.ts +425 -0
  427. package/src/modules/communication_channels/lib/error-classification.ts +144 -0
  428. package/src/modules/communication_channels/lib/gmail-pubsub-jwt.ts +278 -0
  429. package/src/modules/communication_channels/lib/mutation-guards.ts +215 -0
  430. package/src/modules/communication_channels/lib/oauth-client-config.ts +79 -0
  431. package/src/modules/communication_channels/lib/oauth-state.ts +228 -0
  432. package/src/modules/communication_channels/lib/oauth-token.ts +81 -0
  433. package/src/modules/communication_channels/lib/pg-errors.ts +12 -0
  434. package/src/modules/communication_channels/lib/provider-health.ts +47 -0
  435. package/src/modules/communication_channels/lib/push-state.ts +38 -0
  436. package/src/modules/communication_channels/lib/queue.ts +66 -0
  437. package/src/modules/communication_channels/lib/reaction-processor-types.ts +51 -0
  438. package/src/modules/communication_channels/lib/reaction-semantics.ts +48 -0
  439. package/src/modules/communication_channels/lib/registry.ts +99 -0
  440. package/src/modules/communication_channels/lib/route-mutation-guard.ts +68 -0
  441. package/src/modules/communication_channels/lib/sanitize-channel-html.ts +129 -0
  442. package/src/modules/communication_channels/lib/send-as-user.ts +284 -0
  443. package/src/modules/communication_channels/lib/system-user.ts +74 -0
  444. package/src/modules/communication_channels/lib/test-seed.ts +140 -0
  445. package/src/modules/communication_channels/lib/thread-matcher.ts +430 -0
  446. package/src/modules/communication_channels/lib/thread-token.ts +355 -0
  447. package/src/modules/communication_channels/lib/use-connect-channel.ts +73 -0
  448. package/src/modules/communication_channels/migrations/.snapshot-open-mercato.json +2142 -0
  449. package/src/modules/communication_channels/migrations/Migration20260526134719_communication_channels.ts +55 -0
  450. package/src/modules/communication_channels/migrations/Migration20260527195446_communication_channels.ts +20 -0
  451. package/src/modules/communication_channels/migrations/Migration20260529231848_communication_channels.ts +13 -0
  452. package/src/modules/communication_channels/migrations/Migration20260531120000_communication_channels.ts +24 -0
  453. package/src/modules/communication_channels/notifications.client.ts +50 -0
  454. package/src/modules/communication_channels/notifications.handlers.ts +86 -0
  455. package/src/modules/communication_channels/notifications.ts +52 -0
  456. package/src/modules/communication_channels/setup.ts +158 -0
  457. package/src/modules/communication_channels/subscribers/channel-requires-reauth-notification.ts +118 -0
  458. package/src/modules/communication_channels/subscribers/outbound-bridge.ts +175 -0
  459. package/src/modules/communication_channels/subscribers/user-deleted-cascade.ts +100 -0
  460. package/src/modules/communication_channels/widgets/components.ts +36 -0
  461. package/src/modules/communication_channels/widgets/injection/channel-badge/widget.client.tsx +38 -0
  462. package/src/modules/communication_channels/widgets/injection/channel-badge/widget.ts +51 -0
  463. package/src/modules/communication_channels/widgets/injection/channel-info-panel/widget.client.tsx +278 -0
  464. package/src/modules/communication_channels/widgets/injection/channel-info-panel/widget.ts +24 -0
  465. package/src/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.client.tsx +63 -0
  466. package/src/modules/communication_channels/widgets/injection/channel-payload-renderer/widget.ts +29 -0
  467. package/src/modules/communication_channels/widgets/injection/profile-channels-menu/widget.ts +34 -0
  468. package/src/modules/communication_channels/widgets/injection/reaction-bar/widget.client.tsx +177 -0
  469. package/src/modules/communication_channels/widgets/injection/reaction-bar/widget.ts +26 -0
  470. package/src/modules/communication_channels/widgets/injection-table.ts +47 -0
  471. package/src/modules/communication_channels/widgets/notifications/ChannelRequiresReauthRenderer.tsx +48 -0
  472. package/src/modules/communication_channels/widgets/notifications/MessageReceivedRenderer.tsx +45 -0
  473. package/src/modules/communication_channels/widgets/notifications/index.ts +2 -0
  474. package/src/modules/communication_channels/workers/channel-import-history.ts +252 -0
  475. package/src/modules/communication_channels/workers/gmail-history-sync.ts +223 -0
  476. package/src/modules/communication_channels/workers/gmail-renew-watch.ts +141 -0
  477. package/src/modules/communication_channels/workers/inbound-processor.ts +114 -0
  478. package/src/modules/communication_channels/workers/outbound-delivery.ts +155 -0
  479. package/src/modules/communication_channels/workers/poll-channel.ts +391 -0
  480. package/src/modules/communication_channels/workers/poll-tick.ts +210 -0
  481. package/src/modules/communication_channels/workers/reaction-processor.ts +264 -0
  482. package/src/modules/customers/acl.ts +18 -0
  483. package/src/modules/customers/api/activities/route.ts +13 -0
  484. package/src/modules/customers/api/companies/[id]/route.ts +21 -1
  485. package/src/modules/customers/api/interactions/[id]/visibility/route.ts +179 -0
  486. package/src/modules/customers/api/interactions/counts/route.ts +10 -0
  487. package/src/modules/customers/api/interactions/route.ts +51 -5
  488. package/src/modules/customers/api/people/[id]/email-threads/route.ts +92 -0
  489. package/src/modules/customers/api/people/[id]/emails/route.ts +184 -0
  490. package/src/modules/customers/api/people/[id]/route.ts +17 -2
  491. package/src/modules/customers/backend/customers/people-v2/[id]/page.tsx +11 -1
  492. package/src/modules/customers/commands/deals.ts +65 -6
  493. package/src/modules/customers/commands/interactions.ts +30 -0
  494. package/src/modules/customers/components/detail/ActivityCard.tsx +48 -0
  495. package/src/modules/customers/components/detail/ComposeEmailDialog.tsx +329 -0
  496. package/src/modules/customers/components/detail/DealForm.tsx +2 -1
  497. package/src/modules/customers/components/detail/DealsSection.tsx +26 -0
  498. package/src/modules/customers/components/detail/EmailCardActions.tsx +258 -0
  499. package/src/modules/customers/components/detail/EmailReplyForwardActions.tsx +53 -0
  500. package/src/modules/customers/components/detail/PersonDetailTabs.tsx +8 -1
  501. package/src/modules/customers/components/detail/PersonEmailThreadsTab.tsx +448 -0
  502. package/src/modules/customers/data/enrichers.ts +252 -1
  503. package/src/modules/customers/data/entities.ts +46 -1
  504. package/src/modules/customers/data/extensions.ts +26 -0
  505. package/src/modules/customers/encryption.ts +11 -0
  506. package/src/modules/customers/events.ts +4 -0
  507. package/src/modules/customers/i18n/de.json +41 -0
  508. package/src/modules/customers/i18n/en.json +41 -0
  509. package/src/modules/customers/i18n/es.json +41 -0
  510. package/src/modules/customers/i18n/pl.json +41 -0
  511. package/src/modules/customers/lib/findPeopleByAddresses.ts +107 -0
  512. package/src/modules/customers/lib/kysely.ts +16 -0
  513. package/src/modules/customers/lib/link-channel-message-handler.ts +571 -0
  514. package/src/modules/customers/lib/personEmailThreads.ts +325 -0
  515. package/src/modules/customers/lib/visibilityFilter.ts +152 -0
  516. package/src/modules/customers/migrations/.snapshot-open-mercato.json +61 -0
  517. package/src/modules/customers/migrations/Migration20260527012240_customers.ts +23 -0
  518. package/src/modules/customers/setup.ts +1 -0
  519. package/src/modules/customers/subscribers/link-channel-message-received.ts +21 -0
  520. package/src/modules/customers/subscribers/link-channel-message-sent.ts +21 -0
  521. package/src/modules/integrations/AGENTS.md +9 -0
  522. package/src/modules/integrations/data/entities.ts +21 -1
  523. package/src/modules/integrations/lib/credentials-service.ts +49 -13
  524. package/src/modules/integrations/migrations/.snapshot-open-mercato.json +26 -1
  525. package/src/modules/integrations/migrations/Migration20260526154136_integrations.ts +15 -0
  526. package/src/modules/messages/commands/messages.ts +101 -8
  527. package/src/modules/messages/components/ComposeMessagePageClient.tsx +17 -0
  528. package/src/modules/messages/components/MessageDetailPageClient.tsx +43 -0
  529. package/src/modules/messages/components/MessagesInboxPageClient.tsx +4 -0
  530. package/src/modules/messages/data/entities.ts +11 -0
  531. package/src/modules/messages/migrations/.snapshot-open-mercato.json +18 -0
  532. package/src/modules/messages/migrations/Migration20260531130000.ts +15 -0
  533. package/src/modules/messages/widgets/injection-table.ts +29 -0
@@ -0,0 +1,228 @@
1
+ import crypto from 'node:crypto'
2
+
3
+ /**
4
+ * OAuth state-cookie helper for the communication_channels hub.
5
+ *
6
+ * **Ported (re-implemented locally), NOT imported, from `packages/enterprise/src/modules/sso/lib/state-cookie.ts`.**
7
+ * Root `AGENTS.md` rule: `@open-mercato/core` MUST NOT import from `@open-mercato/enterprise`.
8
+ *
9
+ * Design (per email integration spec § OSS Independence + § Hub Deltas → Delta 7):
10
+ * - AES-256-GCM payload encryption.
11
+ * - HKDF (SHA-256) key derivation from `OM_HUB_OAUTH_STATE_KEY` (falling back to
12
+ * `KMS_MASTER_KEY`). In production those dedicated keys are required; only in
13
+ * dev/test do we fall back to `JWT_SECRET` so envs that configure one secret
14
+ * still work. Production refuses the `JWT_SECRET` fallback so a session-secret
15
+ * leak cannot also forge OAuth-state cookies.
16
+ * - 5-minute TTL — short window to bound replay surface.
17
+ * - Payload binds the initiating `userId` so the callback rejects state cookies
18
+ * used by a different session.
19
+ *
20
+ * The output is a base64url string that we set on an HttpOnly + SameSite=Lax cookie.
21
+ * Forgery requires the encryption key (KMS-managed in production).
22
+ */
23
+
24
+ const ALGORITHM = 'aes-256-gcm'
25
+ const IV_LENGTH = 12
26
+ const TAG_LENGTH = 16
27
+ export const COMMUNICATION_CHANNELS_OAUTH_STATE_TTL_MS = 5 * 60 * 1000
28
+ const HKDF_SALT = Buffer.from('open-mercato-channels-oauth-state-v1')
29
+ const HKDF_INFO = Buffer.from('communication_channels-oauth-state-cookie')
30
+
31
+ export const COMMUNICATION_CHANNELS_OAUTH_STATE_COOKIE_NAME =
32
+ 'om_cc_oauth_state'
33
+
34
+ export const DEFAULT_OAUTH_RETURN_URL = '/backend/profile/communication-channels'
35
+
36
+ /** Errors thrown by the helpers. Stable for tests + route mapping. */
37
+ export class OAuthStateError extends Error {
38
+ override name = 'OAuthStateError'
39
+ constructor(
40
+ message: string,
41
+ readonly code:
42
+ | 'missing_secret'
43
+ | 'invalid_cookie'
44
+ | 'expired'
45
+ | 'user_mismatch'
46
+ | 'decrypt_failed',
47
+ ) {
48
+ super(message)
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Canonical state-cookie payload — provider-agnostic. Each OAuth provider
54
+ * adapter (Gmail, …) packs its own per-flow nonce / verifier into
55
+ * the `extra` field rather than extending this shape.
56
+ */
57
+ export interface OAuthStatePayload {
58
+ /** Nonce-like opaque value mirrored into the OAuth `state` query parameter. */
59
+ state: string
60
+ /** Per-flow CSRF nonce, returned alongside the OAuth response. */
61
+ nonce: string
62
+ /** Tenant-scoped user that initiated the flow. Validated on callback. */
63
+ userId: string
64
+ /** Tenant scope so the callback can pin the channel to the same tenant. */
65
+ tenantId: string
66
+ /** Optional organization id for multi-org tenants. */
67
+ organizationId?: string | null
68
+ /** Provider key (e.g. `gmail`) — routes the callback. */
69
+ providerKey: string
70
+ /** Where to redirect on success. Defaults to the profile page in the route. */
71
+ returnUrl?: string
72
+ /** Wall-clock expiry (ms since epoch). */
73
+ expiresAt: number
74
+ /** Provider-specific extras (PKCE code_verifier, scopes, login_hint, …). */
75
+ extra?: Record<string, unknown>
76
+ }
77
+
78
+ export function isSafeOAuthReturnUrl(value: string | null | undefined): value is string {
79
+ if (typeof value !== 'string') return false
80
+ if (value.length === 0 || value.length > 2048) return false
81
+ if (!value.startsWith('/') || value.startsWith('//')) return false
82
+ if (value.includes('\\')) return false
83
+ try {
84
+ const base = new URL('https://open-mercato.local')
85
+ const parsed = new URL(value, base)
86
+ return parsed.origin === base.origin && parsed.pathname.startsWith('/')
87
+ } catch {
88
+ return false
89
+ }
90
+ }
91
+
92
+ export function normalizeOAuthReturnUrl(
93
+ value: string | null | undefined,
94
+ fallback: string = DEFAULT_OAUTH_RETURN_URL,
95
+ ): string {
96
+ return isSafeOAuthReturnUrl(value) ? value : fallback
97
+ }
98
+
99
+ function deriveKey(secret: string): Buffer {
100
+ return Buffer.from(crypto.hkdfSync('sha256', secret, HKDF_SALT, HKDF_INFO, 32))
101
+ }
102
+
103
+ function getSecret(): string {
104
+ const dedicated = process.env.OM_HUB_OAUTH_STATE_KEY ?? process.env.KMS_MASTER_KEY
105
+ if (dedicated) return dedicated
106
+ // No dedicated key configured. Fail closed in production rather than deriving
107
+ // the state-cookie key from JWT_SECRET (the platform session-signing secret) —
108
+ // sharing that key means a JWT_SECRET leak also lets an attacker forge OAuth
109
+ // state cookies, bypassing the userId/tenant binding. In non-production we fall
110
+ // back to JWT_SECRET so dev/test envs that only configure one secret still work.
111
+ if (process.env.NODE_ENV === 'production') {
112
+ throw new Error('[internal] OM_HUB_OAUTH_STATE_KEY or KMS_MASTER_KEY required in production')
113
+ }
114
+ const fallback = process.env.JWT_SECRET
115
+ if (!fallback) {
116
+ throw new OAuthStateError(
117
+ 'OM_HUB_OAUTH_STATE_KEY (or fallback KMS_MASTER_KEY / JWT_SECRET) must be set',
118
+ 'missing_secret',
119
+ )
120
+ }
121
+ return fallback
122
+ }
123
+
124
+ /** Encrypt + sign a state payload. Output is a base64url string suitable for a cookie. */
125
+ export function encryptOAuthState(payload: OAuthStatePayload): string {
126
+ const key = deriveKey(getSecret())
127
+ const iv = crypto.randomBytes(IV_LENGTH)
128
+ const json = JSON.stringify(payload)
129
+
130
+ const cipher = crypto.createCipheriv(ALGORITHM, key, iv)
131
+ const ciphertext = Buffer.concat([cipher.update(json, 'utf8'), cipher.final()])
132
+ const tag = cipher.getAuthTag()
133
+
134
+ return Buffer.concat([iv, tag, ciphertext]).toString('base64url')
135
+ }
136
+
137
+ /**
138
+ * Decrypt + verify a state cookie. Returns the payload or `null` if the cookie
139
+ * is malformed / tampered. Returns the payload (NOT null) when the cookie has
140
+ * expired — callers should check `expiresAt` themselves with the verification
141
+ * helper below for stable status codes.
142
+ */
143
+ export function decryptOAuthState(cookie: string): OAuthStatePayload | null {
144
+ try {
145
+ const key = deriveKey(getSecret())
146
+ const combined = Buffer.from(cookie, 'base64url')
147
+ if (combined.length < IV_LENGTH + TAG_LENGTH) return null
148
+
149
+ const iv = combined.subarray(0, IV_LENGTH)
150
+ const tag = combined.subarray(IV_LENGTH, IV_LENGTH + TAG_LENGTH)
151
+ const ciphertext = combined.subarray(IV_LENGTH + TAG_LENGTH)
152
+
153
+ const decipher = crypto.createDecipheriv(ALGORITHM, key, iv)
154
+ decipher.setAuthTag(tag)
155
+ const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString('utf8')
156
+
157
+ return JSON.parse(decrypted) as OAuthStatePayload
158
+ } catch {
159
+ return null
160
+ }
161
+ }
162
+
163
+ /**
164
+ * Verify a state cookie against the current session.
165
+ *
166
+ * Throws an {@link OAuthStateError} with a stable `code` field on any check
167
+ * failure so route handlers can map to consistent HTTP responses + redirect
168
+ * flash codes.
169
+ */
170
+ export function verifyOAuthState(input: {
171
+ cookie: string | null | undefined
172
+ expectedUserId: string
173
+ expectedProviderKey?: string
174
+ expectedState?: string
175
+ now?: number
176
+ }): OAuthStatePayload {
177
+ if (!input.cookie) {
178
+ throw new OAuthStateError('Missing state cookie', 'invalid_cookie')
179
+ }
180
+ const payload = decryptOAuthState(input.cookie)
181
+ if (!payload) {
182
+ throw new OAuthStateError('Invalid state cookie', 'decrypt_failed')
183
+ }
184
+ const now = input.now ?? Date.now()
185
+ if (payload.expiresAt < now) {
186
+ throw new OAuthStateError('State cookie expired', 'expired')
187
+ }
188
+ if (payload.userId !== input.expectedUserId) {
189
+ throw new OAuthStateError('State cookie userId mismatch', 'user_mismatch')
190
+ }
191
+ if (input.expectedProviderKey && payload.providerKey !== input.expectedProviderKey) {
192
+ throw new OAuthStateError('State cookie providerKey mismatch', 'invalid_cookie')
193
+ }
194
+ if (input.expectedState && payload.state !== input.expectedState) {
195
+ throw new OAuthStateError('State cookie state nonce mismatch', 'invalid_cookie')
196
+ }
197
+ return payload
198
+ }
199
+
200
+ /**
201
+ * Create a fresh state payload + matching `state` query parameter. PKCE
202
+ * verifiers are NOT generated here — the provider adapter decides whether it
203
+ * needs PKCE and packs the verifier into `extra` itself.
204
+ */
205
+ export function createOAuthState(params: {
206
+ userId: string
207
+ tenantId: string
208
+ organizationId?: string | null
209
+ providerKey: string
210
+ returnUrl?: string
211
+ extra?: Record<string, unknown>
212
+ }): { payload: OAuthStatePayload; cookie: string; stateParam: string } {
213
+ const state = crypto.randomBytes(32).toString('base64url')
214
+ const nonce = crypto.randomBytes(16).toString('base64url')
215
+ const payload: OAuthStatePayload = {
216
+ state,
217
+ nonce,
218
+ userId: params.userId,
219
+ tenantId: params.tenantId,
220
+ organizationId: params.organizationId ?? null,
221
+ providerKey: params.providerKey,
222
+ returnUrl: params.returnUrl,
223
+ extra: params.extra,
224
+ expiresAt: Date.now() + COMMUNICATION_CHANNELS_OAUTH_STATE_TTL_MS,
225
+ }
226
+ const cookie = encryptOAuthState(payload)
227
+ return { payload, cookie, stateParam: state }
228
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Shared OAuth2 token primitives for email channel providers. The authorize-URL
3
+ * shape, PKCE usage, and userinfo handling differ per provider (e.g. Gmail)
4
+ * and stay in each package — but the token response shape, the
5
+ * form-urlencoded token POST, and the expiry computation are identical, so they
6
+ * live here.
7
+ */
8
+
9
+ export interface OAuthTokenResponse {
10
+ access_token: string
11
+ refresh_token?: string
12
+ expires_in?: number
13
+ scope?: string
14
+ token_type?: string
15
+ id_token?: string
16
+ error?: string
17
+ error_description?: string
18
+ }
19
+
20
+ /** Compute the absolute access-token expiry from `expires_in`, or `undefined` when absent. */
21
+ export function tokenResponseToExpiresAt(
22
+ token: OAuthTokenResponse,
23
+ nowMs: number = Date.now(),
24
+ ): Date | undefined {
25
+ if (typeof token.expires_in !== 'number') return undefined
26
+ return new Date(nowMs + token.expires_in * 1000)
27
+ }
28
+
29
+ /** Hard timeout for a token endpoint round-trip (ms). Overridable via env. */
30
+ const DEFAULT_OAUTH_TOKEN_TIMEOUT_MS = 10_000
31
+
32
+ function resolveTokenTimeoutMs(): number {
33
+ const fromEnv = Number.parseInt(process.env.OM_OAUTH_TOKEN_TIMEOUT_MS ?? '', 10)
34
+ return Number.isFinite(fromEnv) && fromEnv > 0 ? fromEnv : DEFAULT_OAUTH_TOKEN_TIMEOUT_MS
35
+ }
36
+
37
+ /**
38
+ * POST a form-urlencoded body to an OAuth token endpoint and return the parsed
39
+ * token response. Throws `${errorLabel}: <reason>` when the endpoint returns a
40
+ * non-2xx status, an `error` field, a non-JSON body, or does not respond within
41
+ * the timeout. Bounding the request matters because token refresh sits on the
42
+ * critical path of every poll/send — a hung token endpoint must fail fast, not
43
+ * block the worker, and a proxy HTML error page must surface the real status
44
+ * rather than a confusing JSON `SyntaxError`.
45
+ */
46
+ export async function requestOAuthToken(
47
+ tokenUrl: string,
48
+ params: URLSearchParams,
49
+ options: { errorLabel: string },
50
+ ): Promise<OAuthTokenResponse> {
51
+ const controller = new AbortController()
52
+ const timeout = setTimeout(() => controller.abort(), resolveTokenTimeoutMs())
53
+ let res: Response
54
+ try {
55
+ res = await fetch(tokenUrl, {
56
+ method: 'POST',
57
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
58
+ body: params.toString(),
59
+ signal: controller.signal,
60
+ })
61
+ } catch (err) {
62
+ if (err instanceof Error && err.name === 'AbortError') {
63
+ throw new Error(`${options.errorLabel}: token endpoint timed out`)
64
+ }
65
+ throw new Error(`${options.errorLabel}: ${err instanceof Error ? err.message : String(err)}`)
66
+ } finally {
67
+ clearTimeout(timeout)
68
+ }
69
+
70
+ const raw = await res.text()
71
+ let body: OAuthTokenResponse
72
+ try {
73
+ body = JSON.parse(raw) as OAuthTokenResponse
74
+ } catch {
75
+ throw new Error(`${options.errorLabel}: non-JSON response (status ${res.status})`)
76
+ }
77
+ if (!res.ok || body.error) {
78
+ throw new Error(`${options.errorLabel}: ${body.error_description ?? body.error ?? res.statusText}`)
79
+ }
80
+ return body
81
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Detect a Postgres unique-constraint violation (SQLSTATE 23505) regardless of
3
+ * the ORM/driver layer that surfaces it. Shared across the hub's commands and
4
+ * lib helpers so duplicate-insert handling stays consistent module-wide.
5
+ */
6
+ export function isUniqueViolation(err: unknown): boolean {
7
+ if (!err || typeof err !== 'object') return false
8
+ const code = (err as { code?: string }).code
9
+ if (code === '23505') return true // Postgres unique_violation
10
+ const message = (err as { message?: string }).message
11
+ return typeof message === 'string' && /duplicate key value|unique constraint/i.test(message)
12
+ }
@@ -0,0 +1,47 @@
1
+ import type { ZodType } from 'zod'
2
+ import type { IntegrationScope } from '@open-mercato/shared/modules/integrations/types'
3
+
4
+ export type EmailHealthStatus = 'healthy' | 'degraded' | 'unhealthy'
5
+
6
+ export interface EmailHealthCheckResult {
7
+ status: EmailHealthStatus
8
+ message?: string
9
+ details?: Record<string, unknown>
10
+ }
11
+
12
+ export interface EmailHealthCheck {
13
+ check: (credentials: Record<string, unknown> | null, scope: IntegrationScope) => Promise<EmailHealthCheckResult>
14
+ }
15
+
16
+ /**
17
+ * Build a liveness probe for an OAuth-client-config integration. There is no
18
+ * access token at this layer (the hub passes the tenant-scoped OAuth client
19
+ * config, not per-user channel tokens), so a network call would always 401. The
20
+ * cheap, deterministic probe is: confirm the client config is present and
21
+ * well-formed. Per-user token validity is exercised on the channel itself
22
+ * (send / poll surface `requires_reauth`).
23
+ */
24
+ export function makeClientConfigHealthCheck<T>(options: {
25
+ schema: ZodType<T>
26
+ providerLabel: string
27
+ healthyDetails?: (parsed: T) => Record<string, unknown>
28
+ }): EmailHealthCheck {
29
+ return {
30
+ async check(credentials) {
31
+ const parsed = options.schema.safeParse(credentials ?? {})
32
+ if (!parsed.success) {
33
+ const first = parsed.error.issues[0]
34
+ return {
35
+ status: 'unhealthy',
36
+ message: `${options.providerLabel} OAuth client config invalid: ${first?.message ?? 'unknown validation error'}`,
37
+ details: { reason: 'invalid_oauth_client' },
38
+ }
39
+ }
40
+ return {
41
+ status: 'healthy',
42
+ message: `${options.providerLabel} OAuth client configured`,
43
+ details: { clientIdConfigured: true, ...(options.healthyDetails?.(parsed.data) ?? {}) },
44
+ }
45
+ },
46
+ }
47
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Channel-state keys owned by the push-delivery lifecycle (Spec C), written by the
3
+ * push register/renew commands rather than the sync cursor. They must survive a
4
+ * sync-cursor replacement so watch/subscription renewal keeps working.
5
+ */
6
+ export const PUSH_STATE_KEYS = [
7
+ 'pushStatus',
8
+ 'watchExpirationMs',
9
+ 'pubsubTopic',
10
+ 'lastPushError',
11
+ ] as const
12
+
13
+ /**
14
+ * Replace the channel sync-cursor state with the adapter's freshly decoded cursor
15
+ * while carrying forward the hub-owned push keys the cursor does not manage.
16
+ *
17
+ * This MUST be a full replace, never a `{ ...previous, ...next }` spread: adapters
18
+ * signal "drain finished" by OMITTING the mid-drain resumption tokens
19
+ * (`pendingHistoryPageToken`, `pendingMessagesPageToken`) from
20
+ * `next` (they are set to `undefined`, which `JSON.stringify` drops from the encoded
21
+ * cursor). A spread would retain a stale token from `previous` and mis-route the
22
+ * next push/poll cycle, so the poll worker and both push-sync workers share this
23
+ * helper to stay consistent.
24
+ */
25
+ export function preservePushState(
26
+ previous: unknown,
27
+ next: Record<string, unknown>,
28
+ ): Record<string, unknown> {
29
+ const prev =
30
+ previous && typeof previous === 'object' && !Array.isArray(previous)
31
+ ? (previous as Record<string, unknown>)
32
+ : {}
33
+ const merged: Record<string, unknown> = { ...next }
34
+ for (const key of PUSH_STATE_KEYS) {
35
+ if (!(key in merged) && key in prev) merged[key] = prev[key]
36
+ }
37
+ return merged
38
+ }
@@ -0,0 +1,66 @@
1
+ import { createModuleQueue, type Queue } from '@open-mercato/queue'
2
+
3
+ /**
4
+ * Queue helper for the communication_channels hub. Mirrors the
5
+ * shipping_carriers pattern (`getShippingCarrierQueue`) so the route
6
+ * and the worker share the same queue instance.
7
+ *
8
+ * Worker concurrency is also tunable via env (`COMMUNICATION_CHANNELS_QUEUE_CONCURRENCY`)
9
+ * with a sensible default of 10 (per SPEC-045d §6 inbound flow) and a hard ceiling of
10
+ * 20 (ARCHITECTURE §19 caps queue/worker concurrency at 20).
11
+ */
12
+ const queues = new Map<string, Queue<Record<string, unknown>>>()
13
+
14
+ export function getCommunicationChannelsQueue(queueName: string): Queue<Record<string, unknown>> {
15
+ const existing = queues.get(queueName)
16
+ if (existing) return existing
17
+
18
+ const concurrency = Math.min(
19
+ 20,
20
+ Math.max(
21
+ 1,
22
+ Number.parseInt(process.env.COMMUNICATION_CHANNELS_QUEUE_CONCURRENCY ?? '10', 10) || 10,
23
+ ),
24
+ )
25
+ const created = createModuleQueue<Record<string, unknown>>(queueName, { concurrency })
26
+ queues.set(queueName, created)
27
+ return created
28
+ }
29
+
30
+ /** Canonical queue names exposed by the hub. */
31
+ export const COMMUNICATION_CHANNELS_QUEUES = {
32
+ inbound: 'communication-channels-inbound',
33
+ outbound: 'communication-channels-outbound',
34
+ reactions: 'communication-channels-reactions',
35
+ /**
36
+ * Per-channel polling queue (email integration spec — Phase 0 Delta 6).
37
+ * Populated by `poll-tick` every scheduler tick; one entry per due channel.
38
+ * Processed by `workers/poll-channel.ts`.
39
+ */
40
+ poll: 'communication-channels-poll',
41
+ /**
42
+ * Hub-internal tick queue (email integration spec — Phase 0 Delta 6).
43
+ * One job per scheduler tick (60s default); worker enumerates due channels
44
+ * and fans out to the `poll` queue.
45
+ */
46
+ pollTick: 'communication-channels-poll-tick',
47
+ /**
48
+ * Operator-triggered channel-history import queue (Spec B § Phase B6).
49
+ * One job per `/import-history` call; worker `channel-import-history` runs
50
+ * with concurrency 1 to avoid hammering the provider with parallel scans.
51
+ */
52
+ importHistory: 'communication-channels-import-history',
53
+ /**
54
+ * Spec C § Phase C2 — Gmail Pub/Sub push delivery. The webhook enqueues
55
+ * one job per verified notification; the worker calls
56
+ * `adapter.applyPushNotification` (which delegates to `history.list`).
57
+ */
58
+ gmailHistorySync: 'communication-channels-gmail-history-sync',
59
+ /**
60
+ * Spec C § Phase C4 — Renewal cron queues (daily / 2h cadence).
61
+ */
62
+ gmailRenewWatch: 'communication-channels-gmail-renew-watch',
63
+ } as const
64
+
65
+ export type CommunicationChannelsQueueName =
66
+ (typeof COMMUNICATION_CHANNELS_QUEUES)[keyof typeof COMMUNICATION_CHANNELS_QUEUES]
@@ -0,0 +1,51 @@
1
+ import type { InboundReactionEvent } from './adapter'
2
+
3
+ /**
4
+ * Discriminated union of reaction-queue job payloads.
5
+ *
6
+ * Split into its own file so command modules can reference these types without
7
+ * forming a circular import with the worker module (which references commands).
8
+ */
9
+
10
+ export type ReactionScope = {
11
+ tenantId: string
12
+ organizationId: string | null
13
+ }
14
+
15
+ export type ReactionJobBase = {
16
+ providerKey: string
17
+ channelId: string
18
+ scope: ReactionScope
19
+ /** Attempt number, 1-based. */
20
+ attempt?: number
21
+ }
22
+
23
+ export type ReactionInboundJob = ReactionJobBase & {
24
+ kind: 'inbound'
25
+ channelType: string
26
+ event: InboundReactionEvent
27
+ }
28
+
29
+ export type ReactionOutboundSendJob = ReactionJobBase & {
30
+ kind: 'outbound_send'
31
+ messageId: string
32
+ reactionId: string
33
+ emoji: string
34
+ /** External conversation reference for the provider call (e.g. Slack thread_ts). */
35
+ conversationId?: string
36
+ }
37
+
38
+ export type ReactionOutboundRemoveJob = ReactionJobBase & {
39
+ kind: 'outbound_remove'
40
+ messageId: string
41
+ emoji: string
42
+ externalReactionId: string | null
43
+ conversationId?: string
44
+ }
45
+
46
+ export type ReactionProcessorPayload =
47
+ | ReactionInboundJob
48
+ | ReactionOutboundSendJob
49
+ | ReactionOutboundRemoveJob
50
+
51
+ export const REACTION_PROCESSOR_MAX_ATTEMPTS = 3
@@ -0,0 +1,48 @@
1
+ import type { ChannelCapabilities } from './adapter'
2
+
3
+ /**
4
+ * Reaction semantics — capability-driven decisions about how the hub applies an
5
+ * incoming or outgoing reaction.
6
+ *
7
+ * The platform-side `MessageReaction` table can record any number of rows per
8
+ * (messageId, reactor); the channel-specific semantics decide whether to keep
9
+ * existing reactions when a new one arrives:
10
+ *
11
+ * - **Multi-per-user** (Slack default; `multiReactionPerUser: true`):
12
+ * keep all existing reactions from the same reactor; just insert the new one.
13
+ *
14
+ * - **Single-per-user** (WhatsApp default; `multiReactionPerUser: false`):
15
+ * delete all existing reactions from the same reactor for the same message,
16
+ * then insert the new one. This matches WhatsApp's "one reaction per user"
17
+ * UI behaviour.
18
+ *
19
+ * The helper functions here are pure — they don't talk to the database. The
20
+ * command layer that consumes them is responsible for the actual DELETE/INSERT.
21
+ */
22
+
23
+ /**
24
+ * Returns true when the channel's capabilities allow multiple reactions from
25
+ * the same reactor on the same message (Slack-like).
26
+ */
27
+ export function allowsMultipleReactionsPerUser(
28
+ capabilities: Pick<ChannelCapabilities, 'multiReactionPerUser'> | null | undefined,
29
+ ): boolean {
30
+ // Default to false for safety — a missing capability declaration should not
31
+ // accidentally enable multi-reactions. Real adapters explicitly declare both
32
+ // booleans per SPEC-045d §1.1.
33
+ return capabilities?.multiReactionPerUser === true
34
+ }
35
+
36
+ /**
37
+ * Computes what mutation a new inbound `added` reaction implies, given the
38
+ * channel's capabilities. Pure — no DB calls. The caller maps the result to
39
+ * SQL.
40
+ *
41
+ * @returns `'insert'` to just persist the new reaction; `'replace'` to delete
42
+ * every reaction from the same reactor for the same message before inserting.
43
+ */
44
+ export function resolveInboundAddMutation(
45
+ capabilities: Pick<ChannelCapabilities, 'multiReactionPerUser'> | null | undefined,
46
+ ): 'insert' | 'replace' {
47
+ return allowsMultipleReactionsPerUser(capabilities) ? 'insert' : 'replace'
48
+ }
@@ -0,0 +1,99 @@
1
+ import type { ChannelAdapter } from './adapter'
2
+ import { validateAdapterCapabilities } from './adapter-compat'
3
+
4
+ /**
5
+ * Channel adapter registry — process-level singleton backed by `globalThis`.
6
+ *
7
+ * Mirrors the `shipping_carriers/lib/adapter-registry.ts` pattern so that
8
+ * unauthenticated webhook routes (which have not yet built a tenant-scoped DI
9
+ * container) can resolve adapters by `providerKey`. DI consumers continue to
10
+ * resolve the same registry via the `channelAdapterRegistry` binding declared
11
+ * in `di.ts` — that binding is a thin proxy over these functions.
12
+ *
13
+ * The registry validates each adapter at registration time (see
14
+ * `validateAdapterCapabilities`) and refuses duplicate `providerKey` registrations.
15
+ */
16
+
17
+ const CHANNEL_ADAPTER_REGISTRY_KEY = Symbol.for('@open-mercato/communication-channels/adapter-registry')
18
+
19
+ type GlobalWithChannelRegistry = typeof globalThis & {
20
+ [CHANNEL_ADAPTER_REGISTRY_KEY]?: Map<string, ChannelAdapter>
21
+ }
22
+
23
+ function getRegistryMap(): Map<string, ChannelAdapter> {
24
+ const scope = globalThis as GlobalWithChannelRegistry
25
+ if (!scope[CHANNEL_ADAPTER_REGISTRY_KEY]) {
26
+ scope[CHANNEL_ADAPTER_REGISTRY_KEY] = new Map<string, ChannelAdapter>()
27
+ }
28
+ return scope[CHANNEL_ADAPTER_REGISTRY_KEY]!
29
+ }
30
+
31
+ export function registerChannelAdapter(adapter: ChannelAdapter): () => void {
32
+ validateAdapterCapabilities(adapter)
33
+ const map = getRegistryMap()
34
+ if (map.has(adapter.providerKey)) {
35
+ throw new Error(
36
+ `ChannelAdapter '${adapter.providerKey}' is already registered. ` +
37
+ 'Each provider package must declare a unique providerKey.',
38
+ )
39
+ }
40
+ map.set(adapter.providerKey, adapter)
41
+ return () => {
42
+ map.delete(adapter.providerKey)
43
+ }
44
+ }
45
+
46
+ export function getChannelAdapter(providerKey: string): ChannelAdapter | undefined {
47
+ return getRegistryMap().get(providerKey)
48
+ }
49
+
50
+ export function listChannelAdapters(): ChannelAdapter[] {
51
+ return Array.from(getRegistryMap().values())
52
+ }
53
+
54
+ export function listChannelAdapterProviderKeys(): string[] {
55
+ return Array.from(getRegistryMap().keys())
56
+ }
57
+
58
+ export function hasChannelAdapter(providerKey: string): boolean {
59
+ return getRegistryMap().has(providerKey)
60
+ }
61
+
62
+ /**
63
+ * Clear the registry. Primarily for tests that need a fresh registry between cases.
64
+ * NOT for production use — at runtime, adapters are registered once at boot.
65
+ */
66
+ export function clearChannelAdapters(): void {
67
+ getRegistryMap().clear()
68
+ }
69
+
70
+ /**
71
+ * Class wrapper kept for DI consumers and test ergonomics. All instances back
72
+ * onto the same `globalThis` storage so DI consumers and direct module callers
73
+ * see the same registry.
74
+ */
75
+ export class ChannelAdapterRegistry {
76
+ register(adapter: ChannelAdapter): void {
77
+ registerChannelAdapter(adapter)
78
+ }
79
+
80
+ get(providerKey: string): ChannelAdapter | undefined {
81
+ return getChannelAdapter(providerKey)
82
+ }
83
+
84
+ list(): ChannelAdapter[] {
85
+ return listChannelAdapters()
86
+ }
87
+
88
+ providerKeys(): string[] {
89
+ return listChannelAdapterProviderKeys()
90
+ }
91
+
92
+ has(providerKey: string): boolean {
93
+ return hasChannelAdapter(providerKey)
94
+ }
95
+
96
+ clear(): void {
97
+ clearChannelAdapters()
98
+ }
99
+ }