@headless-lms/core 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (307) hide show
  1. package/dist/assets/events.d.ts +42 -24
  2. package/dist/assets/events.js +1 -1
  3. package/dist/assets/events.js.map +1 -1
  4. package/dist/assets/file-urls.d.ts +20 -0
  5. package/dist/assets/file-urls.js +25 -0
  6. package/dist/assets/file-urls.js.map +1 -0
  7. package/dist/assets/index.d.ts +6 -4
  8. package/dist/assets/index.js +4 -2
  9. package/dist/assets/jobs.d.ts +15 -0
  10. package/dist/assets/jobs.js +30 -0
  11. package/dist/assets/jobs.js.map +1 -0
  12. package/dist/assets/model.d.ts +2 -2
  13. package/dist/assets/ports.d.ts +46 -15
  14. package/dist/assets/service.d.ts +38 -7
  15. package/dist/assets/service.js +287 -38
  16. package/dist/assets/service.js.map +1 -1
  17. package/dist/automations/actions.d.ts +13 -24
  18. package/dist/automations/actions.js +106 -42
  19. package/dist/automations/actions.js.map +1 -1
  20. package/dist/automations/catalog.js +226 -3
  21. package/dist/automations/catalog.js.map +1 -1
  22. package/dist/automations/community-derivations.d.ts +14 -0
  23. package/dist/automations/community-derivations.js +439 -0
  24. package/dist/automations/community-derivations.js.map +1 -0
  25. package/dist/automations/community-mail.d.ts +98 -0
  26. package/dist/automations/community-mail.js +70 -0
  27. package/dist/automations/community-mail.js.map +1 -0
  28. package/dist/automations/events.d.ts +18 -0
  29. package/dist/automations/index.d.ts +13 -4
  30. package/dist/automations/index.js +10 -2
  31. package/dist/automations/model.d.ts +10 -3
  32. package/dist/automations/model.js +15 -2
  33. package/dist/automations/model.js.map +1 -1
  34. package/dist/automations/notifications/debounce.d.ts +25 -0
  35. package/dist/automations/notifications/debounce.js +93 -0
  36. package/dist/automations/notifications/debounce.js.map +1 -0
  37. package/dist/automations/notifications/defaults.d.ts +8 -0
  38. package/dist/automations/notifications/defaults.js +316 -0
  39. package/dist/automations/notifications/defaults.js.map +1 -0
  40. package/dist/automations/notifications/delivery.d.ts +28 -0
  41. package/dist/automations/notifications/delivery.js +88 -0
  42. package/dist/automations/notifications/delivery.js.map +1 -0
  43. package/dist/automations/notifications/processor.d.ts +64 -0
  44. package/dist/automations/notifications/processor.js +270 -0
  45. package/dist/automations/notifications/processor.js.map +1 -0
  46. package/dist/automations/notifications/rules/content.d.ts +6 -0
  47. package/dist/automations/notifications/rules/content.js +39 -0
  48. package/dist/automations/notifications/rules/content.js.map +1 -0
  49. package/dist/automations/notifications/rules/discussion.d.ts +7 -0
  50. package/dist/automations/notifications/rules/discussion.js +678 -0
  51. package/dist/automations/notifications/rules/discussion.js.map +1 -0
  52. package/dist/automations/notifications/rules/entitlements.d.ts +6 -0
  53. package/dist/automations/notifications/rules/entitlements.js +249 -0
  54. package/dist/automations/notifications/rules/entitlements.js.map +1 -0
  55. package/dist/automations/notifications/rules/index.d.ts +6 -0
  56. package/dist/automations/notifications/rules/index.js +15 -0
  57. package/dist/automations/notifications/rules/index.js.map +1 -0
  58. package/dist/automations/notifications/rules/organizations.d.ts +6 -0
  59. package/dist/automations/notifications/rules/organizations.js +28 -0
  60. package/dist/automations/notifications/rules/organizations.js.map +1 -0
  61. package/dist/automations/notifications/rules/recipients.d.ts +32 -0
  62. package/dist/automations/notifications/rules/recipients.js +99 -0
  63. package/dist/automations/notifications/rules/recipients.js.map +1 -0
  64. package/dist/automations/notifications/service.d.ts +27 -0
  65. package/dist/automations/notifications/service.js +45 -0
  66. package/dist/automations/notifications/service.js.map +1 -0
  67. package/dist/automations/notifications/types.d.ts +7 -0
  68. package/dist/automations/notifications/types.js +473 -0
  69. package/dist/automations/notifications/types.js.map +1 -0
  70. package/dist/automations/ports.d.ts +121 -3
  71. package/dist/automations/schedule.d.ts +6 -0
  72. package/dist/automations/schedule.js +41 -0
  73. package/dist/automations/schedule.js.map +1 -0
  74. package/dist/automations/service.d.ts +20 -4
  75. package/dist/automations/service.js +246 -45
  76. package/dist/automations/service.js.map +1 -1
  77. package/dist/content/events.d.ts +712 -50
  78. package/dist/content/events.js +55 -1
  79. package/dist/content/events.js.map +1 -1
  80. package/dist/content/index.d.ts +6 -4
  81. package/dist/content/index.js +3 -1
  82. package/dist/content/model.d.ts +2 -2
  83. package/dist/content/notification-categories.d.ts +36 -0
  84. package/dist/content/notification-categories.js +64 -0
  85. package/dist/content/notification-categories.js.map +1 -0
  86. package/dist/content/ports.d.ts +90 -18
  87. package/dist/content/service.d.ts +57 -18
  88. package/dist/content/service.js +520 -25
  89. package/dist/content/service.js.map +1 -1
  90. package/dist/content/titles.d.ts +22 -0
  91. package/dist/content/titles.js +69 -0
  92. package/dist/content/titles.js.map +1 -0
  93. package/dist/content/types.d.ts +2 -2
  94. package/dist/discussion/authors.d.ts +10 -0
  95. package/dist/discussion/authors.js +23 -0
  96. package/dist/discussion/authors.js.map +1 -0
  97. package/dist/discussion/chats.d.ts +109 -0
  98. package/dist/discussion/chats.js +831 -0
  99. package/dist/discussion/chats.js.map +1 -0
  100. package/dist/discussion/digests.d.ts +24 -0
  101. package/dist/discussion/digests.js +104 -0
  102. package/dist/discussion/digests.js.map +1 -0
  103. package/dist/discussion/events.d.ts +785 -56
  104. package/dist/discussion/events.js +165 -5
  105. package/dist/discussion/events.js.map +1 -1
  106. package/dist/discussion/index.d.ts +12 -3
  107. package/dist/discussion/index.js +9 -1
  108. package/dist/discussion/jobs.d.ts +16 -0
  109. package/dist/discussion/jobs.js +40 -0
  110. package/dist/discussion/jobs.js.map +1 -0
  111. package/dist/discussion/mentions.d.ts +11 -0
  112. package/dist/discussion/mentions.js +35 -0
  113. package/dist/discussion/mentions.js.map +1 -0
  114. package/dist/discussion/ports.d.ts +372 -28
  115. package/dist/discussion/ports.js +6 -1
  116. package/dist/discussion/ports.js.map +1 -0
  117. package/dist/discussion/posts.d.ts +113 -0
  118. package/dist/discussion/posts.js +775 -0
  119. package/dist/discussion/posts.js.map +1 -0
  120. package/dist/discussion/service.d.ts +42 -11
  121. package/dist/discussion/service.js +364 -85
  122. package/dist/discussion/service.js.map +1 -1
  123. package/dist/discussion/subscriptions.d.ts +37 -0
  124. package/dist/discussion/subscriptions.js +99 -0
  125. package/dist/discussion/subscriptions.js.map +1 -0
  126. package/dist/discussion/titles.d.ts +26 -0
  127. package/dist/discussion/titles.js +84 -0
  128. package/dist/discussion/titles.js.map +1 -0
  129. package/dist/entitlements/events.d.ts +178 -11
  130. package/dist/entitlements/events.js +28 -1
  131. package/dist/entitlements/events.js.map +1 -1
  132. package/dist/entitlements/index.d.ts +6 -4
  133. package/dist/entitlements/index.js +3 -1
  134. package/dist/entitlements/jobs.d.ts +12 -0
  135. package/dist/entitlements/jobs.js +18 -0
  136. package/dist/entitlements/jobs.js.map +1 -0
  137. package/dist/entitlements/model.d.ts +3 -2
  138. package/dist/entitlements/ports.d.ts +128 -11
  139. package/dist/entitlements/rules.d.ts +12 -0
  140. package/dist/entitlements/rules.js +17 -0
  141. package/dist/entitlements/rules.js.map +1 -0
  142. package/dist/entitlements/service.d.ts +57 -3
  143. package/dist/entitlements/service.js +492 -2
  144. package/dist/entitlements/service.js.map +1 -1
  145. package/dist/identity/access.d.ts +6 -0
  146. package/dist/identity/access.js +7 -0
  147. package/dist/identity/access.js.map +1 -0
  148. package/dist/identity/devices.d.ts +30 -0
  149. package/dist/identity/devices.js +107 -0
  150. package/dist/identity/devices.js.map +1 -0
  151. package/dist/identity/events.d.ts +2 -2
  152. package/dist/identity/index.d.ts +7 -4
  153. package/dist/identity/index.js +4 -1
  154. package/dist/identity/model.d.ts +2 -1
  155. package/dist/identity/notification-settings.d.ts +28 -0
  156. package/dist/identity/notification-settings.js +113 -0
  157. package/dist/identity/notification-settings.js.map +1 -0
  158. package/dist/identity/ports.d.ts +97 -3
  159. package/dist/identity/service.d.ts +14 -3
  160. package/dist/identity/service.js +62 -5
  161. package/dist/identity/service.js.map +1 -1
  162. package/dist/integrations/events.d.ts +3 -3
  163. package/dist/integrations/model.js +1 -1
  164. package/dist/integrations/model.js.map +1 -1
  165. package/dist/integrations/service.js +2 -2
  166. package/dist/integrations/service.js.map +1 -1
  167. package/dist/organizations/access-scopes.d.ts +19 -0
  168. package/dist/organizations/access-scopes.js +89 -0
  169. package/dist/organizations/access-scopes.js.map +1 -0
  170. package/dist/organizations/events.d.ts +75 -33
  171. package/dist/organizations/events.js +6 -0
  172. package/dist/organizations/events.js.map +1 -1
  173. package/dist/organizations/index.d.ts +8 -6
  174. package/dist/organizations/index.js +5 -3
  175. package/dist/organizations/members.d.ts +8 -5
  176. package/dist/organizations/members.js.map +1 -1
  177. package/dist/organizations/notification-categories.d.ts +32 -0
  178. package/dist/organizations/notification-categories.js +51 -0
  179. package/dist/organizations/notification-categories.js.map +1 -0
  180. package/dist/organizations/ports.d.ts +17 -11
  181. package/dist/organizations/roles.d.ts +6 -10
  182. package/dist/organizations/roles.js +19 -7
  183. package/dist/organizations/roles.js.map +1 -1
  184. package/dist/organizations/service.d.ts +22 -11
  185. package/dist/organizations/service.js +164 -47
  186. package/dist/organizations/service.js.map +1 -1
  187. package/dist/organizations/types.d.ts +1 -10
  188. package/dist/progress/events.d.ts +14 -14
  189. package/dist/progress/index.d.ts +2 -2
  190. package/dist/progress/ports.d.ts +6 -1
  191. package/dist/progress/service.d.ts +3 -1
  192. package/dist/progress/service.js +6 -2
  193. package/dist/progress/service.js.map +1 -1
  194. package/dist/reporting/admin/index.d.ts +4 -0
  195. package/dist/reporting/admin/index.js +2 -0
  196. package/dist/reporting/admin/model.d.ts +108 -0
  197. package/dist/reporting/admin/ports.d.ts +27 -0
  198. package/dist/reporting/admin/service.d.ts +22 -0
  199. package/dist/reporting/admin/service.js +38 -0
  200. package/dist/reporting/admin/service.js.map +1 -0
  201. package/dist/reporting/member/index.d.ts +5 -0
  202. package/dist/reporting/member/index.js +2 -0
  203. package/dist/reporting/member/model.d.ts +157 -0
  204. package/dist/reporting/member/ports.d.ts +48 -0
  205. package/dist/reporting/member/service.d.ts +92 -0
  206. package/dist/reporting/member/service.js +364 -0
  207. package/dist/reporting/member/service.js.map +1 -0
  208. package/dist/shared/actor.d.ts +9 -0
  209. package/dist/shared/errors.d.ts +19 -1
  210. package/dist/shared/errors.js +27 -1
  211. package/dist/shared/errors.js.map +1 -1
  212. package/dist/shared/id.d.ts +14 -0
  213. package/dist/shared/id.js +15 -1
  214. package/dist/shared/id.js.map +1 -1
  215. package/dist/shared/mailer.d.ts +15 -2
  216. package/dist/shared/mailer.js +32 -5
  217. package/dist/shared/mailer.js.map +1 -1
  218. package/dist/shared/network.d.ts +10 -0
  219. package/dist/shared/network.js +211 -0
  220. package/dist/shared/network.js.map +1 -0
  221. package/dist/shared/notification-categories.d.ts +16 -0
  222. package/dist/shared/notification-categories.js +94 -0
  223. package/dist/shared/notification-categories.js.map +1 -0
  224. package/dist/shared/notification-settings.d.ts +14 -0
  225. package/dist/shared/notification-settings.js +61 -0
  226. package/dist/shared/notification-settings.js.map +1 -0
  227. package/dist/shared/ports.d.ts +48 -2
  228. package/dist/shared/settings.d.ts +2 -1
  229. package/dist/shared/settings.js +1 -0
  230. package/dist/shared/settings.js.map +1 -1
  231. package/dist/types/assets.d.ts +2 -2
  232. package/dist/types/automations.d.ts +15 -1
  233. package/dist/types/content.d.ts +2 -2
  234. package/dist/types/discussion.d.ts +11 -3
  235. package/dist/types/email-templates.d.ts +216 -1
  236. package/dist/types/entitlements.d.ts +2 -2
  237. package/dist/types/identity.d.ts +48 -2
  238. package/dist/types/index.d.ts +9 -8
  239. package/dist/types/notifications.d.ts +157 -0
  240. package/dist/types/ports.d.ts +6 -0
  241. package/dist/types/schemas/assets.d.ts +65 -37
  242. package/dist/types/schemas/assets.js +17 -12
  243. package/dist/types/schemas/assets.js.map +1 -1
  244. package/dist/types/schemas/automations.d.ts +9 -1
  245. package/dist/types/schemas/automations.js +13 -6
  246. package/dist/types/schemas/automations.js.map +1 -1
  247. package/dist/types/schemas/content.d.ts +396 -3
  248. package/dist/types/schemas/content.js +161 -3
  249. package/dist/types/schemas/content.js.map +1 -1
  250. package/dist/types/schemas/discussion.d.ts +443 -3
  251. package/dist/types/schemas/discussion.js +233 -4
  252. package/dist/types/schemas/discussion.js.map +1 -1
  253. package/dist/types/schemas/entitlements.d.ts +36 -2
  254. package/dist/types/schemas/entitlements.js +23 -1
  255. package/dist/types/schemas/entitlements.js.map +1 -1
  256. package/dist/types/schemas/identity.d.ts +27 -1
  257. package/dist/types/schemas/identity.js +14 -2
  258. package/dist/types/schemas/identity.js.map +1 -1
  259. package/dist/types/schemas/index.d.ts +9 -8
  260. package/dist/types/schemas/index.js +9 -8
  261. package/dist/types/schemas/notifications.d.ts +95 -0
  262. package/dist/types/schemas/notifications.js +56 -0
  263. package/dist/types/schemas/notifications.js.map +1 -0
  264. package/dist/types/schemas/organizations.d.ts +8 -0
  265. package/dist/types/schemas/organizations.js +2 -0
  266. package/dist/types/schemas/organizations.js.map +1 -1
  267. package/dist/types/schemas/progress.d.ts +3 -3
  268. package/dist/types/schemas/shared.d.ts +3 -1
  269. package/dist/types/schemas/shared.js +5 -1
  270. package/dist/types/schemas/shared.js.map +1 -1
  271. package/package.json +2 -2
  272. package/dist/reporting/courses/index.d.ts +0 -4
  273. package/dist/reporting/courses/index.js +0 -2
  274. package/dist/reporting/courses/model.d.ts +0 -24
  275. package/dist/reporting/courses/ports.d.ts +0 -13
  276. package/dist/reporting/courses/service.d.ts +0 -19
  277. package/dist/reporting/courses/service.js +0 -20
  278. package/dist/reporting/courses/service.js.map +0 -1
  279. package/dist/reporting/dashboard/index.d.ts +0 -4
  280. package/dist/reporting/dashboard/index.js +0 -2
  281. package/dist/reporting/dashboard/model.d.ts +0 -14
  282. package/dist/reporting/dashboard/ports.d.ts +0 -13
  283. package/dist/reporting/dashboard/service.d.ts +0 -19
  284. package/dist/reporting/dashboard/service.js +0 -20
  285. package/dist/reporting/dashboard/service.js.map +0 -1
  286. package/dist/reporting/learn/index.d.ts +0 -5
  287. package/dist/reporting/learn/index.js +0 -2
  288. package/dist/reporting/learn/model.d.ts +0 -23
  289. package/dist/reporting/learn/ports.d.ts +0 -43
  290. package/dist/reporting/learn/service.d.ts +0 -49
  291. package/dist/reporting/learn/service.js +0 -116
  292. package/dist/reporting/learn/service.js.map +0 -1
  293. package/dist/reporting/students/index.d.ts +0 -4
  294. package/dist/reporting/students/index.js +0 -2
  295. package/dist/reporting/students/model.d.ts +0 -51
  296. package/dist/reporting/students/model.js +0 -1
  297. package/dist/reporting/students/ports.d.ts +0 -15
  298. package/dist/reporting/students/ports.js +0 -1
  299. package/dist/reporting/students/service.d.ts +0 -20
  300. package/dist/reporting/students/service.js +0 -23
  301. package/dist/reporting/students/service.js.map +0 -1
  302. /package/dist/reporting/{courses → admin}/model.js +0 -0
  303. /package/dist/reporting/{courses → admin}/ports.js +0 -0
  304. /package/dist/reporting/{dashboard → member}/model.js +0 -0
  305. /package/dist/reporting/{dashboard → member}/ports.js +0 -0
  306. /package/dist/{reporting/learn/model.js → shared/actor.js} +0 -0
  307. /package/dist/{reporting/learn/ports.js → types/notifications.js} +0 -0
