@open-mercato/core 0.6.8-develop.6983.1.e4f794099e → 0.6.8-develop.6985.1.fb93574faa

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 (597) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/dist/generated/entities/notification/index.js +6 -0
  3. package/dist/generated/entities/notification/index.js.map +2 -2
  4. package/dist/generated/entities/notification_preference/index.js +19 -0
  5. package/dist/generated/entities/notification_preference/index.js.map +7 -0
  6. package/dist/generated/entities/notification_type/index.js +21 -0
  7. package/dist/generated/entities/notification_type/index.js.map +7 -0
  8. package/dist/generated/entities/notification_type_override/index.js +17 -0
  9. package/dist/generated/entities/notification_type_override/index.js.map +7 -0
  10. package/dist/generated/entities/push_notification_delivery/index.js +41 -0
  11. package/dist/generated/entities/push_notification_delivery/index.js.map +7 -0
  12. package/dist/generated/entities/user_device/index.js +35 -0
  13. package/dist/generated/entities/user_device/index.js.map +7 -0
  14. package/dist/generated/entities.ids.generated.js +12 -1
  15. package/dist/generated/entities.ids.generated.js.map +2 -2
  16. package/dist/generated/entity-fields-registry.js +72 -0
  17. package/dist/generated/entity-fields-registry.js.map +2 -2
  18. package/dist/helpers/integration/api.js +2 -1
  19. package/dist/helpers/integration/api.js.map +2 -2
  20. package/dist/helpers/integration/appRoot.js +9 -0
  21. package/dist/helpers/integration/appRoot.js.map +7 -0
  22. package/dist/helpers/integration/dbFixtures.js +1 -4
  23. package/dist/helpers/integration/dbFixtures.js.map +2 -2
  24. package/dist/helpers/integration/pushFake.js +126 -0
  25. package/dist/helpers/integration/pushFake.js.map +7 -0
  26. package/dist/modules/auth/notifications.js +16 -0
  27. package/dist/modules/auth/notifications.js.map +2 -2
  28. package/dist/modules/business_rules/notifications.js +2 -0
  29. package/dist/modules/business_rules/notifications.js.map +2 -2
  30. package/dist/modules/catalog/notifications.js +2 -0
  31. package/dist/modules/catalog/notifications.js.map +2 -2
  32. package/dist/modules/communication_channels/acl.js +9 -0
  33. package/dist/modules/communication_channels/acl.js.map +2 -2
  34. package/dist/modules/communication_channels/api/delete/admin/channels/[id]/route.js +133 -0
  35. package/dist/modules/communication_channels/api/delete/admin/channels/[id]/route.js.map +7 -0
  36. package/dist/modules/communication_channels/api/get/channels/[id]/health/route.js +6 -2
  37. package/dist/modules/communication_channels/api/get/channels/[id]/health/route.js.map +2 -2
  38. package/dist/modules/communication_channels/api/get/channels/[id]/route.js +6 -2
  39. package/dist/modules/communication_channels/api/get/channels/[id]/route.js.map +2 -2
  40. package/dist/modules/communication_channels/api/get/channels/route.js +2 -1
  41. package/dist/modules/communication_channels/api/get/channels/route.js.map +2 -2
  42. package/dist/modules/communication_channels/api/post/channels/[id]/test-send/route.js +10 -5
  43. package/dist/modules/communication_channels/api/post/channels/[id]/test-send/route.js.map +2 -2
  44. package/dist/modules/communication_channels/api/post/channels/connect/credentials/route.js +29 -1
  45. package/dist/modules/communication_channels/api/post/channels/connect/credentials/route.js.map +2 -2
  46. package/dist/modules/communication_channels/api/post/channels/connect/tenant-credentials/route.js +157 -0
  47. package/dist/modules/communication_channels/api/post/channels/connect/tenant-credentials/route.js.map +7 -0
  48. package/dist/modules/communication_channels/backend/communication_channels/channels/page.js +8 -1
  49. package/dist/modules/communication_channels/backend/communication_channels/channels/page.js.map +2 -2
  50. package/dist/modules/communication_channels/commands/admin-delete-channel.js +141 -0
  51. package/dist/modules/communication_channels/commands/admin-delete-channel.js.map +7 -0
  52. package/dist/modules/communication_channels/commands/connect-credential-channel.js +25 -8
  53. package/dist/modules/communication_channels/commands/connect-credential-channel.js.map +2 -2
  54. package/dist/modules/communication_channels/commands/deliver-outbound-message.js +2 -2
  55. package/dist/modules/communication_channels/commands/deliver-outbound-message.js.map +2 -2
  56. package/dist/modules/communication_channels/commands/push-register.js +1 -1
  57. package/dist/modules/communication_channels/commands/push-register.js.map +2 -2
  58. package/dist/modules/communication_channels/commands/push-unregister.js +4 -3
  59. package/dist/modules/communication_channels/commands/push-unregister.js.map +2 -2
  60. package/dist/modules/communication_channels/data/entities.js +4 -0
  61. package/dist/modules/communication_channels/data/entities.js.map +2 -2
  62. package/dist/modules/communication_channels/data/validators.js +2 -1
  63. package/dist/modules/communication_channels/data/validators.js.map +2 -2
  64. package/dist/modules/communication_channels/lib/access-control.js +11 -1
  65. package/dist/modules/communication_channels/lib/access-control.js.map +2 -2
  66. package/dist/modules/communication_channels/lib/connect-channel.js +20 -12
  67. package/dist/modules/communication_channels/lib/connect-channel.js.map +2 -2
  68. package/dist/modules/communication_channels/lib/push-adapter.js +68 -0
  69. package/dist/modules/communication_channels/lib/push-adapter.js.map +7 -0
  70. package/dist/modules/communication_channels/lib/push-capabilities.js +31 -0
  71. package/dist/modules/communication_channels/lib/push-capabilities.js.map +7 -0
  72. package/dist/modules/communication_channels/lib/push-connect-error.js +20 -0
  73. package/dist/modules/communication_channels/lib/push-connect-error.js.map +7 -0
  74. package/dist/modules/communication_channels/lib/push-credential-errors.js +41 -0
  75. package/dist/modules/communication_channels/lib/push-credential-errors.js.map +7 -0
  76. package/dist/modules/communication_channels/lib/push-envelope.js +46 -0
  77. package/dist/modules/communication_channels/lib/push-envelope.js.map +7 -0
  78. package/dist/modules/communication_channels/lib/refcounted-client-cache.js +64 -0
  79. package/dist/modules/communication_channels/lib/refcounted-client-cache.js.map +7 -0
  80. package/dist/modules/communication_channels/migrations/Migration20260703123630_communication_channels.js +13 -0
  81. package/dist/modules/communication_channels/migrations/Migration20260703123630_communication_channels.js.map +7 -0
  82. package/dist/modules/communication_channels/notifications.js +4 -0
  83. package/dist/modules/communication_channels/notifications.js.map +2 -2
  84. package/dist/modules/communication_channels/setup.js +2 -0
  85. package/dist/modules/communication_channels/setup.js.map +2 -2
  86. package/dist/modules/configs/lib/upgrade-actions.js +21 -0
  87. package/dist/modules/configs/lib/upgrade-actions.js.map +2 -2
  88. package/dist/modules/customer_accounts/notifications.js +12 -0
  89. package/dist/modules/customer_accounts/notifications.js.map +2 -2
  90. package/dist/modules/customers/notifications.js +4 -0
  91. package/dist/modules/customers/notifications.js.map +2 -2
  92. package/dist/modules/devices/acl.js +11 -0
  93. package/dist/modules/devices/acl.js.map +7 -0
  94. package/dist/modules/devices/api/[id]/route.js +167 -0
  95. package/dist/modules/devices/api/[id]/route.js.map +7 -0
  96. package/dist/modules/devices/api/admin/devices/[id]/route.js +219 -0
  97. package/dist/modules/devices/api/admin/devices/[id]/route.js.map +7 -0
  98. package/dist/modules/devices/api/admin/devices/route.js +140 -0
  99. package/dist/modules/devices/api/admin/devices/route.js.map +7 -0
  100. package/dist/modules/devices/api/auth.js +9 -0
  101. package/dist/modules/devices/api/auth.js.map +7 -0
  102. package/dist/modules/devices/api/deviceList.js +53 -0
  103. package/dist/modules/devices/api/deviceList.js.map +7 -0
  104. package/dist/modules/devices/api/deviceOps.js +118 -0
  105. package/dist/modules/devices/api/deviceOps.js.map +7 -0
  106. package/dist/modules/devices/api/openapi.js +19 -0
  107. package/dist/modules/devices/api/openapi.js.map +7 -0
  108. package/dist/modules/devices/api/route.js +143 -0
  109. package/dist/modules/devices/api/route.js.map +7 -0
  110. package/dist/modules/devices/backend/devices/[id]/page.js +135 -0
  111. package/dist/modules/devices/backend/devices/[id]/page.js.map +7 -0
  112. package/dist/modules/devices/backend/devices/[id]/page.meta.js +16 -0
  113. package/dist/modules/devices/backend/devices/[id]/page.meta.js.map +7 -0
  114. package/dist/modules/devices/backend/devices/create/page.js +68 -0
  115. package/dist/modules/devices/backend/devices/create/page.js.map +7 -0
  116. package/dist/modules/devices/backend/devices/create/page.meta.js +16 -0
  117. package/dist/modules/devices/backend/devices/create/page.meta.js.map +7 -0
  118. package/dist/modules/devices/backend/devices/page.js +235 -0
  119. package/dist/modules/devices/backend/devices/page.js.map +7 -0
  120. package/dist/modules/devices/backend/devices/page.meta.js +23 -0
  121. package/dist/modules/devices/backend/devices/page.meta.js.map +7 -0
  122. package/dist/modules/devices/commands/deactivate.js +107 -0
  123. package/dist/modules/devices/commands/deactivate.js.map +7 -0
  124. package/dist/modules/devices/commands/index.js +4 -0
  125. package/dist/modules/devices/commands/index.js.map +7 -0
  126. package/dist/modules/devices/commands/register.js +183 -0
  127. package/dist/modules/devices/commands/register.js.map +7 -0
  128. package/dist/modules/devices/commands/shared.js +74 -0
  129. package/dist/modules/devices/commands/shared.js.map +7 -0
  130. package/dist/modules/devices/commands/update.js +126 -0
  131. package/dist/modules/devices/commands/update.js.map +7 -0
  132. package/dist/modules/devices/data/entities.js +83 -0
  133. package/dist/modules/devices/data/entities.js.map +7 -0
  134. package/dist/modules/devices/data/validators.js +49 -0
  135. package/dist/modules/devices/data/validators.js.map +7 -0
  136. package/dist/modules/devices/di.js +11 -0
  137. package/dist/modules/devices/di.js.map +7 -0
  138. package/dist/modules/devices/encryption.js +12 -0
  139. package/dist/modules/devices/encryption.js.map +7 -0
  140. package/dist/modules/devices/events.js +19 -0
  141. package/dist/modules/devices/events.js.map +7 -0
  142. package/dist/modules/devices/index.js +17 -0
  143. package/dist/modules/devices/index.js.map +7 -0
  144. package/dist/modules/devices/lib/operationMetadata.js +20 -0
  145. package/dist/modules/devices/lib/operationMetadata.js.map +7 -0
  146. package/dist/modules/devices/migrations/Migration20260602120000.js +15 -0
  147. package/dist/modules/devices/migrations/Migration20260602120000.js.map +7 -0
  148. package/dist/modules/devices/migrations/Migration20260630120000.js +15 -0
  149. package/dist/modules/devices/migrations/Migration20260630120000.js.map +7 -0
  150. package/dist/modules/devices/migrations/Migration20260701120000.js +13 -0
  151. package/dist/modules/devices/migrations/Migration20260701120000.js.map +7 -0
  152. package/dist/modules/devices/migrations/Migration20260721120000.js +15 -0
  153. package/dist/modules/devices/migrations/Migration20260721120000.js.map +7 -0
  154. package/dist/modules/devices/setup.js +13 -0
  155. package/dist/modules/devices/setup.js.map +7 -0
  156. package/dist/modules/entities/cli.js +8 -3
  157. package/dist/modules/entities/cli.js.map +2 -2
  158. package/dist/modules/entities/migrations/Migration20260722120000.js +28 -0
  159. package/dist/modules/entities/migrations/Migration20260722120000.js.map +7 -0
  160. package/dist/modules/inbox_ops/notifications.js +2 -0
  161. package/dist/modules/inbox_ops/notifications.js.map +2 -2
  162. package/dist/modules/messages/notifications.js +2 -0
  163. package/dist/modules/messages/notifications.js.map +2 -2
  164. package/dist/modules/notifications/acl.js +3 -1
  165. package/dist/modules/notifications/acl.js.map +2 -2
  166. package/dist/modules/notifications/api/admin/preferences/route.js +167 -0
  167. package/dist/modules/notifications/api/admin/preferences/route.js.map +7 -0
  168. package/dist/modules/notifications/api/channels/route.js +54 -0
  169. package/dist/modules/notifications/api/channels/route.js.map +7 -0
  170. package/dist/modules/notifications/api/preferences/route.js +126 -0
  171. package/dist/modules/notifications/api/preferences/route.js.map +7 -0
  172. package/dist/modules/notifications/api/route.js +8 -1
  173. package/dist/modules/notifications/api/route.js.map +2 -2
  174. package/dist/modules/notifications/api/types/[id]/channels/[channel]/route.js +187 -0
  175. package/dist/modules/notifications/api/types/[id]/channels/[channel]/route.js.map +7 -0
  176. package/dist/modules/notifications/api/types/route.js +225 -0
  177. package/dist/modules/notifications/api/types/route.js.map +7 -0
  178. package/dist/modules/notifications/api/unread-count/route.js +5 -1
  179. package/dist/modules/notifications/api/unread-count/route.js.map +2 -2
  180. package/dist/modules/notifications/backend/notifications/user-preferences/page.js +10 -0
  181. package/dist/modules/notifications/backend/notifications/user-preferences/page.js.map +7 -0
  182. package/dist/modules/notifications/backend/notifications/user-preferences/page.meta.js +28 -0
  183. package/dist/modules/notifications/backend/notifications/user-preferences/page.meta.js.map +7 -0
  184. package/dist/modules/notifications/backend/profile/notification-preferences/page.js +10 -0
  185. package/dist/modules/notifications/backend/profile/notification-preferences/page.js.map +7 -0
  186. package/dist/modules/notifications/backend/profile/notification-preferences/page.meta.js +33 -0
  187. package/dist/modules/notifications/backend/profile/notification-preferences/page.meta.js.map +7 -0
  188. package/dist/modules/notifications/data/entities.js +128 -1
  189. package/dist/modules/notifications/data/entities.js.map +2 -2
  190. package/dist/modules/notifications/data/validators.js +95 -2
  191. package/dist/modules/notifications/data/validators.js.map +2 -2
  192. package/dist/modules/notifications/di.js +7 -0
  193. package/dist/modules/notifications/di.js.map +2 -2
  194. package/dist/modules/notifications/events.js +17 -0
  195. package/dist/modules/notifications/events.js.map +2 -2
  196. package/dist/modules/notifications/frontend/NotificationPreferenceMatrix.js +190 -0
  197. package/dist/modules/notifications/frontend/NotificationPreferenceMatrix.js.map +7 -0
  198. package/dist/modules/notifications/frontend/NotificationPreferencesPageClient.js +124 -0
  199. package/dist/modules/notifications/frontend/NotificationPreferencesPageClient.js.map +7 -0
  200. package/dist/modules/notifications/frontend/NotificationSettingsPageClient.js +166 -1
  201. package/dist/modules/notifications/frontend/NotificationSettingsPageClient.js.map +2 -2
  202. package/dist/modules/notifications/frontend/NotificationUserPreferencesAdminPageClient.js +172 -0
  203. package/dist/modules/notifications/frontend/NotificationUserPreferencesAdminPageClient.js.map +7 -0
  204. package/dist/modules/notifications/generators.js +63 -0
  205. package/dist/modules/notifications/generators.js.map +7 -0
  206. package/dist/modules/notifications/lib/delivery-strategies-registry.js +19 -0
  207. package/dist/modules/notifications/lib/delivery-strategies-registry.js.map +7 -0
  208. package/dist/modules/notifications/lib/deliveryStrategies.js.map +2 -2
  209. package/dist/modules/notifications/lib/derive-category.js +8 -0
  210. package/dist/modules/notifications/lib/derive-category.js.map +7 -0
  211. package/dist/modules/notifications/lib/notification-channel-registry.js +35 -0
  212. package/dist/modules/notifications/lib/notification-channel-registry.js.map +7 -0
  213. package/dist/modules/notifications/lib/notification-type-registry.js +87 -0
  214. package/dist/modules/notifications/lib/notification-type-registry.js.map +7 -0
  215. package/dist/modules/notifications/lib/notificationCopy.js +14 -0
  216. package/dist/modules/notifications/lib/notificationCopy.js.map +7 -0
  217. package/dist/modules/notifications/lib/notificationFactory.js +4 -1
  218. package/dist/modules/notifications/lib/notificationFactory.js.map +2 -2
  219. package/dist/modules/notifications/lib/notificationMapper.js +2 -0
  220. package/dist/modules/notifications/lib/notificationMapper.js.map +2 -2
  221. package/dist/modules/notifications/lib/notificationPreferenceService.js +83 -0
  222. package/dist/modules/notifications/lib/notificationPreferenceService.js.map +7 -0
  223. package/dist/modules/notifications/lib/notificationService.js +83 -28
  224. package/dist/modules/notifications/lib/notificationService.js.map +2 -2
  225. package/dist/modules/notifications/lib/notificationVisibility.js +24 -0
  226. package/dist/modules/notifications/lib/notificationVisibility.js.map +7 -0
  227. package/dist/modules/notifications/lib/routeHelpers.js +11 -1
  228. package/dist/modules/notifications/lib/routeHelpers.js.map +2 -2
  229. package/dist/modules/notifications/lib/shouldDeliver.js +46 -0
  230. package/dist/modules/notifications/lib/shouldDeliver.js.map +7 -0
  231. package/dist/modules/notifications/lib/strategies/email-delivery-strategy.js +54 -0
  232. package/dist/modules/notifications/lib/strategies/email-delivery-strategy.js.map +7 -0
  233. package/dist/modules/notifications/lib/strategies/in-app-delivery-strategy.js +12 -0
  234. package/dist/modules/notifications/lib/strategies/in-app-delivery-strategy.js.map +7 -0
  235. package/dist/modules/notifications/lib/typeCatalogue.js +71 -0
  236. package/dist/modules/notifications/lib/typeCatalogue.js.map +7 -0
  237. package/dist/modules/notifications/lib/typeChannelSettings.js +12 -0
  238. package/dist/modules/notifications/lib/typeChannelSettings.js.map +7 -0
  239. package/dist/modules/notifications/lib/typeOverrides.js +19 -0
  240. package/dist/modules/notifications/lib/typeOverrides.js.map +7 -0
  241. package/dist/modules/notifications/migrations/Migration20260625122947_notifications.js +14 -0
  242. package/dist/modules/notifications/migrations/Migration20260625122947_notifications.js.map +7 -0
  243. package/dist/modules/notifications/migrations/Migration20260626120500_notifications_push_payload.js +15 -0
  244. package/dist/modules/notifications/migrations/Migration20260626120500_notifications_push_payload.js.map +7 -0
  245. package/dist/modules/notifications/migrations/Migration20260626130000_notifications_non_opt_out.js +13 -0
  246. package/dist/modules/notifications/migrations/Migration20260626130000_notifications_non_opt_out.js.map +7 -0
  247. package/dist/modules/notifications/migrations/Migration20260626140000_notifications_type_category_silent.js +15 -0
  248. package/dist/modules/notifications/migrations/Migration20260626140000_notifications_type_category_silent.js.map +7 -0
  249. package/dist/modules/notifications/migrations/Migration20260706120000_notifications_channels.js +13 -0
  250. package/dist/modules/notifications/migrations/Migration20260706120000_notifications_channels.js.map +7 -0
  251. package/dist/modules/notifications/migrations/Migration20260716123121_notifications.js +14 -0
  252. package/dist/modules/notifications/migrations/Migration20260716123121_notifications.js.map +7 -0
  253. package/dist/modules/notifications/notification-channels.js +26 -0
  254. package/dist/modules/notifications/notification-channels.js.map +7 -0
  255. package/dist/modules/notifications/notifications.delivery-strategies.js +12 -0
  256. package/dist/modules/notifications/notifications.delivery-strategies.js.map +7 -0
  257. package/dist/modules/notifications/setup.js +16 -1
  258. package/dist/modules/notifications/setup.js.map +2 -2
  259. package/dist/modules/notifications/subscribers/deliver-notification.js +66 -57
  260. package/dist/modules/notifications/subscribers/deliver-notification.js.map +2 -2
  261. package/dist/modules/notifications/subscribers/sync-notification-types.js +16 -0
  262. package/dist/modules/notifications/subscribers/sync-notification-types.js.map +7 -0
  263. package/dist/modules/notifications/widgets/injection/profile-preferences-menu/widget.js +23 -0
  264. package/dist/modules/notifications/widgets/injection/profile-preferences-menu/widget.js.map +7 -0
  265. package/dist/modules/notifications/widgets/injection-table.js +14 -0
  266. package/dist/modules/notifications/widgets/injection-table.js.map +7 -0
  267. package/dist/modules/push_notifications/acl.js +13 -0
  268. package/dist/modules/push_notifications/acl.js.map +7 -0
  269. package/dist/modules/push_notifications/api/custom-send/route.js +141 -0
  270. package/dist/modules/push_notifications/api/custom-send/route.js.map +7 -0
  271. package/dist/modules/push_notifications/api/deliveries/[id]/route.js +110 -0
  272. package/dist/modules/push_notifications/api/deliveries/[id]/route.js.map +7 -0
  273. package/dist/modules/push_notifications/api/deliveries/route.js +56 -0
  274. package/dist/modules/push_notifications/api/deliveries/route.js.map +7 -0
  275. package/dist/modules/push_notifications/api/openapi.js +19 -0
  276. package/dist/modules/push_notifications/api/openapi.js.map +7 -0
  277. package/dist/modules/push_notifications/backend/push_notifications/[id]/page.js +103 -0
  278. package/dist/modules/push_notifications/backend/push_notifications/[id]/page.js.map +7 -0
  279. package/dist/modules/push_notifications/backend/push_notifications/[id]/page.meta.js +13 -0
  280. package/dist/modules/push_notifications/backend/push_notifications/[id]/page.meta.js.map +7 -0
  281. package/dist/modules/push_notifications/backend/push_notifications/page.js +213 -0
  282. package/dist/modules/push_notifications/backend/push_notifications/page.js.map +7 -0
  283. package/dist/modules/push_notifications/backend/push_notifications/page.meta.js +23 -0
  284. package/dist/modules/push_notifications/backend/push_notifications/page.meta.js.map +7 -0
  285. package/dist/modules/push_notifications/backend/push_notifications/send/page.js +245 -0
  286. package/dist/modules/push_notifications/backend/push_notifications/send/page.js.map +7 -0
  287. package/dist/modules/push_notifications/backend/push_notifications/send/page.meta.js +26 -0
  288. package/dist/modules/push_notifications/backend/push_notifications/send/page.meta.js.map +7 -0
  289. package/dist/modules/push_notifications/data/entities.js +92 -0
  290. package/dist/modules/push_notifications/data/entities.js.map +7 -0
  291. package/dist/modules/push_notifications/data/extensions.js +23 -0
  292. package/dist/modules/push_notifications/data/extensions.js.map +7 -0
  293. package/dist/modules/push_notifications/data/validators.js +89 -0
  294. package/dist/modules/push_notifications/data/validators.js.map +7 -0
  295. package/dist/modules/push_notifications/di.js +17 -0
  296. package/dist/modules/push_notifications/di.js.map +7 -0
  297. package/dist/modules/push_notifications/events.js +24 -0
  298. package/dist/modules/push_notifications/events.js.map +7 -0
  299. package/dist/modules/push_notifications/index.js +16 -0
  300. package/dist/modules/push_notifications/index.js.map +7 -0
  301. package/dist/modules/push_notifications/lib/fake-provider-recorder.js +80 -0
  302. package/dist/modules/push_notifications/lib/fake-provider-recorder.js.map +7 -0
  303. package/dist/modules/push_notifications/lib/push-delivery-strategy.js +54 -0
  304. package/dist/modules/push_notifications/lib/push-delivery-strategy.js.map +7 -0
  305. package/dist/modules/push_notifications/lib/push-delivery.js +297 -0
  306. package/dist/modules/push_notifications/lib/push-delivery.js.map +7 -0
  307. package/dist/modules/push_notifications/lib/push-fanout.js +138 -0
  308. package/dist/modules/push_notifications/lib/push-fanout.js.map +7 -0
  309. package/dist/modules/push_notifications/lib/push-health.js +32 -0
  310. package/dist/modules/push_notifications/lib/push-health.js.map +7 -0
  311. package/dist/modules/push_notifications/lib/push-reaper.js +87 -0
  312. package/dist/modules/push_notifications/lib/push-reaper.js.map +7 -0
  313. package/dist/modules/push_notifications/lib/push-receipt-reaper.js +136 -0
  314. package/dist/modules/push_notifications/lib/push-receipt-reaper.js.map +7 -0
  315. package/dist/modules/push_notifications/lib/push-stub-adapter.js +63 -0
  316. package/dist/modules/push_notifications/lib/push-stub-adapter.js.map +7 -0
  317. package/dist/modules/push_notifications/lib/queue.js +53 -0
  318. package/dist/modules/push_notifications/lib/queue.js.map +7 -0
  319. package/dist/modules/push_notifications/lib/reclaim-window.js +13 -0
  320. package/dist/modules/push_notifications/lib/reclaim-window.js.map +7 -0
  321. package/dist/modules/push_notifications/lib/send-custom-push.js +48 -0
  322. package/dist/modules/push_notifications/lib/send-custom-push.js.map +7 -0
  323. package/dist/modules/push_notifications/migrations/Migration20260625150049_push_notifications.js +16 -0
  324. package/dist/modules/push_notifications/migrations/Migration20260625150049_push_notifications.js.map +7 -0
  325. package/dist/modules/push_notifications/migrations/Migration20260626120000_push_notifications_silent.js +13 -0
  326. package/dist/modules/push_notifications/migrations/Migration20260626120000_push_notifications_silent.js.map +7 -0
  327. package/dist/modules/push_notifications/notifications.delivery-strategies.js +8 -0
  328. package/dist/modules/push_notifications/notifications.delivery-strategies.js.map +7 -0
  329. package/dist/modules/push_notifications/notifications.js +40 -0
  330. package/dist/modules/push_notifications/notifications.js.map +7 -0
  331. package/dist/modules/push_notifications/setup.js +52 -0
  332. package/dist/modules/push_notifications/setup.js.map +7 -0
  333. package/dist/modules/push_notifications/workers/reclaim-stuck.worker.js +57 -0
  334. package/dist/modules/push_notifications/workers/reclaim-stuck.worker.js.map +7 -0
  335. package/dist/modules/push_notifications/workers/send-push.worker.js +27 -0
  336. package/dist/modules/push_notifications/workers/send-push.worker.js.map +7 -0
  337. package/dist/modules/sales/notifications.js +8 -0
  338. package/dist/modules/sales/notifications.js.map +2 -2
  339. package/dist/modules/staff/notifications.js +6 -0
  340. package/dist/modules/staff/notifications.js.map +2 -2
  341. package/dist/modules/workflows/notifications.js +2 -0
  342. package/dist/modules/workflows/notifications.js.map +2 -2
  343. package/generated/entities/notification/index.ts +3 -0
  344. package/generated/entities/notification_preference/index.ts +8 -0
  345. package/generated/entities/notification_type/index.ts +9 -0
  346. package/generated/entities/notification_type_override/index.ts +7 -0
  347. package/generated/entities/push_notification_delivery/index.ts +19 -0
  348. package/generated/entities/user_device/index.ts +16 -0
  349. package/generated/entities.ids.generated.ts +12 -1
  350. package/generated/entity-fields-registry.ts +72 -0
  351. package/package.json +7 -7
  352. package/src/helpers/integration/api.ts +2 -1
  353. package/src/helpers/integration/appRoot.ts +13 -0
  354. package/src/helpers/integration/dbFixtures.ts +1 -5
  355. package/src/helpers/integration/pushFake.ts +219 -0
  356. package/src/modules/auth/i18n/de.json +1 -0
  357. package/src/modules/auth/i18n/en.json +1 -0
  358. package/src/modules/auth/i18n/es.json +1 -0
  359. package/src/modules/auth/i18n/ko.json +1 -0
  360. package/src/modules/auth/i18n/pl.json +1 -0
  361. package/src/modules/auth/notifications.ts +16 -0
  362. package/src/modules/business_rules/i18n/de.json +2 -1
  363. package/src/modules/business_rules/i18n/en.json +2 -1
  364. package/src/modules/business_rules/i18n/es.json +2 -1
  365. package/src/modules/business_rules/i18n/ko.json +2 -1
  366. package/src/modules/business_rules/i18n/pl.json +2 -1
  367. package/src/modules/business_rules/notifications.ts +2 -0
  368. package/src/modules/catalog/i18n/de.json +2 -1
  369. package/src/modules/catalog/i18n/en.json +2 -1
  370. package/src/modules/catalog/i18n/es.json +2 -1
  371. package/src/modules/catalog/i18n/ko.json +2 -1
  372. package/src/modules/catalog/i18n/pl.json +2 -1
  373. package/src/modules/catalog/notifications.ts +2 -0
  374. package/src/modules/communication_channels/acl.ts +9 -0
  375. package/src/modules/communication_channels/api/delete/admin/channels/[id]/route.ts +168 -0
  376. package/src/modules/communication_channels/api/get/channels/[id]/health/route.ts +6 -2
  377. package/src/modules/communication_channels/api/get/channels/[id]/route.ts +6 -2
  378. package/src/modules/communication_channels/api/get/channels/route.ts +2 -1
  379. package/src/modules/communication_channels/api/post/channels/[id]/test-send/route.ts +16 -5
  380. package/src/modules/communication_channels/api/post/channels/connect/credentials/route.ts +37 -1
  381. package/src/modules/communication_channels/api/post/channels/connect/tenant-credentials/route.ts +175 -0
  382. package/src/modules/communication_channels/backend/communication_channels/channels/page.tsx +11 -1
  383. package/src/modules/communication_channels/commands/admin-delete-channel.ts +203 -0
  384. package/src/modules/communication_channels/commands/connect-credential-channel.ts +63 -15
  385. package/src/modules/communication_channels/commands/deliver-outbound-message.ts +10 -2
  386. package/src/modules/communication_channels/commands/push-register.ts +7 -1
  387. package/src/modules/communication_channels/commands/push-unregister.ts +14 -3
  388. package/src/modules/communication_channels/data/entities.ts +10 -0
  389. package/src/modules/communication_channels/data/validators.ts +1 -0
  390. package/src/modules/communication_channels/i18n/de.json +35 -1
  391. package/src/modules/communication_channels/i18n/en.json +35 -1
  392. package/src/modules/communication_channels/i18n/es.json +35 -1
  393. package/src/modules/communication_channels/i18n/ko.json +35 -1
  394. package/src/modules/communication_channels/i18n/pl.json +35 -1
  395. package/src/modules/communication_channels/lib/access-control.ts +42 -0
  396. package/src/modules/communication_channels/lib/adapter.ts +25 -0
  397. package/src/modules/communication_channels/lib/connect-channel.ts +61 -26
  398. package/src/modules/communication_channels/lib/push-adapter.ts +139 -0
  399. package/src/modules/communication_channels/lib/push-capabilities.ts +42 -0
  400. package/src/modules/communication_channels/lib/push-connect-error.ts +52 -0
  401. package/src/modules/communication_channels/lib/push-credential-errors.ts +58 -0
  402. package/src/modules/communication_channels/lib/push-envelope.ts +104 -0
  403. package/src/modules/communication_channels/lib/refcounted-client-cache.ts +123 -0
  404. package/src/modules/communication_channels/migrations/.snapshot-open-mercato.json +9 -0
  405. package/src/modules/communication_channels/migrations/Migration20260703123630_communication_channels.ts +20 -0
  406. package/src/modules/communication_channels/notifications.ts +4 -0
  407. package/src/modules/communication_channels/setup.ts +2 -0
  408. package/src/modules/configs/i18n/de.json +5 -1
  409. package/src/modules/configs/i18n/en.json +5 -1
  410. package/src/modules/configs/i18n/es.json +5 -1
  411. package/src/modules/configs/i18n/ko.json +5 -1
  412. package/src/modules/configs/i18n/pl.json +5 -1
  413. package/src/modules/configs/lib/upgrade-actions.ts +21 -0
  414. package/src/modules/customer_accounts/i18n/de.json +2 -1
  415. package/src/modules/customer_accounts/i18n/en.json +2 -1
  416. package/src/modules/customer_accounts/i18n/es.json +2 -1
  417. package/src/modules/customer_accounts/i18n/ko.json +2 -1
  418. package/src/modules/customer_accounts/i18n/pl.json +2 -1
  419. package/src/modules/customer_accounts/notifications.ts +12 -0
  420. package/src/modules/customers/i18n/de.json +2 -1
  421. package/src/modules/customers/i18n/en.json +2 -1
  422. package/src/modules/customers/i18n/es.json +2 -1
  423. package/src/modules/customers/i18n/ko.json +2 -1
  424. package/src/modules/customers/i18n/pl.json +2 -1
  425. package/src/modules/customers/notifications.ts +4 -0
  426. package/src/modules/devices/AGENTS.md +101 -0
  427. package/src/modules/devices/acl.ts +7 -0
  428. package/src/modules/devices/api/[id]/route.ts +180 -0
  429. package/src/modules/devices/api/admin/devices/[id]/route.ts +235 -0
  430. package/src/modules/devices/api/admin/devices/route.ts +160 -0
  431. package/src/modules/devices/api/auth.ts +13 -0
  432. package/src/modules/devices/api/deviceList.ts +54 -0
  433. package/src/modules/devices/api/deviceOps.ts +186 -0
  434. package/src/modules/devices/api/openapi.ts +23 -0
  435. package/src/modules/devices/api/route.ts +165 -0
  436. package/src/modules/devices/backend/devices/[id]/page.meta.ts +12 -0
  437. package/src/modules/devices/backend/devices/[id]/page.tsx +164 -0
  438. package/src/modules/devices/backend/devices/create/page.meta.ts +12 -0
  439. package/src/modules/devices/backend/devices/create/page.tsx +81 -0
  440. package/src/modules/devices/backend/devices/page.meta.ts +21 -0
  441. package/src/modules/devices/backend/devices/page.tsx +259 -0
  442. package/src/modules/devices/commands/deactivate.ts +126 -0
  443. package/src/modules/devices/commands/index.ts +3 -0
  444. package/src/modules/devices/commands/register.ts +199 -0
  445. package/src/modules/devices/commands/shared.ts +119 -0
  446. package/src/modules/devices/commands/update.ts +142 -0
  447. package/src/modules/devices/data/entities.ts +79 -0
  448. package/src/modules/devices/data/validators.ts +65 -0
  449. package/src/modules/devices/di.ts +9 -0
  450. package/src/modules/devices/encryption.ts +15 -0
  451. package/src/modules/devices/events.ts +16 -0
  452. package/src/modules/devices/i18n/de.json +45 -0
  453. package/src/modules/devices/i18n/en.json +45 -0
  454. package/src/modules/devices/i18n/es.json +45 -0
  455. package/src/modules/devices/i18n/ko.json +45 -0
  456. package/src/modules/devices/i18n/pl.json +45 -0
  457. package/src/modules/devices/index.ts +15 -0
  458. package/src/modules/devices/lib/operationMetadata.ts +35 -0
  459. package/src/modules/devices/migrations/.snapshot-open-mercato.json +200 -0
  460. package/src/modules/devices/migrations/Migration20260602120000.ts +15 -0
  461. package/src/modules/devices/migrations/Migration20260630120000.ts +20 -0
  462. package/src/modules/devices/migrations/Migration20260701120000.ts +13 -0
  463. package/src/modules/devices/migrations/Migration20260721120000.ts +18 -0
  464. package/src/modules/devices/setup.ts +11 -0
  465. package/src/modules/entities/cli.ts +16 -2
  466. package/src/modules/entities/migrations/Migration20260722120000.ts +38 -0
  467. package/src/modules/inbox_ops/i18n/de.json +2 -1
  468. package/src/modules/inbox_ops/i18n/en.json +2 -1
  469. package/src/modules/inbox_ops/i18n/es.json +2 -1
  470. package/src/modules/inbox_ops/i18n/ko.json +2 -1
  471. package/src/modules/inbox_ops/i18n/pl.json +2 -1
  472. package/src/modules/inbox_ops/notifications.ts +2 -0
  473. package/src/modules/messages/i18n/de.json +2 -1
  474. package/src/modules/messages/i18n/en.json +2 -1
  475. package/src/modules/messages/i18n/es.json +2 -1
  476. package/src/modules/messages/i18n/ko.json +2 -1
  477. package/src/modules/messages/i18n/pl.json +2 -1
  478. package/src/modules/messages/notifications.ts +2 -0
  479. package/src/modules/notifications/AGENTS.md +54 -0
  480. package/src/modules/notifications/acl.ts +2 -0
  481. package/src/modules/notifications/api/admin/preferences/route.ts +193 -0
  482. package/src/modules/notifications/api/channels/route.ts +54 -0
  483. package/src/modules/notifications/api/preferences/route.ts +131 -0
  484. package/src/modules/notifications/api/route.ts +8 -1
  485. package/src/modules/notifications/api/types/[id]/channels/[channel]/route.ts +226 -0
  486. package/src/modules/notifications/api/types/route.ts +239 -0
  487. package/src/modules/notifications/api/unread-count/route.ts +5 -1
  488. package/src/modules/notifications/backend/notifications/user-preferences/page.meta.ts +26 -0
  489. package/src/modules/notifications/backend/notifications/user-preferences/page.tsx +12 -0
  490. package/src/modules/notifications/backend/profile/notification-preferences/page.meta.ts +31 -0
  491. package/src/modules/notifications/backend/profile/notification-preferences/page.tsx +12 -0
  492. package/src/modules/notifications/data/entities.ts +144 -0
  493. package/src/modules/notifications/data/validators.ts +116 -0
  494. package/src/modules/notifications/di.ts +7 -0
  495. package/src/modules/notifications/events.ts +23 -0
  496. package/src/modules/notifications/frontend/NotificationPreferenceMatrix.tsx +287 -0
  497. package/src/modules/notifications/frontend/NotificationPreferencesPageClient.tsx +155 -0
  498. package/src/modules/notifications/frontend/NotificationSettingsPageClient.tsx +273 -2
  499. package/src/modules/notifications/frontend/NotificationUserPreferencesAdminPageClient.tsx +214 -0
  500. package/src/modules/notifications/generators.ts +82 -0
  501. package/src/modules/notifications/i18n/de.json +43 -0
  502. package/src/modules/notifications/i18n/en.json +43 -0
  503. package/src/modules/notifications/i18n/es.json +43 -0
  504. package/src/modules/notifications/i18n/ko.json +43 -0
  505. package/src/modules/notifications/i18n/pl.json +44 -1
  506. package/src/modules/notifications/lib/delivery-strategies-registry.ts +30 -0
  507. package/src/modules/notifications/lib/deliveryStrategies.ts +40 -4
  508. package/src/modules/notifications/lib/derive-category.ts +15 -0
  509. package/src/modules/notifications/lib/notification-channel-registry.ts +59 -0
  510. package/src/modules/notifications/lib/notification-type-registry.ts +182 -0
  511. package/src/modules/notifications/lib/notificationCopy.ts +34 -0
  512. package/src/modules/notifications/lib/notificationFactory.ts +5 -1
  513. package/src/modules/notifications/lib/notificationMapper.ts +2 -0
  514. package/src/modules/notifications/lib/notificationPreferenceService.ts +138 -0
  515. package/src/modules/notifications/lib/notificationService.ts +129 -25
  516. package/src/modules/notifications/lib/notificationVisibility.ts +48 -0
  517. package/src/modules/notifications/lib/routeHelpers.ts +14 -1
  518. package/src/modules/notifications/lib/shouldDeliver.ts +112 -0
  519. package/src/modules/notifications/lib/strategies/email-delivery-strategy.ts +63 -0
  520. package/src/modules/notifications/lib/strategies/in-app-delivery-strategy.ts +21 -0
  521. package/src/modules/notifications/lib/typeCatalogue.ts +141 -0
  522. package/src/modules/notifications/lib/typeChannelSettings.ts +29 -0
  523. package/src/modules/notifications/lib/typeOverrides.ts +46 -0
  524. package/src/modules/notifications/migrations/.snapshot-open-mercato.json +874 -100
  525. package/src/modules/notifications/migrations/Migration20260625122947_notifications.ts +14 -0
  526. package/src/modules/notifications/migrations/Migration20260626120500_notifications_push_payload.ts +15 -0
  527. package/src/modules/notifications/migrations/Migration20260626130000_notifications_non_opt_out.ts +13 -0
  528. package/src/modules/notifications/migrations/Migration20260626140000_notifications_type_category_silent.ts +15 -0
  529. package/src/modules/notifications/migrations/Migration20260706120000_notifications_channels.ts +13 -0
  530. package/src/modules/notifications/migrations/Migration20260716123121_notifications.ts +14 -0
  531. package/src/modules/notifications/notification-channels.ts +30 -0
  532. package/src/modules/notifications/notifications.delivery-strategies.ts +15 -0
  533. package/src/modules/notifications/setup.ts +23 -1
  534. package/src/modules/notifications/subscribers/deliver-notification.ts +105 -71
  535. package/src/modules/notifications/subscribers/sync-notification-types.ts +18 -0
  536. package/src/modules/notifications/widgets/injection/profile-preferences-menu/widget.ts +35 -0
  537. package/src/modules/notifications/widgets/injection-table.ts +18 -0
  538. package/src/modules/push_notifications/AGENTS.md +92 -0
  539. package/src/modules/push_notifications/acl.ts +9 -0
  540. package/src/modules/push_notifications/api/custom-send/route.ts +175 -0
  541. package/src/modules/push_notifications/api/deliveries/[id]/route.ts +122 -0
  542. package/src/modules/push_notifications/api/deliveries/route.ts +58 -0
  543. package/src/modules/push_notifications/api/openapi.ts +22 -0
  544. package/src/modules/push_notifications/backend/push_notifications/[id]/page.meta.ts +9 -0
  545. package/src/modules/push_notifications/backend/push_notifications/[id]/page.tsx +149 -0
  546. package/src/modules/push_notifications/backend/push_notifications/page.meta.ts +21 -0
  547. package/src/modules/push_notifications/backend/push_notifications/page.tsx +239 -0
  548. package/src/modules/push_notifications/backend/push_notifications/send/page.meta.ts +24 -0
  549. package/src/modules/push_notifications/backend/push_notifications/send/page.tsx +327 -0
  550. package/src/modules/push_notifications/data/entities.ts +111 -0
  551. package/src/modules/push_notifications/data/extensions.ts +29 -0
  552. package/src/modules/push_notifications/data/validators.ts +102 -0
  553. package/src/modules/push_notifications/di.ts +20 -0
  554. package/src/modules/push_notifications/events.ts +21 -0
  555. package/src/modules/push_notifications/i18n/de.json +79 -0
  556. package/src/modules/push_notifications/i18n/en.json +79 -0
  557. package/src/modules/push_notifications/i18n/es.json +79 -0
  558. package/src/modules/push_notifications/i18n/ko.json +79 -0
  559. package/src/modules/push_notifications/i18n/pl.json +79 -0
  560. package/src/modules/push_notifications/index.ts +15 -0
  561. package/src/modules/push_notifications/lib/fake-provider-recorder.ts +154 -0
  562. package/src/modules/push_notifications/lib/push-delivery-strategy.ts +90 -0
  563. package/src/modules/push_notifications/lib/push-delivery.ts +455 -0
  564. package/src/modules/push_notifications/lib/push-fanout.ts +237 -0
  565. package/src/modules/push_notifications/lib/push-health.ts +47 -0
  566. package/src/modules/push_notifications/lib/push-reaper.ts +140 -0
  567. package/src/modules/push_notifications/lib/push-receipt-reaper.ts +210 -0
  568. package/src/modules/push_notifications/lib/push-stub-adapter.ts +80 -0
  569. package/src/modules/push_notifications/lib/queue.ts +74 -0
  570. package/src/modules/push_notifications/lib/reclaim-window.ts +16 -0
  571. package/src/modules/push_notifications/lib/send-custom-push.ts +100 -0
  572. package/src/modules/push_notifications/migrations/.snapshot-open-mercato.json +386 -0
  573. package/src/modules/push_notifications/migrations/Migration20260625150049_push_notifications.ts +16 -0
  574. package/src/modules/push_notifications/migrations/Migration20260626120000_push_notifications_silent.ts +13 -0
  575. package/src/modules/push_notifications/notifications.delivery-strategies.ts +8 -0
  576. package/src/modules/push_notifications/notifications.ts +50 -0
  577. package/src/modules/push_notifications/setup.ts +92 -0
  578. package/src/modules/push_notifications/workers/reclaim-stuck.worker.ts +78 -0
  579. package/src/modules/push_notifications/workers/send-push.worker.ts +34 -0
  580. package/src/modules/sales/i18n/de.json +1 -0
  581. package/src/modules/sales/i18n/en.json +1 -0
  582. package/src/modules/sales/i18n/es.json +1 -0
  583. package/src/modules/sales/i18n/ko.json +1 -0
  584. package/src/modules/sales/i18n/pl.json +1 -0
  585. package/src/modules/sales/notifications.ts +8 -0
  586. package/src/modules/staff/i18n/de.json +1 -0
  587. package/src/modules/staff/i18n/en.json +1 -0
  588. package/src/modules/staff/i18n/es.json +1 -0
  589. package/src/modules/staff/i18n/ko.json +1 -0
  590. package/src/modules/staff/i18n/pl.json +1 -0
  591. package/src/modules/staff/notifications.ts +6 -0
  592. package/src/modules/workflows/i18n/de.json +1 -0
  593. package/src/modules/workflows/i18n/en.json +1 -0
  594. package/src/modules/workflows/i18n/es.json +1 -0
  595. package/src/modules/workflows/i18n/ko.json +1 -0
  596. package/src/modules/workflows/i18n/pl.json +1 -0
  597. package/src/modules/workflows/notifications.ts +2 -0
