@headless-lms/server 0.4.0 → 0.5.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 (223) hide show
  1. package/README.md +14 -0
  2. package/dist/app/community-mail.d.ts +5 -3
  3. package/dist/app/community-mail.js +60 -12
  4. package/dist/app/community-mail.js.map +1 -1
  5. package/dist/app/container.d.ts +24 -3
  6. package/dist/app/container.js +102 -38
  7. package/dist/app/container.js.map +1 -1
  8. package/dist/app/notification-channels.js +1 -0
  9. package/dist/app/notification-channels.js.map +1 -1
  10. package/dist/app/notification-reads.d.ts +3 -3
  11. package/dist/app/notification-reads.js +7 -28
  12. package/dist/app/notification-reads.js.map +1 -1
  13. package/dist/app/platform.d.ts +14 -0
  14. package/dist/app/platform.js +14 -0
  15. package/dist/app/platform.js.map +1 -0
  16. package/dist/http/config.d.ts +4 -0
  17. package/dist/http/errors.js +9 -4
  18. package/dist/http/errors.js.map +1 -1
  19. package/dist/http/fastify.d.ts +8 -0
  20. package/dist/http/mcp/catalogue.d.ts +11 -9
  21. package/dist/http/mcp/catalogue.js +15 -21
  22. package/dist/http/mcp/catalogue.js.map +1 -1
  23. package/dist/http/mcp/challenge.js +3 -3
  24. package/dist/http/mcp/challenge.js.map +1 -1
  25. package/dist/http/mcp/route.js +7 -7
  26. package/dist/http/mcp/route.js.map +1 -1
  27. package/dist/http/mcp/test-support.d.ts +6 -5
  28. package/dist/http/mcp/test-support.js +32 -11
  29. package/dist/http/mcp/test-support.js.map +1 -1
  30. package/dist/http/mcp/tool.d.ts +3 -9
  31. package/dist/http/mcp/tool.js +14 -6
  32. package/dist/http/mcp/tool.js.map +1 -1
  33. package/dist/http/mcp/tools/automations.js +10 -20
  34. package/dist/http/mcp/tools/automations.js.map +1 -1
  35. package/dist/http/mcp/tools/catalog.js +25 -42
  36. package/dist/http/mcp/tools/catalog.js.map +1 -1
  37. package/dist/http/mcp/tools/comments.js +9 -19
  38. package/dist/http/mcp/tools/comments.js.map +1 -1
  39. package/dist/http/mcp/tools/communities.js +16 -31
  40. package/dist/http/mcp/tools/communities.js.map +1 -1
  41. package/dist/http/mcp/tools/community-members.js +9 -16
  42. package/dist/http/mcp/tools/community-members.js.map +1 -1
  43. package/dist/http/mcp/tools/community-moderation.js +13 -25
  44. package/dist/http/mcp/tools/community-moderation.js.map +1 -1
  45. package/dist/http/mcp/tools/courses.js +11 -21
  46. package/dist/http/mcp/tools/courses.js.map +1 -1
  47. package/dist/http/mcp/tools/events.d.ts +6 -0
  48. package/dist/http/mcp/tools/events.js +170 -0
  49. package/dist/http/mcp/tools/events.js.map +1 -0
  50. package/dist/http/mcp/tools/index.d.ts +5 -1
  51. package/dist/http/mcp/tools/index.js +45 -10
  52. package/dist/http/mcp/tools/index.js.map +1 -1
  53. package/dist/http/mcp/tools/organization.d.ts +6 -0
  54. package/dist/http/mcp/tools/organization.js +214 -0
  55. package/dist/http/mcp/tools/organization.js.map +1 -0
  56. package/dist/http/mcp/tools/people.js +30 -49
  57. package/dist/http/mcp/tools/people.js.map +1 -1
  58. package/dist/http/plugins/auth.js +37 -8
  59. package/dist/http/plugins/auth.js.map +1 -1
  60. package/dist/http/plugins/error-handler.d.ts +5 -1
  61. package/dist/http/plugins/error-handler.js +10 -2
  62. package/dist/http/plugins/error-handler.js.map +1 -1
  63. package/dist/http/plugins/openapi.js +18 -2
  64. package/dist/http/plugins/openapi.js.map +1 -1
  65. package/dist/http/plugins/platform.d.ts +7 -0
  66. package/dist/http/plugins/platform.js +13 -0
  67. package/dist/http/plugins/platform.js.map +1 -0
  68. package/dist/http/routes/access.js +54 -36
  69. package/dist/http/routes/access.js.map +1 -1
  70. package/dist/http/routes/activities.js +45 -45
  71. package/dist/http/routes/activities.js.map +1 -1
  72. package/dist/http/routes/assets.js +30 -65
  73. package/dist/http/routes/assets.js.map +1 -1
  74. package/dist/http/routes/automations.js +37 -42
  75. package/dist/http/routes/automations.js.map +1 -1
  76. package/dist/http/routes/bundles.js +33 -36
  77. package/dist/http/routes/bundles.js.map +1 -1
  78. package/dist/http/routes/communities.js +93 -145
  79. package/dist/http/routes/communities.js.map +1 -1
  80. package/dist/http/routes/community-moderation.js +83 -137
  81. package/dist/http/routes/community-moderation.js.map +1 -1
  82. package/dist/http/routes/course-analytics.js +32 -15
  83. package/dist/http/routes/course-analytics.js.map +1 -1
  84. package/dist/http/routes/courses.js +25 -32
  85. package/dist/http/routes/courses.js.map +1 -1
  86. package/dist/http/routes/dashboard.js +9 -9
  87. package/dist/http/routes/dashboard.js.map +1 -1
  88. package/dist/http/routes/discussion.js +21 -39
  89. package/dist/http/routes/discussion.js.map +1 -1
  90. package/dist/http/routes/downloads.js +41 -47
  91. package/dist/http/routes/downloads.js.map +1 -1
  92. package/dist/http/routes/entitlements.js +19 -23
  93. package/dist/http/routes/entitlements.js.map +1 -1
  94. package/dist/http/routes/events.d.ts +7 -0
  95. package/dist/http/routes/events.js +333 -0
  96. package/dist/http/routes/events.js.map +1 -0
  97. package/dist/http/routes/integrations.js +40 -73
  98. package/dist/http/routes/integrations.js.map +1 -1
  99. package/dist/http/routes/member/assets.js +10 -13
  100. package/dist/http/routes/member/assets.js.map +1 -1
  101. package/dist/http/routes/member/chats.js +170 -47
  102. package/dist/http/routes/member/chats.js.map +1 -1
  103. package/dist/http/routes/member/comments.js +65 -26
  104. package/dist/http/routes/member/comments.js.map +1 -1
  105. package/dist/http/routes/member/community.js +45 -52
  106. package/dist/http/routes/member/community.js.map +1 -1
  107. package/dist/http/routes/member/courses.js +12 -19
  108. package/dist/http/routes/member/courses.js.map +1 -1
  109. package/dist/http/routes/member/devices.js +9 -10
  110. package/dist/http/routes/member/devices.js.map +1 -1
  111. package/dist/http/routes/member/downloads.js +6 -9
  112. package/dist/http/routes/member/downloads.js.map +1 -1
  113. package/dist/http/routes/member/events.d.ts +7 -0
  114. package/dist/http/routes/member/events.js +386 -0
  115. package/dist/http/routes/member/events.js.map +1 -0
  116. package/dist/http/routes/member/files.js +71 -46
  117. package/dist/http/routes/member/files.js.map +1 -1
  118. package/dist/http/routes/member/index.js +2 -0
  119. package/dist/http/routes/member/index.js.map +1 -1
  120. package/dist/http/routes/member/notification-settings.js +15 -23
  121. package/dist/http/routes/member/notification-settings.js.map +1 -1
  122. package/dist/http/routes/member/notifications.js +16 -16
  123. package/dist/http/routes/member/notifications.js.map +1 -1
  124. package/dist/http/routes/member/org.js +5 -15
  125. package/dist/http/routes/member/org.js.map +1 -1
  126. package/dist/http/routes/member/posts.js +67 -68
  127. package/dist/http/routes/member/posts.js.map +1 -1
  128. package/dist/http/routes/member/profile.js +15 -6
  129. package/dist/http/routes/member/profile.js.map +1 -1
  130. package/dist/http/routes/member/progress.js +7 -11
  131. package/dist/http/routes/member/progress.js.map +1 -1
  132. package/dist/http/routes/organizations.js +319 -135
  133. package/dist/http/routes/organizations.js.map +1 -1
  134. package/dist/http/routes/platform.d.ts +7 -0
  135. package/dist/http/routes/platform.js +61 -0
  136. package/dist/http/routes/platform.js.map +1 -0
  137. package/dist/http/routes/students.js +12 -75
  138. package/dist/http/routes/students.js.map +1 -1
  139. package/dist/http/routes/webhooks.js +30 -39
  140. package/dist/http/routes/webhooks.js.map +1 -1
  141. package/dist/http/routes.js +9 -0
  142. package/dist/http/routes.js.map +1 -1
  143. package/dist/http/schemas/access.d.ts +346 -64
  144. package/dist/http/schemas/access.js +39 -12
  145. package/dist/http/schemas/access.js.map +1 -1
  146. package/dist/http/schemas/assets.d.ts +19 -14
  147. package/dist/http/schemas/assets.js +6 -5
  148. package/dist/http/schemas/assets.js.map +1 -1
  149. package/dist/http/schemas/automations.d.ts +4 -4
  150. package/dist/http/schemas/chats.d.ts +211 -32
  151. package/dist/http/schemas/chats.js +13 -4
  152. package/dist/http/schemas/chats.js.map +1 -1
  153. package/dist/http/schemas/community.d.ts +91 -44
  154. package/dist/http/schemas/community.js +21 -8
  155. package/dist/http/schemas/community.js.map +1 -1
  156. package/dist/http/schemas/course-analytics.d.ts +51 -1
  157. package/dist/http/schemas/course-analytics.js +19 -1
  158. package/dist/http/schemas/course-analytics.js.map +1 -1
  159. package/dist/http/schemas/dashboard.d.ts +1 -0
  160. package/dist/http/schemas/dashboard.js +2 -1
  161. package/dist/http/schemas/dashboard.js.map +1 -1
  162. package/dist/http/schemas/discussion.d.ts +22 -72
  163. package/dist/http/schemas/discussion.js +2 -2
  164. package/dist/http/schemas/discussion.js.map +1 -1
  165. package/dist/http/schemas/entitlements.d.ts +9 -12
  166. package/dist/http/schemas/entitlements.js +11 -5
  167. package/dist/http/schemas/entitlements.js.map +1 -1
  168. package/dist/http/schemas/events.d.ts +527 -0
  169. package/dist/http/schemas/events.js +64 -0
  170. package/dist/http/schemas/events.js.map +1 -0
  171. package/dist/http/schemas/files.d.ts +15 -5
  172. package/dist/http/schemas/files.js +10 -2
  173. package/dist/http/schemas/files.js.map +1 -1
  174. package/dist/http/schemas/index.d.ts +11 -9
  175. package/dist/http/schemas/index.js +11 -9
  176. package/dist/http/schemas/integrations.d.ts +4 -4
  177. package/dist/http/schemas/invites.d.ts +3 -47
  178. package/dist/http/schemas/invites.js +4 -25
  179. package/dist/http/schemas/invites.js.map +1 -1
  180. package/dist/http/schemas/member.d.ts +94 -7
  181. package/dist/http/schemas/member.js +7 -3
  182. package/dist/http/schemas/member.js.map +1 -1
  183. package/dist/http/schemas/members.d.ts +333 -69
  184. package/dist/http/schemas/members.js +67 -25
  185. package/dist/http/schemas/members.js.map +1 -1
  186. package/dist/http/schemas/notifications.d.ts +57 -53
  187. package/dist/http/schemas/platform.d.ts +37 -0
  188. package/dist/http/schemas/platform.js +19 -0
  189. package/dist/http/schemas/platform.js.map +1 -0
  190. package/dist/http/schemas/posts.d.ts +55 -35
  191. package/dist/http/schemas/posts.js +4 -4
  192. package/dist/http/schemas/posts.js.map +1 -1
  193. package/dist/http/schemas/profile.d.ts +1 -1
  194. package/dist/http/schemas/shared.d.ts +1 -11
  195. package/dist/http/schemas/shared.js +1 -10
  196. package/dist/http/schemas/shared.js.map +1 -1
  197. package/dist/http/schemas/students.d.ts +25 -3
  198. package/dist/http/schemas/students.js +3 -1
  199. package/dist/http/schemas/students.js.map +1 -1
  200. package/dist/http/scope.d.ts +9 -13
  201. package/dist/http/scope.js +29 -40
  202. package/dist/http/scope.js.map +1 -1
  203. package/dist/http/server.js +9 -0
  204. package/dist/http/server.js.map +1 -1
  205. package/dist/http/shutdown.d.ts +11 -0
  206. package/dist/http/shutdown.js +40 -0
  207. package/dist/http/shutdown.js.map +1 -0
  208. package/dist/http/sse.d.ts +19 -0
  209. package/dist/http/sse.js +104 -0
  210. package/dist/http/sse.js.map +1 -0
  211. package/dist/http/test-caller.d.ts +13 -0
  212. package/dist/http/test-caller.js +14 -0
  213. package/dist/http/test-caller.js.map +1 -0
  214. package/dist/http/well-known.js +2 -2
  215. package/dist/http/well-known.js.map +1 -1
  216. package/dist/index.d.ts +4 -2
  217. package/dist/index.js +4 -2
  218. package/dist/index.js.map +1 -1
  219. package/dist/packages/server/package.js +1 -1
  220. package/package.json +7 -6
  221. package/dist/http/routes/member/actor.d.ts +0 -14
  222. package/dist/http/routes/member/actor.js +0 -11
  223. package/dist/http/routes/member/actor.js.map +0 -1
@@ -3,7 +3,8 @@ import { loadIntegrations } from "./integrations.js";
3
3
  import { mcpEndpoints } from "./mcp-endpoints.js";
4
4
  import { communityMail } from "./community-mail.js";
5
5
  import { notificationReads } from "./notification-reads.js";
6
- import { DrizzleAdminRepository, DrizzleAssetsRepository, DrizzleAutomationRunsRepository, DrizzleAutomationsRepository, DrizzleChatRepository, DrizzleConnectionsRepository, DrizzleContentRepository, DrizzleCredentialStore, DrizzleDevicesRepository, DrizzleDigestRunRepository, DrizzleDiscussionRepository, DrizzleEntitlementsRepository, DrizzleIdentityRepository, DrizzleMemberRepository, DrizzleMembersRepository, DrizzleMentionsRepository, DrizzleNotificationSettingsRepository, DrizzleNotificationsRepository, DrizzleOrganizationsRepository, DrizzleOutboxAppender, DrizzleOutboxStore, DrizzlePostRepository, DrizzleProgressRepository, DrizzleSettingsRepository, DrizzleSubscriptionsRepository, DrizzleUnitOfWork, createDb } from "@headless-lms/adapter-db";
6
+ import { platformKeyCheck } from "./platform.js";
7
+ import { DrizzleAdminRepository, DrizzleAssetsRepository, DrizzleAutomationRunsRepository, DrizzleAutomationsRepository, DrizzleChatRepository, DrizzleConnectionsRepository, DrizzleContentRepository, DrizzleCredentialStore, DrizzleDevicesRepository, DrizzleDigestRunRepository, DrizzleDiscussionRepository, DrizzleEntitlementsRepository, DrizzleIdentityRepository, DrizzleMemberRepository, DrizzleMembersRepository, DrizzleMentionsRepository, DrizzleNotificationSettingsRepository, DrizzleNotificationsRepository, DrizzleOrganizationsRepository, DrizzleOutboxAppender, DrizzleOutboxStore, DrizzlePostRepository, DrizzleProgressRepository, DrizzleRolesRepository, DrizzleSettingsRepository, DrizzleSubscriptionsRepository, DrizzleUnitOfWork, createDb } from "@headless-lms/adapter-db";
7
8
  import { InMemoryEventBus } from "@headless-lms/adapter-defaults/events";
8
9
  import { PollingOutboxRelay } from "@headless-lms/adapter-defaults/events/outbox-relay";
9
10
  import { InlineAutomationEngine } from "@headless-lms/adapter-defaults/workflows";
@@ -11,13 +12,14 @@ import { FetchWebhookSender } from "@headless-lms/adapter-defaults/webhooks";
11
12
  import { EmailAdapter, StubTemplateRenderer } from "@headless-lms/adapter-defaults/email";
12
13
  import { createRootLogger, requestLogContext } from "@headless-lms/adapter-defaults/logging";
13
14
  import { StorageAdapter } from "@headless-lms/adapter-defaults/storage";
15
+ import { PlainBodyParser } from "@headless-lms/adapter-defaults/body";
14
16
  import { BetterAuth } from "@headless-lms/adapter-auth";
15
- import { CommunityNotificationCategories, ContentItemTitles, ContentService } from "@headless-lms/core/content";
17
+ import { CommunityNotificationCategories, ContentItemTitles, ContentService, contentJobs } from "@headless-lms/core/content";
16
18
  import { EntitlementsServiceImpl, entitlementsJobs, entitlementsRecurringJobs } from "@headless-lms/core/entitlements";
