@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,87 @@
1
+ import { sql } from "kysely";
2
+ import { deriveCategory } from "./derive-category.js";
3
+ const registry = /* @__PURE__ */ new Map();
4
+ let synced = false;
5
+ function registerNotificationTypes(types, options = {}) {
6
+ if (options.replace) {
7
+ registry.clear();
8
+ synced = false;
9
+ }
10
+ for (const type of types) {
11
+ registry.set(type.type, type);
12
+ }
13
+ }
14
+ function getNotificationType(type) {
15
+ return registry.get(type);
16
+ }
17
+ function getNotificationTypes() {
18
+ return Array.from(registry.values());
19
+ }
20
+ async function syncNotificationTypes(em, opts = {}) {
21
+ const definitions = getNotificationTypes();
22
+ if (synced && !opts.force) {
23
+ return { created: 0, updated: 0, deleted: 0, total: definitions.length };
24
+ }
25
+ const db = em.getKysely();
26
+ const existing = await db.selectFrom("notification_types").select(["id", "label_key", "description_key", "category", "silent", "non_opt_out"]).where("tenant_id", "is", null).execute();
27
+ const byId = new Map(existing.map((row) => [row.id, row]));
28
+ const labelKeyFor = (def) => def.labelKey ?? def.titleKey;
29
+ const descKeyFor = (def) => def.descriptionKey ?? null;
30
+ const categoryFor = (def) => def.category ?? deriveCategory(def.type);
31
+ const silentFor = (def) => def.silent === true;
32
+ const nonOptOutFor = (def) => def.nonOptOut === true;
33
+ const visibleDefinitions = definitions.filter((def) => def.hiddenFromSettings !== true);
34
+ const toCreate = visibleDefinitions.filter((def) => !byId.has(def.type));
35
+ const toUpdate = visibleDefinitions.filter((def) => {
36
+ const row = byId.get(def.type);
37
+ if (!row) return false;
38
+ return row.label_key !== labelKeyFor(def) || (row.description_key ?? null) !== descKeyFor(def) || (row.category ?? null) !== categoryFor(def) || row.silent !== silentFor(def) || row.non_opt_out !== nonOptOutFor(def);
39
+ });
40
+ let created = 0;
41
+ let updated = 0;
42
+ let deleted = 0;
43
+ if (toCreate.length) {
44
+ await db.insertInto("notification_types").values(
45
+ toCreate.map((def) => ({
46
+ id: def.type,
47
+ tenant_id: null,
48
+ label_key: labelKeyFor(def),
49
+ description_key: descKeyFor(def),
50
+ category: categoryFor(def),
51
+ silent: silentFor(def),
52
+ non_opt_out: nonOptOutFor(def),
53
+ created_at: sql`now()`,
54
+ updated_at: sql`now()`
55
+ }))
56
+ ).onConflict((oc) => oc.column("id").doNothing()).execute();
57
+ created = toCreate.length;
58
+ }
59
+ for (const def of toUpdate) {
60
+ await db.updateTable("notification_types").set({
61
+ label_key: labelKeyFor(def),
62
+ description_key: descKeyFor(def),
63
+ category: categoryFor(def),
64
+ silent: silentFor(def),
65
+ non_opt_out: nonOptOutFor(def),
66
+ updated_at: sql`now()`
67
+ }).where("id", "=", def.type).where("tenant_id", "is", null).execute();
68
+ updated += 1;
69
+ }
70
+ if (definitions.length > 0) {
71
+ const validIds = new Set(visibleDefinitions.map((def) => def.type));
72
+ const staleIds = existing.filter((row) => !validIds.has(row.id)).map((row) => row.id);
73
+ if (staleIds.length) {
74
+ await db.deleteFrom("notification_types").where("tenant_id", "is", null).where("id", "in", staleIds).execute();
75
+ deleted = staleIds.length;
76
+ }
77
+ }
78
+ synced = true;
79
+ return { created, updated, deleted, total: definitions.length };
80
+ }
81
+ export {
82
+ getNotificationType,
83
+ getNotificationTypes,
84
+ registerNotificationTypes,
85
+ syncNotificationTypes
86
+ };
87
+ //# sourceMappingURL=notification-type-registry.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/notifications/lib/notification-type-registry.ts"],
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { type Kysely, sql } from 'kysely'\nimport type { NotificationTypeDefinition } from '@open-mercato/shared/modules/notifications/types'\nimport { deriveCategory } from './derive-category'\n\nconst registry = new Map<string, NotificationTypeDefinition>()\n\nlet synced = false\n\nexport type RegisterNotificationTypesOptions = {\n replace?: boolean\n}\n\n/**\n * In-memory mirror of the code-registered notification type catalogue. Fed at\n * app bootstrap from the generated aggregate (`notificationTypes`) \u2014 mirrors the\n * `messages` registry. This stays the source of truth for code; the\n * `notification_types` table is a read-through mirror for remote clients.\n */\nexport function registerNotificationTypes(\n types: NotificationTypeDefinition[],\n options: RegisterNotificationTypesOptions = {},\n): void {\n if (options.replace) {\n registry.clear()\n synced = false\n }\n for (const type of types) {\n registry.set(type.type, type)\n }\n}\n\nexport function getNotificationType(type: string): NotificationTypeDefinition | undefined {\n return registry.get(type)\n}\n\nexport function getNotificationTypes(): NotificationTypeDefinition[] {\n return Array.from(registry.values())\n}\n\nexport type SyncNotificationTypesResult = {\n created: number\n updated: number\n deleted: number\n total: number\n}\n\n/**\n * Reconcile the in-memory catalogue into the `notification_types` table.\n * Code-registered types are system-wide, so rows are written with\n * `tenant_id IS NULL`. Idempotent: updates the mirrored columns\n * (`label_key`/`description_key`/`category`/`silent`/`non_opt_out`) only on\n * drift, and prunes system-wide rows no longer in the catalogue. Guarded by a\n * once-per-process flag on the lazy path; pass `force` to bypass it (used by the\n * explicit `notifications.type_registry.sync` subscriber).\n *\n * Writes go through kysely with `INSERT ... ON CONFLICT DO NOTHING` so a\n * concurrent first-sync (or the seedDefaults subscriber racing a `GET /types`)\n * can never raise a duplicate-PK 500 \u2014 mirrors `query_index/lib/jobs.ts`.\n */\nexport async function syncNotificationTypes(\n em: EntityManager,\n opts: { force?: boolean } = {},\n): Promise<SyncNotificationTypesResult> {\n const definitions = getNotificationTypes()\n if (synced && !opts.force) {\n return { created: 0, updated: 0, deleted: 0, total: definitions.length }\n }\n\n const db = em.getKysely<any>() as Kysely<any>\n\n // Read existing system-wide rows via kysely (not the ORM) so they stay out of the identity map and\n // the route's subsequent em.find reflects exactly what this function just wrote.\n const existing = (await db\n .selectFrom('notification_types')\n .select(['id', 'label_key', 'description_key', 'category', 'silent', 'non_opt_out'])\n .where('tenant_id', 'is', null)\n .execute()) as Array<{\n id: string\n label_key: string\n description_key: string | null\n category: string | null\n silent: boolean\n non_opt_out: boolean\n }>\n const byId = new Map(existing.map((row) => [row.id, row]))\n\n const labelKeyFor = (def: NotificationTypeDefinition) => def.labelKey ?? def.titleKey\n const descKeyFor = (def: NotificationTypeDefinition) => def.descriptionKey ?? null\n // Resolved once here so every mirrored row carries a grouping key and read paths\n // (GET /api/notifications/types) never need a fallback branch of their own.\n const categoryFor = (def: NotificationTypeDefinition) => def.category ?? deriveCategory(def.type)\n const silentFor = (def: NotificationTypeDefinition) => def.silent === true\n const nonOptOutFor = (def: NotificationTypeDefinition) => def.nonOptOut === true\n\n // Internal/admin-only types (`hiddenFromSettings`) are never exposed to the client catalogue: they\n // are excluded from create/update, and \u2014 because they drop out of `validIds` below \u2014 any stale row\n // for a type that was flipped to hidden gets pruned. They remain in the in-memory registry for\n // delivery logic. GET /api/notifications/types therefore never lists them.\n const visibleDefinitions = definitions.filter((def) => def.hiddenFromSettings !== true)\n\n const toCreate = visibleDefinitions.filter((def) => !byId.has(def.type))\n const toUpdate = visibleDefinitions.filter((def) => {\n const row = byId.get(def.type)\n if (!row) return false\n return (\n row.label_key !== labelKeyFor(def) ||\n (row.description_key ?? null) !== descKeyFor(def) ||\n (row.category ?? null) !== categoryFor(def) ||\n row.silent !== silentFor(def) ||\n row.non_opt_out !== nonOptOutFor(def)\n )\n })\n\n let created = 0\n let updated = 0\n let deleted = 0\n\n if (toCreate.length) {\n await db\n .insertInto('notification_types')\n .values(\n toCreate.map((def) => ({\n id: def.type,\n tenant_id: null,\n label_key: labelKeyFor(def),\n description_key: descKeyFor(def),\n category: categoryFor(def),\n silent: silentFor(def),\n non_opt_out: nonOptOutFor(def),\n created_at: sql`now()`,\n updated_at: sql`now()`,\n })),\n )\n .onConflict((oc: any) => oc.column('id').doNothing())\n .execute()\n created = toCreate.length\n }\n\n // Bump updated_at only on real drift, so repeated syncs don't churn timestamps.\n for (const def of toUpdate) {\n await db\n .updateTable('notification_types')\n .set({\n label_key: labelKeyFor(def),\n description_key: descKeyFor(def),\n category: categoryFor(def),\n silent: silentFor(def),\n non_opt_out: nonOptOutFor(def),\n updated_at: sql`now()`,\n })\n .where('id', '=', def.type)\n .where('tenant_id', 'is', null)\n .execute()\n updated += 1\n }\n\n // Prune system-wide rows no longer in the catalogue (a removed/renamed module's type stops showing\n // in the preferences matrix). Guarded by a non-empty catalogue so a mis-bootstrapped process with an\n // empty registry can never wipe the table. Known limitation, deliberately not addressed here: the\n // guard is a count, not a completeness check, so a process holding a PARTIAL catalogue still prunes\n // the missing modules' rows \u2014 same for multiple apps with different module sets sharing one DB.\n // Both are bounded: `synced` limits it to one sync per process, the rows come back on the next\n // full-catalogue sync, and nothing cascades (overrides and preferences reference the type by plain\n // string id with no FK, so they survive and re-attach). Closing it properly means gating on the\n // enabled-module set that fed the registry, or moving the prune off this lazy read path entirely.\n if (definitions.length > 0) {\n const validIds = new Set(visibleDefinitions.map((def) => def.type))\n const staleIds = existing.filter((row) => !validIds.has(row.id)).map((row) => row.id)\n if (staleIds.length) {\n await db\n .deleteFrom('notification_types')\n .where('tenant_id', 'is', null)\n .where('id', 'in', staleIds)\n .execute()\n deleted = staleIds.length\n }\n }\n\n synced = true\n return { created, updated, deleted, total: definitions.length }\n}\n"],
5
+ "mappings": "AACA,SAAsB,WAAW;AAEjC,SAAS,sBAAsB;AAE/B,MAAM,WAAW,oBAAI,IAAwC;AAE7D,IAAI,SAAS;AAYN,SAAS,0BACd,OACA,UAA4C,CAAC,GACvC;AACN,MAAI,QAAQ,SAAS;AACnB,aAAS,MAAM;AACf,aAAS;AAAA,EACX;AACA,aAAW,QAAQ,OAAO;AACxB,aAAS,IAAI,KAAK,MAAM,IAAI;AAAA,EAC9B;AACF;AAEO,SAAS,oBAAoB,MAAsD;AACxF,SAAO,SAAS,IAAI,IAAI;AAC1B;AAEO,SAAS,uBAAqD;AACnE,SAAO,MAAM,KAAK,SAAS,OAAO,CAAC;AACrC;AAsBA,eAAsB,sBACpB,IACA,OAA4B,CAAC,GACS;AACtC,QAAM,cAAc,qBAAqB;AACzC,MAAI,UAAU,CAAC,KAAK,OAAO;AACzB,WAAO,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,YAAY,OAAO;AAAA,EACzE;AAEA,QAAM,KAAK,GAAG,UAAe;AAI7B,QAAM,WAAY,MAAM,GACrB,WAAW,oBAAoB,EAC/B,OAAO,CAAC,MAAM,aAAa,mBAAmB,YAAY,UAAU,aAAa,CAAC,EAClF,MAAM,aAAa,MAAM,IAAI,EAC7B,QAAQ;AAQX,QAAM,OAAO,IAAI,IAAI,SAAS,IAAI,CAAC,QAAQ,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC;AAEzD,QAAM,cAAc,CAAC,QAAoC,IAAI,YAAY,IAAI;AAC7E,QAAM,aAAa,CAAC,QAAoC,IAAI,kBAAkB;AAG9E,QAAM,cAAc,CAAC,QAAoC,IAAI,YAAY,eAAe,IAAI,IAAI;AAChG,QAAM,YAAY,CAAC,QAAoC,IAAI,WAAW;AACtE,QAAM,eAAe,CAAC,QAAoC,IAAI,cAAc;AAM5E,QAAM,qBAAqB,YAAY,OAAO,CAAC,QAAQ,IAAI,uBAAuB,IAAI;AAEtF,QAAM,WAAW,mBAAmB,OAAO,CAAC,QAAQ,CAAC,KAAK,IAAI,IAAI,IAAI,CAAC;AACvE,QAAM,WAAW,mBAAmB,OAAO,CAAC,QAAQ;AAClD,UAAM,MAAM,KAAK,IAAI,IAAI,IAAI;AAC7B,QAAI,CAAC,IAAK,QAAO;AACjB,WACE,IAAI,cAAc,YAAY,GAAG,MAChC,IAAI,mBAAmB,UAAU,WAAW,GAAG,MAC/C,IAAI,YAAY,UAAU,YAAY,GAAG,KAC1C,IAAI,WAAW,UAAU,GAAG,KAC5B,IAAI,gBAAgB,aAAa,GAAG;AAAA,EAExC,CAAC;AAED,MAAI,UAAU;AACd,MAAI,UAAU;AACd,MAAI,UAAU;AAEd,MAAI,SAAS,QAAQ;AACnB,UAAM,GACH,WAAW,oBAAoB,EAC/B;AAAA,MACC,SAAS,IAAI,CAAC,SAAS;AAAA,QACrB,IAAI,IAAI;AAAA,QACR,WAAW;AAAA,QACX,WAAW,YAAY,GAAG;AAAA,QAC1B,iBAAiB,WAAW,GAAG;AAAA,QAC/B,UAAU,YAAY,GAAG;AAAA,QACzB,QAAQ,UAAU,GAAG;AAAA,QACrB,aAAa,aAAa,GAAG;AAAA,QAC7B,YAAY;AAAA,QACZ,YAAY;AAAA,MACd,EAAE;AAAA,IACJ,EACC,WAAW,CAAC,OAAY,GAAG,OAAO,IAAI,EAAE,UAAU,CAAC,EACnD,QAAQ;AACX,cAAU,SAAS;AAAA,EACrB;AAGA,aAAW,OAAO,UAAU;AAC1B,UAAM,GACH,YAAY,oBAAoB,EAChC,IAAI;AAAA,MACH,WAAW,YAAY,GAAG;AAAA,MAC1B,iBAAiB,WAAW,GAAG;AAAA,MAC/B,UAAU,YAAY,GAAG;AAAA,MACzB,QAAQ,UAAU,GAAG;AAAA,MACrB,aAAa,aAAa,GAAG;AAAA,MAC7B,YAAY;AAAA,IACd,CAAC,EACA,MAAM,MAAM,KAAK,IAAI,IAAI,EACzB,MAAM,aAAa,MAAM,IAAI,EAC7B,QAAQ;AACX,eAAW;AAAA,EACb;AAWA,MAAI,YAAY,SAAS,GAAG;AAC1B,UAAM,WAAW,IAAI,IAAI,mBAAmB,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC;AAClE,UAAM,WAAW,SAAS,OAAO,CAAC,QAAQ,CAAC,SAAS,IAAI,IAAI,EAAE,CAAC,EAAE,IAAI,CAAC,QAAQ,IAAI,EAAE;AACpF,QAAI,SAAS,QAAQ;AACnB,YAAM,GACH,WAAW,oBAAoB,EAC/B,MAAM,aAAa,MAAM,IAAI,EAC7B,MAAM,MAAM,MAAM,QAAQ,EAC1B,QAAQ;AACX,gBAAU,SAAS;AAAA,IACrB;AAAA,EACF;AAEA,WAAS;AACT,SAAO,EAAE,SAAS,SAAS,SAAS,OAAO,YAAY,OAAO;AAChE;",
6
+ "names": []
7
+ }
@@ -0,0 +1,14 @@
1
+ import { loadDictionary } from "@open-mercato/shared/lib/i18n/server";
2
+ import { createFallbackTranslator } from "@open-mercato/shared/lib/i18n/translate";
3
+ import { defaultLocale } from "@open-mercato/shared/lib/i18n/config";
4
+ async function resolveNotificationCopy(source, locale = defaultLocale) {
5
+ const dictionary = await loadDictionary(locale);
6
+ const t = createFallbackTranslator(dictionary);
7
+ const title = source.titleKey ? t(source.titleKey, source.title ?? source.titleKey, source.titleVariables ?? void 0) : source.title;
8
+ const body = source.bodyKey ? t(source.bodyKey, source.body ?? source.bodyKey ?? "", source.bodyVariables ?? void 0) : source.body ?? null;
9
+ return { title, body, t };
10
+ }
11
+ export {
12
+ resolveNotificationCopy
13
+ };
14
+ //# sourceMappingURL=notificationCopy.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/notifications/lib/notificationCopy.ts"],
4
+ "sourcesContent": ["import { loadDictionary } from '@open-mercato/shared/lib/i18n/server'\nimport { createFallbackTranslator } from '@open-mercato/shared/lib/i18n/translate'\nimport { defaultLocale, type Locale } from '@open-mercato/shared/lib/i18n/config'\n\n/**\n * Localizable notification copy: i18n keys + variables, with already-resolved (or raw) strings as\n * the fallback when a key is absent or has no translation. Structurally satisfied by a `Notification`\n * row (in-app/email delivery) and built ad hoc by the push fan-out (per-device delivery).\n */\nexport type NotificationCopySource = {\n titleKey?: string | null\n bodyKey?: string | null\n titleVariables?: Record<string, string> | null\n bodyVariables?: Record<string, string> | null\n title: string\n body?: string | null\n}\n\n/**\n * Resolve a notification's title/body in a given locale. Dictionaries are memoized by\n * {@link loadDictionary}, so repeated calls per locale are cheap. Also returns the translator so\n * callers can localize sibling strings (email chrome, action labels) without reloading the dictionary.\n */\nexport async function resolveNotificationCopy(source: NotificationCopySource, locale: Locale = defaultLocale) {\n const dictionary = await loadDictionary(locale)\n const t = createFallbackTranslator(dictionary)\n const title = source.titleKey\n ? t(source.titleKey, source.title ?? source.titleKey, source.titleVariables ?? undefined)\n : source.title\n const body = source.bodyKey\n ? t(source.bodyKey, source.body ?? source.bodyKey ?? '', source.bodyVariables ?? undefined)\n : source.body ?? null\n return { title, body, t }\n}\n"],
5
+ "mappings": "AAAA,SAAS,sBAAsB;AAC/B,SAAS,gCAAgC;AACzC,SAAS,qBAAkC;AAqB3C,eAAsB,wBAAwB,QAAgC,SAAiB,eAAe;AAC5G,QAAM,aAAa,MAAM,eAAe,MAAM;AAC9C,QAAM,IAAI,yBAAyB,UAAU;AAC7C,QAAM,QAAQ,OAAO,WACjB,EAAE,OAAO,UAAU,OAAO,SAAS,OAAO,UAAU,OAAO,kBAAkB,MAAS,IACtF,OAAO;AACX,QAAM,OAAO,OAAO,UAChB,EAAE,OAAO,SAAS,OAAO,QAAQ,OAAO,WAAW,IAAI,OAAO,iBAAiB,MAAS,IACxF,OAAO,QAAQ;AACnB,SAAO,EAAE,OAAO,MAAM,EAAE;AAC1B;",
6
+ "names": []
7
+ }
@@ -1,7 +1,7 @@
1
1
  import { Notification } from "../data/entities.js";