@@ -0,0 +1,270 @@
1
+ import { genId } from "../../shared/id.js";
2
+ import { NotFoundError } from "../../shared/errors.js";
3
+ import { noopLogger } from "../../shared/logger.js";
4
+ import { resolveChannels } from "../../shared/notification-settings.js";
5
+ //#region src/automations/notifications/processor.ts
6
+ const IN_APP = "inApp";
7
+ const PUSH = "push";
8
+ const MESSAGE_SENT = "message.sent";
9
+ const DEVICE_WINDOW_MS = 720 * 60 * 60 * 1e3;
10
+ const noChatDebounce = {
11
+ arm: async () => {},
12
+ mentioned: async () => {}
13
+ };
14
+ function sameKeys(a, b) {
15
+ const left = new Set(a);
16
+ const right = new Set(b);
17
+ return left.size === right.size && [...left].every((key) => right.has(key));
18
+ }
19
+ function chatOf(refs) {
20
+ if (refs.parent?.type === "chat") return refs.parent.id;
21
+ return refs.subject?.type === "chat" ? refs.subject.id : null;
22
+ }
23
+ var NotificationProcessor = class {
24
+ rules;
25
+ reads;
26
+ types;
27
+ channels;
28
+ categories;
29
+ settings;
30
+ devices;
31
+ repo;
32
+ engine;
33
+ debounce;
34
+ now;
35
+ logger;
36
+ constructor(params) {
37
+ this.rules = params.rules;
38
+ this.reads = params.reads;
39
+ this.types = params.types;
40
+ this.channels = params.channels;
41
+ this.categories = params.categories;
42
+ this.settings = params.settings;
43
+ this.devices = params.devices;
44
+ this.repo = params.repo;
45
+ this.engine = params.engine;
46
+ this.debounce = params.debounce ?? noChatDebounce;
47
+ this.now = params.now ?? (() => /* @__PURE__ */ new Date());
48
+ this.logger = params.logger ?? noopLogger;
49
+ }
50
+ async process(event) {
51
+ const rule = Object.prototype.hasOwnProperty.call(this.rules, event.type) ? this.rules[event.type] : void 0;
52
+ if (!rule) return;
53
+ const lists = await rule(event, this.reads);
54
+ await this.file(event.orgId, event.id, lists, {
55
+ occurredAt: new Date(event.occurredAt),
56
+ debounce: true
57
+ });
58
+ }
59
+ async file(orgId, sourceEventId, lists, options) {
60
+ const valid = lists.filter((list) => this.isValid(orgId, sourceEventId, list));
61
+ const filed = [];
62
+ for (const group of this.groups(valid)) {
63
+ const categories = await this.categoriesOf(orgId, sourceEventId, group.communityId);
64
+ if (!categories) continue;
65
+ const expanded = [];
66
+ for (const list of group.lists) {
67
+ const ready = list.audience ? await this.expand(orgId, list, categories) : list;
68
+ if (options.debounce && ready.type === "message.sent") {
69
+ await this.debounce.arm(orgId, ready, options.occurredAt);
70
+ continue;
71
+ }
72
+ expanded.push(ready);
73
+ }
74
+ filed.push(...await this.match(orgId, sourceEventId, group.communityId, expanded, categories));
75
+ }
76
+ if (filed.length > 0) {
77
+ await this.repo.insert(orgId, filed.map((entry) => entry.notification), filed.flatMap((entry) => entry.deliveries));
78
+ this.logger.info("notifications filed", {
79
+ orgId,
80
+ sourceEventId,
81
+ count: filed.length,
82
+ types: [...new Set(filed.map((entry) => entry.notification.type))]
83
+ });
84
+ await this.mentionsInChats(orgId, filed);
85
+ }
86
+ await this.dispatch(orgId, sourceEventId);
87
+ }
88
+ isValid(orgId, sourceEventId, list) {
89
+ const drop = (reason) => {
90
+ this.logger.error("notification recipient list dropped", {
91
+ orgId,
92
+ sourceEventId,
93
+ type: list.type,
94
+ scope: list.scope,
95
+ reason
96
+ });
97
+ return false;
98
+ };
99
+ const declaration = this.types.get(list.type);
100
+ if (!declaration) return drop("unknown type");
101
+ if (!declaration.scopes.includes(list.scope)) return drop("scope not declared");
102
+ const keys = declaration.display[list.scope] ?? [];
103
+ if (!sameKeys(Object.keys(list.display), keys)) return drop("display keys do not match the declaration");
104
+ if (list.recipients === void 0 === (list.audience === void 0)) return drop("a list needs either recipients or an audience");
105
+ if ((list.audience ? [list.audience.reason] : (list.recipients ?? []).flatMap((recipient) => recipient.reasons)).some((reason) => !declaration.reasons.includes(reason))) return drop("reason not allowed for the type");
106
+ if (list.audience && list.communityId === null) return drop("an audience needs a community");
107
+ return true;
108
+ }
109
+ groups(lists) {
110
+ const byCommunity = /* @__PURE__ */ new Map();
111
+ for (const list of lists) {
112
+ const group = byCommunity.get(list.communityId) ?? {
113
+ communityId: list.communityId,
114
+ lists: []
115
+ };
116
+ group.lists.push(list);
117
+ byCommunity.set(list.communityId, group);
118
+ }
119
+ return [...byCommunity.values()];
120
+ }
121
+ async categoriesOf(orgId, sourceEventId, communityId) {
122
+ try {
123
+ return await this.categories.listFor(orgId, communityId);
124
+ } catch (err) {
125
+ if (!(err instanceof NotFoundError)) throw err;
126
+ this.logger.warn("notification group dropped: the community no longer exists", {
127
+ orgId,
128
+ sourceEventId,
129
+ communityId
130
+ });
131
+ return null;
132
+ }
133
+ }
134
+ async expand(orgId, list, categories) {
135
+ const audience = list.audience;
136
+ const communityId = list.communityId;
137
+ const category = categories.find((candidate) => candidate.covers.some((cover) => cover.type === list.type && cover.reasons.includes(audience.reason)));
138
+ let orgUserIds = [];
139
+ if (category) if (resolveChannels(category, [], audience.spaceId, this.channels.list()).some((channel) => channel.enabled)) orgUserIds = await this.reads.spaceOpeners(orgId, audience.spaceId);
140
+ else {
141
+ const switched = await this.reads.switchedOn(orgId, communityId, category.key, audience.spaceId);
142
+ orgUserIds = switched.length > 0 ? await this.reads.spaceOpenersAmong(orgId, audience.spaceId, switched) : [];
143
+ }
144
+ const excluded = new Set(audience.exclude);
145
+ const candidates = [...new Set(orgUserIds)].filter((id) => !excluded.has(id));
146
+ const muted = candidates.length > 0 ? new Set(await this.reads.mutedAmong(orgId, {
147
+ type: "space",
148
+ id: audience.spaceId
149
+ }, candidates, this.now())) : /* @__PURE__ */ new Set();
150
+ const kept = candidates.filter((id) => !muted.has(id));
151
+ const userIds = kept.length > 0 ? await this.reads.userIdsOf(orgId, kept) : {};
152
+ const recipients = kept.flatMap((orgUserId) => {
153
+ const userId = userIds[orgUserId];
154
+ return userId ? [{
155
+ orgUserId,
156
+ userId,
157
+ reasons: [audience.reason]
158
+ }] : [];
159
+ });
160
+ const { audience: _audience, ...rest } = list;
161
+ return {
162
+ ...rest,
163
+ recipients
164
+ };
165
+ }
166
+ collect(lists) {
167
+ const people = /* @__PURE__ */ new Map();
168
+ for (const list of lists) for (const recipient of list.recipients ?? []) {
169
+ const person = people.get(recipient.userId) ?? {
170
+ orgUserId: recipient.orgUserId,
171
+ userId: recipient.userId,
172
+ pairs: []
173
+ };
174
+ person.pairs.push({
175
+ list,
176
+ type: list.type,
177
+ reasons: [...new Set(recipient.reasons)]
178
+ });
179
+ people.set(recipient.userId, person);
180
+ }
181
+ return [...people.values()];
182
+ }
183
+ async match(orgId, sourceEventId, communityId, lists, categories) {
184
+ const people = this.collect(lists);
185
+ if (people.length === 0 || categories.length === 0) return [];
186
+ const settings = await this.settings.getMany(orgId, people.map((person) => person.userId), communityId ? { communityId } : "org", categories.map((category) => category.key));
187
+ const configured = this.channels.list();
188
+ const withDevice = await this.peopleWithDevice(orgId, people, categories, settings, configured);
189
+ const filed = [];
190
+ for (const person of people) {
191
+ const entry = this.fileOne(sourceEventId, communityId, person, categories, settings[person.userId] ?? [], configured, withDevice.has(person.userId));
192
+ if (entry) filed.push(entry);
193
+ }
194
+ return filed;
195
+ }
196
+ async peopleWithDevice(orgId, people, categories, settings, configured) {
197
+ const wanting = people.filter((person) => categories.some((category) => this.coveredPairs(category, person.pairs).some(({ pair }) => resolveChannels(category, settings[person.userId] ?? [], pair.list.spaceId, configured).some((channel) => channel.enabled && channel.channel === "push"))));
198
+ if (wanting.length === 0) return /* @__PURE__ */ new Set();
199
+ const since = /* @__PURE__ */ new Date(this.now().getTime() - DEVICE_WINDOW_MS);
200
+ return new Set(await this.devices.withActiveDevices(orgId, wanting.map((person) => person.userId), since));
201
+ }
202
+ fileOne(sourceEventId, communityId, person, categories, settings, configured, hasDevice) {
203
+ for (const category of categories) for (const { pair, cover } of this.coveredPairs(category, person.pairs)) {
204
+ let on = resolveChannels(category, settings, pair.list.spaceId, configured).filter((channel) => channel.enabled).map((channel) => channel.channel);
205
+ if (!hasDevice) on = on.filter((channel) => channel !== PUSH);
206
+ if (on.length > 0) return this.filed(sourceEventId, communityId, person, category, pair, cover, on);
207
+ }
208
+ return null;
209
+ }
210
+ filed(sourceEventId, communityId, person, category, pair, cover, on) {
211
+ const list = pair.list;
212
+ const notificationId = genId("notification");
213
+ const reasons = [...new Set(person.pairs.flatMap((candidate) => candidate.reasons))];
214
+ return {
215
+ notification: {
216
+ id: notificationId,
217
+ userId: person.userId,
218
+ sourceEventId,
219
+ type: pair.type,
220
+ scope: list.scope,
221
+ reasons,
222
+ matchedReason: cover.reasons.find((reason) => pair.reasons.includes(reason)),
223
+ category: category.key,
224
+ communityId,
225
+ spaceId: list.spaceId,
226
+ actorOrgUserId: list.actorOrgUserId,
227
+ subject: list.subject,
228
+ parent: list.parent,
229
+ display: list.display,
230
+ inApp: on.includes(IN_APP),
231
+ createdAt: this.now()
232
+ },
233
+ deliveries: on.filter((channel) => channel !== "inApp" && this.channels.get(channel) !== void 0).map((channel) => ({
234
+ id: genId("notificationDelivery"),
235
+ notificationId,
236
+ channel
237
+ }))
238
+ };
239
+ }
240
+ coveredPairs(category, pairs) {
241
+ const covered = [];
242
+ for (const pair of pairs) {
243
+ if (category.perSpace === true && pair.list.spaceId === null) continue;
244
+ const cover = category.covers.find((candidate) => candidate.type === pair.type && candidate.reasons.some((reason) => pair.reasons.includes(reason)));
245
+ if (cover) covered.push({
246
+ pair,
247
+ cover
248
+ });
249
+ }
250
+ return covered;
251
+ }
252
+ async mentionsInChats(orgId, filed) {
253
+ for (const { notification } of filed) {
254
+ if (notification.type !== "mention" || notification.scope !== "chat") continue;
255
+ const chatId = chatOf(notification);
256
+ if (chatId) await this.debounce.mentioned(orgId, notification.userId, chatId);
257
+ }
258
+ }
259
+ async dispatch(orgId, sourceEventId) {
260
+ const pending = await this.repo.pendingDeliveriesForEvent(orgId, sourceEventId);
261
+ for (const delivery of pending) await this.engine.deliver({
262
+ orgId,
263
+ deliveryId: delivery.id
264
+ });
265
+ }
266
+ };
267
+ //#endregion
268
+ export { DEVICE_WINDOW_MS, IN_APP, MESSAGE_SENT, NotificationProcessor, PUSH, chatOf };
269
+
270
+ //# sourceMappingURL=processor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"processor.js","names":[],"sources":["../../../src/automations/notifications/processor.ts"],"sourcesContent":["import type { DevicesService, NotificationSettingsService } from '../../identity/index.js';\nimport { NotFoundError } from '../../shared/errors.js';\nimport { genId } from '../../shared/id.js';\nimport { noopLogger } from '../../shared/logger.js';\nimport { resolveChannels } from '../../shared/notification-settings.js';\nimport type {\n ChannelRegistry,\n DomainEvent,\n Logger,\n NotificationTypeRegistry,\n} from '../../shared/ports.js';\nimport type {\n NotificationCategory,\n Reason,\n RecipientList,\n StoredSetting,\n} from '../../types/index.js';\nimport type {\n AutomationEngine,\n ChatDebounce,\n NewDelivery,\n NewNotification,\n NotificationCategories,\n NotificationReads,\n NotificationsRepository,\n RecipientRule,\n} from '../ports.js';\n\nexport const IN_APP = 'inApp';\nexport const PUSH = 'push';\nexport const MESSAGE_SENT = 'message.sent';\nexport const DEVICE_WINDOW_MS = 30 * 24 * 60 * 60 * 1000;\n\nconst noChatDebounce: ChatDebounce = {\n arm: async () => {},\n mentioned: async () => {},\n};\n\ntype Recipient = NonNullable<RecipientList['recipients']>[number];\n\ninterface Pair {\n list: RecipientList;\n type: string;\n reasons: Reason[];\n}\n\ninterface Person {\n orgUserId: string;\n userId: string;\n pairs: Pair[];\n}\n\ninterface Group {\n communityId: string | null;\n lists: RecipientList[];\n}\n\ninterface Filed {\n notification: NewNotification;\n deliveries: NewDelivery[];\n}\n\nexport type NotificationProcessorParams = {\n rules: Record<string, RecipientRule>;\n reads: NotificationReads;\n types: Pick<NotificationTypeRegistry, 'get'>;\n channels: Pick<ChannelRegistry, 'list' | 'get'>;\n categories: NotificationCategories;\n settings: Pick<NotificationSettingsService, 'getMany'>;\n devices: Pick<DevicesService, 'withActiveDevices'>;\n repo: Pick<NotificationsRepository, 'insert' | 'pendingDeliveriesForEvent'>;\n engine: Pick<AutomationEngine, 'deliver'>;\n debounce?: ChatDebounce;\n now?: () => Date;\n logger?: Logger;\n};\n\nfunction sameKeys(a: string[], b: string[]): boolean {\n const left = new Set(a);\n const right = new Set(b);\n return left.size === right.size && [...left].every((key) => right.has(key));\n}\n\nexport function chatOf(refs: Pick<RecipientList, 'subject' | 'parent'>): string | null {\n if (refs.parent?.type === 'chat') {\n return refs.parent.id;\n }\n return refs.subject?.type === 'chat' ? refs.subject.id : null;\n}\n\nexport class NotificationProcessor {\n private readonly rules: Record<string, RecipientRule>;\n private readonly reads: NotificationReads;\n private readonly types: Pick<NotificationTypeRegistry, 'get'>;\n private readonly channels: Pick<ChannelRegistry, 'list' | 'get'>;\n private readonly categories: NotificationCategories;\n private readonly settings: Pick<NotificationSettingsService, 'getMany'>;\n private readonly devices: Pick<DevicesService, 'withActiveDevices'>;\n private readonly repo: Pick<NotificationsRepository, 'insert' | 'pendingDeliveriesForEvent'>;\n private readonly engine: Pick<AutomationEngine, 'deliver'>;\n private readonly debounce: ChatDebounce;\n private readonly now: () => Date;\n private readonly logger: Logger;\n\n constructor(params: NotificationProcessorParams) {\n this.rules = params.rules;\n this.reads = params.reads;\n this.types = params.types;\n this.channels = params.channels;\n this.categories = params.categories;\n this.settings = params.settings;\n this.devices = params.devices;\n this.repo = params.repo;\n this.engine = params.engine;\n this.debounce = params.debounce ?? noChatDebounce;\n this.now = params.now ?? (() => new Date());\n this.logger = params.logger ?? noopLogger;\n }\n\n async process(event: DomainEvent): Promise<void> {\n const rule = Object.prototype.hasOwnProperty.call(this.rules, event.type)\n ? this.rules[event.type]\n : undefined;\n if (!rule) {\n return;\n }\n const lists = await rule(event, this.reads);\n await this.file(event.orgId, event.id, lists, {\n occurredAt: new Date(event.occurredAt),\n debounce: true,\n });\n }\n\n async file(\n orgId: string,\n sourceEventId: string,\n lists: RecipientList[],\n options: { occurredAt: Date; debounce: boolean },\n ): Promise<void> {\n const valid = lists.filter((list) => this.isValid(orgId, sourceEventId, list));\n const filed: Filed[] = [];\n for (const group of this.groups(valid)) {\n const categories = await this.categoriesOf(orgId, sourceEventId, group.communityId);\n if (!categories) {\n continue;\n }\n const expanded: RecipientList[] = [];\n for (const list of group.lists) {\n const ready = list.audience ? await this.expand(orgId, list, categories) : list;\n if (options.debounce && ready.type === MESSAGE_SENT) {\n await this.debounce.arm(orgId, ready, options.occurredAt);\n continue;\n }\n expanded.push(ready);\n }\n filed.push(\n ...(await this.match(orgId, sourceEventId, group.communityId, expanded, categories)),\n );\n }\n if (filed.length > 0) {\n await this.repo.insert(\n orgId,\n filed.map((entry) => entry.notification),\n filed.flatMap((entry) => entry.deliveries),\n );\n this.logger.info('notifications filed', {\n orgId,\n sourceEventId,\n count: filed.length,\n types: [...new Set(filed.map((entry) => entry.notification.type))],\n });\n await this.mentionsInChats(orgId, filed);\n }\n await this.dispatch(orgId, sourceEventId);\n }\n\n private isValid(orgId: string, sourceEventId: string, list: RecipientList): boolean {\n const drop = (reason: string): false => {\n this.logger.error('notification recipient list dropped', {\n orgId,\n sourceEventId,\n type: list.type,\n scope: list.scope,\n reason,\n });\n return false;\n };\n const declaration = this.types.get(list.type);\n if (!declaration) {\n return drop('unknown type');\n }\n if (!declaration.scopes.includes(list.scope)) {\n return drop('scope not declared');\n }\n const keys = declaration.display[list.scope] ?? [];\n if (!sameKeys(Object.keys(list.display), keys)) {\n return drop('display keys do not match the declaration');\n }\n if ((list.recipients === undefined) === (list.audience === undefined)) {\n return drop('a list needs either recipients or an audience');\n }\n const reasons = list.audience\n ? [list.audience.reason]\n : (list.recipients ?? []).flatMap((recipient) => recipient.reasons);\n if (reasons.some((reason) => !declaration.reasons.includes(reason))) {\n return drop('reason not allowed for the type');\n }\n if (list.audience && list.communityId === null) {\n return drop('an audience needs a community');\n }\n return true;\n }\n\n private groups(lists: RecipientList[]): Group[] {\n const byCommunity = new Map<string | null, Group>();\n for (const list of lists) {\n const group = byCommunity.get(list.communityId) ?? {\n communityId: list.communityId,\n lists: [],\n };\n group.lists.push(list);\n byCommunity.set(list.communityId, group);\n }\n return [...byCommunity.values()];\n }\n\n private async categoriesOf(\n orgId: string,\n sourceEventId: string,\n communityId: string | null,\n ): Promise<NotificationCategory[] | null> {\n try {\n return await this.categories.listFor(orgId, communityId);\n } catch (err) {\n if (!(err instanceof NotFoundError)) {\n throw err;\n }\n this.logger.warn('notification group dropped: the community no longer exists', {\n orgId,\n sourceEventId,\n communityId,\n });\n return null;\n }\n }\n\n private async expand(\n orgId: string,\n list: RecipientList,\n categories: NotificationCategory[],\n ): Promise<RecipientList> {\n const audience = list.audience!;\n const communityId = list.communityId!;\n const category = categories.find((candidate) =>\n candidate.covers.some(\n (cover) => cover.type === list.type && cover.reasons.includes(audience.reason),\n ),\n );\n let orgUserIds: string[] = [];\n if (category) {\n const onByDefault = resolveChannels(\n category,\n [],\n audience.spaceId,\n this.channels.list(),\n ).some((channel) => channel.enabled);\n if (onByDefault) {\n orgUserIds = await this.reads.spaceOpeners(orgId, audience.spaceId);\n } else {\n const switched = await this.reads.switchedOn(\n orgId,\n communityId,\n category.key,\n audience.spaceId,\n );\n orgUserIds =\n switched.length > 0\n ? await this.reads.spaceOpenersAmong(orgId, audience.spaceId, switched)\n : [];\n }\n }\n const excluded = new Set(audience.exclude);\n const candidates = [...new Set(orgUserIds)].filter((id) => !excluded.has(id));\n const muted =\n candidates.length > 0\n ? new Set(\n await this.reads.mutedAmong(\n orgId,\n { type: 'space', id: audience.spaceId },\n candidates,\n this.now(),\n ),\n )\n : new Set<string>();\n const kept = candidates.filter((id) => !muted.has(id));\n const userIds = kept.length > 0 ? await this.reads.userIdsOf(orgId, kept) : {};\n const recipients: Recipient[] = kept.flatMap((orgUserId) => {\n const userId = userIds[orgUserId];\n return userId ? [{ orgUserId, userId, reasons: [audience.reason] }] : [];\n });\n const { audience: _audience, ...rest } = list;\n return { ...rest, recipients };\n }\n\n private collect(lists: RecipientList[]): Person[] {\n const people = new Map<string, Person>();\n for (const list of lists) {\n for (const recipient of list.recipients ?? []) {\n const person = people.get(recipient.userId) ?? {\n orgUserId: recipient.orgUserId,\n userId: recipient.userId,\n pairs: [],\n };\n person.pairs.push({ list, type: list.type, reasons: [...new Set(recipient.reasons)] });\n people.set(recipient.userId, person);\n }\n }\n return [...people.values()];\n }\n\n private async match(\n orgId: string,\n sourceEventId: string,\n communityId: string | null,\n lists: RecipientList[],\n categories: NotificationCategory[],\n ): Promise<Filed[]> {\n const people = this.collect(lists);\n if (people.length === 0 || categories.length === 0) {\n return [];\n }\n const settings = await this.settings.getMany(\n orgId,\n people.map((person) => person.userId),\n communityId ? { communityId } : 'org',\n categories.map((category) => category.key),\n );\n const configured = this.channels.list();\n const withDevice = await this.peopleWithDevice(\n orgId,\n people,\n categories,\n settings,\n configured,\n );\n const filed: Filed[] = [];\n for (const person of people) {\n const entry = this.fileOne(\n sourceEventId,\n communityId,\n person,\n categories,\n settings[person.userId] ?? [],\n configured,\n withDevice.has(person.userId),\n );\n if (entry) {\n filed.push(entry);\n }\n }\n return filed;\n }\n\n private async peopleWithDevice(\n orgId: string,\n people: Person[],\n categories: NotificationCategory[],\n settings: Record<string, StoredSetting[]>,\n configured: string[],\n ): Promise<Set<string>> {\n const wanting = people.filter((person) =>\n categories.some((category) =>\n this.coveredPairs(category, person.pairs).some(({ pair }) =>\n resolveChannels(\n category,\n settings[person.userId] ?? [],\n pair.list.spaceId,\n configured,\n ).some((channel) => channel.enabled && channel.channel === PUSH),\n ),\n ),\n );\n if (wanting.length === 0) {\n return new Set();\n }\n const since = new Date(this.now().getTime() - DEVICE_WINDOW_MS);\n return new Set(\n await this.devices.withActiveDevices(\n orgId,\n wanting.map((person) => person.userId),\n since,\n ),\n );\n }\n\n private fileOne(\n sourceEventId: string,\n communityId: string | null,\n person: Person,\n categories: NotificationCategory[],\n settings: StoredSetting[],\n configured: string[],\n hasDevice: boolean,\n ): Filed | null {\n for (const category of categories) {\n for (const { pair, cover } of this.coveredPairs(category, person.pairs)) {\n let on = resolveChannels(category, settings, pair.list.spaceId, configured)\n .filter((channel) => channel.enabled)\n .map((channel) => channel.channel);\n if (!hasDevice) {\n on = on.filter((channel) => channel !== PUSH);\n }\n if (on.length > 0) {\n return this.filed(sourceEventId, communityId, person, category, pair, cover, on);\n }\n }\n }\n return null;\n }\n\n private filed(\n sourceEventId: string,\n communityId: string | null,\n person: Person,\n category: NotificationCategory,\n pair: Pair,\n cover: NotificationCategory['covers'][number],\n on: string[],\n ): Filed {\n const list = pair.list;\n const notificationId = genId('notification');\n const reasons = [...new Set(person.pairs.flatMap((candidate) => candidate.reasons))];\n return {\n notification: {\n id: notificationId,\n userId: person.userId,\n sourceEventId,\n type: pair.type,\n scope: list.scope,\n reasons,\n matchedReason: cover.reasons.find((reason) => pair.reasons.includes(reason))!,\n category: category.key,\n communityId,\n spaceId: list.spaceId,\n actorOrgUserId: list.actorOrgUserId,\n subject: list.subject,\n parent: list.parent,\n display: list.display,\n inApp: on.includes(IN_APP),\n createdAt: this.now(),\n },\n deliveries: on\n .filter((channel) => channel !== IN_APP && this.channels.get(channel) !== undefined)\n .map((channel) => ({\n id: genId('notificationDelivery'),\n notificationId,\n channel,\n })),\n };\n }\n\n private coveredPairs(\n category: NotificationCategory,\n pairs: Pair[],\n ): { pair: Pair; cover: NotificationCategory['covers'][number] }[] {\n const covered: { pair: Pair; cover: NotificationCategory['covers'][number] }[] = [];\n for (const pair of pairs) {\n if (category.perSpace === true && pair.list.spaceId === null) {\n continue;\n }\n const cover = category.covers.find(\n (candidate) =>\n candidate.type === pair.type &&\n candidate.reasons.some((reason) => pair.reasons.includes(reason)),\n );\n if (cover) {\n covered.push({ pair, cover });\n }\n }\n return covered;\n }\n\n private async mentionsInChats(orgId: string, filed: Filed[]): Promise<void> {\n for (const { notification } of filed) {\n if (notification.type !== 'mention' || notification.scope !== 'chat') {\n continue;\n }\n const chatId = chatOf(notification);\n if (chatId) {\n await this.debounce.mentioned(orgId, notification.userId, chatId);\n }\n }\n }\n\n private async dispatch(orgId: string, sourceEventId: string): Promise<void> {\n const pending = await this.repo.pendingDeliveriesForEvent(orgId, sourceEventId);\n for (const delivery of pending) {\n await this.engine.deliver({ orgId, deliveryId: delivery.id });\n }\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import { RecipientRule } from "../../ports.js";
2
+ //#region src/automations/notifications/rules/content.d.ts
3
+ declare const CONTENT_RULES: Record<string, RecipientRule>;
4
+ //#endregion
5
+ export { CONTENT_RULES };
6
+ //# sourceMappingURL=content.d.ts.map
@@ -0,0 +1,39 @@
1
+ import { contentEvents } from "../../../content/events.js";
2
+ import { Reasons, actorOr, eventAt, listOf, recipientsOf } from "./recipients.js";
3
+ //#region src/automations/notifications/rules/content.ts
4
+ const activityPublished = async (event, reads) => {
5
+ const parsed = contentEvents.activityPublished.safeParse(event);
6
+ if (!parsed.success) return [];
7
+ const { activityId, courseId } = parsed.data.data;
8
+ const orgId = event.orgId;
9
+ const activity = await reads.activity(orgId, activityId);
10
+ if (!activity) return [];
11
+ return listOf({
12
+ type: "course.activity_added",
13
+ scope: "org",
14
+ actorOrgUserId: actorOr(event, null),
15
+ communityId: null,
16
+ spaceId: null,
17
+ subject: {
18
+ type: "activity",
19
+ id: activityId
20
+ },
21
+ parent: {
22
+ type: "course",
23
+ id: courseId
24
+ },
25
+ display: {
26
+ courseTitle: activity.courseTitle,
27
+ moduleTitle: activity.moduleTitle,
28
+ activityTitle: activity.title
29
+ }
30
+ }, await recipientsOf(reads, orgId, new Reasons().add(await reads.courseHolders(orgId, courseId), "course_member"), {
31
+ exclude: [actorOr(event, null)],
32
+ at: eventAt(event)
33
+ }));
34
+ };
35
+ const CONTENT_RULES = { "content.course.activity.published": activityPublished };
36
+ //#endregion
37
+ export { CONTENT_RULES };
38
+
39
+ //# sourceMappingURL=content.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content.js","names":[],"sources":["../../../../src/automations/notifications/rules/content.ts"],"sourcesContent":["import { contentEvents } from '../../../content/index.js';\nimport type { RecipientRule } from '../../ports.js';\nimport { actorOr, eventAt, listOf, Reasons, recipientsOf } from './recipients.js';\n\nconst activityPublished: RecipientRule = async (event, reads) => {\n const parsed = contentEvents.activityPublished.safeParse(event);\n if (!parsed.success) {\n return [];\n }\n const { activityId, courseId } = parsed.data.data;\n const orgId = event.orgId;\n const activity = await reads.activity(orgId, activityId);\n if (!activity) {\n return [];\n }\n return listOf(\n {\n type: 'course.activity_added',\n scope: 'org',\n actorOrgUserId: actorOr(event, null),\n communityId: null,\n spaceId: null,\n subject: { type: 'activity', id: activityId },\n parent: { type: 'course', id: courseId },\n display: {\n courseTitle: activity.courseTitle,\n moduleTitle: activity.moduleTitle,\n activityTitle: activity.title,\n },\n },\n await recipientsOf(\n reads,\n orgId,\n new Reasons().add(await reads.courseHolders(orgId, courseId), 'course_member'),\n { exclude: [actorOr(event, null)], at: eventAt(event) },\n ),\n );\n};\n\nexport const CONTENT_RULES: Record<string, RecipientRule> = {\n 'content.course.activity.published': activityPublished,\n};\n"],"mappings":";;;AAIA,MAAM,oBAAmC,OAAO,OAAO,UAAU;CAC/D,MAAM,SAAS,cAAc,kBAAkB,UAAU,KAAK;CAC9D,IAAI,CAAC,OAAO,SACV,OAAO,CAAC;CAEV,MAAM,EAAE,YAAY,aAAa,OAAO,KAAK;CAC7C,MAAM,QAAQ,MAAM;CACpB,MAAM,WAAW,MAAM,MAAM,SAAS,OAAO,UAAU;CACvD,IAAI,CAAC,UACH,OAAO,CAAC;CAEV,OAAO,OACL;EACE,MAAM;EACN,OAAO;EACP,gBAAgB,QAAQ,OAAO,IAAI;EACnC,aAAa;EACb,SAAS;EACT,SAAS;GAAE,MAAM;GAAY,IAAI;EAAW;EAC5C,QAAQ;GAAE,MAAM;GAAU,IAAI;EAAS;EACvC,SAAS;GACP,aAAa,SAAS;GACtB,aAAa,SAAS;GACtB,eAAe,SAAS;EAC1B;CACF,GACA,MAAM,aACJ,OACA,OACA,IAAI,QAAQ,CAAC,CAAC,IAAI,MAAM,MAAM,cAAc,OAAO,QAAQ,GAAG,eAAe,GAC7E;EAAE,SAAS,CAAC,QAAQ,OAAO,IAAI,CAAC;EAAG,IAAI,QAAQ,KAAK;CAAE,CACxD,CACF;AACF;AAEA,MAAa,gBAA+C,EAC1D,qCAAqC,kBACvC"}
@@ -0,0 +1,7 @@
1
+ import { RecipientRule } from "../../ports.js";
2
+ //#region src/automations/notifications/rules/discussion.d.ts
3
+ declare function messageSummary(count: number): string;
4
+ declare const DISCUSSION_RULES: Record<string, RecipientRule>;
5
+ //#endregion
6
+ export { DISCUSSION_RULES, messageSummary };
7
+ //# sourceMappingURL=discussion.d.ts.map