17
19
  import { ProgressServiceImpl } from "@headless-lms/core/progress";
18
- import { ChatServiceImpl, DigestServiceImpl, DiscussionItemTitles, DiscussionServiceImpl, PostServiceImpl, SubscriptionServiceImpl, discussionJobs, discussionRecurringJobs } from "@headless-lms/core/discussion";
20
+ import { ChatLiveForwarder, ChatServiceImpl, DigestServiceImpl, DiscussionItemTitles, DiscussionServiceImpl, PostServiceImpl, SubscriptionServiceImpl, discussionJobs, discussionRecurringJobs } from "@headless-lms/core/discussion";
19
21
  import { DevicesServiceImpl, IdentityServiceImpl, NotificationSettingsServiceImpl } from "@headless-lms/core/identity";
20
- import { OrgNotificationCategories, OrganizationServiceImpl, parseRole } from "@headless-lms/core/organizations";
22
+ import { NotificationCatalog, OrgNotificationCategories, OrganizationServiceImpl } from "@headless-lms/core/organizations";
21
23
  import { AssetsServiceImpl, assetsJobs, assetsRecurringJobs, createFileUrls } from "@headless-lms/core/assets";
22
24
  import { IntegrationsServiceImpl } from "@headless-lms/core/integrations";
23
25
  import { AutomationsServiceImpl, DEFAULT_COMMUNITY_CATEGORIES, DEFAULT_ORG_CATEGORIES, NOTIFICATION_TYPES, NotificationDelivery, NotificationProcessor, NotificationsServiceImpl, RECIPIENT_RULES, SchedulerChatDebounce, notificationJobs } from "@headless-lms/core/automations";
