@headless-lms/core 0.2.0 → 0.3.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 (225) hide show
  1. package/dist/assets/events.d.ts +39 -21
  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 +5 -4
  8. package/dist/assets/index.js +3 -2
  9. package/dist/assets/model.d.ts +2 -2
  10. package/dist/assets/ports.d.ts +45 -15
  11. package/dist/assets/service.d.ts +39 -7
  12. package/dist/assets/service.js +291 -38
  13. package/dist/assets/service.js.map +1 -1
  14. package/dist/automations/actions.d.ts +13 -24
  15. package/dist/automations/actions.js +168 -43
  16. package/dist/automations/actions.js.map +1 -1
  17. package/dist/automations/catalog.d.ts +2 -1
  18. package/dist/automations/catalog.js +229 -13
  19. package/dist/automations/catalog.js.map +1 -1
  20. package/dist/automations/community-derivations.d.ts +14 -0
  21. package/dist/automations/community-derivations.js +439 -0
  22. package/dist/automations/community-derivations.js.map +1 -0
  23. package/dist/automations/community-mail.d.ts +112 -0
  24. package/dist/automations/community-mail.js +90 -0
  25. package/dist/automations/community-mail.js.map +1 -0
  26. package/dist/automations/community-notifications.d.ts +16 -0
  27. package/dist/automations/community-notifications.js +84 -0
  28. package/dist/automations/community-notifications.js.map +1 -0
  29. package/dist/automations/events.d.ts +28 -0
  30. package/dist/automations/index.d.ts +4 -2
  31. package/dist/automations/index.js +3 -2
  32. package/dist/automations/model.d.ts +10 -3
  33. package/dist/automations/model.js +15 -2
  34. package/dist/automations/model.js.map +1 -1
  35. package/dist/automations/ports.d.ts +5 -1
  36. package/dist/automations/schedule.d.ts +6 -0
  37. package/dist/automations/schedule.js +41 -0
  38. package/dist/automations/schedule.js.map +1 -0
  39. package/dist/automations/seeds.d.ts +17 -0
  40. package/dist/automations/seeds.js +235 -0
  41. package/dist/automations/seeds.js.map +1 -0
  42. package/dist/automations/service.d.ts +22 -3
  43. package/dist/automations/service.js +306 -43
  44. package/dist/automations/service.js.map +1 -1
  45. package/dist/content/events.d.ts +631 -19
  46. package/dist/content/events.js +44 -1
  47. package/dist/content/events.js.map +1 -1
  48. package/dist/content/index.d.ts +4 -4
  49. package/dist/content/model.d.ts +2 -2
  50. package/dist/content/ports.d.ts +71 -18
  51. package/dist/content/service.d.ts +57 -18
  52. package/dist/content/service.js +502 -23
  53. package/dist/content/service.js.map +1 -1
  54. package/dist/content/types.d.ts +2 -2
  55. package/dist/discussion/authors.d.ts +10 -0
  56. package/dist/discussion/authors.js +23 -0
  57. package/dist/discussion/authors.js.map +1 -0
  58. package/dist/discussion/chats.d.ts +104 -0
  59. package/dist/discussion/chats.js +798 -0
  60. package/dist/discussion/chats.js.map +1 -0
  61. package/dist/discussion/events.d.ts +619 -56
  62. package/dist/discussion/events.js +106 -3
  63. package/dist/discussion/events.js.map +1 -1
  64. package/dist/discussion/index.d.ts +7 -3
  65. package/dist/discussion/index.js +4 -1
  66. package/dist/discussion/ports.d.ts +288 -27
  67. package/dist/discussion/ports.js +6 -1
  68. package/dist/discussion/ports.js.map +1 -0
  69. package/dist/discussion/posts.d.ts +103 -0
  70. package/dist/discussion/posts.js +723 -0
  71. package/dist/discussion/posts.js.map +1 -0
  72. package/dist/discussion/service.d.ts +32 -10
  73. package/dist/discussion/service.js +293 -78
  74. package/dist/discussion/service.js.map +1 -1
  75. package/dist/entitlements/events.d.ts +178 -11
  76. package/dist/entitlements/events.js +28 -1
  77. package/dist/entitlements/events.js.map +1 -1
  78. package/dist/entitlements/index.d.ts +5 -4
  79. package/dist/entitlements/index.js +2 -1
  80. package/dist/entitlements/model.d.ts +3 -2
  81. package/dist/entitlements/ports.d.ts +124 -11
  82. package/dist/entitlements/rules.d.ts +12 -0
  83. package/dist/entitlements/rules.js +17 -0
  84. package/dist/entitlements/rules.js.map +1 -0
  85. package/dist/entitlements/service.d.ts +54 -3
  86. package/dist/entitlements/service.js +472 -2
  87. package/dist/entitlements/service.js.map +1 -1
  88. package/dist/identity/access.d.ts +6 -0
  89. package/dist/identity/access.js +7 -0
  90. package/dist/identity/access.js.map +1 -0
  91. package/dist/identity/index.d.ts +6 -4
  92. package/dist/identity/index.js +3 -1
  93. package/dist/identity/model.d.ts +2 -1
  94. package/dist/identity/notifications.d.ts +27 -0
  95. package/dist/identity/notifications.js +101 -0
  96. package/dist/identity/notifications.js.map +1 -0
  97. package/dist/identity/ports.d.ts +73 -3
  98. package/dist/identity/service.d.ts +14 -3
  99. package/dist/identity/service.js +62 -5
  100. package/dist/identity/service.js.map +1 -1
  101. package/dist/integrations/model.js +1 -1
  102. package/dist/integrations/model.js.map +1 -1
  103. package/dist/integrations/service.js +2 -2
  104. package/dist/integrations/service.js.map +1 -1
  105. package/dist/organizations/access-scopes.d.ts +19 -0
  106. package/dist/organizations/access-scopes.js +89 -0
  107. package/dist/organizations/access-scopes.js.map +1 -0
  108. package/dist/organizations/events.d.ts +74 -32
  109. package/dist/organizations/events.js +6 -0
  110. package/dist/organizations/events.js.map +1 -1
  111. package/dist/organizations/index.d.ts +7 -6
  112. package/dist/organizations/index.js +4 -3
  113. package/dist/organizations/members.d.ts +8 -5
  114. package/dist/organizations/members.js.map +1 -1
  115. package/dist/organizations/ports.d.ts +14 -11
  116. package/dist/organizations/roles.d.ts +6 -10
  117. package/dist/organizations/roles.js +19 -7
  118. package/dist/organizations/roles.js.map +1 -1
  119. package/dist/organizations/service.d.ts +20 -11
  120. package/dist/organizations/service.js +157 -47
  121. package/dist/organizations/service.js.map +1 -1
  122. package/dist/organizations/types.d.ts +1 -10
  123. package/dist/progress/index.d.ts +2 -2
  124. package/dist/progress/ports.d.ts +6 -1
  125. package/dist/progress/service.d.ts +3 -1
  126. package/dist/progress/service.js +6 -2
  127. package/dist/progress/service.js.map +1 -1
  128. package/dist/reporting/admin/index.d.ts +4 -0
  129. package/dist/reporting/admin/index.js +2 -0
  130. package/dist/reporting/admin/model.d.ts +108 -0
  131. package/dist/reporting/admin/ports.d.ts +27 -0
  132. package/dist/reporting/admin/service.d.ts +22 -0
  133. package/dist/reporting/admin/service.js +38 -0
  134. package/dist/reporting/admin/service.js.map +1 -0
  135. package/dist/reporting/member/index.d.ts +5 -0
  136. package/dist/reporting/member/index.js +2 -0
  137. package/dist/reporting/member/model.d.ts +124 -0
  138. package/dist/reporting/member/ports.d.ts +45 -0
  139. package/dist/reporting/member/service.d.ts +73 -0
  140. package/dist/reporting/member/service.js +286 -0
  141. package/dist/reporting/member/service.js.map +1 -0
  142. package/dist/shared/actor.d.ts +9 -0
  143. package/dist/shared/errors.d.ts +7 -1
  144. package/dist/shared/errors.js +10 -1
  145. package/dist/shared/errors.js.map +1 -1
  146. package/dist/shared/id.d.ts +11 -0
  147. package/dist/shared/id.js +12 -1
  148. package/dist/shared/id.js.map +1 -1
  149. package/dist/shared/mailer.d.ts +4 -1
  150. package/dist/shared/mailer.js.map +1 -1
  151. package/dist/shared/network.d.ts +10 -0
  152. package/dist/shared/network.js +211 -0
  153. package/dist/shared/network.js.map +1 -0
  154. package/dist/types/assets.d.ts +2 -2
  155. package/dist/types/automations.d.ts +10 -2
  156. package/dist/types/content.d.ts +2 -2
  157. package/dist/types/discussion.d.ts +11 -3
  158. package/dist/types/email-templates.d.ts +121 -0
  159. package/dist/types/entitlements.d.ts +2 -2
  160. package/dist/types/identity.d.ts +48 -2
  161. package/dist/types/index.d.ts +9 -9
  162. package/dist/types/ports.d.ts +6 -0
  163. package/dist/types/schemas/assets.d.ts +65 -37
  164. package/dist/types/schemas/assets.js +17 -12
  165. package/dist/types/schemas/assets.js.map +1 -1
  166. package/dist/types/schemas/automations.d.ts +18 -1
  167. package/dist/types/schemas/automations.js +15 -6
  168. package/dist/types/schemas/automations.js.map +1 -1
  169. package/dist/types/schemas/content.d.ts +335 -3
  170. package/dist/types/schemas/content.js +139 -3
  171. package/dist/types/schemas/content.js.map +1 -1
  172. package/dist/types/schemas/discussion.d.ts +370 -3
  173. package/dist/types/schemas/discussion.js +199 -4
  174. package/dist/types/schemas/discussion.js.map +1 -1
  175. package/dist/types/schemas/entitlements.d.ts +36 -2
  176. package/dist/types/schemas/entitlements.js +23 -1
  177. package/dist/types/schemas/entitlements.js.map +1 -1
  178. package/dist/types/schemas/identity.d.ts +111 -1
  179. package/dist/types/schemas/identity.js +55 -2
  180. package/dist/types/schemas/identity.js.map +1 -1
  181. package/dist/types/schemas/index.d.ts +8 -8
  182. package/dist/types/schemas/index.js +8 -8
  183. package/dist/types/schemas/organizations.d.ts +8 -0
  184. package/dist/types/schemas/organizations.js +2 -0
  185. package/dist/types/schemas/organizations.js.map +1 -1
  186. package/dist/types/schemas/shared.d.ts +3 -1
  187. package/dist/types/schemas/shared.js +5 -1
  188. package/dist/types/schemas/shared.js.map +1 -1
  189. package/package.json +2 -2
  190. package/dist/reporting/courses/index.d.ts +0 -4
  191. package/dist/reporting/courses/index.js +0 -2
  192. package/dist/reporting/courses/model.d.ts +0 -24
  193. package/dist/reporting/courses/ports.d.ts +0 -13
  194. package/dist/reporting/courses/service.d.ts +0 -19
  195. package/dist/reporting/courses/service.js +0 -20
  196. package/dist/reporting/courses/service.js.map +0 -1
  197. package/dist/reporting/dashboard/index.d.ts +0 -4
  198. package/dist/reporting/dashboard/index.js +0 -2
  199. package/dist/reporting/dashboard/model.d.ts +0 -14
  200. package/dist/reporting/dashboard/ports.d.ts +0 -13
  201. package/dist/reporting/dashboard/service.d.ts +0 -19
  202. package/dist/reporting/dashboard/service.js +0 -20
  203. package/dist/reporting/dashboard/service.js.map +0 -1
  204. package/dist/reporting/learn/index.d.ts +0 -5
  205. package/dist/reporting/learn/index.js +0 -2
  206. package/dist/reporting/learn/model.d.ts +0 -23
  207. package/dist/reporting/learn/ports.d.ts +0 -43
  208. package/dist/reporting/learn/ports.js +0 -1
  209. package/dist/reporting/learn/service.d.ts +0 -49
  210. package/dist/reporting/learn/service.js +0 -116
  211. package/dist/reporting/learn/service.js.map +0 -1
  212. package/dist/reporting/students/index.d.ts +0 -4
  213. package/dist/reporting/students/index.js +0 -2
  214. package/dist/reporting/students/model.d.ts +0 -51
  215. package/dist/reporting/students/model.js +0 -1
  216. package/dist/reporting/students/ports.d.ts +0 -15
  217. package/dist/reporting/students/ports.js +0 -1
  218. package/dist/reporting/students/service.d.ts +0 -20
  219. package/dist/reporting/students/service.js +0 -23
  220. package/dist/reporting/students/service.js.map +0 -1
  221. /package/dist/reporting/{courses → admin}/model.js +0 -0
  222. /package/dist/reporting/{courses → admin}/ports.js +0 -0
  223. /package/dist/reporting/{dashboard → member}/model.js +0 -0
  224. /package/dist/reporting/{dashboard → member}/ports.js +0 -0
  225. /package/dist/{reporting/learn/model.js → shared/actor.js} +0 -0