@@ -0,0 +1,455 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql'
2
+ import { raw, type EntityName } from '@mikro-orm/core'
3
+ import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
4
+ import type { CommandBus } from '@open-mercato/shared/lib/commands'
5
+ import type { ChannelAdapter, SendMessageResult } from '@open-mercato/core/modules/communication_channels/lib/adapter'
6
+ import { DEVICE_UNREGISTERED } from '@open-mercato/core/modules/communication_channels/lib/push-adapter'
7
+ import { refreshCredentialsIfNeeded } from '@open-mercato/core/modules/communication_channels/lib/credential-refresh'
8
+ import type { CommunicationChannel } from '@open-mercato/core/modules/communication_channels/data/entities'
9
+ import type { PushOptions } from '@open-mercato/core/modules/communication_channels/lib/push-envelope'
10
+ import type { UserDevice } from '@open-mercato/core/modules/devices/data/entities'
11
+ import { calculateBackoffDelayMs } from '@open-mercato/shared/lib/delivery/retry'
12
+ import { createLogger } from '@open-mercato/shared/lib/logger'
13
+ import { PushNotificationDelivery } from '../data/entities'
14
+ import { emitPushNotificationsEvent } from '../events'
15
+ import { resolveStuckThresholdMs } from './reclaim-window'
16
+ import { enqueuePushDelivery, type PushDeliveryJob } from './queue'
17
+
18
+ const logger = createLogger('push_notifications')
19
+
20
+ export const MAX_ATTEMPTS = 3
21
+
22
+ // Bound the worker's wait on a single provider send. If a send outlived the stuck-reclaim window the
23
+ // reaper would reclaim the row mid-send and a second worker would re-send it, so the wait is capped
24
+ // well under that window (see ./reclaim-window). Configurable via OM_PUSH_SEND_TIMEOUT_MS; clamped to
25
+ // stay below the reclaim window regardless of the configured value.
26
+ //
27
+ // NOTE: without an AbortSignal through the adapter contract this bounds the WORKER's wait, not the
28
+ // underlying socket — a black-holed SDK call may still complete later, but the lease stays fresh (the
29
+ // worker finalizes/retries within the window) so the reaper never steals it and fenced write-backs keep
30
+ // terminal state consistent. A timeout is surfaced as a retryable `send_timeout` failure.
31
+ const DEFAULT_SEND_TIMEOUT_MS = 60_000
32
+ const MIN_SEND_TIMEOUT_MS = 5_000
33
+ const SEND_TIMEOUT_SAFETY_MARGIN_MS = 60_000
34
+
35
+ function resolvePushSendTimeoutMs(): number {
36
+ const ceiling = Math.max(MIN_SEND_TIMEOUT_MS, resolveStuckThresholdMs() - SEND_TIMEOUT_SAFETY_MARGIN_MS)
37
+ const parsed = Number.parseInt(process.env.OM_PUSH_SEND_TIMEOUT_MS ?? '', 10)
38
+ const configured = Number.isFinite(parsed) && parsed >= MIN_SEND_TIMEOUT_MS ? parsed : DEFAULT_SEND_TIMEOUT_MS
39
+ return Math.min(configured, ceiling)
40
+ }
41
+
42
+ type Resolve = <T = unknown>(name: string) => T
43
+
44
+ interface ChannelAdapterRegistryLike {
45
+ get(providerKey: string): ChannelAdapter | undefined
46
+ }
47
+
48
+ interface CredentialsServiceLike {
49
+ resolve(
50
+ integrationId: string,
51
+ scope: { tenantId: string; organizationId: string; userId?: string | null },
52
+ ): Promise<Record<string, unknown> | null>
53
+ }
54
+
55
+ type PushPayload = {
56
+ title?: string
57
+ body?: string | null
58
+ data?: Record<string, string>
59
+ options?: PushOptions
60
+ silent?: boolean
61
+ }
62
+
63
+ type ProcessResult = { status: PushNotificationDelivery['status']; deliveryId: string } | null
64
+
65
+ // Identifies the exact lease a worker holds on a delivery row. `attempts` is the lease-generation token:
66
+ // it is incremented in the claim itself, and the reaper resets `sending`→`pending` WITHOUT touching it,
67
+ // so the next worker's claim bumps it again. Every write-back is fenced on `(status='sending', attempts)`
68
+ // — if the reaper broke this lease and another worker re-claimed, `attempts` has moved on and the fenced
69
+ // update matches 0 rows, so this worker abandons instead of clobbering the new owner's state.
70
+ type DeliveryLease = { deliveryId: string; tenantId: string; attempts: number }
71
+
72
+ // The `unregistered` sentinel is shared (`deviceUnregisteredResult` / `DEVICE_UNREGISTERED`) across the
73
+ // fcm/apns/expo adapters + the stub, so the device soft-delete below fires uniformly regardless of provider.
74
+ function isUnregistered(result: SendMessageResult): boolean {
75
+ return result.metadata?.unregistered === true || result.error === DEVICE_UNREGISTERED
76
+ }
77
+
78
+ // Apply a delivery state transition as a fenced `nativeUpdate` (never `em.flush()`, so a stale
79
+ // identity-map copy can never issue an unfenced `UPDATE ... WHERE id`). Returns `true` iff this worker
80
+ // still held the lease and the write landed; `false` means the lease was lost (reaper reclaimed it) and
81
+ // the caller MUST abandon — no re-enqueue, no event — to avoid corrupting the new owner's state.
82
+ async function applyFencedTransition(
83
+ em: EntityManager,
84
+ lease: DeliveryLease,
85
+ patch: Record<string, unknown>,
86
+ fromStatus: PushNotificationDelivery['status'] = 'sending',
87
+ ): Promise<boolean> {
88
+ const affected = await em.nativeUpdate(
89
+ PushNotificationDelivery,
90
+ { id: lease.deliveryId, tenantId: lease.tenantId, status: fromStatus, attempts: lease.attempts },
91
+ { ...patch, updatedAt: new Date() },
92
+ )
93
+ return affected > 0
94
+ }
95
+
96
+ async function finalize(
97
+ em: EntityManager,
98
+ delivery: PushNotificationDelivery,
99
+ lease: DeliveryLease,
100
+ event: 'push_notifications.delivery.sent' | 'push_notifications.delivery.failed',
101
+ patch: Record<string, unknown>,
102
+ ): Promise<ProcessResult> {
103
+ // A terminal row never has a pending retry scheduled.
104
+ const held = await applyFencedTransition(em, lease, { ...patch, nextRetryAt: null })
105
+ if (!held) {
106
+ // Lease lost — the reaper reclaimed this row and another actor now owns it. Emitting or re-enqueueing
107
+ // here would corrupt the new owner's terminal state, so abandon quietly.
108
+ logger.warn('Push delivery lease lost before finalize; abandoning', {
109
+ deliveryId: lease.deliveryId,
110
+ tenantId: lease.tenantId,
111
+ })
112
+ return { status: delivery.status, deliveryId: delivery.id }
113
+ }
114
+ const finalStatus = (patch.status as PushNotificationDelivery['status'] | undefined) ?? delivery.status
115
+ await emitPushNotificationsEvent(
116
+ event,
117
+ {
118
+ deliveryId: delivery.id,
119
+ tenantId: delivery.tenantId,
120
+ organizationId: delivery.organizationId ?? null,
121
+ userId: delivery.userId,
122
+ provider: delivery.provider,
123
+ status: finalStatus,
124
+ },
125
+ { persistent: true },
126
+ )
127
+ return { status: finalStatus, deliveryId: delivery.id }
128
+ }
129
+
130
+ async function failTerminal(
131
+ em: EntityManager,
132
+ delivery: PushNotificationDelivery,
133
+ lease: DeliveryLease,
134
+ reason: string,
135
+ ): Promise<ProcessResult> {
136
+ return finalize(em, delivery, lease, 'push_notifications.delivery.failed', {
137
+ status: 'failed',
138
+ lastError: reason,
139
+ })
140
+ }
141
+
142
+ async function handleRetryableFailure(
143
+ em: EntityManager,
144
+ delivery: PushNotificationDelivery,
145
+ lease: DeliveryLease,
146
+ job: PushDeliveryJob,
147
+ reason: string,
148
+ providerResponse: Record<string, unknown> | null,
149
+ ): Promise<ProcessResult> {
150
+ // `lease.attempts` is the count already persisted at claim time, so it is the real cap on provider
151
+ // sends (each claim = one send attempt). Compare against it, not a stale in-memory field.
152
+ if (lease.attempts < MAX_ATTEMPTS) {
153
+ // Release the claim (`pending`) and re-enqueue with exponential backoff + jitter; per-delivery
154
+ // isolation. Jitter avoids a thundering herd when a provider outage fails many deliveries at once.
155
+ const delayMs = calculateBackoffDelayMs(lease.attempts)
156
+ const held = await applyFencedTransition(em, lease, {
157
+ status: 'pending',
158
+ lastError: reason,
159
+ providerResponse,
160
+ nextRetryAt: new Date(Date.now() + delayMs),
161
+ })
162
+ if (!held) {
163
+ // Lease lost mid-flight (reaper reclaimed + another worker re-owns it). Do NOT re-enqueue — that
164
+ // would rewind the new owner's attempt counter and schedule a duplicate job.
165
+ logger.warn('Push delivery lease lost before retry; abandoning', {
166
+ deliveryId: lease.deliveryId,
167
+ tenantId: lease.tenantId,
168
+ })
169
+ return { status: delivery.status, deliveryId: delivery.id }
170
+ }
171
+ try {
172
+ await enqueuePushDelivery(job, delayMs)
173
+ } catch (error) {
174
+ // The row is now `pending` under our lease; fence the terminal failure on that state. If the
175
+ // reaper already grabbed it (it will not — we just stamped updated_at), the write no-ops.
176
+ const failReason = error instanceof Error ? `retry_scheduling_failed: ${error.message}` : 'retry_scheduling_failed'
177
+ const failed = await applyFencedTransition(
178
+ em,
179
+ lease,
180
+ { status: 'failed', lastError: failReason, nextRetryAt: null },
181
+ 'pending',
182
+ )
183
+ if (failed) {
184
+ await emitPushNotificationsEvent(
185
+ 'push_notifications.delivery.failed',
186
+ {
187
+ deliveryId: delivery.id,
188
+ tenantId: delivery.tenantId,
189
+ organizationId: delivery.organizationId ?? null,
190
+ userId: delivery.userId,
191
+ provider: delivery.provider,
192
+ status: 'failed',
193
+ },
194
+ { persistent: true },
195
+ )
196
+ }
197
+ return { status: 'failed', deliveryId: delivery.id }
198
+ }
199
+ await emitPushNotificationsEvent(
200
+ 'push_notifications.delivery.failed',
201
+ {
202
+ deliveryId: delivery.id,
203
+ tenantId: delivery.tenantId,
204
+ organizationId: delivery.organizationId ?? null,
205
+ userId: delivery.userId,
206
+ provider: delivery.provider,
207
+ // The row is reset to `pending` to release the claim for the re-enqueued attempt, but the
208
+ // logical outcome of THIS attempt is "failed, retry scheduled". Emit `retrying` (not the reset
209
+ // row status `pending`) so subscribers keying off `status` aren't misled; `willRetry` still gates
210
+ // ultimate-failure counters.
211
+ status: 'retrying',
212
+ willRetry: true,
213
+ },
214
+ { persistent: true },
215
+ )
216
+ return { status: 'pending', deliveryId: delivery.id }
217
+ }
218
+ // Retries exhausted: distinct terminal `expired` status (vs `failed` for terminal errors), so the
219
+ // admin log can tell "gave up after N attempts" from "channel unavailable / no adapter".
220
+ return finalize(em, delivery, lease, 'push_notifications.delivery.failed', {
221
+ status: 'expired',
222
+ lastError: reason,
223
+ providerResponse,
224
+ })
225
+ }
226
+
227
+ // Soft-delete the device through the devices module's own command (audit/events/undo stay
228
+ // consistent) rather than mutating its table directly. Dispatched by command id with a trusted
229
+ // system context — no business-logic import, no authenticated actor.
230
+ export async function softDeleteUnregisteredDevice(
231
+ resolve: Resolve,
232
+ input: { id: string; tenantId: string; userId: string; organizationId: string | null },
233
+ ): Promise<void> {
234
+ try {
235
+ const commandBus = resolve('commandBus') as CommandBus
236
+ await commandBus.execute('devices.user_devices.deactivate', {
237
+ input,
238
+ ctx: {
239
+ container: { resolve } as never,
240
+ auth: null,
241
+ organizationScope: null,
242
+ selectedOrganizationId: input.organizationId,
243
+ organizationIds: input.organizationId ? [input.organizationId] : null,
244
+ systemActor: true,
245
+ } as never,
246
+ })
247
+ } catch (error) {
248
+ logger.error('Failed to deactivate unregistered device', {
249
+ userDeviceId: input.id,
250
+ error: error instanceof Error ? error.message : String(error),
251
+ })
252
+ }
253
+ }
254
+
255
+ // Bound the wait on a single provider send (B3a): a hung/black-holed call must not outlive the
256
+ // stuck-reclaim window, or the reaper would reclaim the row mid-send and a second worker would re-send.
257
+ // On timeout we return a retryable `send_timeout` result and move on; the underlying SDK promise is left
258
+ // to settle on its own (its late rejection is swallowed to avoid an unhandled rejection).
259
+ async function sendWithTimeout(
260
+ adapter: ChannelAdapter,
261
+ input: Parameters<ChannelAdapter['sendMessage']>[0],
262
+ timeoutMs: number,
263
+ ): Promise<SendMessageResult> {
264
+ let timer: ReturnType<typeof setTimeout> | undefined
265
+ const timeout = new Promise<SendMessageResult>((resolve) => {
266
+ timer = setTimeout(() => resolve({ externalMessageId: '', status: 'failed', error: 'send_timeout' }), timeoutMs)
267
+ })
268
+ const send = adapter.sendMessage(input)
269
+ send.catch(() => {})
270
+ try {
271
+ return await Promise.race([send, timeout])
272
+ } finally {
273
+ if (timer) clearTimeout(timer)
274
+ }
275
+ }
276
+
277
+ /**
278
+ * Send one push delivery row through the communication_channels hub.
279
+ *
280
+ * Idempotent on the delivery id: the row is claimed atomically, so a redelivered job that cannot win
281
+ * the claim (already `sending`/terminal) is a no-op. Mirrors the `test-send` route flow — resolve
282
+ * channel → adapter → credentials → convertOutbound → sendMessage — but adds delivery-row
283
+ * bookkeeping, retry/backoff, and device soft-delete on `unregistered`.
284
+ *
285
+ * Lease fencing (B3): the claim increments `attempts` atomically, and every post-claim state write goes
286
+ * through `applyFencedTransition`, fenced on `(status='sending', attempts=<claimed>)`. If the reaper
287
+ * broke this worker's lease and another worker re-claimed the row (bumping `attempts`), this worker's
288
+ * writes match 0 rows and it abandons — so a stolen lease can neither rewind the attempt counter nor
289
+ * overwrite the new owner's terminal state.
290
+ */
291
+ export async function processPushDeliveryJob(
292
+ em: EntityManager,
293
+ job: PushDeliveryJob,
294
+ resolve: Resolve,
295
+ ): Promise<ProcessResult> {
296
+ // Atomically claim the row (`pending` → `sending`) so a redelivered/duplicated job — inherent to
297
+ // at-least-once queues — is processed by exactly one worker. Only the worker whose update wins the
298
+ // race proceeds; the loser (or a non-pending/terminal row) is a no-op. Mirrors the `messages`
299
+ // send-email claim pattern, which is stronger than a plain read-then-check status guard.
300
+ // Count the attempt AT CLAIM time, in the same atomic update. `attempts` doubles as the lease token
301
+ // (see DeliveryLease): each claim increments it, the reaper never touches it, so it uniquely tags this
302
+ // lease generation. Persisting it before the send makes MAX_ATTEMPTS a real cap on provider sends
303
+ // (a crash after the send can't lose the increment). Only the worker whose update wins the race
304
+ // proceeds; the loser (or a non-pending/terminal row) is a no-op.
305
+ const claimed = await em.nativeUpdate(
306
+ PushNotificationDelivery,
307
+ { id: job.deliveryId, tenantId: job.tenantId, status: 'pending' },
308
+ { status: 'sending', attempts: raw('"attempts" + 1'), updatedAt: new Date() },
309
+ )
310
+ // Fresh read (forked EM per job → empty identity map → hits DB) so `attempts` reflects the increment.
311
+ const delivery = await em.findOne(
312
+ PushNotificationDelivery,
313
+ { id: job.deliveryId, tenantId: job.tenantId },
314
+ { refresh: true },
315
+ )
316
+ if (!delivery) return null
317
+ if (claimed === 0) return { status: delivery.status, deliveryId: delivery.id }
318
+
319
+ const lease: DeliveryLease = { deliveryId: delivery.id, tenantId: delivery.tenantId, attempts: delivery.attempts }
320
+
321
+ // Resolve the device for its full (secret) push token. Soft via DI token to avoid coupling.
322
+ // `push_token` is encrypted at rest, so decrypt on read (no-op when encryption is disabled).
323
+ const DeviceRef = resolve('UserDevice') as EntityName<UserDevice>
324
+ const device = await findOneWithDecryption(
325
+ em,
326
+ DeviceRef,
327
+ { id: delivery.userDeviceId, tenantId: job.tenantId, deletedAt: null },
328
+ undefined,
329
+ { tenantId: job.tenantId, organizationId: job.organizationId ?? null },
330
+ )
331
+ if (!device || !device.pushToken) {
332
+ const held = await applyFencedTransition(em, lease, {
333
+ status: 'skipped',
334
+ lastError: 'device_unavailable',
335
+ nextRetryAt: null,
336
+ })
337
+ return { status: held ? 'skipped' : delivery.status, deliveryId: delivery.id }
338
+ }
339
+
340
+ // Resolve the tenant's push channel matching the snapshotted provider.
341
+ const ChannelRef = resolve('CommunicationChannel') as EntityName<CommunicationChannel>
342
+ const channel = await em.findOne(ChannelRef, {
343
+ tenantId: job.tenantId,
344
+ providerKey: delivery.provider,
345
+ channelType: 'push',
346
+ isActive: true,
347
+ deletedAt: null,
348
+ })
349
+ if (!channel) return failTerminal(em, delivery, lease, 'channel_unavailable')
350
+
351
+ const registry = resolve('channelAdapterRegistry') as ChannelAdapterRegistryLike | undefined
352
+ const adapter = registry?.get(delivery.provider)
353
+ // No provider package registered this provider's adapter (e.g. channel-fcm/apns/expo not installed).
354
+ // Terminal, no retry.
355
+ if (!adapter) return failTerminal(em, delivery, lease, 'no_adapter')
356
+
357
+ // Resolve credentials (best-effort, mirrors test-send).
358
+ let credentialsService: CredentialsServiceLike | undefined
359
+ try {
360
+ credentialsService = resolve('integrationCredentialsService') as CredentialsServiceLike | undefined
361
+ } catch {
362
+ credentialsService = undefined
363
+ }
364
+ // Credentials are keyed by the CHANNEL's org context, not the notification's: at connect time they
365
+ // are stored under `channel.organizationId ?? tenantId` (see communication_channels
366
+ // connect-credential-channel). Using the notification's `job.organizationId` here would miss the
367
+ // creds whenever a tenant-level (org-less) channel serves an org-scoped notification.
368
+ const credentialScope = {
369
+ tenantId: job.tenantId,
370
+ organizationId: channel.organizationId ?? job.tenantId,
371
+ userId: channel.userId ?? null,
372
+ }
373
+ let credentials: Record<string, unknown> = {}
374
+ if (channel.credentialsRef && credentialsService) {
375
+ credentials = (await credentialsService.resolve(`channel_${delivery.provider}`, credentialScope).catch(() => null)) ?? {}
376
+ }
377
+
378
+ // Mirror the test-send flow: refresh near-expiry OAuth credentials before sending so providers with
379
+ // short-lived access tokens (FCM/APNs in Phase 4) don't fail with a stale token. No-op for adapters
380
+ // without `refreshCredentials` (e.g. the push_stub). Best-effort — on failure we keep current creds.
381
+ try {
382
+ const refreshed = await refreshCredentialsIfNeeded(
383
+ { adapter, channelId: channel.id, credentials, scope: credentialScope },
384
+ { credentialsService: credentialsService ?? null },
385
+ )
386
+ credentials = refreshed.credentials
387
+ } catch (error) {
388
+ logger.error('Credential refresh failed; sending with current credentials', {
389
+ deliveryId: delivery.id,
390
+ provider: delivery.provider,
391
+ error: error instanceof Error ? error.message : String(error),
392
+ })
393
+ }
394
+
395
+ const scope = { tenantId: job.tenantId, organizationId: job.organizationId ?? job.tenantId }
396
+ const payload = (delivery.payload ?? {}) as PushPayload
397
+ const body = payload.body ?? payload.title ?? ''
398
+
399
+ try {
400
+ const converted = await adapter.convertOutbound({ body, bodyFormat: 'text' })
401
+ const result = await sendWithTimeout(
402
+ adapter,
403
+ {
404
+ content: {
405
+ ...converted.content,
406
+ raw: {
407
+ title: payload.title ?? '',
408
+ body: payload.body ?? null,
409
+ data: payload.data ?? {},
410
+ options: payload.options ?? {},
411
+ silent: payload.silent === true,
412
+ },
413
+ },
414
+ credentials,
415
+ scope,
416
+ metadata: {
417
+ pushToken: device.pushToken,
418
+ platform: device.platform,
419
+ userDeviceId: device.id,
420
+ provider: delivery.provider,
421
+ },
422
+ },
423
+ resolvePushSendTimeoutMs(),
424
+ )
425
+
426
+ if (result.status === 'sent' || result.status === 'queued') {
427
+ return finalize(em, delivery, lease, 'push_notifications.delivery.sent', {
428
+ status: 'sent',
429
+ sentAt: new Date(),
430
+ lastError: null,
431
+ providerResponse: result.metadata ?? { externalMessageId: result.externalMessageId },
432
+ })
433
+ }
434
+
435
+ if (isUnregistered(result)) {
436
+ const outcome = await finalize(em, delivery, lease, 'push_notifications.delivery.failed', {
437
+ status: 'failed',
438
+ lastError: DEVICE_UNREGISTERED,
439
+ providerResponse: result.metadata ?? null,
440
+ })
441
+ await softDeleteUnregisteredDevice(resolve, {
442
+ id: device.id,
443
+ tenantId: device.tenantId,
444
+ userId: device.userId,
445
+ organizationId: device.organizationId ?? null,
446
+ })
447
+ return outcome
448
+ }
449
+
450
+ return handleRetryableFailure(em, delivery, lease, job, result.error ?? 'send_failed', result.metadata ?? null)
451
+ } catch (error) {
452
+ const message = error instanceof Error ? error.message : 'send_failed'
453
+ return handleRetryableFailure(em, delivery, lease, job, message, null)
454
+ }
455
+ }