@@ -54,12 +56,15 @@ async function buildContainer(config, options) {
54
56
  const automationsLogger = logger.child({ name: "automations" });
55
57
  const reportingLogger = logger.child({ name: "reporting" });
56
58
  const outboxLogger = logger.child({ name: "outbox" });
57
- const db = createDb(config.databaseUrl);
59
+ const db = createDb(config.databaseUrl, logger.child({ name: "db" }), config.databasePool);
58
60
  const email = options?.adapters?.email ?? new EmailAdapter(logger.child({ name: "email" }));
59
61
  const storage = options?.adapters?.storage ?? new StorageAdapter(logger.child({ name: "storage" }));
60
62
  const templates = options?.adapters?.templates ?? new StubTemplateRenderer(logger.child({ name: "email" }));
63
+ const bodies = options?.adapters?.bodyParser ?? new PlainBodyParser();
61
64
  const workflows = options?.adapters?.workflows;
62
65
  const automationEngine = typeof workflows === "function" ? workflows(automationsLogger) : workflows ?? new InlineAutomationEngine(automationsLogger);
66
+ const pubsubOption = options?.adapters?.pubsub;
67
+ const pubsub = typeof pubsubOption === "function" ? pubsubOption(logger.child({ name: "pubsub" })) : pubsubOption ?? null;
63
68
  const mailer = new Mailer(templates, email, {
64
69
  ...config.emailBranding ?? {
65
70
  brandName: "Headless LMS",
@@ -67,7 +72,7 @@ async function buildContainer(config, options) {
67
72
  },
68
73
  studentPortalUrl: config.studentPortalUrl
69
74
  });
70
- const fileUrls = createFileUrls(config.authBaseURL);
75
+ const fileUrls = createFileUrls(config.authBaseURL, (key) => storage.publicUrl(key));
71
76
  const assets = new AssetsServiceImpl({
72
77
  storage,
73
78
  repo: new DrizzleAssetsRepository(db, assetsLogger),
@@ -76,6 +81,7 @@ async function buildContainer(config, options) {
76
81
  outbox: new DrizzleOutboxAppender(tx, outboxLogger)
77
82
  })),
78
83
  memberImageMaxBytes: config.memberImageMaxBytes,
84
+ memberFileMaxBytes: config.memberFileMaxBytes,
79
85
  downloadLinkExpirySeconds: config.deliveryExpirySeconds,
80
86
  fileUrls,
81
87
  logger: assetsLogger
@@ -95,24 +101,46 @@ async function buildContainer(config, options) {
95
101
  const organizationsRepo = new DrizzleOrganizationsRepository(db, logger.child({ name: "org" }));
96
102
  const organizationsUow = new DrizzleUnitOfWork(db, (tx) => ({
97
103
  organizations: new DrizzleOrganizationsRepository(tx, logger.child({ name: "org" })),
104
+ roles: new DrizzleRolesRepository(tx),
98
105
  outbox: new DrizzleOutboxAppender(tx, logger.child({ name: "org" }))
99
106
  }));
100
107
  const orgAdminRef = { current: void 0 };
108
+ const platform = config.platform ? platformKeyCheck(config.platform) : null;
101
109
  const organizations = new OrganizationServiceImpl({
102
110
  repo: organizationsRepo,
111
+ roles: new DrizzleRolesRepository(db),
112
+ ...options?.roles && { defaultRoles: options.roles },
113
+ apiKeys: () => {
114
+ if (!orgAdminRef.current) throw new Error("api keys not initialised");
115
+ return orgAdminRef.current;
116
+ },
117
+ accounts: () => {
118
+ if (!orgAdminRef.current) throw new Error("accounts not initialised");
119
+ return orgAdminRef.current;
120
+ },
121
+ ...config.platform && { platform: {
122
+ email: config.platform.accountEmail,
123
+ name: config.platform.accountName ?? "Platform"
124
+ } },
103
125
  membersRepo: new DrizzleMembersRepository(db, logger.child({ name: "org" })),
104
126
  people: identity,
127
+ images: assets,
105
128
  uow: organizationsUow,
106
129
  orgAdmin: () => {
107
130
  if (!orgAdminRef.current) throw new Error("orgAdmin not initialised");
108
131
  return orgAdminRef.current;
109
132
  },
133
+ sessions: () => {
134
+ if (!orgAdminRef.current) throw new Error("sessions not initialised");
135
+ return orgAdminRef.current;
136
+ },
110
137
  logger: logger.child({ name: "org" }),
111
138
  mailer,
112
139
  inviteUrls: {
113
140
  studentPortalUrl: config.studentPortalUrl,
114
141
  adminAppUrl: config.adminAppUrl
115
- }
142
+ },
143
+ grants: () => entitlements
116
144
  });
117
145
  const settings = new SettingsService(new DrizzleSettingsRepository(db, logger.child({ name: "settings" })));
118
146
  const contentUow = new DrizzleUnitOfWork(db, (tx) => ({
@@ -125,6 +153,7 @@ async function buildContainer(config, options) {
125
153
  libraryAssets: assets,
126
154
  files: assets,
127
155
  fileUrls,
156
+ scheduler: automationEngine,
128
157
  logger: contentLogger
129
158
  });
130
159
  const pushConfig = options?.adapters?.push ?? {};
@@ -158,24 +187,21 @@ async function buildContainer(config, options) {
158
187
  logger: logger.child({ name: "org" })
159
188
  })
160
189
  };
161
- const notificationCategoryLists = { listFor: async (orgId, communityId) => (communityId ? await notificationCategories.community.get(orgId, communityId) : await notificationCategories.org.get(orgId, orgId)).categories };
190
+ const notificationCatalog = new NotificationCatalog({
191
+ types: notificationRegistries.types,
192
+ channels: notificationRegistries.channels
193
+ });
194
+ const notificationCategoryLists = { listFor: async (orgId, communityId) => (communityId ? await notificationCategories.community.get(communityId, organizations.systemActor(orgId)) : await notificationCategories.org.get(orgId, organizations.systemActor(orgId))).categories };
162
195
  const entitlementsUow = new DrizzleUnitOfWork(db, (tx) => ({
163
196
  entitlements: new DrizzleEntitlementsRepository(tx, entitlementsLogger),
164
197
  spaces: new DrizzleContentRepository(tx, contentLogger),
165
198
  outbox: new DrizzleOutboxAppender(tx, outboxLogger)
166
199
  }));
167
- const authors = {
168
- roleOf: async (orgId, orgUserId) => {
169
- return (await organizationsRepo.findOrgUserById(orgId, orgUserId))?.role ?? null;
170
- },
171
- rolesOf: async (orgId, orgUserIds) => {
172
- const rows = await organizationsRepo.findOrgUsersByIds(orgId, orgUserIds);
173
- return Object.fromEntries(rows.map((row) => [row.id, row.role]));
174
- }
175
- };
200
+ const authors = organizations;
176
201
  const entitlements = new EntitlementsServiceImpl({
177
202
  repo: new DrizzleEntitlementsRepository(db, entitlementsLogger),
178
203
  content,
204
+ permissions: organizations,
179
205
  uow: entitlementsUow,
180
206
  logger: entitlementsLogger
181
207
  });
@@ -183,10 +209,10 @@ async function buildContainer(config, options) {
183
209
  const notificationSettings = new NotificationSettingsServiceImpl({
184
210
  repo: new DrizzleNotificationSettingsRepository(db, identityLogger),
185
211
  categories: {
186
- communityList: async (orgId, communityId) => (await notificationCategories.community.get(orgId, communityId)).categories,
187
- orgList: async (orgId) => (await notificationCategories.org.get(orgId, orgId)).categories
212
+ communityList: async (orgId, communityId) => (await notificationCategories.community.get(communityId, organizations.systemActor(orgId))).categories,
213
+ orgList: async (orgId) => (await notificationCategories.org.get(orgId, organizations.systemActor(orgId))).categories
188
214
  },
189
- access: { canOpen: (orgId, actor, communityId) => entitlements.isCommunityMember(orgId, actor, communityId) },
215
+ access: { canOpen: (orgId, actor, communityId) => entitlements.isCommunityMember(communityId, actor) },
190
216
  channels: notificationRegistries.channels,
191
217
  logger: identityLogger
192
218
  });
@@ -203,6 +229,7 @@ async function buildContainer(config, options) {
203
229
  repo: new DrizzleProgressRepository(db, progressLogger),
204
230
  content,
205
231
  access: entitlements,
232
+ system: organizations,
206
233
  uow: progressUow,
207
234
  logger: progressLogger
208
235
  });
@@ -222,6 +249,7 @@ async function buildContainer(config, options) {
222
249
  mentions: mentionReads,
223
250
  images: assets,
224
251
  fileUrls,
252
+ bodies,
225
253
  uow: new DrizzleUnitOfWork(db, (tx) => ({
226
254
  posts: new DrizzlePostRepository(tx, discussionLogger),
227
255
  mentions: new DrizzleMentionsRepository(tx, discussionLogger),
@@ -245,6 +273,8 @@ async function buildContainer(config, options) {
245
273
  mentions: new DrizzleMentionsRepository(tx, discussionLogger),
246
274
  outbox: new DrizzleOutboxAppender(tx, outboxLogger)
247
275
  })),
276
+ ...pubsub && { pubsub },
277
+ ...options?.live && { live: options.live },
248
278
  logger: discussionLogger
249
279
  });
250
280
  const discussion = new DiscussionServiceImpl({
@@ -253,6 +283,7 @@ async function buildContainer(config, options) {
253
283
  content,
254
284
  posts,
255
285
  spaces: entitlements,
286
+ authors,
256
287
  mentions: mentionReads,
257
288
  fileUrls,
258
289
  uow: new DrizzleUnitOfWork(db, (tx) => ({
@@ -268,6 +299,10 @@ async function buildContainer(config, options) {
268
299
  access: entitlements,
269
300
  posts,
270
301
  chats,
302
+ uow: new DrizzleUnitOfWork(db, (tx) => ({
303
+ subscriptions: new DrizzleSubscriptionsRepository(tx, discussionLogger),
304
+ outbox: new DrizzleOutboxAppender(tx, outboxLogger)
305
+ })),
271
306
  logger: discussionLogger
272
307
  });
273
308
  const digests = new DigestServiceImpl({
@@ -292,15 +327,23 @@ async function buildContainer(config, options) {
292
327
  messages: chatReads,
293
328
  profiles: discussionReads,
294
329
  activities: content,
295
- access: entitlements
330
+ access: entitlements,
331
+ actors: organizations
296
332
  }),
297
333
  content: new ContentItemTitles({
298
334
  content,
299
- access: entitlements
335
+ access: entitlements,
336
+ organizations
300
337
  })
301
338
  };
302
339
  const reporting = {
303
- admin: new AdminReportServiceImpl({ repo: new DrizzleAdminRepository(db, reportingLogger) }),
340
+ admin: new AdminReportServiceImpl({
341
+ repo: new DrizzleAdminRepository(db, reportingLogger),
342
+ permissions: organizations,
343
+ spaces: entitlements,
344
+ events: content,
345
+ fileUrls
346
+ }),
304
347
  member: new MemberReportServiceImpl({
305
348
  repo: new DrizzleMemberRepository(db, reportingLogger),
306
349
  content,
@@ -308,12 +351,14 @@ async function buildContainer(config, options) {
308
351
  files: assets,
309
352
  fileUrls,
310
353
  access: entitlements,
354
+ people: organizations,
311
355
  notifications: notificationList,
312
356
  titles: itemTitles,
313
357
  subscriptions,
314
358
  notificationCategories,
315
359
  notificationSettings,
316
- channels: notificationRegistries.channels
360
+ channels: notificationRegistries.channels,
361
+ studentPortalUrl: config.studentPortalUrl
317
362
  })
318
363
  };
319
364
  const credentialStore = new DrizzleCredentialStore(db, config.credentialStoreKey, integrationsLogger);
@@ -346,23 +391,30 @@ async function buildContainer(config, options) {
346
391
  } : null;
347
392
  },
348
393
  contentInfo: async (orgId, contentId) => {
349
- const course = await content.getCourse(orgId, contentId);
394
+ const system = organizations.systemActor(orgId);
395
+ const course = await content.findCourse(contentId, system);
350
396
  if (course) return {
351
397
  id: course.id,
352
398
  title: course.title,
353
399
  type: course.type
354
400
  };
355
- const download = await content.getDownload(orgId, contentId);
401
+ const download = await content.findDownload(contentId, system);
356
402
  if (download) return {
357
403
  id: download.id,
358
404
  title: download.title,
359
405
  type: download.type
360
406
  };
361
- const community = await content.getCommunity(orgId, contentId);
362
- return community ? {
407
+ const community = await content.findCommunity(contentId, system);
408
+ if (community) return {
363
409
  id: community.id,
364
410
  title: community.title,
365
411
  type: community.type
412
+ };
413
+ const event = await content.findEvent(contentId, system);
414
+ return event ? {
415
+ id: event.id,
416
+ title: event.title,
417
+ type: event.type
366
418
  } : null;
367
419
  }
368
420
  };
@@ -375,7 +427,8 @@ async function buildContainer(config, options) {
375
427
  posts,
376
428
  subscriptions,
377
429
  discussion,
378
- chats
430
+ chats,
431
+ bodies
379
432
  });
380
433
  const recipientReads = notificationReads({
381
434
  content,
@@ -386,6 +439,7 @@ async function buildContainer(config, options) {
386
439
  subscriptions,
387
440
  discussion,
388
441
  chats,
442
+ bodies,
389
443
  settings: notificationSettings
390
444
  });
391
445
  const notificationProcessor = new NotificationProcessor({
@@ -409,6 +463,7 @@ async function buildContainer(config, options) {
409
463
  mailer,
410
464
  lookups: mailerLookups,
411
465
  integrations,
466
+ systemActor: (orgId) => organizations.systemActor(orgId),
412
467
  credentials: credentialStore,
413
468
  webhooks: options?.adapters?.webhooks ?? new FetchWebhookSender({ allowPrivateNetwork: config.webhookAllowPrivateNetwork }),
414
469
  mail: wired.mail,
@@ -429,6 +484,10 @@ async function buildContainer(config, options) {
429
484
  digests,
430
485
  logger: discussionLogger
431
486
  }),
487
+ ...contentJobs({
488
+ events: content,
489
+ logger: contentLogger
490
+ }),
432
491
  ...entitlementsJobs({ grants: entitlements }),
433
492
  ...assetsJobs({
434
493
  assets,
@@ -446,6 +505,10 @@ async function buildContainer(config, options) {
446
505
  ...assetsRecurringJobs
447
506
  ]);
448
507
  const eventBus = new InMemoryEventBus();
508
+ if (pubsub) new ChatLiveForwarder({
509
+ pubsub,
510
+ logger: discussionLogger
511
+ }).attach(eventBus);
449
512
  eventBus.subscribeAll((event) => automations.handle(event));
450
513
  const outboxConfig = resolveOutboxConfig(config.outbox);
451
514
  const outboxRelay = new PollingOutboxRelay(new DrizzleOutboxStore(db, outboxLogger), eventBus, outboxConfig, outboxLogger);
@@ -504,6 +567,10 @@ async function buildContainer(config, options) {
504
567
  beforeCreateSession: authHook("beforeCreateSession", async (session) => {
505
568
  const person = await identity.getUserByExternalId(session.userId);
506
569
  if (!person) return;
570
+ if (organizations.isPlatformAccount(person.email)) {
571
+ authLogger.warn("sign-in refused for the platform account", { userId: person.id });
572
+ return false;
573
+ }
507
574
  const orgs = await organizations.getOrgUsersForUser(person.id);
508
575
  if (orgs.length !== 1) return;
509
576
  const org = await organizations.getById(orgs[0].orgId);
@@ -514,7 +581,7 @@ async function buildContainer(config, options) {
514
581
  } };
515
582
  }),
516
583
  beforeCreateOrganization: authHook("beforeCreateOrganization", async ({ organization: org, user: baUser }) => {
517
- const domainOrg = await organizations.createOrganization({
584
+ const domainOrg = await organizations.provisionOrganization({
518
585
  ownerId: baUser.id,
519
586
  ...org,
520
587
  logo: org.logo ?? void 0
@@ -524,22 +591,15 @@ async function buildContainer(config, options) {
524
591
  id: domainOrg.id
525
592
  } };
526
593
  }),
527
- beforeUpdateOrganization: authHook("beforeUpdateOrganization", async ({ organization }) => {
528
- await organizations.updateOrganization(organization.id, organization);
529
- }),
530
594
  beforeDeleteOrganization: authHook("beforeDeleteOrganization", async ({ organization }) => {
531
595
  await organizations.deleteOrganization(organization.id);
532
596
  }),
533
597
  beforeAddMember: authHook("beforeAddMember", async ({ member }) => {
534
598
  await organizations.addOrgUser({
535
599
  orgId: member.organizationId,
536
- userId: member.userId,
537
- role: parseRole(member.role)
600
+ userId: member.userId
538
601
  });
539
602
  }),
540
- beforeUpdateMemberRole: authHook("beforeUpdateMemberRole", async ({ member, newRole }) => {
541
- await organizations.updateOrgUser(member.organizationId, member.userId, { role: parseRole(newRole) });
542
- }),
543
603
  beforeRemoveMember: authHook("beforeRemoveMember", async ({ member }) => {
544
604
  await organizations.removeOrgUser(member.organizationId, member.userId);
545
605
  })
@@ -552,6 +612,7 @@ async function buildContainer(config, options) {
552
612
  return {
553
613
  auth,
554
614
  mcp,
615
+ platform,
555
616
  identity,
556
617
  organizations,
557
618
  orgProvider: auth,
@@ -568,6 +629,7 @@ async function buildContainer(config, options) {
568
629
  notificationTypes: notificationRegistries.types,
569
630
  notificationChannels: notificationRegistries.channels,
570
631
  notificationCategories,
632
+ notificationCatalog,
571
633
  notificationCategoryLists,
572
634
  notificationSettings,
573
635
  devices,
@@ -580,6 +642,8 @@ async function buildContainer(config, options) {
580
642
  outboxRelay,
581
643
  automationEngine,
582
644
  scheduler: automationEngine,
645
+ pubsub,
646
+ chatHub: chats.hub,
583
647
  logger,
584
648
  loggerInstance,
585
649
  requestContext: requestLogContext,
@@ -1 +1 @@
1
- {"version":3,"file":"container.js","names":[],"sources":["../../src/app/container.ts"],"sourcesContent":["// Wires adapters + services in dependency order. Starts nothing.\nimport {\n createDb,\n DrizzleAssetsRepository,\n DrizzleAutomationRunsRepository,\n DrizzleAutomationsRepository,\n DrizzleConnectionsRepository,\n DrizzleContentRepository,\n DrizzleCredentialStore,\n DrizzleAdminRepository,\n DrizzleDigestRunRepository,\n DrizzleDevicesRepository,\n DrizzleDiscussionRepository,\n DrizzlePostRepository,\n DrizzleChatRepository,\n DrizzleEntitlementsRepository,\n DrizzleIdentityRepository,\n DrizzleMemberRepository,\n DrizzleMembersRepository,\n DrizzleMentionsRepository,\n DrizzleNotificationsRepository,\n DrizzleNotificationSettingsRepository,\n DrizzleOrganizationsRepository,\n DrizzleOutboxAppender,\n DrizzleOutboxStore,\n DrizzleProgressRepository,\n DrizzleSettingsRepository,\n DrizzleSubscriptionsRepository,\n DrizzleUnitOfWork,\n} from '@headless-lms/adapter-db';\nimport { InMemoryEventBus } from '@headless-lms/adapter-defaults/events';\nimport {\n PollingOutboxRelay,\n type PollingOutboxRelayConfig,\n} from '@headless-lms/adapter-defaults/events/outbox-relay';\nimport { InlineAutomationEngine } from '@headless-lms/adapter-defaults/workflows';\nimport { FetchWebhookSender } from '@headless-lms/adapter-defaults/webhooks';\nimport { EmailAdapter, StubTemplateRenderer } from '@headless-lms/adapter-defaults/email';\nimport {\n createRootLogger,\n type LogLevel,\n type PinoInstance,\n requestLogContext,\n type RequestLogContext,\n} from '@headless-lms/adapter-defaults/logging';\nimport { StorageAdapter } from '@headless-lms/adapter-defaults/storage';\nimport { BetterAuth } from '@headless-lms/adapter-auth';\n\nimport {\n CommunityNotificationCategories,\n ContentItemTitles,\n ContentService,\n} from '@headless-lms/core/content';\nimport {\n EntitlementsServiceImpl,\n entitlementsJobs,\n entitlementsRecurringJobs,\n} from '@headless-lms/core/entitlements';\nimport { ProgressServiceImpl } from '@headless-lms/core/progress';\nimport {\n ChatServiceImpl,\n DigestServiceImpl,\n DiscussionItemTitles,\n DiscussionServiceImpl,\n PostServiceImpl,\n SubscriptionServiceImpl,\n discussionJobs,\n discussionRecurringJobs,\n} from '@headless-lms/core/discussion';\nimport {\n DevicesServiceImpl,\n IdentityServiceImpl,\n NotificationSettingsServiceImpl,\n type SessionAdmin,\n} from '@headless-lms/core/identity';\nimport {\n type OrgAdmin,\n OrganizationServiceImpl,\n OrgNotificationCategories,\n parseRole,\n} from '@headless-lms/core/organizations';\nimport {\n AssetsServiceImpl,\n assetsJobs,\n assetsRecurringJobs,\n createFileUrls,\n} from '@headless-lms/core/assets';\nimport { IntegrationsServiceImpl } from '@headless-lms/core/integrations';\nimport {\n AutomationsServiceImpl,\n DEFAULT_COMMUNITY_CATEGORIES,\n DEFAULT_ORG_CATEGORIES,\n NOTIFICATION_TYPES,\n NotificationDelivery,\n NotificationProcessor,\n NotificationsServiceImpl,\n RECIPIENT_RULES,\n SchedulerChatDebounce,\n notificationJobs,\n type NotificationCategories,\n} from '@headless-lms/core/automations';\nimport { buildNotificationRegistries, configuredPushSender } from './notification-channels.js';\nimport { loadIntegrations } from './integrations.js';\nimport { mcpEndpoints, type McpEndpoints } from './mcp-endpoints.js';\nimport { communityMail } from './community-mail.js';\nimport { notificationReads } from './notification-reads.js';\nimport { AdminReportServiceImpl } from '@headless-lms/core/reporting/admin';\nimport { MemberReportServiceImpl } from '@headless-lms/core/reporting/member';\nimport { Mailer, type MailerLookups } from '@headless-lms/core/shared/mailer';\nimport { SettingsService } from '@headless-lms/core/shared/settings';\n\nimport type {\n ChannelRegistry,\n CredentialStore,\n EmailSender,\n Logger,\n NotificationChannel,\n NotificationTypeRegistry,\n ObjectStorage,\n OutboxRelay,\n PushSender,\n Scheduler,\n TemplateContext,\n TemplateRenderer,\n} from '@headless-lms/core/shared/ports';\nimport type { AutomationEngine, PushConfig, WebhookSender } from '@headless-lms/core/types';\n\n/** Installation-supplied ports; an absent slot falls back to a fail-loudly stub. */\nexport interface AdapterOverrides {\n email?: EmailSender;\n storage?: ObjectStorage;\n /** Resolves email templates to rendered content. Absent → fail-loudly stub. */\n templates?: TemplateRenderer;\n /** Durable automation engine, or a factory given the automations logger. Absent → InlineAutomationEngine (in-process, one attempt per action). */\n workflows?: (AutomationEngine & Scheduler) | ((logger: Logger) => AutomationEngine & Scheduler);\n /** Outbound HTTP for webhook deliveries. Absent → FetchWebhookSender (public addresses only, 10s timeout). */\n webhooks?: WebhookSender;\n /** Notification channels beyond in-app, email and push; each key must be new. */\n notificationChannels?: NotificationChannel[];\n /** Push credentials per provider. Absent → no provider is configured and devices cannot register. */\n push?: PushConfig;\n pushSender?: PushSender;\n}\n\nexport interface BuildContainerOptions {\n /** Installation's plugins folder, scanned by loadIntegrations. Absent → no integrations. */\n pluginsDir?: string;\n adapters?: AdapterOverrides;\n}\n\nexport interface Config {\n databaseUrl: string;\n authBaseURL: string;\n authSecret: string;\n trustedOrigins: string[];\n /** Branding threaded into every email template. Default: brandName \"Headless LMS\", baseUrl = adminAppUrl.\n * (studentPortalUrl is composed in from the top-level config field.) */\n emailBranding?: Omit<TemplateContext, 'studentPortalUrl'>;\n /** base64-encoded 32-byte key for the credential store (CREDENTIAL_STORE_KEY). */\n credentialStoreKey: string;\n /** Parent domain for cross-subdomain session cookies (e.g. \".example.com\"); undefined → host-only cookie. */\n cookieDomain?: string;\n /** Mark session cookies Secure (set behind HTTPS / in production). */\n secureCookies?: boolean;\n /** Student portal origin — invite links for students, and the origin whose signups are invite-gated. */\n studentPortalUrl: string;\n /** Admin app origin — invite links for staff. */\n adminAppUrl: string;\n /** Transactional-outbox relay tuning. All optional — see OUTBOX_DEFAULTS. */\n outbox?: OutboxConfig;\n /** Log level for the process-wide logger (HTTP + domain + relay). Default \"info\". */\n logging?: LoggingConfig;\n /** Lifetime of every signed file URL a response carries, seconds. Mirrors\n * ServerConfig.deliveryExpirySeconds — createContainer threads it through. */\n deliveryExpirySeconds: number;\n memberImageMaxBytes: number;\n /** Let the default webhook sender reach loopback and private network addresses. Local development only. */\n webhookAllowPrivateNetwork?: boolean;\n}\n\n/** Tuning for the transactional-outbox relay. Every field is optional; the\n * container resolves against OUTBOX_DEFAULTS. */\nexport interface OutboxConfig {\n /** Master switch for the same-process relay. Default true. */\n enabled?: boolean;\n /** Idle delay between polls. Default 1000. */\n pollIntervalMs?: number;\n /** Max rows fetched/dispatched per tick. Default 100. */\n batchSize?: number;\n concurrency?: number;\n}\n\nexport const OUTBOX_DEFAULTS: PollingOutboxRelayConfig = {\n enabled: true,\n pollIntervalMs: 1000,\n batchSize: 100,\n concurrency: 16,\n};\n\nexport function resolveOutboxConfig(config: OutboxConfig = {}): PollingOutboxRelayConfig {\n const overrides = Object.fromEntries(\n Object.entries(config).filter(([, value]) => value !== undefined),\n );\n return { ...OUTBOX_DEFAULTS, ...overrides };\n}\n\n/** Logging tuning. Optional; resolved against LOGGING_DEFAULTS. */\nexport interface LoggingConfig {\n /** Minimum level emitted. Default \"info\". */\n level?: LogLevel;\n}\n\nexport const LOGGING_DEFAULTS: Required<LoggingConfig> = { level: 'info' };\n\nexport function resolveLoggingConfig(config: LoggingConfig = {}): Required<LoggingConfig> {\n return { level: config.level ?? LOGGING_DEFAULTS.level };\n}\n\nexport interface Container {\n auth: BetterAuth;\n mcp: McpEndpoints;\n // Org Provider\n orgProvider: OrgAdmin;\n\n // Domains\n identity: IdentityServiceImpl;\n organizations: OrganizationServiceImpl;\n content: ContentService;\n entitlements: EntitlementsServiceImpl;\n progress: ProgressServiceImpl;\n discussion: DiscussionServiceImpl;\n posts: PostServiceImpl;\n chats: ChatServiceImpl;\n subscriptions: SubscriptionServiceImpl;\n assets: AssetsServiceImpl;\n integrations: IntegrationsServiceImpl;\n automations: AutomationsServiceImpl;\n notificationTypes: NotificationTypeRegistry;\n notificationChannels: ChannelRegistry;\n notificationCategories: {\n community: CommunityNotificationCategories;\n org: OrgNotificationCategories;\n };\n notificationCategoryLists: NotificationCategories;\n notificationSettings: NotificationSettingsServiceImpl;\n devices: DevicesServiceImpl;\n notificationList: NotificationsServiceImpl;\n /** Cross-cutting settings store. Not a context — a leaf every context may use;\n * inject `settings.for('<namespace>')` into a service, never the service itself. */\n settings: SettingsService;\n // Reporting read layer (composed cross-context reads; owns no domain rules).\n reporting: {\n admin: AdminReportServiceImpl;\n member: MemberReportServiceImpl;\n };\n storage: ObjectStorage;\n mailer: Mailer;\n /** Shared secure credential store — encrypted at rest, org-scoped, decrypt at point of use. */\n credentials: CredentialStore;\n /** The outbox relay — constructed but NEVER started by the container; the\n * installation's entry point starts it after listen (gen-openapi must not\n * poll). buildServer stops it onClose. */\n outboxRelay: OutboxRelay;\n /** The automation engine — constructed but NEVER started by the container;\n * the installation's entry point starts it after listen (gen-openapi must\n * not run a worker). buildServer stops it onClose. */\n automationEngine: AutomationEngine;\n scheduler: Scheduler;\n /** Root logger port — components receive children bound with { name }. */\n logger: Logger;\n /** The raw pino root; buildServer hands it to Fastify so HTTP shares the stream. */\n loggerInstance: PinoInstance;\n /** Request-scoped log correlation; the HTTP layer enters it per request. */\n requestContext: RequestLogContext;\n /** Identity's session-write port; the auth adapter fulfils it. */\n sessions: SessionAdmin;\n}\n\nexport async function buildContainer(\n config: Config,\n options?: BuildContainerOptions,\n): Promise<Container> {\n const { instance: loggerInstance, logger } = createRootLogger(\n resolveLoggingConfig(config.logging).level,\n );\n // One child per domain — a context's service and repositories share it.\n\n const contentLogger = logger.child({ name: 'content' });\n const entitlementsLogger = logger.child({ name: 'entitlements' });\n const progressLogger = logger.child({ name: 'progress' });\n const discussionLogger = logger.child({ name: 'discussion' });\n const assetsLogger = logger.child({ name: 'assets' });\n const integrationsLogger = logger.child({ name: 'integrations' });\n const automationsLogger = logger.child({ name: 'automations' });\n const reportingLogger = logger.child({ name: 'reporting' });\n const outboxLogger = logger.child({ name: 'outbox' });\n\n // Outbound adapters\n const db = createDb(config.databaseUrl);\n const email = options?.adapters?.email ?? new EmailAdapter(logger.child({ name: 'email' }));\n const storage: ObjectStorage =\n options?.adapters?.storage ?? new StorageAdapter(logger.child({ name: 'storage' }));\n const templates =\n options?.adapters?.templates ?? new StubTemplateRenderer(logger.child({ name: 'email' }));\n const workflows = options?.adapters?.workflows;\n const automationEngine: AutomationEngine & Scheduler =\n typeof workflows === 'function'\n ? workflows(automationsLogger)\n : (workflows ?? new InlineAutomationEngine(automationsLogger));\n const mailer = new Mailer(templates, email, {\n ...(config.emailBranding ?? { brandName: 'Headless LMS', baseUrl: config.adminAppUrl }),\n studentPortalUrl: config.studentPortalUrl,\n });\n\n const fileUrls = createFileUrls(config.authBaseURL);\n const assets = new AssetsServiceImpl({\n storage,\n repo: new DrizzleAssetsRepository(db, assetsLogger),\n uow: new DrizzleUnitOfWork(db, (tx) => ({\n assets: new DrizzleAssetsRepository(tx, assetsLogger),\n outbox: new DrizzleOutboxAppender(tx, outboxLogger),\n })),\n memberImageMaxBytes: config.memberImageMaxBytes,\n downloadLinkExpirySeconds: config.deliveryExpirySeconds,\n fileUrls,\n logger: assetsLogger,\n });\n\n /*\n * Identity\n */\n const identityUoW = new DrizzleUnitOfWork(db, (tx) => ({\n identity: new DrizzleIdentityRepository(tx, logger.child({ name: 'identity' })),\n outbox: new DrizzleOutboxAppender(tx, logger.child({ name: 'identity' })),\n }));\n const identity = new IdentityServiceImpl({\n repo: new DrizzleIdentityRepository(db, logger.child({ name: 'identity_repo' })),\n logger: logger.child({ name: 'identity' }),\n uow: identityUoW,\n mailer,\n images: assets,\n fileUrls,\n });\n\n /*\n * Organization\n */\n const organizationsRepo = new DrizzleOrganizationsRepository(db, logger.child({ name: 'org' }));\n const organizationsUow = new DrizzleUnitOfWork(db, (tx) => ({\n organizations: new DrizzleOrganizationsRepository(tx, logger.child({ name: 'org' })),\n outbox: new DrizzleOutboxAppender(tx, logger.child({ name: 'org' })),\n }));\n const orgAdminRef: { current: OrgAdmin | undefined } = { current: undefined };\n const organizations = new OrganizationServiceImpl({\n repo: organizationsRepo,\n membersRepo: new DrizzleMembersRepository(db, logger.child({ name: 'org' })),\n people: identity,\n uow: organizationsUow,\n orgAdmin: () => {\n if (!orgAdminRef.current) {\n throw new Error('orgAdmin not initialised');\n }\n return orgAdminRef.current;\n },\n logger: logger.child({ name: 'org' }),\n mailer,\n inviteUrls: { studentPortalUrl: config.studentPortalUrl, adminAppUrl: config.adminAppUrl },\n });\n\n /*\n * Settings\n */\n const settings = new SettingsService(\n new DrizzleSettingsRepository(db, logger.child({ name: 'settings' })),\n );\n // Content: reads on the root db; course writes + outbox append in one tx.\n const contentUow = new DrizzleUnitOfWork(db, (tx) => ({\n content: new DrizzleContentRepository(tx, contentLogger),\n outbox: new DrizzleOutboxAppender(tx, outboxLogger),\n }));\n const content = new ContentService({\n repo: new DrizzleContentRepository(db, contentLogger),\n uow: contentUow,\n libraryAssets: assets,\n files: assets,\n fileUrls,\n logger: contentLogger,\n });\n const pushConfig = options?.adapters?.push ?? {};\n const pushSender = configuredPushSender(pushConfig, options?.adapters?.pushSender);\n const notificationRegistries = buildNotificationRegistries({\n declarations: NOTIFICATION_TYPES,\n defaults: { community: DEFAULT_COMMUNITY_CATEGORIES, org: DEFAULT_ORG_CATEGORIES },\n mailer: options?.adapters?.email ? mailer : null,\n emailOf: async (_orgId, userId) => (await identity.getUserById(userId))?.email ?? null,\n push: pushSender\n ? {\n sender: pushSender,\n activeDevices: (orgId, userId, since) => devices.activeDevices(orgId, userId, since),\n }\n : null,\n channels: options?.adapters?.notificationChannels ?? [],\n });\n const notificationCategories = {\n community: new CommunityNotificationCategories({\n communities: new DrizzleContentRepository(db, contentLogger),\n settings,\n types: notificationRegistries.types,\n channels: notificationRegistries.channels,\n logger: contentLogger,\n }),\n org: new OrgNotificationCategories({\n settings,\n types: notificationRegistries.types,\n channels: notificationRegistries.channels,\n logger: logger.child({ name: 'org' }),\n }),\n };\n const notificationCategoryLists: NotificationCategories = {\n listFor: async (orgId, communityId) =>\n (communityId\n ? await notificationCategories.community.get(orgId, communityId)\n : await notificationCategories.org.get(orgId, orgId)\n ).categories,\n };\n // Entitlements: reads on the root db; writes + outbox append in one tx.\n const entitlementsUow = new DrizzleUnitOfWork(db, (tx) => ({\n entitlements: new DrizzleEntitlementsRepository(tx, entitlementsLogger),\n spaces: new DrizzleContentRepository(tx, contentLogger),\n outbox: new DrizzleOutboxAppender(tx, outboxLogger),\n }));\n const authors = {\n roleOf: async (orgId: string, orgUserId: string) => {\n const row = await organizationsRepo.findOrgUserById(orgId, orgUserId);\n return row?.role ?? null;\n },\n rolesOf: async (orgId: string, orgUserIds: string[]) => {\n const rows = await organizationsRepo.findOrgUsersByIds(orgId, orgUserIds);\n return Object.fromEntries(rows.map((row) => [row.id, row.role]));\n },\n };\n const entitlementsRepo = new DrizzleEntitlementsRepository(db, entitlementsLogger);\n const entitlements = new EntitlementsServiceImpl({\n repo: entitlementsRepo,\n content,\n uow: entitlementsUow,\n logger: entitlementsLogger,\n });\n const identityLogger = logger.child({ name: 'identity' });\n const notificationSettings = new NotificationSettingsServiceImpl({\n repo: new DrizzleNotificationSettingsRepository(db, identityLogger),\n categories: {\n communityList: async (orgId, communityId) =>\n (await notificationCategories.community.get(orgId, communityId)).categories,\n orgList: async (orgId) => (await notificationCategories.org.get(orgId, orgId)).categories,\n },\n access: {\n canOpen: (orgId, actor, communityId) =>\n entitlements.isCommunityMember(orgId, actor, communityId),\n },\n channels: notificationRegistries.channels,\n logger: identityLogger,\n });\n const devices = new DevicesServiceImpl({\n repo: new DrizzleDevicesRepository(db, identityLogger),\n push: pushConfig,\n logger: identityLogger,\n });\n // Progress: report writes + outbox append in one tx; content supplies the\n // structure and completion rules the service evaluates against.\n const progressUow = new DrizzleUnitOfWork(db, (tx) => ({\n progress: new DrizzleProgressRepository(tx, progressLogger),\n outbox: new DrizzleOutboxAppender(tx, outboxLogger),\n }));\n const progress = new ProgressServiceImpl({\n repo: new DrizzleProgressRepository(db, progressLogger),\n content,\n access: entitlements,\n uow: progressUow,\n logger: progressLogger,\n });\n\n const postReads = new DrizzlePostRepository(db, discussionLogger);\n const discussionReads = new DrizzleDiscussionRepository(db, discussionLogger);\n const chatReads = new DrizzleChatRepository(db, discussionLogger);\n const subscriptionReads = new DrizzleSubscriptionsRepository(db, discussionLogger);\n const mentionReads = new DrizzleMentionsRepository(db, discussionLogger);\n const posts = new PostServiceImpl({\n repo: postReads,\n access: entitlements,\n content,\n authors,\n profiles: discussionReads,\n comments: discussionReads,\n subscriptions: subscriptionReads,\n mentions: mentionReads,\n images: assets,\n fileUrls,\n uow: new DrizzleUnitOfWork(db, (tx) => ({\n posts: new DrizzlePostRepository(tx, discussionLogger),\n mentions: new DrizzleMentionsRepository(tx, discussionLogger),\n outbox: new DrizzleOutboxAppender(tx, outboxLogger),\n })),\n scheduler: automationEngine,\n logger: discussionLogger,\n });\n const chats = new ChatServiceImpl({\n repo: chatReads,\n access: entitlements,\n content,\n authors,\n profiles: discussionReads,\n subscriptions: subscriptionReads,\n mentions: mentionReads,\n images: assets,\n fileUrls,\n uow: new DrizzleUnitOfWork(db, (tx) => ({\n chats: new DrizzleChatRepository(tx, discussionLogger),\n mentions: new DrizzleMentionsRepository(tx, discussionLogger),\n outbox: new DrizzleOutboxAppender(tx, outboxLogger),\n })),\n logger: discussionLogger,\n });\n const discussion = new DiscussionServiceImpl({\n repo: discussionReads,\n access: entitlements,\n content: content,\n posts,\n spaces: entitlements,\n mentions: mentionReads,\n fileUrls,\n uow: new DrizzleUnitOfWork(db, (tx) => ({\n discussion: new DrizzleDiscussionRepository(tx, discussionLogger),\n mentions: new DrizzleMentionsRepository(tx, discussionLogger),\n outbox: new DrizzleOutboxAppender(tx, outboxLogger),\n })),\n settings,\n logger: discussionLogger,\n });\n\n const subscriptions = new SubscriptionServiceImpl({\n repo: subscriptionReads,\n access: entitlements,\n posts,\n chats,\n logger: discussionLogger,\n });\n\n const digests = new DigestServiceImpl({\n schedules: { communitiesWithDigest: () => content.communitiesWithDigest() },\n runs: new DrizzleDigestRunRepository(db, discussionLogger),\n uow: new DrizzleUnitOfWork(db, (tx) => ({\n digests: new DrizzleDigestRunRepository(tx, discussionLogger),\n outbox: new DrizzleOutboxAppender(tx, outboxLogger),\n })),\n logger: discussionLogger,\n });\n\n const notificationsRepo = new DrizzleNotificationsRepository(db);\n const notificationList = new NotificationsServiceImpl({\n repo: notificationsRepo,\n logger: automationsLogger,\n });\n const itemTitles = {\n discussion: new DiscussionItemTitles({\n posts,\n chats,\n comments: discussionReads,\n messages: chatReads,\n profiles: discussionReads,\n activities: content,\n access: entitlements,\n }),\n content: new ContentItemTitles({ content, access: entitlements }),\n };\n\n const reporting = {\n admin: new AdminReportServiceImpl({\n repo: new DrizzleAdminRepository(db, reportingLogger),\n }),\n member: new MemberReportServiceImpl({\n repo: new DrizzleMemberRepository(db, reportingLogger),\n content,\n progress,\n files: assets,\n fileUrls,\n access: entitlements,\n notifications: notificationList,\n titles: itemTitles,\n subscriptions,\n notificationCategories,\n notificationSettings,\n channels: notificationRegistries.channels,\n }),\n };\n\n const credentialStore = new DrizzleCredentialStore(\n db,\n config.credentialStoreKey,\n integrationsLogger,\n );\n // The integrations this deployment supports: the installation's declared\n // plugins folder (directory name = integration id), loaded at startup.\n // Connect/configure reject undeclared ids and validate config with the\n // integration's own schema. No folder → no integrations.\n const integrationsRegistry = await loadIntegrations(options?.pluginsDir, integrationsLogger);\n // Integrations: credential + connection writes + outbox append in one tx\n // (a tx-bound credential store instance shares the scope's transaction).\n const integrationsUow = new DrizzleUnitOfWork(db, (tx) => ({\n connections: new DrizzleConnectionsRepository(tx, integrationsLogger),\n credentials: new DrizzleCredentialStore(tx, config.credentialStoreKey, integrationsLogger),\n outbox: new DrizzleOutboxAppender(tx, outboxLogger),\n }));\n const integrations = new IntegrationsServiceImpl({\n registry: integrationsRegistry,\n repo: new DrizzleConnectionsRepository(db, integrationsLogger),\n uow: integrationsUow,\n credentials: credentialStore,\n logger: integrationsLogger,\n });\n\n // Automations: run writes + outbox append in one tx; the engine drives\n // execution (register below), integrations supplies the loaded\n // integrations' actions for `available`.\n const automationsUow = new DrizzleUnitOfWork(db, (tx) => ({\n automations: new DrizzleAutomationsRepository(tx, automationsLogger),\n runs: new DrizzleAutomationRunsRepository(tx, automationsLogger),\n credentials: new DrizzleCredentialStore(tx, config.credentialStoreKey, automationsLogger),\n outbox: new DrizzleOutboxAppender(tx, automationsLogger),\n }));\n // Emails triggered by row-shaped events resolve their recipient and content\n // details at send time — the event carries ids only.\n const mailerLookups: MailerLookups = {\n orgUserPerson: async (orgId, orgUserId) => {\n const orgUser = await organizationsRepo.findOrgUserById(orgId, orgUserId);\n const user = orgUser ? await identity.getUserById(orgUser.userId) : null;\n return user ? { userId: user.id, email: user.email } : null;\n },\n contentInfo: async (orgId, contentId) => {\n const course = await content.getCourse(orgId, contentId);\n if (course) {\n return { id: course.id, title: course.title, type: course.type };\n }\n const download = await content.getDownload(orgId, contentId);\n if (download) {\n return { id: download.id, title: download.title, type: download.type };\n }\n const community = await content.getCommunity(orgId, contentId);\n return community ? { id: community.id, title: community.title, type: community.type } : null;\n },\n };\n\n const runsRepo = new DrizzleAutomationRunsRepository(db, automationsLogger);\n const wired = communityMail({\n content,\n entitlements,\n organizations,\n identity,\n posts,\n subscriptions,\n discussion,\n chats,\n });\n const recipientReads = notificationReads({\n content,\n entitlements,\n organizations,\n identity,\n posts,\n subscriptions,\n discussion,\n chats,\n settings: notificationSettings,\n });\n const notificationProcessor = new NotificationProcessor({\n rules: RECIPIENT_RULES,\n reads: recipientReads,\n types: notificationRegistries.types,\n channels: notificationRegistries.channels,\n categories: notificationCategoryLists,\n settings: notificationSettings,\n devices,\n repo: notificationsRepo,\n engine: automationEngine,\n debounce: new SchedulerChatDebounce(automationEngine),\n logger: automationsLogger,\n });\n const automations = new AutomationsServiceImpl({\n repo: new DrizzleAutomationsRepository(db, automationsLogger),\n runsRepo,\n uow: automationsUow,\n engine: automationEngine,\n mailer,\n lookups: mailerLookups,\n integrations,\n credentials: credentialStore,\n webhooks:\n options?.adapters?.webhooks ??\n new FetchWebhookSender({ allowPrivateNetwork: config.webhookAllowPrivateNetwork }),\n mail: wired.mail,\n grants: wired.grants,\n delivery: new NotificationDelivery({\n repo: notificationsRepo,\n channels: notificationRegistries.channels,\n devices,\n logger: automationsLogger,\n }),\n processor: notificationProcessor,\n logger: automationsLogger,\n });\n automationEngine.register(automations);\n automationEngine.registerJobs(\n [\n ...discussionJobs({ posts, digests, logger: discussionLogger }),\n ...entitlementsJobs({ grants: entitlements }),\n ...assetsJobs({ assets, orgs: organizations, logger: assetsLogger }),\n ...notificationJobs({\n processor: notificationProcessor,\n reads: recipientReads,\n logger: automationsLogger,\n }),\n ],\n [...discussionRecurringJobs, ...entitlementsRecurringJobs, ...assetsRecurringJobs],\n );\n\n const eventBus = new InMemoryEventBus();\n eventBus.subscribeAll((event) => automations.handle(event));\n const outboxConfig = resolveOutboxConfig(config.outbox);\n const outboxRelay = new PollingOutboxRelay(\n new DrizzleOutboxStore(db, outboxLogger),\n eventBus,\n outboxConfig,\n outboxLogger,\n );\n\n /*\n * Auth Adapter\n */\n const authLogger = logger.child({ name: 'auth' });\n // Hook failures otherwise surface through Better Auth's route as an opaque\n // 500 — log which hook died before rethrowing.\n const authHook =\n <A extends unknown[], R>(name: string, fn: (...args: A) => Promise<R>) =>\n async (...args: A): Promise<R> => {\n try {\n return await fn(...args);\n } catch (err) {\n authLogger.error(`auth hook ${name} failed`, { err });\n throw err;\n }\n };\n const mcp = mcpEndpoints(config);\n if (!mcp.oauthEnabled) {\n authLogger.warn(\n 'MCP OAuth is off: BETTER_AUTH_URL must be https or a loopback host; /mcp accepts API keys only',\n { authBaseURL: config.authBaseURL },\n );\n }\n const auth = new BetterAuth({\n db,\n baseUrl: config.authBaseURL,\n mcp: {\n oauthEnabled: mcp.oauthEnabled,\n resource: mcp.resource,\n loginPage: mcp.loginPage,\n consentPage: mcp.consentPage,\n orgPickerPage: mcp.orgPickerPage,\n },\n secret: config.authSecret,\n trustedOrigins: config.trustedOrigins,\n hooks: {\n sendResetPassword: async ({ user, url }) => {\n await identity.sendPasswordReset({ email: user.email, url });\n },\n onPasswordReset: async ({ user }) => {\n await identity.sendPasswordChanged({ email: user.email });\n },\n sendMagicLink: authHook('sendMagicLink', async ({ email, url }) => {\n await identity.sendMagicLink({\n email,\n url,\n });\n }),\n beforeUserCreate: authHook('beforeUserCreate', async (user) => {\n const { email, name } = user;\n const [firstName, lastName] = name.split(' ');\n // Better Auth has not minted an id yet — the id returned below becomes\n // its account id. A person provisioned at invite time is linked instead of re-created.\n const domainUser = await identity.linkOrCreateUser({\n email,\n ...(firstName !== undefined && { firstName }),\n ...(lastName !== undefined && { lastName }),\n });\n\n return {\n data: {\n ...user,\n id: domainUser.id,\n },\n };\n }),\n beforeCreateSession: authHook('beforeCreateSession', async (session) => {\n const person = await identity.getUserByExternalId(session.userId);\n if (!person) {\n return;\n }\n const orgs = await organizations.getOrgUsersForUser(person.id);\n if (orgs.length !== 1) {\n return;\n }\n // TODO - fix, why default to the first?\n const org = await organizations.getById(orgs[0]!.orgId);\n if (!org) {\n return;\n }\n\n return { data: { ...session, activeOrganizationId: org.id } };\n }),\n /*\n * We're making sure the domain org creates and using the same ID for better-auth org ID.\n */\n beforeCreateOrganization: authHook(\n 'beforeCreateOrganization',\n async ({ organization: org, user: baUser }) => {\n const domainOrg = await organizations.createOrganization({\n ownerId: baUser.id,\n ...org,\n logo: org.logo ?? undefined,\n });\n return { data: { ...org, id: domainOrg.id } };\n },\n ),\n beforeUpdateOrganization: authHook('beforeUpdateOrganization', async ({ organization }) => {\n await organizations.updateOrganization(organization.id, organization);\n }),\n beforeDeleteOrganization: authHook('beforeDeleteOrganization', async ({ organization }) => {\n await organizations.deleteOrganization(organization.id);\n }),\n beforeAddMember: authHook('beforeAddMember', async ({ member }) => {\n await organizations.addOrgUser({\n orgId: member.organizationId,\n userId: member.userId,\n role: parseRole(member.role),\n });\n }),\n beforeUpdateMemberRole: authHook('beforeUpdateMemberRole', async ({ member, newRole }) => {\n await organizations.updateOrgUser(member.organizationId, member.userId, {\n role: parseRole(newRole),\n });\n }),\n beforeRemoveMember: authHook('beforeRemoveMember', async ({ member }) => {\n await organizations.removeOrgUser(member.organizationId, member.userId);\n }),\n },\n logger: authLogger,\n cookieDomain: config.cookieDomain,\n secureCookies: config.secureCookies,\n });\n orgAdminRef.current = auth;\n\n return {\n auth,\n mcp,\n identity,\n organizations,\n // Better Auth owns org writes; the same instance fulfils the OrgAdmin port.\n orgProvider: auth,\n content,\n entitlements,\n progress,\n discussion,\n posts,\n chats,\n subscriptions,\n assets,\n integrations,\n automations,\n notificationTypes: notificationRegistries.types,\n notificationChannels: notificationRegistries.channels,\n notificationCategories,\n notificationCategoryLists,\n notificationSettings,\n devices,\n notificationList,\n settings,\n reporting,\n storage,\n mailer,\n credentials: credentialStore,\n outboxRelay,\n automationEngine,\n scheduler: automationEngine,\n logger,\n loggerInstance,\n requestContext: requestLogContext,\n // Better Auth owns the session store; the same instance fulfils SessionAdmin.\n sessions: auth,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"container.js","names":[],"sources":["../../src/app/container.ts"],"sourcesContent":["// Wires adapters + services in dependency order. Starts nothing.\nimport {\n createDb,\n type DatabasePoolOptions,\n DrizzleAssetsRepository,\n DrizzleAutomationRunsRepository,\n DrizzleAutomationsRepository,\n DrizzleConnectionsRepository,\n DrizzleContentRepository,\n DrizzleCredentialStore,\n DrizzleAdminRepository,\n DrizzleDigestRunRepository,\n DrizzleDevicesRepository,\n DrizzleDiscussionRepository,\n DrizzlePostRepository,\n DrizzleChatRepository,\n DrizzleEntitlementsRepository,\n DrizzleIdentityRepository,\n DrizzleMemberRepository,\n DrizzleMembersRepository,\n DrizzleMentionsRepository,\n DrizzleNotificationsRepository,\n DrizzleNotificationSettingsRepository,\n DrizzleOrganizationsRepository,\n DrizzleRolesRepository,\n DrizzleOutboxAppender,\n DrizzleOutboxStore,\n DrizzleProgressRepository,\n DrizzleSettingsRepository,\n DrizzleSubscriptionsRepository,\n DrizzleUnitOfWork,\n} from '@headless-lms/adapter-db';\nimport { InMemoryEventBus } from '@headless-lms/adapter-defaults/events';\nimport {\n PollingOutboxRelay,\n type PollingOutboxRelayConfig,\n} from '@headless-lms/adapter-defaults/events/outbox-relay';\nimport { InlineAutomationEngine } from '@headless-lms/adapter-defaults/workflows';\nimport { FetchWebhookSender } from '@headless-lms/adapter-defaults/webhooks';\nimport { EmailAdapter, StubTemplateRenderer } from '@headless-lms/adapter-defaults/email';\nimport {\n createRootLogger,\n type LogLevel,\n type PinoInstance,\n requestLogContext,\n type RequestLogContext,\n} from '@headless-lms/adapter-defaults/logging';\nimport { StorageAdapter } from '@headless-lms/adapter-defaults/storage';\nimport { PlainBodyParser } from '@headless-lms/adapter-defaults/body';\nimport { BetterAuth } from '@headless-lms/adapter-auth';\n\nimport {\n CommunityNotificationCategories,\n ContentItemTitles,\n ContentService,\n contentJobs,\n} from '@headless-lms/core/content';\nimport {\n EntitlementsServiceImpl,\n entitlementsJobs,\n entitlementsRecurringJobs,\n} from '@headless-lms/core/entitlements';\nimport { ProgressServiceImpl } from '@headless-lms/core/progress';\nimport {\n type ChatHub,\n type ChatHubOptions,\n ChatLiveForwarder,\n ChatServiceImpl,\n DigestServiceImpl,\n DiscussionItemTitles,\n DiscussionServiceImpl,\n type AuthorPermissionReader,\n PostServiceImpl,\n SubscriptionServiceImpl,\n discussionJobs,\n discussionRecurringJobs,\n} from '@headless-lms/core/discussion';\nimport {\n DevicesServiceImpl,\n IdentityServiceImpl,\n NotificationSettingsServiceImpl,\n type SessionAdmin,\n} from '@headless-lms/core/identity';\nimport {\n type ApiKeys,\n type GrantReader,\n type OrgAdmin,\n NotificationCatalog,\n OrganizationServiceImpl,\n OrgNotificationCategories,\n type RoleDefinition,\n} from '@headless-lms/core/organizations';\nimport {\n AssetsServiceImpl,\n assetsJobs,\n assetsRecurringJobs,\n createFileUrls,\n} from '@headless-lms/core/assets';\nimport { IntegrationsServiceImpl } from '@headless-lms/core/integrations';\nimport {\n AutomationsServiceImpl,\n DEFAULT_COMMUNITY_CATEGORIES,\n DEFAULT_ORG_CATEGORIES,\n NOTIFICATION_TYPES,\n NotificationDelivery,\n NotificationProcessor,\n NotificationsServiceImpl,\n RECIPIENT_RULES,\n SchedulerChatDebounce,\n notificationJobs,\n type NotificationCategories,\n} from '@headless-lms/core/automations';\nimport { buildNotificationRegistries, configuredPushSender } from './notification-channels.js';\nimport { loadIntegrations } from './integrations.js';\nimport { mcpEndpoints, type McpEndpoints } from './mcp-endpoints.js';\nimport { communityMail } from './community-mail.js';\nimport { notificationReads } from './notification-reads.js';\nimport { AdminReportServiceImpl } from '@headless-lms/core/reporting/admin';\nimport { MemberReportServiceImpl } from '@headless-lms/core/reporting/member';\nimport { Mailer, type MailerLookups } from '@headless-lms/core/shared/mailer';\nimport { SettingsService } from '@headless-lms/core/shared/settings';\nimport { platformKeyCheck, type PlatformConfig, type PlatformKeyCheck } from './platform.js';\n\nimport type {\n BodyParser,\n ChannelRegistry,\n CredentialStore,\n EmailSender,\n Logger,\n NotificationChannel,\n NotificationTypeRegistry,\n ObjectStorage,\n OutboxRelay,\n PubSub,\n PushSender,\n Scheduler,\n TemplateContext,\n TemplateRenderer,\n} from '@headless-lms/core/shared/ports';\nimport type { AutomationEngine, PushConfig, WebhookSender } from '@headless-lms/core/types';\n\n/** Installation-supplied ports; an absent slot falls back to a fail-loudly stub. */\nexport interface AdapterOverrides {\n email?: EmailSender;\n storage?: ObjectStorage;\n /** Resolves email templates to rendered content. Absent → fail-loudly stub. */\n templates?: TemplateRenderer;\n /** Durable automation engine, or a factory given the automations logger. Absent → InlineAutomationEngine (in-process, one attempt per action). */\n workflows?: (AutomationEngine & Scheduler) | ((logger: Logger) => AutomationEngine & Scheduler);\n /** Outbound HTTP for webhook deliveries. Absent → FetchWebhookSender (public addresses only, 10s timeout). */\n webhooks?: WebhookSender;\n /** Notification channels beyond in-app, email and push; each key must be new. */\n notificationChannels?: NotificationChannel[];\n /** Push credentials per provider. Absent → no provider is configured and devices cannot register. */\n push?: PushConfig;\n pushSender?: PushSender;\n /** Reads post bodies: their text, mentions and images. Absent → PlainBodyParser (plain text, no images). */\n bodyParser?: BodyParser;\n /** Delivery to every API instance, or a factory given its logger. Absent → no live chat; the chat stream answers 503. */\n pubsub?: PubSub | ((logger: Logger) => PubSub);\n}\n\nexport interface BuildContainerOptions {\n /** Installation's plugins folder, scanned by loadIntegrations. Absent → no integrations. */\n pluginsDir?: string;\n adapters?: AdapterOverrides;\n roles?: readonly RoleDefinition[];\n /** Live chat hub timings. Absent → the defaults (1 s state flush, 30 s session check). */\n live?: ChatHubOptions;\n}\n\nexport interface Config {\n databaseUrl: string;\n databasePool?: DatabasePoolOptions;\n authBaseURL: string;\n authSecret: string;\n trustedOrigins: string[];\n /** Branding threaded into every email template. Default: brandName \"Headless LMS\", baseUrl = adminAppUrl.\n * (studentPortalUrl is composed in from the top-level config field.) */\n emailBranding?: Omit<TemplateContext, 'studentPortalUrl'>;\n /** base64-encoded 32-byte key for the credential store (CREDENTIAL_STORE_KEY). */\n credentialStoreKey: string;\n /** Parent domain for cross-subdomain session cookies (e.g. \".example.com\"); undefined → host-only cookie. */\n cookieDomain?: string;\n /** Mark session cookies Secure (set behind HTTPS / in production). */\n secureCookies?: boolean;\n /** Student portal origin — invite links for students, and the origin whose signups are invite-gated. */\n studentPortalUrl: string;\n /** Admin app origin — invite links for staff. */\n adminAppUrl: string;\n /** Transactional-outbox relay tuning. All optional — see OUTBOX_DEFAULTS. */\n outbox?: OutboxConfig;\n /** Log level for the process-wide logger (HTTP + domain + relay). Default \"info\". */\n logging?: LoggingConfig;\n /** Lifetime of every signed file URL a response carries, seconds. Mirrors\n * ServerConfig.deliveryExpirySeconds — createContainer threads it through. */\n deliveryExpirySeconds: number;\n memberImageMaxBytes: number;\n memberFileMaxBytes: number;\n /** Let the default webhook sender reach loopback and private network addresses. Local development only. */\n webhookAllowPrivateNetwork?: boolean;\n platform?: PlatformConfig;\n}\n\n/** Tuning for the transactional-outbox relay. Every field is optional; the\n * container resolves against OUTBOX_DEFAULTS. */\nexport interface OutboxConfig {\n /** Master switch for the same-process relay. Default true. */\n enabled?: boolean;\n /** Idle delay between polls. Default 1000. */\n pollIntervalMs?: number;\n /** Max rows fetched/dispatched per tick. Default 100. */\n batchSize?: number;\n concurrency?: number;\n}\n\nexport const OUTBOX_DEFAULTS: PollingOutboxRelayConfig = {\n enabled: true,\n pollIntervalMs: 1000,\n batchSize: 100,\n concurrency: 16,\n};\n\nexport function resolveOutboxConfig(config: OutboxConfig = {}): PollingOutboxRelayConfig {\n const overrides = Object.fromEntries(\n Object.entries(config).filter(([, value]) => value !== undefined),\n );\n return { ...OUTBOX_DEFAULTS, ...overrides };\n}\n\n/** Logging tuning. Optional; resolved against LOGGING_DEFAULTS. */\nexport interface LoggingConfig {\n /** Minimum level emitted. Default \"info\". */\n level?: LogLevel;\n}\n\nexport const LOGGING_DEFAULTS: Required<LoggingConfig> = { level: 'info' };\n\nexport function resolveLoggingConfig(config: LoggingConfig = {}): Required<LoggingConfig> {\n return { level: config.level ?? LOGGING_DEFAULTS.level };\n}\n\nexport interface Container {\n auth: BetterAuth;\n mcp: McpEndpoints;\n platform: PlatformKeyCheck | null;\n // Org Provider\n orgProvider: OrgAdmin;\n\n // Domains\n identity: IdentityServiceImpl;\n organizations: OrganizationServiceImpl;\n content: ContentService;\n entitlements: EntitlementsServiceImpl;\n progress: ProgressServiceImpl;\n discussion: DiscussionServiceImpl;\n posts: PostServiceImpl;\n chats: ChatServiceImpl;\n subscriptions: SubscriptionServiceImpl;\n assets: AssetsServiceImpl;\n integrations: IntegrationsServiceImpl;\n automations: AutomationsServiceImpl;\n notificationTypes: NotificationTypeRegistry;\n notificationChannels: ChannelRegistry;\n notificationCategories: {\n community: CommunityNotificationCategories;\n org: OrgNotificationCategories;\n };\n notificationCatalog: NotificationCatalog;\n notificationCategoryLists: NotificationCategories;\n notificationSettings: NotificationSettingsServiceImpl;\n devices: DevicesServiceImpl;\n notificationList: NotificationsServiceImpl;\n /** Cross-cutting settings store. Not a context — a leaf every context may use;\n * inject `settings.for('<namespace>')` into a service, never the service itself. */\n settings: SettingsService;\n // Reporting read layer (composed cross-context reads; owns no domain rules).\n reporting: {\n admin: AdminReportServiceImpl;\n member: MemberReportServiceImpl;\n };\n storage: ObjectStorage;\n mailer: Mailer;\n /** Shared secure credential store — encrypted at rest, org-scoped, decrypt at point of use. */\n credentials: CredentialStore;\n /** The outbox relay — constructed but NEVER started by the container; the\n * installation's entry point starts it after listen (gen-openapi must not\n * poll). buildServer stops it onClose. */\n outboxRelay: OutboxRelay;\n /** The automation engine — constructed but NEVER started by the container;\n * the installation's entry point starts it after listen (gen-openapi must\n * not run a worker). buildServer stops it onClose. */\n automationEngine: AutomationEngine;\n scheduler: Scheduler;\n /** The PubSub adapter — constructed but NEVER started by the container; the\n * installation's entry point starts it after listen. buildServer stops it\n * onClose. Null without one. */\n pubsub: PubSub | null;\n /** This process's live chat hub; started by the entry point after `pubsub`,\n * closed by buildServer preClose. Null without a PubSub. */\n chatHub: ChatHub | null;\n /** Root logger port — components receive children bound with { name }. */\n logger: Logger;\n /** The raw pino root; buildServer hands it to Fastify so HTTP shares the stream. */\n loggerInstance: PinoInstance;\n /** Request-scoped log correlation; the HTTP layer enters it per request. */\n requestContext: RequestLogContext;\n /** Identity's session-write port; the auth adapter fulfils it. */\n sessions: SessionAdmin;\n}\n\nexport async function buildContainer(\n config: Config,\n options?: BuildContainerOptions,\n): Promise<Container> {\n const { instance: loggerInstance, logger } = createRootLogger(\n resolveLoggingConfig(config.logging).level,\n );\n // One child per domain — a context's service and repositories share it.\n\n const contentLogger = logger.child({ name: 'content' });\n const entitlementsLogger = logger.child({ name: 'entitlements' });\n const progressLogger = logger.child({ name: 'progress' });\n const discussionLogger = logger.child({ name: 'discussion' });\n const assetsLogger = logger.child({ name: 'assets' });\n const integrationsLogger = logger.child({ name: 'integrations' });\n const automationsLogger = logger.child({ name: 'automations' });\n const reportingLogger = logger.child({ name: 'reporting' });\n const outboxLogger = logger.child({ name: 'outbox' });\n\n // Outbound adapters\n const db = createDb(config.databaseUrl, logger.child({ name: 'db' }), config.databasePool);\n const email = options?.adapters?.email ?? new EmailAdapter(logger.child({ name: 'email' }));\n const storage: ObjectStorage =\n options?.adapters?.storage ?? new StorageAdapter(logger.child({ name: 'storage' }));\n const templates =\n options?.adapters?.templates ?? new StubTemplateRenderer(logger.child({ name: 'email' }));\n const bodies = options?.adapters?.bodyParser ?? new PlainBodyParser();\n const workflows = options?.adapters?.workflows;\n const automationEngine: AutomationEngine & Scheduler =\n typeof workflows === 'function'\n ? workflows(automationsLogger)\n : (workflows ?? new InlineAutomationEngine(automationsLogger));\n const pubsubOption = options?.adapters?.pubsub;\n const pubsub: PubSub | null =\n typeof pubsubOption === 'function'\n ? pubsubOption(logger.child({ name: 'pubsub' }))\n : (pubsubOption ?? null);\n const mailer = new Mailer(templates, email, {\n ...(config.emailBranding ?? { brandName: 'Headless LMS', baseUrl: config.adminAppUrl }),\n studentPortalUrl: config.studentPortalUrl,\n });\n\n const fileUrls = createFileUrls(config.authBaseURL, (key) => storage.publicUrl(key));\n const assets = new AssetsServiceImpl({\n storage,\n repo: new DrizzleAssetsRepository(db, assetsLogger),\n uow: new DrizzleUnitOfWork(db, (tx) => ({\n assets: new DrizzleAssetsRepository(tx, assetsLogger),\n outbox: new DrizzleOutboxAppender(tx, outboxLogger),\n })),\n memberImageMaxBytes: config.memberImageMaxBytes,\n memberFileMaxBytes: config.memberFileMaxBytes,\n downloadLinkExpirySeconds: config.deliveryExpirySeconds,\n fileUrls,\n logger: assetsLogger,\n });\n\n /*\n * Identity\n */\n const identityUoW = new DrizzleUnitOfWork(db, (tx) => ({\n identity: new DrizzleIdentityRepository(tx, logger.child({ name: 'identity' })),\n outbox: new DrizzleOutboxAppender(tx, logger.child({ name: 'identity' })),\n }));\n const identity = new IdentityServiceImpl({\n repo: new DrizzleIdentityRepository(db, logger.child({ name: 'identity_repo' })),\n logger: logger.child({ name: 'identity' }),\n uow: identityUoW,\n mailer,\n images: assets,\n fileUrls,\n });\n\n /*\n * Organization\n */\n const organizationsRepo = new DrizzleOrganizationsRepository(db, logger.child({ name: 'org' }));\n const organizationsUow = new DrizzleUnitOfWork(db, (tx) => ({\n organizations: new DrizzleOrganizationsRepository(tx, logger.child({ name: 'org' })),\n roles: new DrizzleRolesRepository(tx),\n outbox: new DrizzleOutboxAppender(tx, logger.child({ name: 'org' })),\n }));\n const orgAdminRef: { current: BetterAuth | undefined } = { current: undefined };\n const platform = config.platform ? platformKeyCheck(config.platform) : null;\n const organizations: OrganizationServiceImpl = new OrganizationServiceImpl({\n repo: organizationsRepo,\n roles: new DrizzleRolesRepository(db),\n ...(options?.roles && { defaultRoles: options.roles }),\n apiKeys: (): ApiKeys => {\n if (!orgAdminRef.current) {\n throw new Error('api keys not initialised');\n }\n return orgAdminRef.current;\n },\n accounts: () => {\n if (!orgAdminRef.current) {\n throw new Error('accounts not initialised');\n }\n return orgAdminRef.current;\n },\n ...(config.platform && {\n platform: {\n email: config.platform.accountEmail,\n name: config.platform.accountName ?? 'Platform',\n },\n }),\n membersRepo: new DrizzleMembersRepository(db, logger.child({ name: 'org' })),\n people: identity,\n images: assets,\n uow: organizationsUow,\n orgAdmin: () => {\n if (!orgAdminRef.current) {\n throw new Error('orgAdmin not initialised');\n }\n return orgAdminRef.current;\n },\n sessions: () => {\n if (!orgAdminRef.current) {\n throw new Error('sessions not initialised');\n }\n return orgAdminRef.current;\n },\n logger: logger.child({ name: 'org' }),\n mailer,\n inviteUrls: { studentPortalUrl: config.studentPortalUrl, adminAppUrl: config.adminAppUrl },\n grants: (): GrantReader => entitlements,\n });\n\n /*\n * Settings\n */\n const settings = new SettingsService(\n new DrizzleSettingsRepository(db, logger.child({ name: 'settings' })),\n );\n // Content: reads on the root db; course writes + outbox append in one tx.\n const contentUow = new DrizzleUnitOfWork(db, (tx) => ({\n content: new DrizzleContentRepository(tx, contentLogger),\n outbox: new DrizzleOutboxAppender(tx, outboxLogger),\n }));\n const content = new ContentService({\n repo: new DrizzleContentRepository(db, contentLogger),\n uow: contentUow,\n libraryAssets: assets,\n files: assets,\n fileUrls,\n scheduler: automationEngine,\n logger: contentLogger,\n });\n const pushConfig = options?.adapters?.push ?? {};\n const pushSender = configuredPushSender(pushConfig, options?.adapters?.pushSender);\n const notificationRegistries = buildNotificationRegistries({\n declarations: NOTIFICATION_TYPES,\n defaults: { community: DEFAULT_COMMUNITY_CATEGORIES, org: DEFAULT_ORG_CATEGORIES },\n mailer: options?.adapters?.email ? mailer : null,\n emailOf: async (_orgId, userId) => (await identity.getUserById(userId))?.email ?? null,\n push: pushSender\n ? {\n sender: pushSender,\n activeDevices: (orgId, userId, since) => devices.activeDevices(orgId, userId, since),\n }\n : null,\n channels: options?.adapters?.notificationChannels ?? [],\n });\n const notificationCategories = {\n community: new CommunityNotificationCategories({\n communities: new DrizzleContentRepository(db, contentLogger),\n settings,\n types: notificationRegistries.types,\n channels: notificationRegistries.channels,\n logger: contentLogger,\n }),\n org: new OrgNotificationCategories({\n settings,\n types: notificationRegistries.types,\n channels: notificationRegistries.channels,\n logger: logger.child({ name: 'org' }),\n }),\n };\n const notificationCatalog = new NotificationCatalog({\n types: notificationRegistries.types,\n channels: notificationRegistries.channels,\n });\n const notificationCategoryLists: NotificationCategories = {\n listFor: async (orgId, communityId) =>\n (communityId\n ? await notificationCategories.community.get(communityId, organizations.systemActor(orgId))\n : await notificationCategories.org.get(orgId, organizations.systemActor(orgId))\n ).categories,\n };\n // Entitlements: reads on the root db; writes + outbox append in one tx.\n const entitlementsUow = new DrizzleUnitOfWork(db, (tx) => ({\n entitlements: new DrizzleEntitlementsRepository(tx, entitlementsLogger),\n spaces: new DrizzleContentRepository(tx, contentLogger),\n outbox: new DrizzleOutboxAppender(tx, outboxLogger),\n }));\n const authors: AuthorPermissionReader = organizations;\n const entitlementsRepo = new DrizzleEntitlementsRepository(db, entitlementsLogger);\n const entitlements = new EntitlementsServiceImpl({\n repo: entitlementsRepo,\n content,\n permissions: organizations,\n uow: entitlementsUow,\n logger: entitlementsLogger,\n });\n const identityLogger = logger.child({ name: 'identity' });\n const notificationSettings = new NotificationSettingsServiceImpl({\n repo: new DrizzleNotificationSettingsRepository(db, identityLogger),\n categories: {\n communityList: async (orgId, communityId) =>\n (await notificationCategories.community.get(communityId, organizations.systemActor(orgId)))\n .categories,\n orgList: async (orgId) =>\n (await notificationCategories.org.get(orgId, organizations.systemActor(orgId))).categories,\n },\n access: {\n canOpen: (orgId, actor, communityId) => entitlements.isCommunityMember(communityId, actor),\n },\n channels: notificationRegistries.channels,\n logger: identityLogger,\n });\n const devices = new DevicesServiceImpl({\n repo: new DrizzleDevicesRepository(db, identityLogger),\n push: pushConfig,\n logger: identityLogger,\n });\n // Progress: report writes + outbox append in one tx; content supplies the\n // structure and completion rules the service evaluates against.\n const progressUow = new DrizzleUnitOfWork(db, (tx) => ({\n progress: new DrizzleProgressRepository(tx, progressLogger),\n outbox: new DrizzleOutboxAppender(tx, outboxLogger),\n }));\n const progress = new ProgressServiceImpl({\n repo: new DrizzleProgressRepository(db, progressLogger),\n content,\n access: entitlements,\n system: organizations,\n uow: progressUow,\n logger: progressLogger,\n });\n\n const postReads = new DrizzlePostRepository(db, discussionLogger);\n const discussionReads = new DrizzleDiscussionRepository(db, discussionLogger);\n const chatReads = new DrizzleChatRepository(db, discussionLogger);\n const subscriptionReads = new DrizzleSubscriptionsRepository(db, discussionLogger);\n const mentionReads = new DrizzleMentionsRepository(db, discussionLogger);\n const posts = new PostServiceImpl({\n repo: postReads,\n access: entitlements,\n content,\n authors,\n profiles: discussionReads,\n comments: discussionReads,\n subscriptions: subscriptionReads,\n mentions: mentionReads,\n images: assets,\n fileUrls,\n bodies,\n uow: new DrizzleUnitOfWork(db, (tx) => ({\n posts: new DrizzlePostRepository(tx, discussionLogger),\n mentions: new DrizzleMentionsRepository(tx, discussionLogger),\n outbox: new DrizzleOutboxAppender(tx, outboxLogger),\n })),\n scheduler: automationEngine,\n logger: discussionLogger,\n });\n const chats = new ChatServiceImpl({\n repo: chatReads,\n access: entitlements,\n content,\n authors,\n profiles: discussionReads,\n subscriptions: subscriptionReads,\n mentions: mentionReads,\n images: assets,\n fileUrls,\n uow: new DrizzleUnitOfWork(db, (tx) => ({\n chats: new DrizzleChatRepository(tx, discussionLogger),\n mentions: new DrizzleMentionsRepository(tx, discussionLogger),\n outbox: new DrizzleOutboxAppender(tx, outboxLogger),\n })),\n ...(pubsub && { pubsub }),\n ...(options?.live && { live: options.live }),\n logger: discussionLogger,\n });\n const discussion = new DiscussionServiceImpl({\n repo: discussionReads,\n access: entitlements,\n content: content,\n posts,\n spaces: entitlements,\n authors,\n mentions: mentionReads,\n fileUrls,\n uow: new DrizzleUnitOfWork(db, (tx) => ({\n discussion: new DrizzleDiscussionRepository(tx, discussionLogger),\n mentions: new DrizzleMentionsRepository(tx, discussionLogger),\n outbox: new DrizzleOutboxAppender(tx, outboxLogger),\n })),\n settings,\n logger: discussionLogger,\n });\n\n const subscriptions = new SubscriptionServiceImpl({\n repo: subscriptionReads,\n access: entitlements,\n posts,\n chats,\n uow: new DrizzleUnitOfWork(db, (tx) => ({\n subscriptions: new DrizzleSubscriptionsRepository(tx, discussionLogger),\n outbox: new DrizzleOutboxAppender(tx, outboxLogger),\n })),\n logger: discussionLogger,\n });\n\n const digests = new DigestServiceImpl({\n schedules: { communitiesWithDigest: () => content.communitiesWithDigest() },\n runs: new DrizzleDigestRunRepository(db, discussionLogger),\n uow: new DrizzleUnitOfWork(db, (tx) => ({\n digests: new DrizzleDigestRunRepository(tx, discussionLogger),\n outbox: new DrizzleOutboxAppender(tx, outboxLogger),\n })),\n logger: discussionLogger,\n });\n\n const notificationsRepo = new DrizzleNotificationsRepository(db);\n const notificationList = new NotificationsServiceImpl({\n repo: notificationsRepo,\n logger: automationsLogger,\n });\n const itemTitles = {\n discussion: new DiscussionItemTitles({\n posts,\n chats,\n comments: discussionReads,\n messages: chatReads,\n profiles: discussionReads,\n activities: content,\n access: entitlements,\n actors: organizations,\n }),\n content: new ContentItemTitles({ content, access: entitlements, organizations }),\n };\n\n const reporting = {\n admin: new AdminReportServiceImpl({\n repo: new DrizzleAdminRepository(db, reportingLogger),\n permissions: organizations,\n spaces: entitlements,\n events: content,\n fileUrls,\n }),\n member: new MemberReportServiceImpl({\n repo: new DrizzleMemberRepository(db, reportingLogger),\n content,\n progress,\n files: assets,\n fileUrls,\n access: entitlements,\n people: organizations,\n notifications: notificationList,\n titles: itemTitles,\n subscriptions,\n notificationCategories,\n notificationSettings,\n channels: notificationRegistries.channels,\n studentPortalUrl: config.studentPortalUrl,\n }),\n };\n\n const credentialStore = new DrizzleCredentialStore(\n db,\n config.credentialStoreKey,\n integrationsLogger,\n );\n // The integrations this deployment supports: the installation's declared\n // plugins folder (directory name = integration id), loaded at startup.\n // Connect/configure reject undeclared ids and validate config with the\n // integration's own schema. No folder → no integrations.\n const integrationsRegistry = await loadIntegrations(options?.pluginsDir, integrationsLogger);\n // Integrations: credential + connection writes + outbox append in one tx\n // (a tx-bound credential store instance shares the scope's transaction).\n const integrationsUow = new DrizzleUnitOfWork(db, (tx) => ({\n connections: new DrizzleConnectionsRepository(tx, integrationsLogger),\n credentials: new DrizzleCredentialStore(tx, config.credentialStoreKey, integrationsLogger),\n outbox: new DrizzleOutboxAppender(tx, outboxLogger),\n }));\n const integrations = new IntegrationsServiceImpl({\n registry: integrationsRegistry,\n repo: new DrizzleConnectionsRepository(db, integrationsLogger),\n uow: integrationsUow,\n credentials: credentialStore,\n logger: integrationsLogger,\n });\n\n // Automations: run writes + outbox append in one tx; the engine drives\n // execution (register below), integrations supplies the loaded\n // integrations' actions for `available`.\n const automationsUow = new DrizzleUnitOfWork(db, (tx) => ({\n automations: new DrizzleAutomationsRepository(tx, automationsLogger),\n runs: new DrizzleAutomationRunsRepository(tx, automationsLogger),\n credentials: new DrizzleCredentialStore(tx, config.credentialStoreKey, automationsLogger),\n outbox: new DrizzleOutboxAppender(tx, automationsLogger),\n }));\n // Emails triggered by row-shaped events resolve their recipient and content\n // details at send time — the event carries ids only.\n const mailerLookups: MailerLookups = {\n orgUserPerson: async (orgId, orgUserId) => {\n const orgUser = await organizationsRepo.findOrgUserById(orgId, orgUserId);\n const user = orgUser ? await identity.getUserById(orgUser.userId) : null;\n return user ? { userId: user.id, email: user.email } : null;\n },\n contentInfo: async (orgId, contentId) => {\n const system = organizations.systemActor(orgId);\n const course = await content.findCourse(contentId, system);\n if (course) {\n return { id: course.id, title: course.title, type: course.type };\n }\n const download = await content.findDownload(contentId, system);\n if (download) {\n return { id: download.id, title: download.title, type: download.type };\n }\n const community = await content.findCommunity(contentId, system);\n if (community) {\n return { id: community.id, title: community.title, type: community.type };\n }\n const event = await content.findEvent(contentId, system);\n return event ? { id: event.id, title: event.title, type: event.type } : null;\n },\n };\n\n const runsRepo = new DrizzleAutomationRunsRepository(db, automationsLogger);\n const wired = communityMail({\n content,\n entitlements,\n organizations,\n identity,\n posts,\n subscriptions,\n discussion,\n chats,\n bodies,\n });\n const recipientReads = notificationReads({\n content,\n entitlements,\n organizations,\n identity,\n posts,\n subscriptions,\n discussion,\n chats,\n bodies,\n settings: notificationSettings,\n });\n const notificationProcessor = new NotificationProcessor({\n rules: RECIPIENT_RULES,\n reads: recipientReads,\n types: notificationRegistries.types,\n channels: notificationRegistries.channels,\n categories: notificationCategoryLists,\n settings: notificationSettings,\n devices,\n repo: notificationsRepo,\n engine: automationEngine,\n debounce: new SchedulerChatDebounce(automationEngine),\n logger: automationsLogger,\n });\n const automations = new AutomationsServiceImpl({\n repo: new DrizzleAutomationsRepository(db, automationsLogger),\n runsRepo,\n uow: automationsUow,\n engine: automationEngine,\n mailer,\n lookups: mailerLookups,\n integrations,\n systemActor: (orgId) => organizations.systemActor(orgId),\n credentials: credentialStore,\n webhooks:\n options?.adapters?.webhooks ??\n new FetchWebhookSender({ allowPrivateNetwork: config.webhookAllowPrivateNetwork }),\n mail: wired.mail,\n grants: wired.grants,\n delivery: new NotificationDelivery({\n repo: notificationsRepo,\n channels: notificationRegistries.channels,\n devices,\n logger: automationsLogger,\n }),\n processor: notificationProcessor,\n logger: automationsLogger,\n });\n automationEngine.register(automations);\n automationEngine.registerJobs(\n [\n ...discussionJobs({ posts, digests, logger: discussionLogger }),\n ...contentJobs({ events: content, logger: contentLogger }),\n ...entitlementsJobs({ grants: entitlements }),\n ...assetsJobs({ assets, orgs: organizations, logger: assetsLogger }),\n ...notificationJobs({\n processor: notificationProcessor,\n reads: recipientReads,\n logger: automationsLogger,\n }),\n ],\n [...discussionRecurringJobs, ...entitlementsRecurringJobs, ...assetsRecurringJobs],\n );\n\n const eventBus = new InMemoryEventBus();\n if (pubsub) {\n new ChatLiveForwarder({ pubsub, logger: discussionLogger }).attach(eventBus);\n }\n eventBus.subscribeAll((event) => automations.handle(event));\n const outboxConfig = resolveOutboxConfig(config.outbox);\n const outboxRelay = new PollingOutboxRelay(\n new DrizzleOutboxStore(db, outboxLogger),\n eventBus,\n outboxConfig,\n outboxLogger,\n );\n\n /*\n * Auth Adapter\n */\n const authLogger = logger.child({ name: 'auth' });\n // Hook failures otherwise surface through Better Auth's route as an opaque\n // 500 — log which hook died before rethrowing.\n const authHook =\n <A extends unknown[], R>(name: string, fn: (...args: A) => Promise<R>) =>\n async (...args: A): Promise<R> => {\n try {\n return await fn(...args);\n } catch (err) {\n authLogger.error(`auth hook ${name} failed`, { err });\n throw err;\n }\n };\n const mcp = mcpEndpoints(config);\n if (!mcp.oauthEnabled) {\n authLogger.warn(\n 'MCP OAuth is off: BETTER_AUTH_URL must be https or a loopback host; /mcp accepts API keys only',\n { authBaseURL: config.authBaseURL },\n );\n }\n const auth: BetterAuth = new BetterAuth({\n db,\n baseUrl: config.authBaseURL,\n mcp: {\n oauthEnabled: mcp.oauthEnabled,\n resource: mcp.resource,\n loginPage: mcp.loginPage,\n consentPage: mcp.consentPage,\n orgPickerPage: mcp.orgPickerPage,\n },\n secret: config.authSecret,\n trustedOrigins: config.trustedOrigins,\n hooks: {\n sendResetPassword: async ({ user, url }) => {\n await identity.sendPasswordReset({ email: user.email, url });\n },\n onPasswordReset: async ({ user }) => {\n await identity.sendPasswordChanged({ email: user.email });\n },\n sendMagicLink: authHook('sendMagicLink', async ({ email, url }) => {\n await identity.sendMagicLink({\n email,\n url,\n });\n }),\n beforeUserCreate: authHook('beforeUserCreate', async (user) => {\n const { email, name } = user;\n const [firstName, lastName] = name.split(' ');\n // Better Auth has not minted an id yet — the id returned below becomes\n // its account id. A person provisioned at invite time is linked instead of re-created.\n const domainUser = await identity.linkOrCreateUser({\n email,\n ...(firstName !== undefined && { firstName }),\n ...(lastName !== undefined && { lastName }),\n });\n\n return {\n data: {\n ...user,\n id: domainUser.id,\n },\n };\n }),\n beforeCreateSession: authHook('beforeCreateSession', async (session) => {\n const person = await identity.getUserByExternalId(session.userId);\n if (!person) {\n return;\n }\n if (organizations.isPlatformAccount(person.email)) {\n authLogger.warn('sign-in refused for the platform account', { userId: person.id });\n return false;\n }\n const orgs = await organizations.getOrgUsersForUser(person.id);\n if (orgs.length !== 1) {\n return;\n }\n // TODO - fix, why default to the first?\n const org = await organizations.getById(orgs[0]!.orgId);\n if (!org) {\n return;\n }\n\n return { data: { ...session, activeOrganizationId: org.id } };\n }),\n /*\n * We're making sure the domain org creates and using the same ID for better-auth org ID.\n */\n beforeCreateOrganization: authHook(\n 'beforeCreateOrganization',\n async ({ organization: org, user: baUser }) => {\n const domainOrg = await organizations.provisionOrganization({\n ownerId: baUser.id,\n ...org,\n logo: org.logo ?? undefined,\n });\n return { data: { ...org, id: domainOrg.id } };\n },\n ),\n beforeDeleteOrganization: authHook('beforeDeleteOrganization', async ({ organization }) => {\n await organizations.deleteOrganization(organization.id);\n }),\n beforeAddMember: authHook('beforeAddMember', async ({ member }) => {\n await organizations.addOrgUser({\n orgId: member.organizationId,\n userId: member.userId,\n });\n }),\n beforeRemoveMember: authHook('beforeRemoveMember', async ({ member }) => {\n await organizations.removeOrgUser(member.organizationId, member.userId);\n }),\n },\n logger: authLogger,\n cookieDomain: config.cookieDomain,\n secureCookies: config.secureCookies,\n });\n orgAdminRef.current = auth;\n\n return {\n auth,\n mcp,\n platform,\n identity,\n organizations,\n // Better Auth owns org writes; the same instance fulfils the OrgAdmin port.\n orgProvider: auth,\n content,\n entitlements,\n progress,\n discussion,\n posts,\n chats,\n subscriptions,\n assets,\n integrations,\n automations,\n notificationTypes: notificationRegistries.types,\n notificationChannels: notificationRegistries.channels,\n notificationCategories,\n notificationCatalog,\n notificationCategoryLists,\n notificationSettings,\n devices,\n notificationList,\n settings,\n reporting,\n storage,\n mailer,\n credentials: credentialStore,\n outboxRelay,\n automationEngine,\n scheduler: automationEngine,\n pubsub,\n chatHub: chats.hub,\n logger,\n loggerInstance,\n requestContext: requestLogContext,\n // Better Auth owns the session store; the same instance fulfils SessionAdmin.\n sessions: auth,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwNA,MAAa,kBAA4C;CACvD,SAAS;CACT,gBAAgB;CAChB,WAAW;CACX,aAAa;AACf;AAEA,SAAgB,oBAAoB,SAAuB,CAAC,GAA6B;CACvF,MAAM,YAAY,OAAO,YACvB,OAAO,QAAQ,MAAM,CAAC,CAAC,QAAQ,GAAG,WAAW,UAAU,KAAA,CAAS,CAClE;CACA,OAAO;EAAE,GAAG;EAAiB,GAAG;CAAU;AAC5C;AAQA,MAAa,mBAA4C,EAAE,OAAO,OAAO;AAEzE,SAAgB,qBAAqB,SAAwB,CAAC,GAA4B;CACxF,OAAO,EAAE,OAAO,OAAO,SAAS,iBAAiB,MAAM;AACzD;AAuEA,eAAsB,eACpB,QACA,SACoB;CACpB,MAAM,EAAE,UAAU,gBAAgB,WAAW,iBAC3C,qBAAqB,OAAO,OAAO,CAAC,CAAC,KACvC;CAGA,MAAM,gBAAgB,OAAO,MAAM,EAAE,MAAM,UAAU,CAAC;CACtD,MAAM,qBAAqB,OAAO,MAAM,EAAE,MAAM,eAAe,CAAC;CAChE,MAAM,iBAAiB,OAAO,MAAM,EAAE,MAAM,WAAW,CAAC;CACxD,MAAM,mBAAmB,OAAO,MAAM,EAAE,MAAM,aAAa,CAAC;CAC5D,MAAM,eAAe,OAAO,MAAM,EAAE,MAAM,SAAS,CAAC;CACpD,MAAM,qBAAqB,OAAO,MAAM,EAAE,MAAM,eAAe,CAAC;CAChE,MAAM,oBAAoB,OAAO,MAAM,EAAE,MAAM,cAAc,CAAC;CAC9D,MAAM,kBAAkB,OAAO,MAAM,EAAE,MAAM,YAAY,CAAC;CAC1D,MAAM,eAAe,OAAO,MAAM,EAAE,MAAM,SAAS,CAAC;CAGpD,MAAM,KAAK,SAAS,OAAO,aAAa,OAAO,MAAM,EAAE,MAAM,KAAK,CAAC,GAAG,OAAO,YAAY;CACzF,MAAM,QAAQ,SAAS,UAAU,SAAS,IAAI,aAAa,OAAO,MAAM,EAAE,MAAM,QAAQ,CAAC,CAAC;CAC1F,MAAM,UACJ,SAAS,UAAU,WAAW,IAAI,eAAe,OAAO,MAAM,EAAE,MAAM,UAAU,CAAC,CAAC;CACpF,MAAM,YACJ,SAAS,UAAU,aAAa,IAAI,qBAAqB,OAAO,MAAM,EAAE,MAAM,QAAQ,CAAC,CAAC;CAC1F,MAAM,SAAS,SAAS,UAAU,cAAc,IAAI,gBAAgB;CACpE,MAAM,YAAY,SAAS,UAAU;CACrC,MAAM,mBACJ,OAAO,cAAc,aACjB,UAAU,iBAAiB,IAC1B,aAAa,IAAI,uBAAuB,iBAAiB;CAChE,MAAM,eAAe,SAAS,UAAU;CACxC,MAAM,SACJ,OAAO,iBAAiB,aACpB,aAAa,OAAO,MAAM,EAAE,MAAM,SAAS,CAAC,CAAC,IAC5C,gBAAgB;CACvB,MAAM,SAAS,IAAI,OAAO,WAAW,OAAO;EAC1C,GAAI,OAAO,iBAAiB;GAAE,WAAW;GAAgB,SAAS,OAAO;EAAY;EACrF,kBAAkB,OAAO;CAC3B,CAAC;CAED,MAAM,WAAW,eAAe,OAAO,cAAc,QAAQ,QAAQ,UAAU,GAAG,CAAC;CACnF,MAAM,SAAS,IAAI,kBAAkB;EACnC;EACA,MAAM,IAAI,wBAAwB,IAAI,YAAY;EAClD,KAAK,IAAI,kBAAkB,KAAK,QAAQ;GACtC,QAAQ,IAAI,wBAAwB,IAAI,YAAY;GACpD,QAAQ,IAAI,sBAAsB,IAAI,YAAY;EACpD,EAAE;EACF,qBAAqB,OAAO;EAC5B,oBAAoB,OAAO;EAC3B,2BAA2B,OAAO;EAClC;EACA,QAAQ;CACV,CAAC;CAKD,MAAM,cAAc,IAAI,kBAAkB,KAAK,QAAQ;EACrD,UAAU,IAAI,0BAA0B,IAAI,OAAO,MAAM,EAAE,MAAM,WAAW,CAAC,CAAC;EAC9E,QAAQ,IAAI,sBAAsB,IAAI,OAAO,MAAM,EAAE,MAAM,WAAW,CAAC,CAAC;CAC1E,EAAE;CACF,MAAM,WAAW,IAAI,oBAAoB;EACvC,MAAM,IAAI,0BAA0B,IAAI,OAAO,MAAM,EAAE,MAAM,gBAAgB,CAAC,CAAC;EAC/E,QAAQ,OAAO,MAAM,EAAE,MAAM,WAAW,CAAC;EACzC,KAAK;EACL;EACA,QAAQ;EACR;CACF,CAAC;CAKD,MAAM,oBAAoB,IAAI,+BAA+B,IAAI,OAAO,MAAM,EAAE,MAAM,MAAM,CAAC,CAAC;CAC9F,MAAM,mBAAmB,IAAI,kBAAkB,KAAK,QAAQ;EAC1D,eAAe,IAAI,+BAA+B,IAAI,OAAO,MAAM,EAAE,MAAM,MAAM,CAAC,CAAC;EACnF,OAAO,IAAI,uBAAuB,EAAE;EACpC,QAAQ,IAAI,sBAAsB,IAAI,OAAO,MAAM,EAAE,MAAM,MAAM,CAAC,CAAC;CACrE,EAAE;CACF,MAAM,cAAmD,EAAE,SAAS,KAAA,EAAU;CAC9E,MAAM,WAAW,OAAO,WAAW,iBAAiB,OAAO,QAAQ,IAAI;CACvE,MAAM,gBAAyC,IAAI,wBAAwB;EACzE,MAAM;EACN,OAAO,IAAI,uBAAuB,EAAE;EACpC,GAAI,SAAS,SAAS,EAAE,cAAc,QAAQ,MAAM;EACpD,eAAwB;GACtB,IAAI,CAAC,YAAY,SACf,MAAM,IAAI,MAAM,0BAA0B;GAE5C,OAAO,YAAY;EACrB;EACA,gBAAgB;GACd,IAAI,CAAC,YAAY,SACf,MAAM,IAAI,MAAM,0BAA0B;GAE5C,OAAO,YAAY;EACrB;EACA,GAAI,OAAO,YAAY,EACrB,UAAU;GACR,OAAO,OAAO,SAAS;GACvB,MAAM,OAAO,SAAS,eAAe;EACvC,EACF;EACA,aAAa,IAAI,yBAAyB,IAAI,OAAO,MAAM,EAAE,MAAM,MAAM,CAAC,CAAC;EAC3E,QAAQ;EACR,QAAQ;EACR,KAAK;EACL,gBAAgB;GACd,IAAI,CAAC,YAAY,SACf,MAAM,IAAI,MAAM,0BAA0B;GAE5C,OAAO,YAAY;EACrB;EACA,gBAAgB;GACd,IAAI,CAAC,YAAY,SACf,MAAM,IAAI,MAAM,0BAA0B;GAE5C,OAAO,YAAY;EACrB;EACA,QAAQ,OAAO,MAAM,EAAE,MAAM,MAAM,CAAC;EACpC;EACA,YAAY;GAAE,kBAAkB,OAAO;GAAkB,aAAa,OAAO;EAAY;EACzF,cAA2B;CAC7B,CAAC;CAKD,MAAM,WAAW,IAAI,gBACnB,IAAI,0BAA0B,IAAI,OAAO,MAAM,EAAE,MAAM,WAAW,CAAC,CAAC,CACtE;CAEA,MAAM,aAAa,IAAI,kBAAkB,KAAK,QAAQ;EACpD,SAAS,IAAI,yBAAyB,IAAI,aAAa;EACvD,QAAQ,IAAI,sBAAsB,IAAI,YAAY;CACpD,EAAE;CACF,MAAM,UAAU,IAAI,eAAe;EACjC,MAAM,IAAI,yBAAyB,IAAI,aAAa;EACpD,KAAK;EACL,eAAe;EACf,OAAO;EACP;EACA,WAAW;EACX,QAAQ;CACV,CAAC;CACD,MAAM,aAAa,SAAS,UAAU,QAAQ,CAAC;CAC/C,MAAM,aAAa,qBAAqB,YAAY,SAAS,UAAU,UAAU;CACjF,MAAM,yBAAyB,4BAA4B;EACzD,cAAc;EACd,UAAU;GAAE,WAAW;GAA8B,KAAK;EAAuB;EACjF,QAAQ,SAAS,UAAU,QAAQ,SAAS;EAC5C,SAAS,OAAO,QAAQ,YAAY,MAAM,SAAS,YAAY,MAAM,EAAA,EAAI,SAAS;EAClF,MAAM,aACF;GACE,QAAQ;GACR,gBAAgB,OAAO,QAAQ,UAAU,QAAQ,cAAc,OAAO,QAAQ,KAAK;EACrF,IACA;EACJ,UAAU,SAAS,UAAU,wBAAwB,CAAC;CACxD,CAAC;CACD,MAAM,yBAAyB;EAC7B,WAAW,IAAI,gCAAgC;GAC7C,aAAa,IAAI,yBAAyB,IAAI,aAAa;GAC3D;GACA,OAAO,uBAAuB;GAC9B,UAAU,uBAAuB;GACjC,QAAQ;EACV,CAAC;EACD,KAAK,IAAI,0BAA0B;GACjC;GACA,OAAO,uBAAuB;GAC9B,UAAU,uBAAuB;GACjC,QAAQ,OAAO,MAAM,EAAE,MAAM,MAAM,CAAC;EACtC,CAAC;CACH;CACA,MAAM,sBAAsB,IAAI,oBAAoB;EAClD,OAAO,uBAAuB;EAC9B,UAAU,uBAAuB;CACnC,CAAC;CACD,MAAM,4BAAoD,EACxD,SAAS,OAAO,OAAO,iBACpB,cACG,MAAM,uBAAuB,UAAU,IAAI,aAAa,cAAc,YAAY,KAAK,CAAC,IACxF,MAAM,uBAAuB,IAAI,IAAI,OAAO,cAAc,YAAY,KAAK,CAAC,EAAA,CAC9E,WACN;CAEA,MAAM,kBAAkB,IAAI,kBAAkB,KAAK,QAAQ;EACzD,cAAc,IAAI,8BAA8B,IAAI,kBAAkB;EACtE,QAAQ,IAAI,yBAAyB,IAAI,aAAa;EACtD,QAAQ,IAAI,sBAAsB,IAAI,YAAY;CACpD,EAAE;CACF,MAAM,UAAkC;CAExC,MAAM,eAAe,IAAI,wBAAwB;EAC/C,MAAM,IAFqB,8BAA8B,IAAI,kBAExC;EACrB;EACA,aAAa;EACb,KAAK;EACL,QAAQ;CACV,CAAC;CACD,MAAM,iBAAiB,OAAO,MAAM,EAAE,MAAM,WAAW,CAAC;CACxD,MAAM,uBAAuB,IAAI,gCAAgC;EAC/D,MAAM,IAAI,sCAAsC,IAAI,cAAc;EAClE,YAAY;GACV,eAAe,OAAO,OAAO,iBAC1B,MAAM,uBAAuB,UAAU,IAAI,aAAa,cAAc,YAAY,KAAK,CAAC,EAAA,CACtF;GACL,SAAS,OAAO,WACb,MAAM,uBAAuB,IAAI,IAAI,OAAO,cAAc,YAAY,KAAK,CAAC,EAAA,CAAG;EACpF;EACA,QAAQ,EACN,UAAU,OAAO,OAAO,gBAAgB,aAAa,kBAAkB,aAAa,KAAK,EAC3F;EACA,UAAU,uBAAuB;EACjC,QAAQ;CACV,CAAC;CACD,MAAM,UAAU,IAAI,mBAAmB;EACrC,MAAM,IAAI,yBAAyB,IAAI,cAAc;EACrD,MAAM;EACN,QAAQ;CACV,CAAC;CAGD,MAAM,cAAc,IAAI,kBAAkB,KAAK,QAAQ;EACrD,UAAU,IAAI,0BAA0B,IAAI,cAAc;EAC1D,QAAQ,IAAI,sBAAsB,IAAI,YAAY;CACpD,EAAE;CACF,MAAM,WAAW,IAAI,oBAAoB;EACvC,MAAM,IAAI,0BAA0B,IAAI,cAAc;EACtD;EACA,QAAQ;EACR,QAAQ;EACR,KAAK;EACL,QAAQ;CACV,CAAC;CAED,MAAM,YAAY,IAAI,sBAAsB,IAAI,gBAAgB;CAChE,MAAM,kBAAkB,IAAI,4BAA4B,IAAI,gBAAgB;CAC5E,MAAM,YAAY,IAAI,sBAAsB,IAAI,gBAAgB;CAChE,MAAM,oBAAoB,IAAI,+BAA+B,IAAI,gBAAgB;CACjF,MAAM,eAAe,IAAI,0BAA0B,IAAI,gBAAgB;CACvE,MAAM,QAAQ,IAAI,gBAAgB;EAChC,MAAM;EACN,QAAQ;EACR;EACA;EACA,UAAU;EACV,UAAU;EACV,eAAe;EACf,UAAU;EACV,QAAQ;EACR;EACA;EACA,KAAK,IAAI,kBAAkB,KAAK,QAAQ;GACtC,OAAO,IAAI,sBAAsB,IAAI,gBAAgB;GACrD,UAAU,IAAI,0BAA0B,IAAI,gBAAgB;GAC5D,QAAQ,IAAI,sBAAsB,IAAI,YAAY;EACpD,EAAE;EACF,WAAW;EACX,QAAQ;CACV,CAAC;CACD,MAAM,QAAQ,IAAI,gBAAgB;EAChC,MAAM;EACN,QAAQ;EACR;EACA;EACA,UAAU;EACV,eAAe;EACf,UAAU;EACV,QAAQ;EACR;EACA,KAAK,IAAI,kBAAkB,KAAK,QAAQ;GACtC,OAAO,IAAI,sBAAsB,IAAI,gBAAgB;GACrD,UAAU,IAAI,0BAA0B,IAAI,gBAAgB;GAC5D,QAAQ,IAAI,sBAAsB,IAAI,YAAY;EACpD,EAAE;EACF,GAAI,UAAU,EAAE,OAAO;EACvB,GAAI,SAAS,QAAQ,EAAE,MAAM,QAAQ,KAAK;EAC1C,QAAQ;CACV,CAAC;CACD,MAAM,aAAa,IAAI,sBAAsB;EAC3C,MAAM;EACN,QAAQ;EACC;EACT;EACA,QAAQ;EACR;EACA,UAAU;EACV;EACA,KAAK,IAAI,kBAAkB,KAAK,QAAQ;GACtC,YAAY,IAAI,4BAA4B,IAAI,gBAAgB;GAChE,UAAU,IAAI,0BAA0B,IAAI,gBAAgB;GAC5D,QAAQ,IAAI,sBAAsB,IAAI,YAAY;EACpD,EAAE;EACF;EACA,QAAQ;CACV,CAAC;CAED,MAAM,gBAAgB,IAAI,wBAAwB;EAChD,MAAM;EACN,QAAQ;EACR;EACA;EACA,KAAK,IAAI,kBAAkB,KAAK,QAAQ;GACtC,eAAe,IAAI,+BAA+B,IAAI,gBAAgB;GACtE,QAAQ,IAAI,sBAAsB,IAAI,YAAY;EACpD,EAAE;EACF,QAAQ;CACV,CAAC;CAED,MAAM,UAAU,IAAI,kBAAkB;EACpC,WAAW,EAAE,6BAA6B,QAAQ,sBAAsB,EAAE;EAC1E,MAAM,IAAI,2BAA2B,IAAI,gBAAgB;EACzD,KAAK,IAAI,kBAAkB,KAAK,QAAQ;GACtC,SAAS,IAAI,2BAA2B,IAAI,gBAAgB;GAC5D,QAAQ,IAAI,sBAAsB,IAAI,YAAY;EACpD,EAAE;EACF,QAAQ;CACV,CAAC;CAED,MAAM,oBAAoB,IAAI,+BAA+B,EAAE;CAC/D,MAAM,mBAAmB,IAAI,yBAAyB;EACpD,MAAM;EACN,QAAQ;CACV,CAAC;CACD,MAAM,aAAa;EACjB,YAAY,IAAI,qBAAqB;GACnC;GACA;GACA,UAAU;GACV,UAAU;GACV,UAAU;GACV,YAAY;GACZ,QAAQ;GACR,QAAQ;EACV,CAAC;EACD,SAAS,IAAI,kBAAkB;GAAE;GAAS,QAAQ;GAAc;EAAc,CAAC;CACjF;CAEA,MAAM,YAAY;EAChB,OAAO,IAAI,uBAAuB;GAChC,MAAM,IAAI,uBAAuB,IAAI,eAAe;GACpD,aAAa;GACb,QAAQ;GACR,QAAQ;GACR;EACF,CAAC;EACD,QAAQ,IAAI,wBAAwB;GAClC,MAAM,IAAI,wBAAwB,IAAI,eAAe;GACrD;GACA;GACA,OAAO;GACP;GACA,QAAQ;GACR,QAAQ;GACR,eAAe;GACf,QAAQ;GACR;GACA;GACA;GACA,UAAU,uBAAuB;GACjC,kBAAkB,OAAO;EAC3B,CAAC;CACH;CAEA,MAAM,kBAAkB,IAAI,uBAC1B,IACA,OAAO,oBACP,kBACF;CAKA,MAAM,uBAAuB,MAAM,iBAAiB,SAAS,YAAY,kBAAkB;CAG3F,MAAM,kBAAkB,IAAI,kBAAkB,KAAK,QAAQ;EACzD,aAAa,IAAI,6BAA6B,IAAI,kBAAkB;EACpE,aAAa,IAAI,uBAAuB,IAAI,OAAO,oBAAoB,kBAAkB;EACzF,QAAQ,IAAI,sBAAsB,IAAI,YAAY;CACpD,EAAE;CACF,MAAM,eAAe,IAAI,wBAAwB;EAC/C,UAAU;EACV,MAAM,IAAI,6BAA6B,IAAI,kBAAkB;EAC7D,KAAK;EACL,aAAa;EACb,QAAQ;CACV,CAAC;CAKD,MAAM,iBAAiB,IAAI,kBAAkB,KAAK,QAAQ;EACxD,aAAa,IAAI,6BAA6B,IAAI,iBAAiB;EACnE,MAAM,IAAI,gCAAgC,IAAI,iBAAiB;EAC/D,aAAa,IAAI,uBAAuB,IAAI,OAAO,oBAAoB,iBAAiB;EACxF,QAAQ,IAAI,sBAAsB,IAAI,iBAAiB;CACzD,EAAE;CAGF,MAAM,gBAA+B;EACnC,eAAe,OAAO,OAAO,cAAc;GACzC,MAAM,UAAU,MAAM,kBAAkB,gBAAgB,OAAO,SAAS;GACxE,MAAM,OAAO,UAAU,MAAM,SAAS,YAAY,QAAQ,MAAM,IAAI;GACpE,OAAO,OAAO;IAAE,QAAQ,KAAK;IAAI,OAAO,KAAK;GAAM,IAAI;EACzD;EACA,aAAa,OAAO,OAAO,cAAc;GACvC,MAAM,SAAS,cAAc,YAAY,KAAK;GAC9C,MAAM,SAAS,MAAM,QAAQ,WAAW,WAAW,MAAM;GACzD,IAAI,QACF,OAAO;IAAE,IAAI,OAAO;IAAI,OAAO,OAAO;IAAO,MAAM,OAAO;GAAK;GAEjE,MAAM,WAAW,MAAM,QAAQ,aAAa,WAAW,MAAM;GAC7D,IAAI,UACF,OAAO;IAAE,IAAI,SAAS;IAAI,OAAO,SAAS;IAAO,MAAM,SAAS;GAAK;GAEvE,MAAM,YAAY,MAAM,QAAQ,cAAc,WAAW,MAAM;GAC/D,IAAI,WACF,OAAO;IAAE,IAAI,UAAU;IAAI,OAAO,UAAU;IAAO,MAAM,UAAU;GAAK;GAE1E,MAAM,QAAQ,MAAM,QAAQ,UAAU,WAAW,MAAM;GACvD,OAAO,QAAQ;IAAE,IAAI,MAAM;IAAI,OAAO,MAAM;IAAO,MAAM,MAAM;GAAK,IAAI;EAC1E;CACF;CAEA,MAAM,WAAW,IAAI,gCAAgC,IAAI,iBAAiB;CAC1E,MAAM,QAAQ,cAAc;EAC1B;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF,CAAC;CACD,MAAM,iBAAiB,kBAAkB;EACvC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,UAAU;CACZ,CAAC;CACD,MAAM,wBAAwB,IAAI,sBAAsB;EACtD,OAAO;EACP,OAAO;EACP,OAAO,uBAAuB;EAC9B,UAAU,uBAAuB;EACjC,YAAY;EACZ,UAAU;EACV;EACA,MAAM;EACN,QAAQ;EACR,UAAU,IAAI,sBAAsB,gBAAgB;EACpD,QAAQ;CACV,CAAC;CACD,MAAM,cAAc,IAAI,uBAAuB;EAC7C,MAAM,IAAI,6BAA6B,IAAI,iBAAiB;EAC5D;EACA,KAAK;EACL,QAAQ;EACR;EACA,SAAS;EACT;EACA,cAAc,UAAU,cAAc,YAAY,KAAK;EACvD,aAAa;EACb,UACE,SAAS,UAAU,YACnB,IAAI,mBAAmB,EAAE,qBAAqB,OAAO,2BAA2B,CAAC;EACnF,MAAM,MAAM;EACZ,QAAQ,MAAM;EACd,UAAU,IAAI,qBAAqB;GACjC,MAAM;GACN,UAAU,uBAAuB;GACjC;GACA,QAAQ;EACV,CAAC;EACD,WAAW;EACX,QAAQ;CACV,CAAC;CACD,iBAAiB,SAAS,WAAW;CACrC,iBAAiB,aACf;EACE,GAAG,eAAe;GAAE;GAAO;GAAS,QAAQ;EAAiB,CAAC;EAC9D,GAAG,YAAY;GAAE,QAAQ;GAAS,QAAQ;EAAc,CAAC;EACzD,GAAG,iBAAiB,EAAE,QAAQ,aAAa,CAAC;EAC5C,GAAG,WAAW;GAAE;GAAQ,MAAM;GAAe,QAAQ;EAAa,CAAC;EACnE,GAAG,iBAAiB;GAClB,WAAW;GACX,OAAO;GACP,QAAQ;EACV,CAAC;CACH,GACA;EAAC,GAAG;EAAyB,GAAG;EAA2B,GAAG;CAAmB,CACnF;CAEA,MAAM,WAAW,IAAI,iBAAiB;CACtC,IAAI,QACF,IAAI,kBAAkB;EAAE;EAAQ,QAAQ;CAAiB,CAAC,CAAC,CAAC,OAAO,QAAQ;CAE7E,SAAS,cAAc,UAAU,YAAY,OAAO,KAAK,CAAC;CAC1D,MAAM,eAAe,oBAAoB,OAAO,MAAM;CACtD,MAAM,cAAc,IAAI,mBACtB,IAAI,mBAAmB,IAAI,YAAY,GACvC,UACA,cACA,YACF;CAKA,MAAM,aAAa,OAAO,MAAM,EAAE,MAAM,OAAO,CAAC;CAGhD,MAAM,YACqB,MAAc,OACvC,OAAO,GAAG,SAAwB;EAChC,IAAI;GACF,OAAO,MAAM,GAAG,GAAG,IAAI;EACzB,SAAS,KAAK;GACZ,WAAW,MAAM,aAAa,KAAK,UAAU,EAAE,IAAI,CAAC;GACpD,MAAM;EACR;CACF;CACF,MAAM,MAAM,aAAa,MAAM;CAC/B,IAAI,CAAC,IAAI,cACP,WAAW,KACT,kGACA,EAAE,aAAa,OAAO,YAAY,CACpC;CAEF,MAAM,OAAmB,IAAI,WAAW;EACtC;EACA,SAAS,OAAO;EAChB,KAAK;GACH,cAAc,IAAI;GAClB,UAAU,IAAI;GACd,WAAW,IAAI;GACf,aAAa,IAAI;GACjB,eAAe,IAAI;EACrB;EACA,QAAQ,OAAO;EACf,gBAAgB,OAAO;EACvB,OAAO;GACL,mBAAmB,OAAO,EAAE,MAAM,UAAU;IAC1C,MAAM,SAAS,kBAAkB;KAAE,OAAO,KAAK;KAAO;IAAI,CAAC;GAC7D;GACA,iBAAiB,OAAO,EAAE,WAAW;IACnC,MAAM,SAAS,oBAAoB,EAAE,OAAO,KAAK,MAAM,CAAC;GAC1D;GACA,eAAe,SAAS,iBAAiB,OAAO,EAAE,OAAO,UAAU;IACjE,MAAM,SAAS,cAAc;KAC3B;KACA;IACF,CAAC;GACH,CAAC;GACD,kBAAkB,SAAS,oBAAoB,OAAO,SAAS;IAC7D,MAAM,EAAE,OAAO,SAAS;IACxB,MAAM,CAAC,WAAW,YAAY,KAAK,MAAM,GAAG;IAG5C,MAAM,aAAa,MAAM,SAAS,iBAAiB;KACjD;KACA,GAAI,cAAc,KAAA,KAAa,EAAE,UAAU;KAC3C,GAAI,aAAa,KAAA,KAAa,EAAE,SAAS;IAC3C,CAAC;IAED,OAAO,EACL,MAAM;KACJ,GAAG;KACH,IAAI,WAAW;IACjB,EACF;GACF,CAAC;GACD,qBAAqB,SAAS,uBAAuB,OAAO,YAAY;IACtE,MAAM,SAAS,MAAM,SAAS,oBAAoB,QAAQ,MAAM;IAChE,IAAI,CAAC,QACH;IAEF,IAAI,cAAc,kBAAkB,OAAO,KAAK,GAAG;KACjD,WAAW,KAAK,4CAA4C,EAAE,QAAQ,OAAO,GAAG,CAAC;KACjF,OAAO;IACT;IACA,MAAM,OAAO,MAAM,cAAc,mBAAmB,OAAO,EAAE;IAC7D,IAAI,KAAK,WAAW,GAClB;IAGF,MAAM,MAAM,MAAM,cAAc,QAAQ,KAAK,EAAE,CAAE,KAAK;IACtD,IAAI,CAAC,KACH;IAGF,OAAO,EAAE,MAAM;KAAE,GAAG;KAAS,sBAAsB,IAAI;IAAG,EAAE;GAC9D,CAAC;GAID,0BAA0B,SACxB,4BACA,OAAO,EAAE,cAAc,KAAK,MAAM,aAAa;IAC7C,MAAM,YAAY,MAAM,cAAc,sBAAsB;KAC1D,SAAS,OAAO;KAChB,GAAG;KACH,MAAM,IAAI,QAAQ,KAAA;IACpB,CAAC;IACD,OAAO,EAAE,MAAM;KAAE,GAAG;KAAK,IAAI,UAAU;IAAG,EAAE;GAC9C,CACF;GACA,0BAA0B,SAAS,4BAA4B,OAAO,EAAE,mBAAmB;IACzF,MAAM,cAAc,mBAAmB,aAAa,EAAE;GACxD,CAAC;GACD,iBAAiB,SAAS,mBAAmB,OAAO,EAAE,aAAa;IACjE,MAAM,cAAc,WAAW;KAC7B,OAAO,OAAO;KACd,QAAQ,OAAO;IACjB,CAAC;GACH,CAAC;GACD,oBAAoB,SAAS,sBAAsB,OAAO,EAAE,aAAa;IACvE,MAAM,cAAc,cAAc,OAAO,gBAAgB,OAAO,MAAM;GACxE,CAAC;EACH;EACA,QAAQ;EACR,cAAc,OAAO;EACrB,eAAe,OAAO;CACxB,CAAC;CACD,YAAY,UAAU;CAEtB,OAAO;EACL;EACA;EACA;EACA;EACA;EAEA,aAAa;EACb;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,mBAAmB,uBAAuB;EAC1C,sBAAsB,uBAAuB;EAC7C;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,aAAa;EACb;EACA;EACA,WAAW;EACX;EACA,SAAS,MAAM;EACf;EACA;EACA,gBAAgB;EAEhB,UAAU;CACZ;AACF"}
@@ -8,6 +8,7 @@ const CONTENT_SUBJECTS = /* @__PURE__ */ new Set([
8
8
  "course",
9
9
  "download",
10
10
  "community",
11
+ "event",
11
12
  "bundle"
12
13
  ]);
13
14
  function buildNotificationTypeRegistry(declarations, defaults, configured) {