@@ -1,75 +1,189 @@
1
+ import { notificationKindSchema } from "../types/schemas/identity.js";
1
2
  import { deliverWebhookInputSchema } from "../types/schemas/automations.js";
3
+ import { noopLogger } from "../shared/logger.js";
2
4
  import { entitlementEvents } from "../entitlements/events.js";
3
- import { ALL_EMAIL_TEMPLATE_IDS, DELIVER_WEBHOOK_ACTION, publicDataSchema } from "./catalog.js";
5
+ import { ActionFailedError } from "./model.js";
6
+ import { ALL_EMAIL_TEMPLATE_IDS, DELIVER_WEBHOOK_ACTION, NOTIFY_ACTION, publicDataSchema } from "./catalog.js";
4
7
  import { webhookHeaders } from "./webhook-signing.js";
8
+ import { address, firedAtOf } from "./community-mail.js";
9
+ import { COMMUNITY_DERIVATIONS } from "./community-derivations.js";
10
+ import { NOTIFICATION_PLACEMENTS } from "./community-notifications.js";
5
11
  //#region src/automations/actions.ts
6
12
  const SEND_EMAIL_DERIVATIONS = {
7
13
  accessGranted: {
8
- trigger: "entitlement.created",
9
- derive: async (event, lookups) => {
14
+ triggers: ["entitlement.created"],
15
+ derive: async (event, deps) => {
10
16
  const result = entitlementEvents.entitlementCreated.safeParse(event);
11
17
  if (!result.success) return;
12
18
  const entitlement = result.data.data;
13
- if (!entitlement.contentId) return;
14
- const [to, content] = await Promise.all([lookups.orgUserEmail(entitlement.orgId, entitlement.orgUserId), lookups.contentInfo(entitlement.orgId, entitlement.contentId)]);
15
- if (!to || !content) return;
16
- return {
17
- to,
18
- params: {
19
- contentTitle: content.title,
20
- contentId: content.id,
21
- contentType: content.type
22
- }
23
- };
19
+ if (!entitlement.contentId) return [];
20
+ const content = await deps.lookups.contentInfo(entitlement.orgId, entitlement.contentId);
21
+ if (!content) return;
22
+ return address(deps, event, entitlement.orgId, [entitlement.orgUserId], {
23
+ contentTitle: content.title,
24
+ contentId: content.id,
25
+ contentType: content.type
26
+ });
24
27
  }
25
28
  },
26
29
  accessRevoked: {
27
- trigger: "entitlement.deleted",
28
- derive: async (event, lookups) => {
30
+ triggers: ["entitlement.deleted"],
31
+ derive: async (event, deps) => {
29
32
  const result = entitlementEvents.entitlementDeleted.safeParse(event);
30
33
  if (!result.success) return;
31
34
  const entitlement = result.data.data;
32
- if (!entitlement.contentId) return;
33
- const [to, content] = await Promise.all([lookups.orgUserEmail(entitlement.orgId, entitlement.orgUserId), lookups.contentInfo(entitlement.orgId, entitlement.contentId)]);
34
- if (!to || !content) return;
35
- return {
36
- to,
37
- params: { contentTitle: content.title }
38
- };
35
+ if (!entitlement.contentId) return [];
36
+ const content = await deps.lookups.contentInfo(entitlement.orgId, entitlement.contentId);
37
+ if (!content) return;
38
+ return address(deps, event, entitlement.orgId, [entitlement.orgUserId], { contentTitle: content.title });
39
39
  }
40
- }
40
+ },
41
+ ...COMMUNITY_DERIVATIONS
41
42
  };
43
+ function reachedNoOne() {
44
+ return {
45
+ recipients: [],
46
+ failed: []
47
+ };
48
+ }
49
+ function assertSomeDelivered(action, outcome) {
50
+ if (outcome.recipients.length === 0 && outcome.failed.length > 0) throw new ActionFailedError(`${action}: delivery failed for all ${outcome.failed.length} recipients`);
51
+ return outcome;
52
+ }
42
53
  function isEmailTemplateId(value) {
43
54
  return typeof value === "string" && value in ALL_EMAIL_TEMPLATE_IDS;
44
55
  }
45
- /** Throws on any failure — the engine owns retry policy and failure bookkeeping. */
56
+ /** Throws on any failure but a partial fan-out — the engine owns retry policy and failure bookkeeping. */
46
57
  async function executeAction(action, event, deps) {
47
58
  switch (action.type) {
48
59
  case "sendEmail": {
49
60
  const template = action.input["template"];
50
- if (!isEmailTemplateId(template)) throw new Error(`sendEmail: unknown template "${String(template)}"`);
61
+ if (!isEmailTemplateId(template)) throw new ActionFailedError(`sendEmail: unknown template "${String(template)}"`);
51
62
  const derivation = SEND_EMAIL_DERIVATIONS[template];
52
- if (!derivation || derivation.trigger !== event.type) throw new Error(`sendEmail: template "${template}" cannot be derived from event "${event.type}"`);
53
- const derived = await derivation.derive(event, deps.lookups);
54
- if (!derived) throw new Error(`sendEmail: event "${event.type}" is missing the data required to derive template "${template}"`);
55
- await deps.mailer.send(derived.to, template, derived.params);
56
- return;
63
+ if (!derivation || !derivation.triggers.includes(event.type)) throw new ActionFailedError(`sendEmail: template "${template}" cannot be derived from event "${event.type}"`);
64
+ const derived = await derivation.derive(event, {
65
+ ...deps,
66
+ actionInput: action.input
67
+ });
68
+ if (!derived) throw new ActionFailedError(`sendEmail: event "${event.type}" is missing the data required to derive template "${template}"`);
69
+ const outcome = reachedNoOne();
70
+ for (const recipient of derived) (await send(template, recipient, event, deps) ? outcome.recipients : outcome.failed).push(recipient.orgUserId);
71
+ return assertSomeDelivered("sendEmail", outcome);
57
72
  }
73
+ case NOTIFY_ACTION: {
74
+ const kind = notificationKindSchema.safeParse(action.input["kind"]);
75
+ if (!kind.success) throw new ActionFailedError(`${NOTIFY_ACTION}: unknown kind "${String(action.input["kind"])}"`);
76
+ return notify(kind.data, event, {
77
+ ...deps,
78
+ actionInput: action.input
79
+ });
80
+ }
81
+ case "grantAccess":
82
+ await grantAccess(action, event, deps);
83
+ return reachedNoOne();
84
+ case "publishDuePosts":
85
+ await deps.scheduledWork.publishDuePosts(event.orgId, firedAtOf(event));
86
+ return reachedNoOne();
87
+ case "expireDueGrants":
88
+ await deps.scheduledWork.expireDueGrants(event.orgId, firedAtOf(event));
89
+ return reachedNoOne();
90
+ case "sendDigest":
91
+ await deps.scheduledWork.sendDigest(event.orgId, firedAtOf(event));
92
+ return reachedNoOne();
93
+ case "removeUnattachedUploads":
94
+ await deps.scheduledWork.removeUnattachedUploads(event.orgId, firedAtOf(event));
95
+ return reachedNoOne();
58
96
  case DELIVER_WEBHOOK_ACTION:
59
97
  await deliverWebhook(action, event, deps);
60
- return;
61
- default: throw new Error(`unknown automation action type "${action.type}"`);
98
+ return reachedNoOne();
99
+ default: throw new ActionFailedError(`unknown automation action type "${action.type}"`);
100
+ }
101
+ }
102
+ async function send(template, recipient, event, deps) {
103
+ try {
104
+ await deps.mailer.send(recipient.to, template, recipient.params);
105
+ return true;
106
+ } catch (err) {
107
+ (deps.logger ?? noopLogger).warn("automation email not delivered", {
108
+ orgId: event.orgId,
109
+ automationId: deps.automationId,
110
+ trigger: event.type,
111
+ template,
112
+ orgUserId: recipient.orgUserId,
113
+ err
114
+ });
115
+ return false;
62
116
  }
63
117
  }
118
+ async function notify(kind, event, deps) {
119
+ const derivation = SEND_EMAIL_DERIVATIONS[kind];
120
+ if (!derivation || !derivation.triggers.includes(event.type)) throw new ActionFailedError(`${NOTIFY_ACTION}: kind "${kind}" cannot be derived from event "${event.type}"`);
121
+ const recipients = await derivation.derive(event, deps);
122
+ if (!recipients) throw new ActionFailedError(`${NOTIFY_ACTION}: event "${event.type}" is missing the data required to derive kind "${kind}"`);
123
+ const preferences = await deps.desk.preferencesFor(event.orgId, recipients.map((recipient) => recipient.userId), kind);
124
+ const placement = NOTIFICATION_PLACEMENTS[kind];
125
+ const notifications = [];
126
+ const outcome = reachedNoOne();
127
+ for (const recipient of recipients) {
128
+ const preference = preferences[recipient.userId];
129
+ const email = preference?.email !== false;
130
+ const inApp = preference?.inApp !== false;
131
+ const emailed = email && await send(kind, recipient, event, deps);
132
+ let placed = false;
133
+ if (inApp) try {
134
+ const placements = await placement.place(event, recipient, deps);
135
+ notifications.push(...placements.map((spot) => ({
136
+ ...spot,
137
+ userId: recipient.userId,
138
+ kind
139
+ })));
140
+ placed = true;
141
+ } catch (err) {
142
+ (deps.logger ?? noopLogger).warn("automation notification not placed", {
143
+ orgId: event.orgId,
144
+ automationId: deps.automationId,
145
+ trigger: event.type,
146
+ kind,
147
+ orgUserId: recipient.orgUserId,
148
+ err
149
+ });
150
+ }
151
+ if (emailed || placed) outcome.recipients.push(recipient.orgUserId);
152
+ if (email && !emailed || inApp && !placed) outcome.failed.push(recipient.orgUserId);
153
+ }
154
+ await deps.desk.notify(event.orgId, notifications);
155
+ return assertSomeDelivered(NOTIFY_ACTION, outcome);
156
+ }
157
+ function grantRecipient(event) {
158
+ if (typeof event.data !== "object" || event.data === null) return;
159
+ const data = event.data;
160
+ if (event.type === "organization.student.created" && typeof data.id === "string") return data.id;
161
+ if (typeof data.orgUserId === "string") return data.orgUserId;
162
+ }
163
+ async function grantAccess(action, event, deps) {
164
+ const contentId = typeof action.input.contentId === "string" ? action.input.contentId : null;
165
+ const bundleId = typeof action.input.bundleId === "string" ? action.input.bundleId : null;
166
+ if (contentId == null === (bundleId == null)) throw new ActionFailedError("grantAccess: provide exactly one of contentId or bundleId");
167
+ const orgUserId = grantRecipient(event);
168
+ if (!orgUserId) throw new ActionFailedError(`grantAccess: event "${event.type}" has no recipient`);
169
+ const days = action.input.expiresInDays;
170
+ const expiresAt = typeof days === "number" && Number.isFinite(days) ? new Date(Date.now() + days * 864e5) : null;
171
+ await deps.grants.grant(event.orgId, {
172
+ orgUserId,
173
+ contentId,
174
+ bundleId,
175
+ expiresAt
176
+ });
177
+ }
64
178
  async function deliverWebhook(action, event, deps) {
65
179
  const input = deliverWebhookInputSchema.safeParse(action.input);
66
- if (!input.success) throw new Error(`${DELIVER_WEBHOOK_ACTION}: invalid input`);
180
+ if (!input.success) throw new ActionFailedError(`${DELIVER_WEBHOOK_ACTION}: invalid input`);
67
181
  const publicData = publicDataSchema(event.type);
68
- if (!publicData) throw new Error(`${DELIVER_WEBHOOK_ACTION}: event "${event.type}" is not published`);
182
+ if (!publicData) throw new ActionFailedError(`${DELIVER_WEBHOOK_ACTION}: event "${event.type}" is not published`);
69
183
  const data = publicData.safeParse(event.data);
70
- if (!data.success) throw new Error(`${DELIVER_WEBHOOK_ACTION}: event "${event.type}" does not match its published shape`);
184
+ if (!data.success) throw new ActionFailedError(`${DELIVER_WEBHOOK_ACTION}: event "${event.type}" does not match its published shape`);
71
185
  const secret = (await deps.credentials.reveal(event.orgId, input.data.secretRef))?.["secret"];
72
- if (typeof secret !== "string") throw new Error(`${DELIVER_WEBHOOK_ACTION}: signing secret not found`);
186
+ if (typeof secret !== "string") throw new ActionFailedError(`${DELIVER_WEBHOOK_ACTION}: signing secret not found`);
73
187
  const body = JSON.stringify({
74
188
  id: event.id,
75
189
  type: event.type,
@@ -79,12 +193,23 @@ async function deliverWebhook(action, event, deps) {
79
193
  data: data.data
80
194
  });
81
195
  const timestamp = Math.floor(Date.now() / 1e3);
82
- const response = await deps.webhooks.send({
83
- url: input.data.url,
84
- headers: webhookHeaders(secret, event.id, timestamp, body),
85
- body
86
- });
87
- if (response.status < 200 || response.status >= 300) throw new Error(`${DELIVER_WEBHOOK_ACTION}: endpoint responded ${response.status}`);
196
+ let response;
197
+ try {
198
+ response = await deps.webhooks.send({
199
+ url: input.data.url,
200
+ headers: webhookHeaders(secret, event.id, timestamp, body),
201
+ body
202
+ });
203
+ } catch (err) {
204
+ (deps.logger ?? noopLogger).warn("webhook endpoint not reached", {
205
+ orgId: event.orgId,
206
+ automationId: deps.automationId,
207
+ eventId: event.id,
208
+ err
209
+ });
210
+ throw new ActionFailedError(`${DELIVER_WEBHOOK_ACTION}: the endpoint could not be reached`);
211
+ }
212
+ if (response.status < 200 || response.status >= 300) throw new ActionFailedError(`${DELIVER_WEBHOOK_ACTION}: endpoint responded ${response.status}`);
88
213
  }
89
214
  //#endregion
90
215
  export { SEND_EMAIL_DERIVATIONS, executeAction };
@@ -1 +1 @@
1
- {"version":3,"file":"actions.js","names":[],"sources":["../../src/automations/actions.ts"],"sourcesContent":["// automations context — action runners. `executeAction` maps one\n// AutomationAction against the DomainEvent that triggered its run.\n//\n// SEND_EMAIL_DERIVATIONS is the sendEmail runner's internal derivation table:\n// which (trigger, template) pairings are derivable. A template with no entry\n// (e.g. courseCompleted) makes `executeAction` throw a named error, recorded\n// by the engine as a failed action.\nimport type { EmailTemplateId, EmailTemplateParams } from '../types/index.js';\nimport { deliverWebhookInputSchema } from '../types/schemas/index.js';\nimport { entitlementEvents } from '../entitlements/index.js';\nimport type { Mailer, MailerLookups } from '../shared/mailer.js';\nimport type { CredentialStore, DomainEvent } from '../shared/ports.js';\nimport type { AutomationAction } from './model.js';\nimport type { WebhookSender } from './ports.js';\nimport { ALL_EMAIL_TEMPLATE_IDS, DELIVER_WEBHOOK_ACTION, publicDataSchema } from './catalog.js';\nimport { webhookHeaders } from './webhook-signing.js';\n\nexport interface ActionDeps {\n mailer: Pick<Mailer, 'send'>;\n lookups: MailerLookups;\n credentials: Pick<CredentialStore, 'reveal'>;\n webhooks: WebhookSender;\n}\n\ninterface SendEmailDerivation<K extends EmailTemplateId> {\n /** The only event type this template's params can be derived from. */\n trigger: string;\n /** Undefined = the recipient or content behind the event's ids cannot be resolved. */\n derive(\n event: DomainEvent,\n lookups: MailerLookups,\n ): Promise<{ to: string; params: EmailTemplateParams[K] } | undefined>;\n}\n\ntype SendEmailDerivations = { [K in EmailTemplateId]?: SendEmailDerivation<K> };\n\nexport const SEND_EMAIL_DERIVATIONS: SendEmailDerivations = {\n accessGranted: {\n trigger: 'entitlement.created',\n derive: async (event, lookups) => {\n const result = entitlementEvents.entitlementCreated.safeParse(event);\n if (!result.success) {\n return undefined;\n }\n const entitlement = result.data.data;\n // Bundle grants carry no content id — this email is per content item.\n if (!entitlement.contentId) {\n return undefined;\n }\n const [to, content] = await Promise.all([\n lookups.orgUserEmail(entitlement.orgId, entitlement.orgUserId),\n lookups.contentInfo(entitlement.orgId, entitlement.contentId),\n ]);\n if (!to || !content) {\n return undefined;\n }\n return {\n to,\n params: { contentTitle: content.title, contentId: content.id, contentType: content.type },\n };\n },\n },\n accessRevoked: {\n trigger: 'entitlement.deleted',\n derive: async (event, lookups) => {\n const result = entitlementEvents.entitlementDeleted.safeParse(event);\n if (!result.success) {\n return undefined;\n }\n const entitlement = result.data.data;\n // Bundle grants carry no content id — this email is per content item.\n if (!entitlement.contentId) {\n return undefined;\n }\n const [to, content] = await Promise.all([\n lookups.orgUserEmail(entitlement.orgId, entitlement.orgUserId),\n lookups.contentInfo(entitlement.orgId, entitlement.contentId),\n ]);\n if (!to || !content) {\n return undefined;\n }\n return {\n to,\n params: { contentTitle: content.title },\n };\n },\n },\n};\n\nfunction isEmailTemplateId(value: unknown): value is EmailTemplateId {\n return typeof value === 'string' && value in ALL_EMAIL_TEMPLATE_IDS;\n}\n\n/** Throws on any failure — the engine owns retry policy and failure bookkeeping. */\nexport async function executeAction(\n action: AutomationAction,\n event: DomainEvent,\n deps: ActionDeps,\n): Promise<void> {\n switch (action.type) {\n case 'sendEmail': {\n const template = action.input['template'];\n if (!isEmailTemplateId(template)) {\n throw new Error(`sendEmail: unknown template \"${String(template)}\"`);\n }\n const derivation = SEND_EMAIL_DERIVATIONS[template];\n if (!derivation || derivation.trigger !== event.type) {\n throw new Error(\n `sendEmail: template \"${template}\" cannot be derived from event \"${event.type}\"`,\n );\n }\n const derived = await derivation.derive(event, deps.lookups);\n if (!derived) {\n throw new Error(\n `sendEmail: event \"${event.type}\" is missing the data required to derive template \"${template}\"`,\n );\n }\n await deps.mailer.send(derived.to, template, derived.params);\n return;\n }\n case DELIVER_WEBHOOK_ACTION: {\n await deliverWebhook(action, event, deps);\n return;\n }\n default: {\n throw new Error(`unknown automation action type \"${action.type}\"`);\n }\n }\n}\n\nasync function deliverWebhook(\n action: AutomationAction,\n event: DomainEvent,\n deps: ActionDeps,\n): Promise<void> {\n const input = deliverWebhookInputSchema.safeParse(action.input);\n if (!input.success) {\n throw new Error(`${DELIVER_WEBHOOK_ACTION}: invalid input`);\n }\n const publicData = publicDataSchema(event.type);\n if (!publicData) {\n throw new Error(`${DELIVER_WEBHOOK_ACTION}: event \"${event.type}\" is not published`);\n }\n const data = publicData.safeParse(event.data);\n if (!data.success) {\n throw new Error(\n `${DELIVER_WEBHOOK_ACTION}: event \"${event.type}\" does not match its published shape`,\n );\n }\n const secrets = await deps.credentials.reveal(event.orgId, input.data.secretRef);\n const secret = secrets?.['secret'];\n if (typeof secret !== 'string') {\n throw new Error(`${DELIVER_WEBHOOK_ACTION}: signing secret not found`);\n }\n const body = JSON.stringify({\n id: event.id,\n type: event.type,\n version: event.version,\n orgId: event.orgId,\n occurredAt: event.occurredAt,\n data: data.data,\n });\n const timestamp = Math.floor(Date.now() / 1000);\n const response = await deps.webhooks.send({\n url: input.data.url,\n headers: webhookHeaders(secret, event.id, timestamp, body),\n body,\n });\n if (response.status < 200 || response.status >= 300) {\n throw new Error(`${DELIVER_WEBHOOK_ACTION}: endpoint responded ${response.status}`);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"actions.js","names":[],"sources":["../../src/automations/actions.ts"],"sourcesContent":["// automations context — action runners. `executeAction` maps one\n// AutomationAction against the DomainEvent that triggered its run.\n//\n// SEND_EMAIL_DERIVATIONS is the sendEmail runner's internal derivation table:\n// which (trigger, template) pairings are derivable. A template with no entry\n// (e.g. courseCompleted) makes `executeAction` throw a named error, recorded\n// by the engine as a failed action.\nimport type { EmailTemplateId, NotificationKind } from '../types/index.js';\nimport { deliverWebhookInputSchema, notificationKindSchema } from '../types/schemas/index.js';\nimport { entitlementEvents } from '../entitlements/index.js';\nimport type { NewNotification } from '../identity/index.js';\nimport type { DomainEvent } from '../shared/ports.js';\nimport { noopLogger } from '../shared/logger.js';\nimport { ActionFailedError, type AutomationAction } from './model.js';\nimport type { WebhookResponse } from './ports.js';\nimport {\n ALL_EMAIL_TEMPLATE_IDS,\n DELIVER_WEBHOOK_ACTION,\n NOTIFY_ACTION,\n publicDataSchema,\n} from './catalog.js';\nimport { webhookHeaders } from './webhook-signing.js';\nimport { address, firedAtOf, type ActionDeps, type EmailRecipient } from './community-mail.js';\nimport { COMMUNITY_DERIVATIONS, type EmailDerivation } from './community-derivations.js';\nimport { NOTIFICATION_PLACEMENTS } from './community-notifications.js';\n\ntype SendEmailDerivations = { [K in EmailTemplateId]?: EmailDerivation<K> };\n\nexport const SEND_EMAIL_DERIVATIONS: SendEmailDerivations = {\n accessGranted: {\n triggers: ['entitlement.created'],\n derive: async (event, deps) => {\n const result = entitlementEvents.entitlementCreated.safeParse(event);\n if (!result.success) {\n return undefined;\n }\n const entitlement = result.data.data;\n if (!entitlement.contentId) {\n return [];\n }\n const content = await deps.lookups.contentInfo(entitlement.orgId, entitlement.contentId);\n if (!content) {\n return undefined;\n }\n return address(deps, event, entitlement.orgId, [entitlement.orgUserId], {\n contentTitle: content.title,\n contentId: content.id,\n contentType: content.type,\n });\n },\n },\n accessRevoked: {\n triggers: ['entitlement.deleted'],\n derive: async (event, deps) => {\n const result = entitlementEvents.entitlementDeleted.safeParse(event);\n if (!result.success) {\n return undefined;\n }\n const entitlement = result.data.data;\n if (!entitlement.contentId) {\n return [];\n }\n const content = await deps.lookups.contentInfo(entitlement.orgId, entitlement.contentId);\n if (!content) {\n return undefined;\n }\n return address(deps, event, entitlement.orgId, [entitlement.orgUserId], {\n contentTitle: content.title,\n });\n },\n },\n ...COMMUNITY_DERIVATIONS,\n};\n\nexport type { ActionDeps, EmailRecipient };\n\n/** Who an action reached, and who a delivery to failed for (a partial failure does not throw). */\nexport interface ActionOutcome {\n recipients: string[];\n failed: string[];\n}\n\nfunction reachedNoOne(): ActionOutcome {\n return { recipients: [], failed: [] };\n}\n\nfunction assertSomeDelivered(action: string, outcome: ActionOutcome): ActionOutcome {\n if (outcome.recipients.length === 0 && outcome.failed.length > 0) {\n throw new ActionFailedError(\n `${action}: delivery failed for all ${outcome.failed.length} recipients`,\n );\n }\n return outcome;\n}\n\nfunction isEmailTemplateId(value: unknown): value is EmailTemplateId {\n return typeof value === 'string' && value in ALL_EMAIL_TEMPLATE_IDS;\n}\n\n/** Throws on any failure but a partial fan-out — the engine owns retry policy and failure bookkeeping. */\nexport async function executeAction(\n action: AutomationAction,\n event: DomainEvent,\n deps: ActionDeps,\n): Promise<ActionOutcome> {\n switch (action.type) {\n case 'sendEmail': {\n const template = action.input['template'];\n if (!isEmailTemplateId(template)) {\n throw new ActionFailedError(`sendEmail: unknown template \"${String(template)}\"`);\n }\n const derivation = SEND_EMAIL_DERIVATIONS[template];\n if (!derivation || !derivation.triggers.includes(event.type)) {\n throw new ActionFailedError(\n `sendEmail: template \"${template}\" cannot be derived from event \"${event.type}\"`,\n );\n }\n const derived = await derivation.derive(event, { ...deps, actionInput: action.input });\n if (!derived) {\n throw new ActionFailedError(\n `sendEmail: event \"${event.type}\" is missing the data required to derive template \"${template}\"`,\n );\n }\n const outcome = reachedNoOne();\n for (const recipient of derived) {\n const list = (await send(template, recipient, event, deps))\n ? outcome.recipients\n : outcome.failed;\n list.push(recipient.orgUserId);\n }\n return assertSomeDelivered('sendEmail', outcome);\n }\n case NOTIFY_ACTION: {\n const kind = notificationKindSchema.safeParse(action.input['kind']);\n if (!kind.success) {\n throw new ActionFailedError(\n `${NOTIFY_ACTION}: unknown kind \"${String(action.input['kind'])}\"`,\n );\n }\n return notify(kind.data, event, { ...deps, actionInput: action.input });\n }\n case 'grantAccess': {\n await grantAccess(action, event, deps);\n return reachedNoOne();\n }\n case 'publishDuePosts': {\n await deps.scheduledWork.publishDuePosts(event.orgId, firedAtOf(event));\n return reachedNoOne();\n }\n case 'expireDueGrants': {\n await deps.scheduledWork.expireDueGrants(event.orgId, firedAtOf(event));\n return reachedNoOne();\n }\n case 'sendDigest': {\n await deps.scheduledWork.sendDigest(event.orgId, firedAtOf(event));\n return reachedNoOne();\n }\n case 'removeUnattachedUploads': {\n await deps.scheduledWork.removeUnattachedUploads(event.orgId, firedAtOf(event));\n return reachedNoOne();\n }\n case DELIVER_WEBHOOK_ACTION: {\n await deliverWebhook(action, event, deps);\n return reachedNoOne();\n }\n default: {\n throw new ActionFailedError(`unknown automation action type \"${action.type}\"`);\n }\n }\n}\n\nasync function send<K extends EmailTemplateId>(\n template: K,\n recipient: EmailRecipient<K>,\n event: DomainEvent,\n deps: ActionDeps,\n): Promise<boolean> {\n try {\n await deps.mailer.send(recipient.to, template, recipient.params);\n return true;\n } catch (err) {\n (deps.logger ?? noopLogger).warn('automation email not delivered', {\n orgId: event.orgId,\n automationId: deps.automationId,\n trigger: event.type,\n template,\n orgUserId: recipient.orgUserId,\n err,\n });\n return false;\n }\n}\n\nasync function notify<K extends NotificationKind>(\n kind: K,\n event: DomainEvent,\n deps: ActionDeps,\n): Promise<ActionOutcome> {\n const derivation = SEND_EMAIL_DERIVATIONS[kind] as EmailDerivation<K> | undefined;\n if (!derivation || !derivation.triggers.includes(event.type)) {\n throw new ActionFailedError(\n `${NOTIFY_ACTION}: kind \"${kind}\" cannot be derived from event \"${event.type}\"`,\n );\n }\n const recipients = await derivation.derive(event, deps);\n if (!recipients) {\n throw new ActionFailedError(\n `${NOTIFY_ACTION}: event \"${event.type}\" is missing the data required to derive kind \"${kind}\"`,\n );\n }\n const preferences = await deps.desk.preferencesFor(\n event.orgId,\n recipients.map((recipient) => recipient.userId),\n kind,\n );\n const placement = NOTIFICATION_PLACEMENTS[kind];\n const notifications: NewNotification[] = [];\n const outcome = reachedNoOne();\n for (const recipient of recipients) {\n const preference = preferences[recipient.userId];\n const email = preference?.email !== false;\n const inApp = preference?.inApp !== false;\n const emailed = email && (await send(kind, recipient, event, deps));\n let placed = false;\n if (inApp) {\n try {\n const placements = await placement.place(event, recipient, deps);\n notifications.push(\n ...placements.map((spot) => ({ ...spot, userId: recipient.userId, kind })),\n );\n placed = true;\n } catch (err) {\n (deps.logger ?? noopLogger).warn('automation notification not placed', {\n orgId: event.orgId,\n automationId: deps.automationId,\n trigger: event.type,\n kind,\n orgUserId: recipient.orgUserId,\n err,\n });\n }\n }\n if (emailed || placed) {\n outcome.recipients.push(recipient.orgUserId);\n }\n if ((email && !emailed) || (inApp && !placed)) {\n outcome.failed.push(recipient.orgUserId);\n }\n }\n await deps.desk.notify(event.orgId, notifications);\n return assertSomeDelivered(NOTIFY_ACTION, outcome);\n}\n\nfunction grantRecipient(event: DomainEvent): string | undefined {\n if (typeof event.data !== 'object' || event.data === null) {\n return undefined;\n }\n const data = event.data as Record<string, unknown>;\n if (event.type === 'organization.student.created' && typeof data.id === 'string') {\n return data.id;\n }\n if (typeof data.orgUserId === 'string') {\n return data.orgUserId;\n }\n return undefined;\n}\n\nasync function grantAccess(\n action: AutomationAction,\n event: DomainEvent,\n deps: ActionDeps,\n): Promise<void> {\n const contentId = typeof action.input.contentId === 'string' ? action.input.contentId : null;\n const bundleId = typeof action.input.bundleId === 'string' ? action.input.bundleId : null;\n if ((contentId == null) === (bundleId == null)) {\n throw new ActionFailedError('grantAccess: provide exactly one of contentId or bundleId');\n }\n const orgUserId = grantRecipient(event);\n if (!orgUserId) {\n throw new ActionFailedError(`grantAccess: event \"${event.type}\" has no recipient`);\n }\n const days = action.input.expiresInDays;\n const expiresAt =\n typeof days === 'number' && Number.isFinite(days)\n ? new Date(Date.now() + days * 86_400_000)\n : null;\n await deps.grants.grant(event.orgId, { orgUserId, contentId, bundleId, expiresAt });\n}\n\nasync function deliverWebhook(\n action: AutomationAction,\n event: DomainEvent,\n deps: ActionDeps,\n): Promise<void> {\n const input = deliverWebhookInputSchema.safeParse(action.input);\n if (!input.success) {\n throw new ActionFailedError(`${DELIVER_WEBHOOK_ACTION}: invalid input`);\n }\n const publicData = publicDataSchema(event.type);\n if (!publicData) {\n throw new ActionFailedError(\n `${DELIVER_WEBHOOK_ACTION}: event \"${event.type}\" is not published`,\n );\n }\n const data = publicData.safeParse(event.data);\n if (!data.success) {\n throw new ActionFailedError(\n `${DELIVER_WEBHOOK_ACTION}: event \"${event.type}\" does not match its published shape`,\n );\n }\n const secrets = await deps.credentials.reveal(event.orgId, input.data.secretRef);\n const secret = secrets?.['secret'];\n if (typeof secret !== 'string') {\n throw new ActionFailedError(`${DELIVER_WEBHOOK_ACTION}: signing secret not found`);\n }\n const body = JSON.stringify({\n id: event.id,\n type: event.type,\n version: event.version,\n orgId: event.orgId,\n occurredAt: event.occurredAt,\n data: data.data,\n });\n const timestamp = Math.floor(Date.now() / 1000);\n let response: WebhookResponse;\n try {\n response = await deps.webhooks.send({\n url: input.data.url,\n headers: webhookHeaders(secret, event.id, timestamp, body),\n body,\n });\n } catch (err) {\n (deps.logger ?? noopLogger).warn('webhook endpoint not reached', {\n orgId: event.orgId,\n automationId: deps.automationId,\n eventId: event.id,\n err,\n });\n throw new ActionFailedError(`${DELIVER_WEBHOOK_ACTION}: the endpoint could not be reached`);\n }\n if (response.status < 200 || response.status >= 300) {\n throw new ActionFailedError(`${DELIVER_WEBHOOK_ACTION}: endpoint responded ${response.status}`);\n }\n}\n"],"mappings":"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"}
@@ -7,9 +7,10 @@ import { z } from "zod";
7
7
  /** Every EmailTemplateId — a missing key here is a compile error, kept exhaustive by construction. */
8
8
  declare const ALL_EMAIL_TEMPLATE_IDS: Record<EmailTemplateId, true>;
9
9
  declare const DELIVER_WEBHOOK_ACTION = "deliverWebhook";
10
+ declare const NOTIFY_ACTION = "notify";
10
11
  declare function catalogActions(): AvailableActions;
11
12
  declare function catalogTriggers(): AvailableTriggers['triggers'];
12
13
  declare function publicDataSchema(type: string): z.ZodType | undefined;
13
14
  //#endregion
14
- export { ALL_EMAIL_TEMPLATE_IDS, DELIVER_WEBHOOK_ACTION, catalogActions, catalogTriggers, publicDataSchema };
15
+ export { ALL_EMAIL_TEMPLATE_IDS, DELIVER_WEBHOOK_ACTION, NOTIFY_ACTION, catalogActions, catalogTriggers, publicDataSchema };
15
16
  //# sourceMappingURL=catalog.d.ts.map
@@ -1,10 +1,11 @@
1
- import { entitlementEvents } from "../entitlements/events.js";
1
+ import { notificationKindSchema } from "../types/schemas/identity.js";
2
2
  import { assetEvents } from "../assets/events.js";
3
+ import { organizationEvents } from "../organizations/events.js";
4
+ import { entitlementEvents } from "../entitlements/events.js";
3
5
  import { contentEvents } from "../content/events.js";
4
6
  import { discussionEvents } from "../discussion/events.js";
5
7
  import { identityEvents } from "../identity/events.js";
6
8
  import { integrationEvents } from "../integrations/events.js";
7
- import { organizationEvents } from "../organizations/events.js";
8
9
  import { progressEvents } from "../progress/events.js";
9
10
  //#region src/automations/catalog.ts
10
11
  /** Every EmailTemplateId — a missing key here is a compile error, kept exhaustive by construction. */
@@ -17,20 +18,91 @@ const ALL_EMAIL_TEMPLATE_IDS = {
17
18
  emailVerification: true,
18
19
  accessGranted: true,
19
20
  accessRevoked: true,
20
- courseCompleted: true
21
+ courseCompleted: true,
22
+ welcomeToCommunity: true,
23
+ membershipEndingSoon: true,
24
+ membershipEnded: true,
25
+ newPostInSpace: true,
26
+ yourPostWasPublished: true,
27
+ yourPostWasRemoved: true,
28
+ newCommentOnPost: true,
29
+ replyToYourComment: true,
30
+ yourCommentWasRemoved: true,
31
+ requestApproved: true,
32
+ requestDeclined: true,
33
+ invitedToSpace: true,
34
+ messageRequest: true,
35
+ newMessage: true,
36
+ addedToGroupChat: true,
37
+ youAreNowAModerator: true,
38
+ digest: true,
39
+ postAwaitingReview: true,
40
+ joinRequest: true,
41
+ somethingWasFlagged: true,
42
+ messageWasFlagged: true
21
43
  };
22
44
  const DELIVER_WEBHOOK_ACTION = "deliverWebhook";
45
+ const NOTIFY_ACTION = "notify";
23
46
  function catalogActions() {
24
- return [{
25
- type: "sendEmail",
26
- description: "Send a transactional email using a built-in template.",
27
- inputSchema: {
28
- type: "object",
29
- required: ["template"],
30
- properties: { template: { enum: Object.keys(ALL_EMAIL_TEMPLATE_IDS) } }
47
+ return [
48
+ {
49
+ type: "sendEmail",
50
+ description: "Send a transactional email using a built-in template.",
51
+ inputSchema: {
52
+ type: "object",
53
+ required: ["template"],
54
+ properties: { template: { enum: Object.keys(ALL_EMAIL_TEMPLATE_IDS) } }
55
+ },
56
+ source: "system"
57
+ },
58
+ {
59
+ type: NOTIFY_ACTION,
60
+ description: "Tell each person concerned, in the app and by email, as they chose.",
61
+ inputSchema: {
62
+ type: "object",
63
+ required: ["kind"],
64
+ properties: { kind: { enum: [...notificationKindSchema.options] } }
65
+ },
66
+ source: "system"
31
67
  },
32
- source: "system"
33
- }];
68
+ {
69
+ type: "grantAccess",
70
+ description: "Grant a content item or bundle to the person on the event.",
71
+ inputSchema: {
72
+ type: "object",
73
+ properties: {
74
+ contentId: { type: "string" },
75
+ bundleId: { type: "string" },
76
+ expiresInDays: { type: "number" }
77
+ }
78
+ },
79
+ source: "system"
80
+ },
81
+ {
82
+ type: "publishDuePosts",
83
+ description: "Submit scheduled posts whose time has passed.",
84
+ inputSchema: { type: "object" },
85
+ source: "system"
86
+ },
87
+ {
88
+ type: "expireDueGrants",
89
+ description: "Expire grants whose time has passed.",
90
+ inputSchema: { type: "object" },
91
+ source: "system"
92
+ },
93
+ {
94
+ type: "sendDigest",
95
+ description: "Open the daily digest for every community.",
96
+ inputSchema: { type: "object" },
97
+ source: "system"
98
+ },
99
+ {
100
+ type: "removeUnattachedUploads",
101
+ description: "Remove member uploads older than an hour that nothing references.",
102
+ inputSchema: { type: "object" },
103
+ source: "system"
104
+ }
105
+ ];
34
106
  }
35
107
  const TRIGGERS = [
36
108
  {
@@ -75,6 +147,12 @@ const TRIGGERS = [
75
147
  category: "Organization",
76
148
  description: "a user was removed from an organization"
77
149
  },
150
+ {
151
+ event: organizationEvents.orgUserUpdated,
152
+ label: "Member updated",
153
+ category: "Organization",
154
+ description: "an organization member was updated"
155
+ },
78
156
  {
79
157
  event: organizationEvents.inviteCreated,
80
158
  label: "Invite sent",
@@ -213,6 +291,48 @@ const TRIGGERS = [
213
291
  category: "Downloads",
214
292
  description: "download assets were reordered"
215
293
  },
294
+ {
295
+ event: contentEvents.communityCreated,
296
+ label: "Community created",
297
+ category: "Communities",
298
+ description: "a community was created"
299
+ },
300
+ {
301
+ event: contentEvents.communityUpdated,
302
+ label: "Community updated",
303
+ category: "Communities",
304
+ description: "a community was updated"
305
+ },
306
+ {
307
+ event: contentEvents.communityDeleted,
308
+ label: "Community deleted",
309
+ category: "Communities",
310
+ description: "a community was deleted"
311
+ },
312
+ {
313
+ event: contentEvents.spaceCreated,
314
+ label: "Space created",
315
+ category: "Communities",
316
+ description: "a space was created"
317
+ },
318
+ {
319
+ event: contentEvents.spaceUpdated,
320
+ label: "Space updated",
321
+ category: "Communities",
322
+ description: "a space was updated"
323
+ },
324
+ {
325
+ event: contentEvents.spaceDeleted,
326
+ label: "Space deleted",
327
+ category: "Communities",
328
+ description: "a space was deleted"
329
+ },
330
+ {
331
+ event: contentEvents.spacesReordered,
332
+ label: "Spaces reordered",
333
+ category: "Communities",
334
+ description: "spaces were reordered"
335
+ },
216
336
  {
217
337
  event: entitlementEvents.entitlementCreated,
218
338
  label: "Access granted",
@@ -237,6 +357,30 @@ const TRIGGERS = [
237
357
  category: "Access",
238
358
  description: "an entitlement passed its expiry"
239
359
  },
360
+ {
361
+ event: entitlementEvents.entitlementEndingSoon,
362
+ label: "Access ending soon",
363
+ category: "Access",
364
+ description: "an entitlement expires within a week"
365
+ },
366
+ {
367
+ event: entitlementEvents.spaceMemberCreated,
368
+ label: "Space member created",
369
+ category: "Access",
370
+ description: "a person was added to a space"
371
+ },
372
+ {
373
+ event: entitlementEvents.spaceMemberUpdated,
374
+ label: "Space member updated",
375
+ category: "Access",
376
+ description: "a space membership changed"
377
+ },
378
+ {
379
+ event: entitlementEvents.spaceMemberDeleted,
380
+ label: "Space member removed",
381
+ category: "Access",
382
+ description: "a space membership was removed"
383
+ },
240
384
  {
241
385
  event: progressEvents.progressStarted,
242
386
  label: "Progress started",
@@ -291,6 +435,78 @@ const TRIGGERS = [
291
435
  category: "Discussion",
292
436
  description: "a comment was reported"
293
437
  },
438
+ {
439
+ event: discussionEvents.postCreated,
440
+ label: "Post created",
441
+ category: "Discussion",
442
+ description: "a post was created"
443
+ },
444
+ {
445
+ event: discussionEvents.postUpdated,
446
+ label: "Post updated",
447
+ category: "Discussion",
448
+ description: "a post was updated"
449
+ },
450
+ {
451
+ event: discussionEvents.postPublished,
452
+ label: "Post published",
453
+ category: "Discussion",
454
+ description: "a post was published"
455
+ },
456
+ {
457
+ event: discussionEvents.postRemoved,
458
+ label: "Post removed",
459
+ category: "Discussion",
460
+ description: "a post was removed"
461
+ },
462
+ {
463
+ event: discussionEvents.postReported,
464
+ label: "Post reported",
465
+ category: "Discussion",
466
+ description: "a post was reported"
467
+ },
468
+ {
469
+ event: discussionEvents.chatCreated,
470
+ label: "Chat created",
471
+ category: "Discussion",
472
+ description: "a chat was created"
473
+ },
474
+ {
475
+ event: discussionEvents.chatUpdated,
476
+ label: "Chat updated",
477
+ category: "Discussion",
478
+ description: "a chat was updated"
479
+ },
480
+ {
481
+ event: discussionEvents.chatParticipantAdded,
482
+ label: "Chat participant added",
483
+ category: "Discussion",
484
+ description: "a person was added to a chat"
485
+ },
486
+ {
487
+ event: discussionEvents.messageSent,
488
+ label: "Message sent",
489
+ category: "Discussion",
490
+ description: "a message was sent"
491
+ },
492
+ {
493
+ event: discussionEvents.messageDeleted,
494
+ label: "Message deleted",
495
+ category: "Discussion",
496
+ description: "a message was deleted"
497
+ },
498
+ {
499
+ event: discussionEvents.messageReported,
500
+ label: "Message reported",
501
+ category: "Discussion",
502
+ description: "a message was reported"
503
+ },
504
+ {
505
+ event: discussionEvents.digestDue,
506
+ label: "Digest due",
507
+ category: "Discussion",
508
+ description: "a community digest period opened"
509
+ },
294
510
  {
295
511
  event: assetEvents.assetCreated,
296
512
  label: "Upload requested",
@@ -320,6 +536,6 @@ function publicDataSchema(type) {
320
536
  return TRIGGERS.find((trigger) => trigger.event.type === type)?.event.publicData;
321
537
  }
322
538
  //#endregion
323
- export { ALL_EMAIL_TEMPLATE_IDS, DELIVER_WEBHOOK_ACTION, catalogActions, catalogTriggers, publicDataSchema };
539
+ export { ALL_EMAIL_TEMPLATE_IDS, DELIVER_WEBHOOK_ACTION, NOTIFY_ACTION, catalogActions, catalogTriggers, publicDataSchema };
324
540
 
325
541
  //# sourceMappingURL=catalog.js.map