2
2
  import { NOTIFICATION_EVENTS } from "./events.js";
3
3
  import { assertSafeNotificationHref, sanitizeNotificationActions } from "./safeHref.js";
4
- function buildNotificationEntity(em, input, recipientUserId, ctx) {
4
+ function buildNotificationEntity(em, input, recipientUserId, ctx, channels = null) {
5
5
  const actions = sanitizeNotificationActions(input.actions);
6
6
  const linkHref = assertSafeNotificationHref(input.linkHref);
7
7
  return em.create(Notification, {
@@ -26,6 +26,9 @@ function buildNotificationEntity(em, input, recipientUserId, ctx) {
26
26
  sourceEntityId: input.sourceEntityId,
27
27
  linkHref,
28
28
  groupKey: input.groupKey,
29
+ data: input.data ?? null,
30
+ pushOptions: input.pushOptions ?? null,
31
+ channels,
29
32
  expiresAt: input.expiresAt ? new Date(input.expiresAt) : null,
30
33
  tenantId: ctx.tenantId,
31
34
  organizationId: ctx.organizationId
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/notifications/lib/notificationFactory.ts"],
4
- "sourcesContent": ["import type { EntityManager } from '@mikro-orm/core'\nimport { Notification } from '../data/entities'\nimport type { CreateNotificationInput } from '../data/validators'\nimport { NOTIFICATION_EVENTS } from './events'\nimport { assertSafeNotificationHref, sanitizeNotificationActions } from './safeHref'\n\nexport type NotificationContentInput = Omit<CreateNotificationInput, 'recipientUserId'>\n\nexport type NotificationTenantContext = {\n tenantId: string\n organizationId?: string | null\n}\n\nexport function buildNotificationEntity(\n em: EntityManager,\n input: NotificationContentInput,\n recipientUserId: string,\n ctx: NotificationTenantContext\n): Notification {\n const actions = sanitizeNotificationActions(input.actions)\n const linkHref = assertSafeNotificationHref(input.linkHref)\n\n return em.create(Notification, {\n recipientUserId,\n type: input.type,\n // i18n-first: store keys and variables for translation at display time\n titleKey: input.titleKey,\n bodyKey: input.bodyKey,\n titleVariables: input.titleVariables,\n bodyVariables: input.bodyVariables,\n // Fallback text (required for backward compatibility)\n title: input.title || input.titleKey || '',\n body: input.body,\n icon: input.icon,\n severity: input.severity ?? 'info',\n actionData: actions\n ? {\n actions,\n primaryActionId: input.primaryActionId,\n }\n : null,\n sourceModule: input.sourceModule,\n sourceEntityType: input.sourceEntityType,\n sourceEntityId: input.sourceEntityId,\n linkHref,\n groupKey: input.groupKey,\n expiresAt: input.expiresAt ? new Date(input.expiresAt) : null,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n })\n}\n\nexport async function emitNotificationCreated(\n eventBus: { emit: (event: string, payload: unknown) => Promise<void> },\n notification: Notification,\n ctx: NotificationTenantContext\n): Promise<void> {\n await eventBus.emit(NOTIFICATION_EVENTS.CREATED, {\n notificationId: notification.id,\n recipientUserId: notification.recipientUserId,\n type: notification.type,\n title: notification.title,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n })\n}\n\nexport async function emitNotificationCreatedBatch(\n eventBus: { emit: (event: string, payload: unknown) => Promise<void> },\n notifications: Notification[],\n ctx: NotificationTenantContext\n): Promise<void> {\n for (const notification of notifications) {\n await emitNotificationCreated(eventBus, notification, ctx)\n }\n}\n"],
5
- "mappings": "AACA,SAAS,oBAAoB;AAE7B,SAAS,2BAA2B;AACpC,SAAS,4BAA4B,mCAAmC;AASjE,SAAS,wBACd,IACA,OACA,iBACA,KACc;AACd,QAAM,UAAU,4BAA4B,MAAM,OAAO;AACzD,QAAM,WAAW,2BAA2B,MAAM,QAAQ;AAE1D,SAAO,GAAG,OAAO,cAAc;AAAA,IAC7B;AAAA,IACA,MAAM,MAAM;AAAA;AAAA,IAEZ,UAAU,MAAM;AAAA,IAChB,SAAS,MAAM;AAAA,IACf,gBAAgB,MAAM;AAAA,IACtB,eAAe,MAAM;AAAA;AAAA,IAErB,OAAO,MAAM,SAAS,MAAM,YAAY;AAAA,IACxC,MAAM,MAAM;AAAA,IACZ,MAAM,MAAM;AAAA,IACZ,UAAU,MAAM,YAAY;AAAA,IAC5B,YAAY,UACR;AAAA,MACE;AAAA,MACA,iBAAiB,MAAM;AAAA,IACzB,IACA;AAAA,IACJ,cAAc,MAAM;AAAA,IACpB,kBAAkB,MAAM;AAAA,IACxB,gBAAgB,MAAM;AAAA,IACtB;AAAA,IACA,UAAU,MAAM;AAAA,IAChB,WAAW,MAAM,YAAY,IAAI,KAAK,MAAM,SAAS,IAAI;AAAA,IACzD,UAAU,IAAI;AAAA,IACd,gBAAgB,IAAI;AAAA,EACtB,CAAC;AACH;AAEA,eAAsB,wBACpB,UACA,cACA,KACe;AACf,QAAM,SAAS,KAAK,oBAAoB,SAAS;AAAA,IAC/C,gBAAgB,aAAa;AAAA,IAC7B,iBAAiB,aAAa;AAAA,IAC9B,MAAM,aAAa;AAAA,IACnB,OAAO,aAAa;AAAA,IACpB,UAAU,IAAI;AAAA,IACd,gBAAgB,IAAI;AAAA,EACtB,CAAC;AACH;AAEA,eAAsB,6BACpB,UACA,eACA,KACe;AACf,aAAW,gBAAgB,eAAe;AACxC,UAAM,wBAAwB,UAAU,cAAc,GAAG;AAAA,EAC3D;AACF;",
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/core'\nimport { Notification } from '../data/entities'\nimport type { CreateNotificationInput } from '../data/validators'\nimport { NOTIFICATION_EVENTS } from './events'\nimport { assertSafeNotificationHref, sanitizeNotificationActions } from './safeHref'\n\nexport type NotificationContentInput = Omit<CreateNotificationInput, 'recipientUserId'>\n\nexport type NotificationTenantContext = {\n tenantId: string\n organizationId?: string | null\n}\n\nexport function buildNotificationEntity(\n em: EntityManager,\n input: NotificationContentInput,\n recipientUserId: string,\n ctx: NotificationTenantContext,\n channels: string[] | null = null\n): Notification {\n const actions = sanitizeNotificationActions(input.actions)\n const linkHref = assertSafeNotificationHref(input.linkHref)\n\n return em.create(Notification, {\n recipientUserId,\n type: input.type,\n // i18n-first: store keys and variables for translation at display time\n titleKey: input.titleKey,\n bodyKey: input.bodyKey,\n titleVariables: input.titleVariables,\n bodyVariables: input.bodyVariables,\n // Fallback text (required for backward compatibility)\n title: input.title || input.titleKey || '',\n body: input.body,\n icon: input.icon,\n severity: input.severity ?? 'info',\n actionData: actions\n ? {\n actions,\n primaryActionId: input.primaryActionId,\n }\n : null,\n sourceModule: input.sourceModule,\n sourceEntityType: input.sourceEntityType,\n sourceEntityId: input.sourceEntityId,\n linkHref,\n groupKey: input.groupKey,\n data: input.data ?? null,\n pushOptions: input.pushOptions ?? null,\n channels,\n expiresAt: input.expiresAt ? new Date(input.expiresAt) : null,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n })\n}\n\nexport async function emitNotificationCreated(\n eventBus: { emit: (event: string, payload: unknown) => Promise<void> },\n notification: Notification,\n ctx: NotificationTenantContext\n): Promise<void> {\n await eventBus.emit(NOTIFICATION_EVENTS.CREATED, {\n notificationId: notification.id,\n recipientUserId: notification.recipientUserId,\n type: notification.type,\n title: notification.title,\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n })\n}\n\nexport async function emitNotificationCreatedBatch(\n eventBus: { emit: (event: string, payload: unknown) => Promise<void> },\n notifications: Notification[],\n ctx: NotificationTenantContext\n): Promise<void> {\n for (const notification of notifications) {\n await emitNotificationCreated(eventBus, notification, ctx)\n }\n}\n"],
5
+ "mappings": "AACA,SAAS,oBAAoB;AAE7B,SAAS,2BAA2B;AACpC,SAAS,4BAA4B,mCAAmC;AASjE,SAAS,wBACd,IACA,OACA,iBACA,KACA,WAA4B,MACd;AACd,QAAM,UAAU,4BAA4B,MAAM,OAAO;AACzD,QAAM,WAAW,2BAA2B,MAAM,QAAQ;AAE1D,SAAO,GAAG,OAAO,cAAc;AAAA,IAC7B;AAAA,IACA,MAAM,MAAM;AAAA;AAAA,IAEZ,UAAU,MAAM;AAAA,IAChB,SAAS,MAAM;AAAA,IACf,gBAAgB,MAAM;AAAA,IACtB,eAAe,MAAM;AAAA;AAAA,IAErB,OAAO,MAAM,SAAS,MAAM,YAAY;AAAA,IACxC,MAAM,MAAM;AAAA,IACZ,MAAM,MAAM;AAAA,IACZ,UAAU,MAAM,YAAY;AAAA,IAC5B,YAAY,UACR;AAAA,MACE;AAAA,MACA,iBAAiB,MAAM;AAAA,IACzB,IACA;AAAA,IACJ,cAAc,MAAM;AAAA,IACpB,kBAAkB,MAAM;AAAA,IACxB,gBAAgB,MAAM;AAAA,IACtB;AAAA,IACA,UAAU,MAAM;AAAA,IAChB,MAAM,MAAM,QAAQ;AAAA,IACpB,aAAa,MAAM,eAAe;AAAA,IAClC;AAAA,IACA,WAAW,MAAM,YAAY,IAAI,KAAK,MAAM,SAAS,IAAI;AAAA,IACzD,UAAU,IAAI;AAAA,IACd,gBAAgB,IAAI;AAAA,EACtB,CAAC;AACH;AAEA,eAAsB,wBACpB,UACA,cACA,KACe;AACf,QAAM,SAAS,KAAK,oBAAoB,SAAS;AAAA,IAC/C,gBAAgB,aAAa;AAAA,IAC7B,iBAAiB,aAAa;AAAA,IAC9B,MAAM,aAAa;AAAA,IACnB,OAAO,aAAa;AAAA,IACpB,UAAU,IAAI;AAAA,IACd,gBAAgB,IAAI;AAAA,EACtB,CAAC;AACH;AAEA,eAAsB,6BACpB,UACA,eACA,KACe;AACf,aAAW,gBAAgB,eAAe;AACxC,UAAM,wBAAwB,UAAU,cAAc,GAAG;AAAA,EAC3D;AACF;",
6
6
  "names": []
7
7
  }
@@ -31,6 +31,8 @@ function toNotificationDto(notification) {
31
31
  sourceEntityType: notification.sourceEntityType,
32
32
  sourceEntityId: notification.sourceEntityId,
33
33
  linkHref: notification.linkHref,
34
+ data: notification.data ?? null,
35
+ channels: notification.channels ?? null,
34
36
  createdAt: createdAt.toISOString(),
35
37
  readAt: notification.readAt?.toISOString() ?? null,
36
38
  actionTaken: notification.actionTaken
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/notifications/lib/notificationMapper.ts"],
4
- "sourcesContent": ["import type { NotificationDto } from '@open-mercato/shared/modules/notifications/types'\nimport { Notification } from '../data/entities'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('notifications').child({ component: 'mapper' })\n\nexport function toNotificationDto(notification: Notification): NotificationDto {\n const createdAt = notification.createdAt instanceof Date\n ? notification.createdAt\n : (() => {\n if (process.env.NODE_ENV !== 'test') {\n logger.warn('Invalid createdAt on notification entity, falling back to current time', { id: notification.id, createdAt: notification.createdAt })\n }\n return new Date()\n })()\n return {\n id: notification.id,\n type: notification.type,\n title: notification.title,\n body: notification.body,\n titleKey: notification.titleKey,\n bodyKey: notification.bodyKey,\n titleVariables: notification.titleVariables,\n bodyVariables: notification.bodyVariables,\n icon: notification.icon,\n severity: notification.severity,\n status: notification.status,\n actions: notification.actionData?.actions?.map((action) => ({\n id: action.id,\n label: action.label,\n labelKey: action.labelKey,\n variant: action.variant,\n icon: action.icon,\n })) ?? [],\n primaryActionId: notification.actionData?.primaryActionId,\n sourceModule: notification.sourceModule,\n sourceEntityType: notification.sourceEntityType,\n sourceEntityId: notification.sourceEntityId,\n linkHref: notification.linkHref,\n createdAt: createdAt.toISOString(),\n readAt: notification.readAt?.toISOString() ?? null,\n actionTaken: notification.actionTaken,\n }\n}\n"],
5
- "mappings": "AAEA,SAAS,oBAAoB;AAE7B,MAAM,SAAS,aAAa,eAAe,EAAE,MAAM,EAAE,WAAW,SAAS,CAAC;AAEnE,SAAS,kBAAkB,cAA6C;AAC7E,QAAM,YAAY,aAAa,qBAAqB,OAChD,aAAa,aACZ,MAAM;AACP,QAAI,QAAQ,IAAI,aAAa,QAAQ;AACnC,aAAO,KAAK,0EAA0E,EAAE,IAAI,aAAa,IAAI,WAAW,aAAa,UAAU,CAAC;AAAA,IAClJ;AACA,WAAO,oBAAI,KAAK;AAAA,EAClB,GAAG;AACL,SAAO;AAAA,IACL,IAAI,aAAa;AAAA,IACjB,MAAM,aAAa;AAAA,IACnB,OAAO,aAAa;AAAA,IACpB,MAAM,aAAa;AAAA,IACnB,UAAU,aAAa;AAAA,IACvB,SAAS,aAAa;AAAA,IACtB,gBAAgB,aAAa;AAAA,IAC7B,eAAe,aAAa;AAAA,IAC5B,MAAM,aAAa;AAAA,IACnB,UAAU,aAAa;AAAA,IACvB,QAAQ,aAAa;AAAA,IACrB,SAAS,aAAa,YAAY,SAAS,IAAI,CAAC,YAAY;AAAA,MAC1D,IAAI,OAAO;AAAA,MACX,OAAO,OAAO;AAAA,MACd,UAAU,OAAO;AAAA,MACjB,SAAS,OAAO;AAAA,MAChB,MAAM,OAAO;AAAA,IACf,EAAE,KAAK,CAAC;AAAA,IACR,iBAAiB,aAAa,YAAY;AAAA,IAC1C,cAAc,aAAa;AAAA,IAC3B,kBAAkB,aAAa;AAAA,IAC/B,gBAAgB,aAAa;AAAA,IAC7B,UAAU,aAAa;AAAA,IACvB,WAAW,UAAU,YAAY;AAAA,IACjC,QAAQ,aAAa,QAAQ,YAAY,KAAK;AAAA,IAC9C,aAAa,aAAa;AAAA,EAC5B;AACF;",
4
+ "sourcesContent": ["import type { NotificationDto } from '@open-mercato/shared/modules/notifications/types'\nimport { Notification } from '../data/entities'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('notifications').child({ component: 'mapper' })\n\nexport function toNotificationDto(notification: Notification): NotificationDto {\n const createdAt = notification.createdAt instanceof Date\n ? notification.createdAt\n : (() => {\n if (process.env.NODE_ENV !== 'test') {\n logger.warn('Invalid createdAt on notification entity, falling back to current time', { id: notification.id, createdAt: notification.createdAt })\n }\n return new Date()\n })()\n return {\n id: notification.id,\n type: notification.type,\n title: notification.title,\n body: notification.body,\n titleKey: notification.titleKey,\n bodyKey: notification.bodyKey,\n titleVariables: notification.titleVariables,\n bodyVariables: notification.bodyVariables,\n icon: notification.icon,\n severity: notification.severity,\n status: notification.status,\n actions: notification.actionData?.actions?.map((action) => ({\n id: action.id,\n label: action.label,\n labelKey: action.labelKey,\n variant: action.variant,\n icon: action.icon,\n })) ?? [],\n primaryActionId: notification.actionData?.primaryActionId,\n sourceModule: notification.sourceModule,\n sourceEntityType: notification.sourceEntityType,\n sourceEntityId: notification.sourceEntityId,\n linkHref: notification.linkHref,\n data: notification.data ?? null,\n channels: notification.channels ?? null,\n createdAt: createdAt.toISOString(),\n readAt: notification.readAt?.toISOString() ?? null,\n actionTaken: notification.actionTaken,\n }\n}\n"],
5
+ "mappings": "AAEA,SAAS,oBAAoB;AAE7B,MAAM,SAAS,aAAa,eAAe,EAAE,MAAM,EAAE,WAAW,SAAS,CAAC;AAEnE,SAAS,kBAAkB,cAA6C;AAC7E,QAAM,YAAY,aAAa,qBAAqB,OAChD,aAAa,aACZ,MAAM;AACP,QAAI,QAAQ,IAAI,aAAa,QAAQ;AACnC,aAAO,KAAK,0EAA0E,EAAE,IAAI,aAAa,IAAI,WAAW,aAAa,UAAU,CAAC;AAAA,IAClJ;AACA,WAAO,oBAAI,KAAK;AAAA,EAClB,GAAG;AACL,SAAO;AAAA,IACL,IAAI,aAAa;AAAA,IACjB,MAAM,aAAa;AAAA,IACnB,OAAO,aAAa;AAAA,IACpB,MAAM,aAAa;AAAA,IACnB,UAAU,aAAa;AAAA,IACvB,SAAS,aAAa;AAAA,IACtB,gBAAgB,aAAa;AAAA,IAC7B,eAAe,aAAa;AAAA,IAC5B,MAAM,aAAa;AAAA,IACnB,UAAU,aAAa;AAAA,IACvB,QAAQ,aAAa;AAAA,IACrB,SAAS,aAAa,YAAY,SAAS,IAAI,CAAC,YAAY;AAAA,MAC1D,IAAI,OAAO;AAAA,MACX,OAAO,OAAO;AAAA,MACd,UAAU,OAAO;AAAA,MACjB,SAAS,OAAO;AAAA,MAChB,MAAM,OAAO;AAAA,IACf,EAAE,KAAK,CAAC;AAAA,IACR,iBAAiB,aAAa,YAAY;AAAA,IAC1C,cAAc,aAAa;AAAA,IAC3B,kBAAkB,aAAa;AAAA,IAC/B,gBAAgB,aAAa;AAAA,IAC7B,UAAU,aAAa;AAAA,IACvB,MAAM,aAAa,QAAQ;AAAA,IAC3B,UAAU,aAAa,YAAY;AAAA,IACnC,WAAW,UAAU,YAAY;AAAA,IACjC,QAAQ,aAAa,QAAQ,YAAY,KAAK;AAAA,IAC9C,aAAa,aAAa;AAAA,EAC5B;AACF;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,83 @@
1
+ import { withAtomicFlush } from "@open-mercato/shared/lib/commands/flush";
2
+ import { NotificationPreference } from "../data/entities.js";
3
+ import { getNotificationType } from "./notification-type-registry.js";
4
+ import { getNotificationTypeOverrides } from "./typeOverrides.js";
5
+ function createNotificationPreferenceService(deps) {
6
+ const { em: rootEm } = deps;
7
+ return {
8
+ async isChannelEnabled(scope, typeId, channel) {
9
+ const row = await rootEm.findOne(NotificationPreference, {
10
+ tenantId: scope.tenantId,
11
+ userId: scope.userId,
12
+ notificationTypeId: typeId,
13
+ channel
14
+ });
15
+ return row ? row.enabled : true;
16
+ },
17
+ async listForUser(scope) {
18
+ return rootEm.find(
19
+ NotificationPreference,
20
+ { tenantId: scope.tenantId, userId: scope.userId },
21
+ { orderBy: { notificationTypeId: "asc", channel: "asc" } }
22
+ );
23
+ },
24
+ async setPreferences(scope, items) {
25
+ const storedOverrides = items.length ? await getNotificationTypeOverrides(rootEm.fork(), scope.tenantId, items.map((item) => item.typeId)) : /* @__PURE__ */ new Map();
26
+ const optOutFiltered = items.filter((item) => {
27
+ if (item.enabled === true) return true;
28
+ const nonOptOut = storedOverrides.get(item.typeId)?.nonOptOut ?? getNotificationType(item.typeId)?.nonOptOut;
29
+ return nonOptOut !== true;
30
+ });
31
+ const writable = optOutFiltered.filter((item) => {
32
+ const eligible = storedOverrides.get(item.typeId)?.channels ?? getNotificationType(item.typeId)?.channels;
33
+ return !eligible || eligible.includes(item.channel);
34
+ });
35
+ if (writable.length === 0) return 0;
36
+ const em = rootEm.fork();
37
+ const existing = await em.find(NotificationPreference, {
38
+ tenantId: scope.tenantId,
39
+ userId: scope.userId
40
+ });
41
+ const byKey = new Map(
42
+ existing.map((row) => [`${row.notificationTypeId}::${row.channel}`, row])
43
+ );
44
+ let changed = 0;
45
+ await withAtomicFlush(
46
+ em,
47
+ [
48
+ () => {
49
+ for (const item of writable) {
50
+ const row = byKey.get(`${item.typeId}::${item.channel}`);
51
+ if (row) {
52
+ if (row.enabled === item.enabled) continue;
53
+ row.enabled = item.enabled;
54
+ changed += 1;
55
+ continue;
56
+ }
57
+ const next = em.create(NotificationPreference, {
58
+ tenantId: scope.tenantId,
59
+ userId: scope.userId,
60
+ notificationTypeId: item.typeId,
61
+ channel: item.channel,
62
+ enabled: item.enabled
63
+ });
64
+ em.persist(next);
65
+ changed += 1;
66
+ }
67
+ }
68
+ ],
69
+ { transaction: true, label: "notifications.setPreferences" }
70
+ );
71
+ return changed;
72
+ }
73
+ };
74
+ }
75
+ function resolveNotificationPreferenceService(container) {
76
+ const em = container.resolve("em");
77
+ return createNotificationPreferenceService({ em });
78
+ }
79
+ export {
80
+ createNotificationPreferenceService,
81
+ resolveNotificationPreferenceService
82
+ };
83
+ //# sourceMappingURL=notificationPreferenceService.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/notifications/lib/notificationPreferenceService.ts"],
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'\nimport { NotificationPreference } from '../data/entities'\nimport { getNotificationType } from './notification-type-registry'\nimport { getNotificationTypeOverrides } from './typeOverrides'\n\n/**\n * Tenant + user scope for preference operations. Tenant scoping is mandatory,\n * so it is part of every call (the spec's bare `userId` signatures are widened\n * here to stay tenant-safe).\n */\nexport interface NotificationPreferenceScope {\n tenantId: string\n userId: string\n}\n\nexport interface NotificationPreferenceInput {\n typeId: string\n channel: string\n enabled: boolean\n}\n\nexport interface NotificationPreferenceService {\n /**\n * Whether a channel is enabled for a user + type. Defaults to `true` when no\n * row exists (lazy-seed, default-on) \u2014 does not write.\n */\n isChannelEnabled(scope: NotificationPreferenceScope, typeId: string, channel: string): Promise<boolean>\n /**\n * Bulk find-or-upsert of preference rows for the scoped user. Returns the number of rows actually\n * changed (created, or whose `enabled` flipped) so callers can skip emitting events on no-op writes.\n */\n setPreferences(scope: NotificationPreferenceScope, items: NotificationPreferenceInput[]): Promise<number>\n /** All stored preference rows for the scoped user (absence \u21D2 enabled). */\n listForUser(scope: NotificationPreferenceScope): Promise<NotificationPreference[]>\n}\n\nexport interface NotificationPreferenceServiceDeps {\n em: EntityManager\n}\n\nexport function createNotificationPreferenceService(\n deps: NotificationPreferenceServiceDeps,\n): NotificationPreferenceService {\n const { em: rootEm } = deps\n\n return {\n async isChannelEnabled(scope, typeId, channel) {\n const row = await rootEm.findOne(NotificationPreference, {\n tenantId: scope.tenantId,\n userId: scope.userId,\n notificationTypeId: typeId,\n channel,\n })\n return row ? row.enabled : true\n },\n\n async listForUser(scope) {\n return rootEm.find(\n NotificationPreference,\n { tenantId: scope.tenantId, userId: scope.userId },\n { orderBy: { notificationTypeId: 'asc', channel: 'asc' } },\n )\n },\n\n async setPreferences(scope, items) {\n const storedOverrides = items.length\n ? await getNotificationTypeOverrides(rootEm.fork(), scope.tenantId, items.map((item) => item.typeId))\n : new Map()\n // Effectively-nonOptOut types (operator override ?? code flag) ignore stored preferences at\n // delivery time; refuse to persist an opt-out row for them so the stored state can never\n // contradict enforcement. An `enabled: true` write matches the forced-on state and is\n // allowed through (a preferences UI can still confirm it).\n const optOutFiltered = items.filter((item) => {\n if (item.enabled === true) return true\n const nonOptOut =\n storedOverrides.get(item.typeId)?.nonOptOut ?? getNotificationType(item.typeId)?.nonOptOut\n return nonOptOut !== true\n })\n // Channels outside the type's effective eligibility (operator override on\n // `notification_types.channels`, else the code-declared `type.channels`) are locked:\n // delivery rejects them before preferences and the UI renders the cell off, so a stored\n // row would only lie. Drop those writes server-side (the UI lock is not a guarantee).\n const writable = optOutFiltered.filter((item) => {\n const eligible = storedOverrides.get(item.typeId)?.channels ?? getNotificationType(item.typeId)?.channels\n return !eligible || eligible.includes(item.channel)\n })\n if (writable.length === 0) return 0\n const em = rootEm.fork()\n const existing = await em.find(NotificationPreference, {\n tenantId: scope.tenantId,\n userId: scope.userId,\n })\n const byKey = new Map(\n existing.map((row) => [`${row.notificationTypeId}::${row.channel}`, row]),\n )\n\n let changed = 0\n await withAtomicFlush(\n em,\n [\n () => {\n for (const item of writable) {\n const row = byKey.get(`${item.typeId}::${item.channel}`)\n if (row) {\n if (row.enabled === item.enabled) continue\n row.enabled = item.enabled\n changed += 1\n continue\n }\n const next = em.create(NotificationPreference, {\n tenantId: scope.tenantId,\n userId: scope.userId,\n notificationTypeId: item.typeId,\n channel: item.channel,\n enabled: item.enabled,\n })\n em.persist(next)\n changed += 1\n }\n },\n ],\n { transaction: true, label: 'notifications.setPreferences' },\n )\n return changed\n },\n }\n}\n\nexport function resolveNotificationPreferenceService(container: {\n resolve: (name: string) => unknown\n}): NotificationPreferenceService {\n // Construct from the request-scoped `em`, mirroring `resolveNotificationService`. (The request\n // container does not expose the module's scoped service bindings, so resolving `em` directly is the\n // module convention \u2014 see notificationService.ts.)\n const em = container.resolve('em') as EntityManager\n return createNotificationPreferenceService({ em })\n}\n"],
5
+ "mappings": "AACA,SAAS,uBAAuB;AAChC,SAAS,8BAA8B;AACvC,SAAS,2BAA2B;AACpC,SAAS,oCAAoC;AAqCtC,SAAS,oCACd,MAC+B;AAC/B,QAAM,EAAE,IAAI,OAAO,IAAI;AAEvB,SAAO;AAAA,IACL,MAAM,iBAAiB,OAAO,QAAQ,SAAS;AAC7C,YAAM,MAAM,MAAM,OAAO,QAAQ,wBAAwB;AAAA,QACvD,UAAU,MAAM;AAAA,QAChB,QAAQ,MAAM;AAAA,QACd,oBAAoB;AAAA,QACpB;AAAA,MACF,CAAC;AACD,aAAO,MAAM,IAAI,UAAU;AAAA,IAC7B;AAAA,IAEA,MAAM,YAAY,OAAO;AACvB,aAAO,OAAO;AAAA,QACZ;AAAA,QACA,EAAE,UAAU,MAAM,UAAU,QAAQ,MAAM,OAAO;AAAA,QACjD,EAAE,SAAS,EAAE,oBAAoB,OAAO,SAAS,MAAM,EAAE;AAAA,MAC3D;AAAA,IACF;AAAA,IAEA,MAAM,eAAe,OAAO,OAAO;AACjC,YAAM,kBAAkB,MAAM,SAC1B,MAAM,6BAA6B,OAAO,KAAK,GAAG,MAAM,UAAU,MAAM,IAAI,CAAC,SAAS,KAAK,MAAM,CAAC,IAClG,oBAAI,IAAI;AAKZ,YAAM,iBAAiB,MAAM,OAAO,CAAC,SAAS;AAC5C,YAAI,KAAK,YAAY,KAAM,QAAO;AAClC,cAAM,YACJ,gBAAgB,IAAI,KAAK,MAAM,GAAG,aAAa,oBAAoB,KAAK,MAAM,GAAG;AACnF,eAAO,cAAc;AAAA,MACvB,CAAC;AAKD,YAAM,WAAW,eAAe,OAAO,CAAC,SAAS;AAC/C,cAAM,WAAW,gBAAgB,IAAI,KAAK,MAAM,GAAG,YAAY,oBAAoB,KAAK,MAAM,GAAG;AACjG,eAAO,CAAC,YAAY,SAAS,SAAS,KAAK,OAAO;AAAA,MACpD,CAAC;AACD,UAAI,SAAS,WAAW,EAAG,QAAO;AAClC,YAAM,KAAK,OAAO,KAAK;AACvB,YAAM,WAAW,MAAM,GAAG,KAAK,wBAAwB;AAAA,QACrD,UAAU,MAAM;AAAA,QAChB,QAAQ,MAAM;AAAA,MAChB,CAAC;AACD,YAAM,QAAQ,IAAI;AAAA,QAChB,SAAS,IAAI,CAAC,QAAQ,CAAC,GAAG,IAAI,kBAAkB,KAAK,IAAI,OAAO,IAAI,GAAG,CAAC;AAAA,MAC1E;AAEA,UAAI,UAAU;AACd,YAAM;AAAA,QACJ;AAAA,QACA;AAAA,UACE,MAAM;AACJ,uBAAW,QAAQ,UAAU;AAC3B,oBAAM,MAAM,MAAM,IAAI,GAAG,KAAK,MAAM,KAAK,KAAK,OAAO,EAAE;AACvD,kBAAI,KAAK;AACP,oBAAI,IAAI,YAAY,KAAK,QAAS;AAClC,oBAAI,UAAU,KAAK;AACnB,2BAAW;AACX;AAAA,cACF;AACA,oBAAM,OAAO,GAAG,OAAO,wBAAwB;AAAA,gBAC7C,UAAU,MAAM;AAAA,gBAChB,QAAQ,MAAM;AAAA,gBACd,oBAAoB,KAAK;AAAA,gBACzB,SAAS,KAAK;AAAA,gBACd,SAAS,KAAK;AAAA,cAChB,CAAC;AACD,iBAAG,QAAQ,IAAI;AACf,yBAAW;AAAA,YACb;AAAA,UACF;AAAA,QACF;AAAA,QACA,EAAE,aAAa,MAAM,OAAO,+BAA+B;AAAA,MAC7D;AACA,aAAO;AAAA,IACT;AAAA,EACF;AACF;AAEO,SAAS,qCAAqC,WAEnB;AAIhC,QAAM,KAAK,UAAU,QAAQ,IAAI;AACjC,SAAO,oCAAoC,EAAE,GAAG,CAAC;AACnD;",
6
+ "names": []
7
+ }
@@ -18,6 +18,14 @@ import {
18
18
  } from "./notificationRecipients.js";
19
19
  import { assertSafeNotificationHref, sanitizeNotificationActions } from "./safeHref.js";
20
20
  import { createLogger } from "@open-mercato/shared/lib/logger";
21
+ import { getNotificationType } from "./notification-type-registry.js";
22
+ import { getNotificationTypeOverrides } from "./typeOverrides.js";
23
+ import { getNotificationDeliveryStrategies } from "./deliveryStrategies.js";
24
+ import { resolveEffectiveChannels } from "./shouldDeliver.js";
25
+ import {
26
+ createNotificationPreferenceService
27
+ } from "./notificationPreferenceService.js";
28
+ import { inAppVisibleFilter, inAppVisibleSql, isInAppVisible } from "./notificationVisibility.js";
21
29
  const logger = createLogger("notifications").child({ component: "service" });
22
30
  function debug(message, ...details) {
23
31
  logger.debug(message, details.length ? { details } : void 0);
@@ -55,7 +63,7 @@ async function assertNotificationRecipientsInScope(em, recipientUserIds, ctx) {
55
63
  throw new CrudHttpError(404, { error: "Notification recipient not found" });
56
64
  }
57
65
  }
58
- function applyNotificationContent(notification, input, recipientUserId, ctx) {
66
+ function applyNotificationContent(notification, input, recipientUserId, ctx, channels) {
59
67
  const actions = sanitizeNotificationActions(input.actions);
60
68
  const linkHref = assertSafeNotificationHref(input.linkHref);
61
69
  notification.recipientUserId = recipientUserId;
@@ -77,6 +85,7 @@ function applyNotificationContent(notification, input, recipientUserId, ctx) {
77
85
  notification.sourceEntityId = input.sourceEntityId;
78
86
  notification.linkHref = linkHref;
79
87
  notification.groupKey = input.groupKey;
88
+ notification.channels = channels;
80
89
  notification.expiresAt = input.expiresAt ? new Date(input.expiresAt) : null;
81
90
  notification.tenantId = ctx.tenantId;
82
91
  notification.organizationId = normalizeOrgScope(ctx.organizationId);
@@ -108,14 +117,17 @@ async function findScopedNotificationOrThrow(em, notificationId, ctx) {
108
117
  }
109
118
  return notification;
110
119
  }
111
- async function emitNotificationSseEvents(eventBus, notifications, ctx, recipientUserIds) {
120
+ async function emitNotificationSseEvents(eventBus, notifications, ctx) {
121
+ const visible = notifications.filter((notification) => isInAppVisible(notification.channels));
122
+ if (visible.length === 0) return;
123
+ const visibleRecipientUserIds = Array.from(new Set(visible.map((n) => n.recipientUserId)));
112
124
  await eventBus.emit(NOTIFICATION_SSE_EVENTS.BATCH_CREATED, {
113
125
  tenantId: ctx.tenantId,
114
126
  organizationId: normalizeOrgScope(ctx.organizationId),
115
- recipientUserIds,
116
- count: notifications.length
127
+ recipientUserIds: visibleRecipientUserIds,
128
+ count: visible.length
117
129
  });
118
- for (const notification of notifications) {
130
+ for (const notification of visible) {
119
131
  await eventBus.emit(NOTIFICATION_SSE_EVENTS.CREATED, {
120
132
  tenantId: notification.tenantId,
121
133
  organizationId: notification.organizationId ?? null,
@@ -124,7 +136,7 @@ async function emitNotificationSseEvents(eventBus, notifications, ctx, recipient
124
136
  });
125
137
  }
126
138
  }
127
- async function createOrRefreshNotification(em, input, recipientUserId, ctx) {
139
+ async function createOrRefreshNotification(em, input, recipientUserId, ctx, channels) {
128
140
  if (input.groupKey && input.groupKey.trim().length > 0) {
129
141
  const orgScope = normalizeOrgScope(ctx.organizationId) ?? "global";
130
142
  const lockKey = `notifications:${ctx.tenantId}:${orgScope}:${recipientUserId}:${input.type}:${input.groupKey}`;
@@ -144,50 +156,82 @@ async function createOrRefreshNotification(em, input, recipientUserId, ctx) {
144
156
  orderBy: { createdAt: "desc" }
145
157
  });
146
158
  if (existing) {
147
- applyNotificationContent(existing, input, recipientUserId, ctx);
159
+ applyNotificationContent(existing, input, recipientUserId, ctx, channels);
148
160
  return existing;
149
161
  }
150
162
  }
151
- return buildNotificationEntity(em, input, recipientUserId, ctx);
163
+ return buildNotificationEntity(em, input, recipientUserId, ctx, channels);
152
164
  }
153
165
  function createNotificationService(deps) {
154
166
  const { em: rootEm, eventBus, commandBus, container } = deps;
167
+ const resolveChannelsFor = async (content, recipientUserId, scopeCtx, preferences = createNotificationPreferenceService({ em: rootEm.fork() }), typeOverrides) => {
168
+ const registeredChannels = getNotificationDeliveryStrategies().map((strategy) => strategy.id);
169
+ if (registeredChannels.length === 0) return null;
170
+ const targetChannels = content.channels && content.channels.length > 0 ? content.channels : null;
171
+ const overrides = typeOverrides === void 0 ? (await getNotificationTypeOverrides(rootEm.fork(), scopeCtx.tenantId, [content.type])).get(content.type) ?? null : typeOverrides;
172
+ return resolveEffectiveChannels({
173
+ typeId: content.type,
174
+ type: getNotificationType(content.type),
175
+ scope: { tenantId: scopeCtx.tenantId, userId: recipientUserId },
176
+ targetChannels,
177
+ registeredChannels,
178
+ preferences,
179
+ channelsOverride: overrides?.channels ?? null,
180
+ nonOptOutOverride: overrides?.nonOptOut ?? null
181
+ });
182
+ };
183
+ const resolveChannelsForRecipients = async (content, recipientUserIds, scopeCtx) => {
184
+ const preferences = createNotificationPreferenceService({ em: rootEm.fork() });
185
+ const typeOverrides = (await getNotificationTypeOverrides(rootEm.fork(), scopeCtx.tenantId, [content.type])).get(content.type) ?? null;
186
+ const resolved = [];
187
+ for (const recipientUserId of recipientUserIds) {
188
+ resolved.push({
189
+ recipientUserId,
190
+ channels: await resolveChannelsFor(content, recipientUserId, scopeCtx, preferences, typeOverrides)
191
+ });
192
+ }
193
+ return resolved;
194
+ };
155
195
  return {
156
196
  async create(input, ctx) {
157
197
  const { recipientUserId, ...content } = input;
198
+ const channels = await resolveChannelsFor(content, recipientUserId, ctx);
158
199
  const writeEm = rootEm.fork();
159
200
  const notification = await writeEm.transactional(async (tx) => {
160
201
  await assertNotificationRecipientsInScope(tx, [recipientUserId], ctx);
161
- const entity = await createOrRefreshNotification(tx, content, recipientUserId, ctx);
202
+ const entity = await createOrRefreshNotification(tx, content, recipientUserId, ctx, channels);
162
203
  await tx.flush();
163
204
  return entity;
164
205
  });
165
206
  await invalidateNotificationCache(container, ctx, "created");
166
207
  await emitNotificationCreated(eventBus, notification, ctx);
167
- await eventBus.emit(NOTIFICATION_SSE_EVENTS.CREATED, {
168
- tenantId: notification.tenantId,
169
- organizationId: notification.organizationId ?? null,
170
- recipientUserId: notification.recipientUserId,
171
- notification: toNotificationDto(notification)
172
- });
208
+ if (isInAppVisible(notification.channels)) {
209
+ await eventBus.emit(NOTIFICATION_SSE_EVENTS.CREATED, {
210
+ tenantId: notification.tenantId,
211
+ organizationId: notification.organizationId ?? null,
212
+ recipientUserId: notification.recipientUserId,
213
+ notification: toNotificationDto(notification)
214
+ });
215
+ }
173
216
  return notification;
174
217
  },
175
218
  async createBatch(input, ctx) {
176
219
  const recipientUserIds = Array.from(new Set(input.recipientUserIds));
177
220
  const { recipientUserIds: _recipientUserIds, ...content } = input;
178
221
  const notifications = [];
222
+ const resolved = await resolveChannelsForRecipients(content, recipientUserIds, ctx);
179
223
  const writeEm = rootEm.fork();
180
224
  await writeEm.transactional(async (tx) => {
181
225
  await assertNotificationRecipientsInScope(tx, recipientUserIds, ctx);
182
- for (const recipientUserId of recipientUserIds) {
183
- const notification = await createOrRefreshNotification(tx, content, recipientUserId, ctx);
226
+ for (const { recipientUserId, channels } of resolved) {
227
+ const notification = await createOrRefreshNotification(tx, content, recipientUserId, ctx, channels);
184
228
  notifications.push(notification);
185
229
  }
186
230
  await tx.flush();
187
231
  });
188
232
  await invalidateNotificationCache(container, ctx, "created");
189
233
  await emitNotificationCreatedBatch(eventBus, notifications, ctx);
190
- await emitNotificationSseEvents(eventBus, notifications, ctx, recipientUserIds);
234
+ await emitNotificationSseEvents(eventBus, notifications, ctx);
191
235
  return notifications;
192
236
  },
193
237
  async createForRole(input, ctx) {
@@ -200,17 +244,18 @@ function createNotificationService(deps) {
200
244
  const { roleId: _roleId, ...content } = input;
201
245
  const notifications = [];
202
246
  const uniqueRecipientUserIds = Array.from(new Set(recipientUserIds));
247
+ const resolved = await resolveChannelsForRecipients(content, uniqueRecipientUserIds, ctx);
203
248
  const writeEm = rootEm.fork();
204
249
  await writeEm.transactional(async (tx) => {
205
- for (const recipientUserId of uniqueRecipientUserIds) {
206
- const notification = await createOrRefreshNotification(tx, content, recipientUserId, ctx);
250
+ for (const { recipientUserId, channels } of resolved) {
251
+ const notification = await createOrRefreshNotification(tx, content, recipientUserId, ctx, channels);
207
252
  notifications.push(notification);
208
253
  }
209
254
  await tx.flush();
210
255
  });
211
256
  await invalidateNotificationCache(container, ctx, "created");
212
257
  await emitNotificationCreatedBatch(eventBus, notifications, ctx);
213
- await emitNotificationSseEvents(eventBus, notifications, ctx, uniqueRecipientUserIds);
258
+ await emitNotificationSseEvents(eventBus, notifications, ctx);
214
259
  return notifications;
215
260
  },
216
261
  async createForFeature(input, ctx) {
@@ -289,17 +334,18 @@ function createNotificationService(deps) {
289
334
  } = input;
290
335
  const notifications = [];
291
336
  const uniqueRecipientUserIds = Array.from(new Set(authorizedRecipientUserIds));
337
+ const resolved = await resolveChannelsForRecipients(content, uniqueRecipientUserIds, ctx);
292
338
  const writeEm = rootEm.fork();
293
339
  await writeEm.transactional(async (tx) => {
294
- for (const recipientUserId of uniqueRecipientUserIds) {
295
- const notification = await createOrRefreshNotification(tx, content, recipientUserId, ctx);
340
+ for (const { recipientUserId, channels } of resolved) {
341
+ const notification = await createOrRefreshNotification(tx, content, recipientUserId, ctx, channels);
296
342
  notifications.push(notification);
297
343
  }
298
344
  await tx.flush();
299
345
  });
300
346
  await invalidateNotificationCache(container, ctx, "created");
301
347
  await emitNotificationCreatedBatch(eventBus, notifications, ctx);
302
- await emitNotificationSseEvents(eventBus, notifications, ctx, uniqueRecipientUserIds);
348
+ await emitNotificationSseEvents(eventBus, notifications, ctx);
303
349
  return notifications;
304
350
  },
305
351
  async markAsRead(notificationId, ctx) {
@@ -322,7 +368,7 @@ function createNotificationService(deps) {
322
368
  const em = rootEm.fork();
323
369
  const db = getDb(em);
324
370
  const applyScope = (q) => {
325
- let chain = q.where("recipient_user_id", "=", ctx.userId).where("tenant_id", "=", ctx.tenantId).where("status", "=", "unread");
371
+ let chain = q.where("recipient_user_id", "=", ctx.userId).where("tenant_id", "=", ctx.tenantId).where("status", "=", "unread").where(inAppVisibleSql());
326
372
  if (ctx.organizationId) {
327
373
  chain = chain.where("organization_id", "=", ctx.organizationId);
328
374
  }
@@ -496,7 +542,10 @@ function createNotificationService(deps) {
496
542
  recipientUserId: ctx.userId,
497
543
  tenantId: ctx.tenantId,
498
544
  status: "unread",
499
- ...buildNotificationReadScopeWhere(ctx)
545
+ $and: [
546
+ buildNotificationReadScopeWhere(ctx),
547
+ inAppVisibleFilter()
548
+ ]
500
549
  });
501
550
  },
502
551
  async getPollData(ctx, since) {
@@ -504,7 +553,10 @@ function createNotificationService(deps) {
504
553
  const filters = {
505
554
  recipientUserId: ctx.userId,
506
555
  tenantId: ctx.tenantId,
507
- ...buildNotificationReadScopeWhere(ctx)
556
+ $and: [
557
+ buildNotificationReadScopeWhere(ctx),
558
+ inAppVisibleFilter()
559
+ ]
508
560
  };
509
561
  if (since) {
510
562
  filters.createdAt = { $gt: new Date(since) };
@@ -518,7 +570,10 @@ function createNotificationService(deps) {
518
570
  recipientUserId: ctx.userId,
519
571
  tenantId: ctx.tenantId,
520
572
  status: "unread",
521
- ...buildNotificationReadScopeWhere(ctx)
573
+ $and: [
574
+ buildNotificationReadScopeWhere(ctx),
575
+ inAppVisibleFilter()
576
+ ]
522
577
  })
523
578
  ]);
524
579
  const recent = notifications.map(toNotificationDto);