@headless-lms/server 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (351) hide show
  1. package/README.md +1 -1
  2. package/dist/_virtual/_rolldown/runtime.js +5 -0
  3. package/dist/app/community-mail.d.ts +26 -0
  4. package/dist/app/community-mail.js +96 -0
  5. package/dist/app/community-mail.js.map +1 -0
  6. package/dist/app/container.d.ts +22 -14
  7. package/dist/app/container.js +186 -62
  8. package/dist/app/container.js.map +1 -1
  9. package/dist/app/mcp-endpoints.d.ts +13 -0
  10. package/dist/app/mcp-endpoints.js +25 -0
  11. package/dist/app/mcp-endpoints.js.map +1 -0
  12. package/dist/http/config.d.ts +2 -1
  13. package/dist/http/errors.d.ts +14 -0
  14. package/dist/http/errors.js +147 -0
  15. package/dist/http/errors.js.map +1 -0
  16. package/dist/http/mcp/catalogue.d.ts +37 -0
  17. package/dist/http/mcp/catalogue.js +69 -0
  18. package/dist/http/mcp/catalogue.js.map +1 -0
  19. package/dist/http/mcp/challenge.d.ts +7 -0
  20. package/dist/http/mcp/challenge.js +35 -0
  21. package/dist/http/mcp/challenge.js.map +1 -0
  22. package/dist/http/mcp/input-schema.d.ts +7 -0
  23. package/dist/http/mcp/input-schema.js +28 -0
  24. package/dist/http/mcp/input-schema.js.map +1 -0
  25. package/dist/http/mcp/route.d.ts +8 -0
  26. package/dist/http/mcp/route.js +75 -0
  27. package/dist/http/mcp/route.js.map +1 -0
  28. package/dist/http/mcp/server.d.ts +8 -0
  29. package/dist/http/mcp/server.js +31 -0
  30. package/dist/http/mcp/server.js.map +1 -0
  31. package/dist/http/mcp/test-support.d.ts +55 -0
  32. package/dist/http/mcp/test-support.js +151 -0
  33. package/dist/http/mcp/test-support.js.map +1 -0
  34. package/dist/http/mcp/tool.d.ts +48 -0
  35. package/dist/http/mcp/tool.js +58 -0
  36. package/dist/http/mcp/tool.js.map +1 -0
  37. package/dist/http/mcp/tools/automations.d.ts +6 -0
  38. package/dist/http/mcp/tools/automations.js +120 -0
  39. package/dist/http/mcp/tools/automations.js.map +1 -0
  40. package/dist/http/mcp/tools/catalog.d.ts +6 -0
  41. package/dist/http/mcp/tools/catalog.js +200 -0
  42. package/dist/http/mcp/tools/catalog.js.map +1 -0
  43. package/dist/http/mcp/tools/comments.d.ts +6 -0
  44. package/dist/http/mcp/tools/comments.js +70 -0
  45. package/dist/http/mcp/tools/comments.js.map +1 -0
  46. package/dist/http/mcp/tools/communities.d.ts +6 -0
  47. package/dist/http/mcp/tools/communities.js +175 -0
  48. package/dist/http/mcp/tools/communities.js.map +1 -0
  49. package/dist/http/mcp/tools/community-members.d.ts +6 -0
  50. package/dist/http/mcp/tools/community-members.js +89 -0
  51. package/dist/http/mcp/tools/community-members.js.map +1 -0
  52. package/dist/http/mcp/tools/community-moderation.d.ts +6 -0
  53. package/dist/http/mcp/tools/community-moderation.js +144 -0
  54. package/dist/http/mcp/tools/community-moderation.js.map +1 -0
  55. package/dist/http/mcp/tools/courses.d.ts +6 -0
  56. package/dist/http/mcp/tools/courses.js +121 -0
  57. package/dist/http/mcp/tools/courses.js.map +1 -0
  58. package/dist/http/mcp/tools/index.d.ts +6 -0
  59. package/dist/http/mcp/tools/index.js +23 -0
  60. package/dist/http/mcp/tools/index.js.map +1 -0
  61. package/dist/http/mcp/tools/people.d.ts +6 -0
  62. package/dist/http/mcp/tools/people.js +145 -0
  63. package/dist/http/mcp/tools/people.js.map +1 -0
  64. package/dist/http/plugins/auth.js +13 -5
  65. package/dist/http/plugins/auth.js.map +1 -1
  66. package/dist/http/plugins/error-handler.d.ts +3 -2
  67. package/dist/http/plugins/error-handler.js +52 -57
  68. package/dist/http/plugins/error-handler.js.map +1 -1
  69. package/dist/http/plugins/openapi.js +70 -20
  70. package/dist/http/plugins/openapi.js.map +1 -1
  71. package/dist/http/routes/access.d.ts +7 -0
  72. package/dist/http/routes/access.js +140 -0
  73. package/dist/http/routes/access.js.map +1 -0
  74. package/dist/http/routes/activities.js +97 -20
  75. package/dist/http/routes/activities.js.map +1 -1
  76. package/dist/http/routes/assets.js +60 -51
  77. package/dist/http/routes/assets.js.map +1 -1
  78. package/dist/http/routes/automations.js +64 -20
  79. package/dist/http/routes/automations.js.map +1 -1
  80. package/dist/http/routes/bundles.d.ts +7 -0
  81. package/dist/http/routes/bundles.js +181 -0
  82. package/dist/http/routes/bundles.js.map +1 -0
  83. package/dist/http/routes/communities.d.ts +7 -0
  84. package/dist/http/routes/communities.js +462 -0
  85. package/dist/http/routes/communities.js.map +1 -0
  86. package/dist/http/routes/community-moderation.d.ts +7 -0
  87. package/dist/http/routes/community-moderation.js +425 -0
  88. package/dist/http/routes/community-moderation.js.map +1 -0
  89. package/dist/http/routes/course-analytics.d.ts +7 -0
  90. package/dist/http/routes/course-analytics.js +60 -0
  91. package/dist/http/routes/course-analytics.js.map +1 -0
  92. package/dist/http/routes/courses.js +38 -23
  93. package/dist/http/routes/courses.js.map +1 -1
  94. package/dist/http/routes/dashboard.js +17 -8
  95. package/dist/http/routes/dashboard.js.map +1 -1
  96. package/dist/http/routes/discussion.js +35 -27
  97. package/dist/http/routes/discussion.js.map +1 -1
  98. package/dist/http/routes/downloads.js +58 -40
  99. package/dist/http/routes/downloads.js.map +1 -1
  100. package/dist/http/routes/entitlements.js +16 -7
  101. package/dist/http/routes/entitlements.js.map +1 -1
  102. package/dist/http/routes/integrations.js +38 -41
  103. package/dist/http/routes/integrations.js.map +1 -1
  104. package/dist/http/routes/member/actor.d.ts +14 -0
  105. package/dist/http/routes/member/actor.js +11 -0
  106. package/dist/http/routes/member/actor.js.map +1 -0
  107. package/dist/http/routes/member/assets.d.ts +7 -0
  108. package/dist/http/routes/member/assets.js +79 -0
  109. package/dist/http/routes/member/assets.js.map +1 -0
  110. package/dist/http/routes/member/chats.d.ts +7 -0
  111. package/dist/http/routes/member/chats.js +456 -0
  112. package/dist/http/routes/member/chats.js.map +1 -0
  113. package/dist/http/routes/member/comments.d.ts +7 -0
  114. package/dist/http/routes/member/comments.js +187 -0
  115. package/dist/http/routes/member/comments.js.map +1 -0
  116. package/dist/http/routes/member/community.d.ts +7 -0
  117. package/dist/http/routes/member/community.js +288 -0
  118. package/dist/http/routes/member/community.js.map +1 -0
  119. package/dist/http/routes/member/courses.d.ts +7 -0
  120. package/dist/http/routes/member/courses.js +101 -0
  121. package/dist/http/routes/member/courses.js.map +1 -0
  122. package/dist/http/routes/member/downloads.d.ts +7 -0
  123. package/dist/http/routes/member/downloads.js +56 -0
  124. package/dist/http/routes/member/downloads.js.map +1 -0
  125. package/dist/http/routes/member/files.d.ts +7 -0
  126. package/dist/http/routes/member/files.js +184 -0
  127. package/dist/http/routes/member/files.js.map +1 -0
  128. package/dist/http/routes/member/index.d.ts +7 -0
  129. package/dist/http/routes/member/index.js +31 -0
  130. package/dist/http/routes/member/index.js.map +1 -0
  131. package/dist/http/routes/member/notifications.d.ts +7 -0
  132. package/dist/http/routes/member/notifications.js +101 -0
  133. package/dist/http/routes/member/notifications.js.map +1 -0
  134. package/dist/http/routes/member/org.d.ts +7 -0
  135. package/dist/http/routes/member/org.js +56 -0
  136. package/dist/http/routes/member/org.js.map +1 -0
  137. package/dist/http/routes/member/posts.d.ts +7 -0
  138. package/dist/http/routes/member/posts.js +612 -0
  139. package/dist/http/routes/member/posts.js.map +1 -0
  140. package/dist/http/routes/member/profile.d.ts +7 -0
  141. package/dist/http/routes/member/profile.js +48 -0
  142. package/dist/http/routes/member/profile.js.map +1 -0
  143. package/dist/http/routes/member/progress.d.ts +7 -0
  144. package/dist/http/routes/member/progress.js +60 -0
  145. package/dist/http/routes/member/progress.js.map +1 -0
  146. package/dist/http/routes/organizations.js +49 -24
  147. package/dist/http/routes/organizations.js.map +1 -1
  148. package/dist/http/routes/students.js +43 -37
  149. package/dist/http/routes/students.js.map +1 -1
  150. package/dist/http/routes/webhooks.d.ts +7 -0
  151. package/dist/http/routes/webhooks.js +167 -0
  152. package/dist/http/routes/webhooks.js.map +1 -0
  153. package/dist/http/routes.js +14 -2
  154. package/dist/http/routes.js.map +1 -1
  155. package/dist/http/schemas/access.d.ts +146 -0
  156. package/dist/http/schemas/access.js +36 -0
  157. package/dist/http/schemas/access.js.map +1 -0
  158. package/dist/http/schemas/activities.d.ts +36 -1
  159. package/dist/http/schemas/activities.js +9 -4
  160. package/dist/http/schemas/activities.js.map +1 -1
  161. package/dist/http/schemas/assets.d.ts +76 -73
  162. package/dist/http/schemas/assets.js +23 -13
  163. package/dist/http/schemas/assets.js.map +1 -1
  164. package/dist/http/schemas/automations.d.ts +85 -8
  165. package/dist/http/schemas/automations.js +13 -2
  166. package/dist/http/schemas/automations.js.map +1 -1
  167. package/dist/http/schemas/bundles.d.ts +63 -0
  168. package/dist/http/schemas/bundles.js +23 -0
  169. package/dist/http/schemas/bundles.js.map +1 -0
  170. package/dist/http/schemas/chats.d.ts +386 -0
  171. package/dist/http/schemas/chats.js +58 -0
  172. package/dist/http/schemas/chats.js.map +1 -0
  173. package/dist/http/schemas/community.d.ts +537 -0
  174. package/dist/http/schemas/community.js +129 -0
  175. package/dist/http/schemas/community.js.map +1 -0
  176. package/dist/http/schemas/content.d.ts +22 -13
  177. package/dist/http/schemas/content.js +11 -9
  178. package/dist/http/schemas/content.js.map +1 -1
  179. package/dist/http/schemas/course-analytics.d.ts +29 -0
  180. package/dist/http/schemas/course-analytics.js +21 -0
  181. package/dist/http/schemas/course-analytics.js.map +1 -0
  182. package/dist/http/schemas/dashboard.js.map +1 -1
  183. package/dist/http/schemas/discussion.d.ts +85 -23
  184. package/dist/http/schemas/discussion.js +12 -11
  185. package/dist/http/schemas/discussion.js.map +1 -1
  186. package/dist/http/schemas/downloads.d.ts +13 -5
  187. package/dist/http/schemas/downloads.js +14 -11
  188. package/dist/http/schemas/downloads.js.map +1 -1
  189. package/dist/http/schemas/entitlements.d.ts +14 -6
  190. package/dist/http/schemas/entitlements.js +8 -6
  191. package/dist/http/schemas/entitlements.js.map +1 -1
  192. package/dist/http/schemas/files.d.ts +45 -0
  193. package/dist/http/schemas/files.js +25 -0
  194. package/dist/http/schemas/files.js.map +1 -0
  195. package/dist/http/schemas/index.d.ts +19 -10
  196. package/dist/http/schemas/index.js +19 -10
  197. package/dist/http/schemas/integrations.d.ts +4 -4
  198. package/dist/http/schemas/integrations.js.map +1 -1
  199. package/dist/http/schemas/invites.d.ts +4 -0
  200. package/dist/http/schemas/invites.js +3 -1
  201. package/dist/http/schemas/invites.js.map +1 -1
  202. package/dist/http/schemas/{learn.d.ts → member.d.ts} +42 -19
  203. package/dist/http/schemas/{learn.js → member.js} +17 -13
  204. package/dist/http/schemas/member.js.map +1 -0
  205. package/dist/http/schemas/members.d.ts +39 -6
  206. package/dist/http/schemas/members.js +17 -3
  207. package/dist/http/schemas/members.js.map +1 -1
  208. package/dist/http/schemas/notifications.d.ts +227 -0
  209. package/dist/http/schemas/notifications.js +44 -0
  210. package/dist/http/schemas/notifications.js.map +1 -0
  211. package/dist/http/schemas/organizations.js.map +1 -1
  212. package/dist/http/schemas/posts.d.ts +316 -0
  213. package/dist/http/schemas/posts.js +47 -0
  214. package/dist/http/schemas/posts.js.map +1 -0
  215. package/dist/http/schemas/profile.d.ts +23 -0
  216. package/dist/http/schemas/profile.js +15 -0
  217. package/dist/http/schemas/profile.js.map +1 -0
  218. package/dist/http/schemas/settings.js.map +1 -1
  219. package/dist/http/schemas/shared.d.ts +26 -1
  220. package/dist/http/schemas/shared.js +22 -3
  221. package/dist/http/schemas/shared.js.map +1 -1
  222. package/dist/http/schemas/students.d.ts +32 -10
  223. package/dist/http/schemas/students.js +19 -6
  224. package/dist/http/schemas/students.js.map +1 -1
  225. package/dist/http/scope.d.ts +2 -1
  226. package/dist/http/scope.js +20 -4
  227. package/dist/http/scope.js.map +1 -1
  228. package/dist/http/server.js +10 -3
  229. package/dist/http/server.js.map +1 -1
  230. package/dist/http/web-bridge.d.ts +1 -1
  231. package/dist/http/web-bridge.js +2 -2
  232. package/dist/http/web-bridge.js.map +1 -1
  233. package/dist/http/well-known.d.ts +7 -0
  234. package/dist/http/well-known.js +22 -0
  235. package/dist/http/well-known.js.map +1 -0
  236. package/dist/index.d.ts +3 -2
  237. package/dist/index.js +4 -2
  238. package/dist/index.js.map +1 -1
  239. package/dist/node_modules/.pnpm/@jridgewell_sourcemap-codec@1.5.5/node_modules/@jridgewell/sourcemap-codec/dist/sourcemap-codec.js +78 -0
  240. package/dist/node_modules/.pnpm/@jridgewell_sourcemap-codec@1.5.5/node_modules/@jridgewell/sourcemap-codec/dist/sourcemap-codec.js.map +1 -0
  241. package/dist/node_modules/.pnpm/@vitest_expect@2.1.9/node_modules/@vitest/expect/dist/index.js +1267 -0
  242. package/dist/node_modules/.pnpm/@vitest_expect@2.1.9/node_modules/@vitest/expect/dist/index.js.map +1 -0
  243. package/dist/node_modules/.pnpm/@vitest_pretty-format@2.1.9/node_modules/@vitest/pretty-format/dist/index.js +814 -0
  244. package/dist/node_modules/.pnpm/@vitest_pretty-format@2.1.9/node_modules/@vitest/pretty-format/dist/index.js.map +1 -0
  245. package/dist/node_modules/.pnpm/@vitest_runner@2.1.9/node_modules/@vitest/runner/dist/chunk-tasks.js +43 -0
  246. package/dist/node_modules/.pnpm/@vitest_runner@2.1.9/node_modules/@vitest/runner/dist/chunk-tasks.js.map +1 -0
  247. package/dist/node_modules/.pnpm/@vitest_runner@2.1.9/node_modules/@vitest/runner/dist/index.js +527 -0
  248. package/dist/node_modules/.pnpm/@vitest_runner@2.1.9/node_modules/@vitest/runner/dist/index.js.map +1 -0
  249. package/dist/node_modules/.pnpm/@vitest_runner@2.1.9/node_modules/@vitest/runner/dist/utils.js +2 -0
  250. package/dist/node_modules/.pnpm/@vitest_snapshot@2.1.9/node_modules/@vitest/snapshot/dist/index.js +1737 -0
  251. package/dist/node_modules/.pnpm/@vitest_snapshot@2.1.9/node_modules/@vitest/snapshot/dist/index.js.map +1 -0
  252. package/dist/node_modules/.pnpm/@vitest_spy@2.1.9/node_modules/@vitest/spy/dist/index.js +137 -0
  253. package/dist/node_modules/.pnpm/@vitest_spy@2.1.9/node_modules/@vitest/spy/dist/index.js.map +1 -0
  254. package/dist/node_modules/.pnpm/@vitest_utils@2.1.9/node_modules/@vitest/utils/dist/chunk-_commonjsHelpers.js +118 -0
  255. package/dist/node_modules/.pnpm/@vitest_utils@2.1.9/node_modules/@vitest/utils/dist/chunk-_commonjsHelpers.js.map +1 -0
  256. package/dist/node_modules/.pnpm/@vitest_utils@2.1.9/node_modules/@vitest/utils/dist/diff.js +1222 -0
  257. package/dist/node_modules/.pnpm/@vitest_utils@2.1.9/node_modules/@vitest/utils/dist/diff.js.map +1 -0
  258. package/dist/node_modules/.pnpm/@vitest_utils@2.1.9/node_modules/@vitest/utils/dist/error.js +91 -0
  259. package/dist/node_modules/.pnpm/@vitest_utils@2.1.9/node_modules/@vitest/utils/dist/error.js.map +1 -0
  260. package/dist/node_modules/.pnpm/@vitest_utils@2.1.9/node_modules/@vitest/utils/dist/helpers.js +113 -0
  261. package/dist/node_modules/.pnpm/@vitest_utils@2.1.9/node_modules/@vitest/utils/dist/helpers.js.map +1 -0
  262. package/dist/node_modules/.pnpm/@vitest_utils@2.1.9/node_modules/@vitest/utils/dist/index.js +449 -0
  263. package/dist/node_modules/.pnpm/@vitest_utils@2.1.9/node_modules/@vitest/utils/dist/index.js.map +1 -0
  264. package/dist/node_modules/.pnpm/@vitest_utils@2.1.9/node_modules/@vitest/utils/dist/source-map.js +163 -0
  265. package/dist/node_modules/.pnpm/@vitest_utils@2.1.9/node_modules/@vitest/utils/dist/source-map.js.map +1 -0
  266. package/dist/node_modules/.pnpm/chai@5.3.3/node_modules/chai/index.js +3187 -0
  267. package/dist/node_modules/.pnpm/chai@5.3.3/node_modules/chai/index.js.map +1 -0
  268. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/arguments.js +11 -0
  269. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/arguments.js.map +1 -0
  270. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/array.js +16 -0
  271. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/array.js.map +1 -0
  272. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/bigint.js +11 -0
  273. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/bigint.js.map +1 -0
  274. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/class.js +15 -0
  275. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/class.js.map +1 -0
  276. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/date.js +13 -0
  277. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/date.js.map +1 -0
  278. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/error.js +34 -0
  279. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/error.js.map +1 -0
  280. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/function.js +12 -0
  281. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/function.js.map +1 -0
  282. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/helpers.js +122 -0
  283. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/helpers.js.map +1 -0
  284. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/html.js +39 -0
  285. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/html.js.map +1 -0
  286. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/index.js +99 -0
  287. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/index.js.map +1 -0
  288. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/map.js +25 -0
  289. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/map.js.map +1 -0
  290. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/number.js +14 -0
  291. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/number.js.map +1 -0
  292. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/object.js +21 -0
  293. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/object.js.map +1 -0
  294. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/promise.js +6 -0
  295. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/promise.js.map +1 -0
  296. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/regexp.js +12 -0
  297. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/regexp.js.map +1 -0
  298. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/set.js +18 -0
  299. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/set.js.map +1 -0
  300. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/string.js +24 -0
  301. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/string.js.map +1 -0
  302. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/symbol.js +9 -0
  303. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/symbol.js.map +1 -0
  304. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/typedarray.js +30 -0
  305. package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/typedarray.js.map +1 -0
  306. package/dist/node_modules/.pnpm/magic-string@0.30.21/node_modules/magic-string/dist/magic-string.es.js +939 -0
  307. package/dist/node_modules/.pnpm/magic-string@0.30.21/node_modules/magic-string/dist/magic-string.es.js.map +1 -0
  308. package/dist/node_modules/.pnpm/pathe@1.1.2/node_modules/pathe/dist/shared/pathe.ff20891b.js +82 -0
  309. package/dist/node_modules/.pnpm/pathe@1.1.2/node_modules/pathe/dist/shared/pathe.ff20891b.js.map +1 -0
  310. package/dist/node_modules/.pnpm/tinyrainbow@1.2.0/node_modules/tinyrainbow/dist/chunk-BVHSVHOK.js +82 -0
  311. package/dist/node_modules/.pnpm/tinyrainbow@1.2.0/node_modules/tinyrainbow/dist/chunk-BVHSVHOK.js.map +1 -0
  312. package/dist/node_modules/.pnpm/tinyrainbow@1.2.0/node_modules/tinyrainbow/dist/node.js +8 -0
  313. package/dist/node_modules/.pnpm/tinyrainbow@1.2.0/node_modules/tinyrainbow/dist/node.js.map +1 -0
  314. package/dist/node_modules/.pnpm/tinyspy@3.0.2/node_modules/tinyspy/dist/index.js +83 -0
  315. package/dist/node_modules/.pnpm/tinyspy@3.0.2/node_modules/tinyspy/dist/index.js.map +1 -0
  316. package/dist/node_modules/.pnpm/vitest@2.1.9_@types_node@22.20.0_jsdom@25.0.1_lightningcss@1.32.0/node_modules/vitest/dist/chunks/_commonjsHelpers.BFTU3MAI.js +9 -0
  317. package/dist/node_modules/.pnpm/vitest@2.1.9_@types_node@22.20.0_jsdom@25.0.1_lightningcss@1.32.0/node_modules/vitest/dist/chunks/_commonjsHelpers.BFTU3MAI.js.map +1 -0
  318. package/dist/node_modules/.pnpm/vitest@2.1.9_@types_node@22.20.0_jsdom@25.0.1_lightningcss@1.32.0/node_modules/vitest/dist/chunks/date.W2xKR2qe.js +51 -0
  319. package/dist/node_modules/.pnpm/vitest@2.1.9_@types_node@22.20.0_jsdom@25.0.1_lightningcss@1.32.0/node_modules/vitest/dist/chunks/date.W2xKR2qe.js.map +1 -0
  320. package/dist/node_modules/.pnpm/vitest@2.1.9_@types_node@22.20.0_jsdom@25.0.1_lightningcss@1.32.0/node_modules/vitest/dist/chunks/utils.C8RiOc4B.js +48 -0
  321. package/dist/node_modules/.pnpm/vitest@2.1.9_@types_node@22.20.0_jsdom@25.0.1_lightningcss@1.32.0/node_modules/vitest/dist/chunks/utils.C8RiOc4B.js.map +1 -0
  322. package/dist/node_modules/.pnpm/vitest@2.1.9_@types_node@22.20.0_jsdom@25.0.1_lightningcss@1.32.0/node_modules/vitest/dist/chunks/vi.DgezovHB.js +2426 -0
  323. package/dist/node_modules/.pnpm/vitest@2.1.9_@types_node@22.20.0_jsdom@25.0.1_lightningcss@1.32.0/node_modules/vitest/dist/chunks/vi.DgezovHB.js.map +1 -0
  324. package/dist/packages/server/package.js +6 -0
  325. package/dist/packages/server/package.js.map +1 -0
  326. package/package.json +9 -7
  327. package/dist/app/notifications.d.ts +0 -7
  328. package/dist/app/notifications.js +0 -23
  329. package/dist/app/notifications.js.map +0 -1
  330. package/dist/http/routes/learn/assets.d.ts +0 -8
  331. package/dist/http/routes/learn/assets.js +0 -32
  332. package/dist/http/routes/learn/assets.js.map +0 -1
  333. package/dist/http/routes/learn/comments.d.ts +0 -7
  334. package/dist/http/routes/learn/comments.js +0 -175
  335. package/dist/http/routes/learn/comments.js.map +0 -1
  336. package/dist/http/routes/learn/courses.d.ts +0 -7
  337. package/dist/http/routes/learn/courses.js +0 -75
  338. package/dist/http/routes/learn/courses.js.map +0 -1
  339. package/dist/http/routes/learn/downloads.d.ts +0 -7
  340. package/dist/http/routes/learn/downloads.js +0 -76
  341. package/dist/http/routes/learn/downloads.js.map +0 -1
  342. package/dist/http/routes/learn/index.d.ts +0 -7
  343. package/dist/http/routes/learn/index.js +0 -19
  344. package/dist/http/routes/learn/index.js.map +0 -1
  345. package/dist/http/routes/learn/org.d.ts +0 -7
  346. package/dist/http/routes/learn/org.js +0 -50
  347. package/dist/http/routes/learn/org.js.map +0 -1
  348. package/dist/http/routes/learn/progress.d.ts +0 -7
  349. package/dist/http/routes/learn/progress.js +0 -63
  350. package/dist/http/routes/learn/progress.js.map +0 -1
  351. package/dist/http/schemas/learn.js.map +0 -1
@@ -1,25 +1,26 @@
1
1
  import { loadIntegrations } from "./integrations.js";
2
- import { registerNotificationSubscribers } from "./notifications.js";
3
- import { DrizzleAssetsRepository, DrizzleAutomationRunsRepository, DrizzleAutomationsRepository, DrizzleConnectionsRepository, DrizzleContentRepository, DrizzleCredentialStore, DrizzleDashboardRepository, DrizzleDiscussionRepository, DrizzleEntitlementsRepository, DrizzleIdentityRepository, DrizzleLearnRepository, DrizzleMembersRepository, DrizzleOrganizationsRepository, DrizzleOutboxAppender, DrizzleOutboxStore, DrizzleProgressRepository, DrizzleSettingsRepository, DrizzleStudentsRepository, DrizzleUnitOfWork, createDb } from "@headless-lms/adapter-db";
2
+ import { mcpEndpoints } from "./mcp-endpoints.js";
3
+ import { communityMail } from "./community-mail.js";
4
+ import { DrizzleAdminRepository, DrizzleAssetsRepository, DrizzleAutomationRunsRepository, DrizzleAutomationsRepository, DrizzleChatRepository, DrizzleConnectionsRepository, DrizzleContentRepository, DrizzleCredentialStore, DrizzleDiscussionRepository, DrizzleEntitlementsRepository, DrizzleIdentityRepository, DrizzleMemberRepository, DrizzleMembersRepository, DrizzleNotificationsRepository, DrizzleOrganizationsRepository, DrizzleOutboxAppender, DrizzleOutboxStore, DrizzlePostRepository, DrizzleProgressRepository, DrizzleSettingsRepository, DrizzleUnitOfWork, createDb } from "@headless-lms/adapter-db";
4
5
  import { InMemoryEventBus } from "@headless-lms/adapter-defaults/events";
5
6
  import { PollingOutboxRelay } from "@headless-lms/adapter-defaults/events/outbox-relay";
6
7
  import { InlineAutomationEngine } from "@headless-lms/adapter-defaults/workflows";
8
+ import { FetchWebhookSender } from "@headless-lms/adapter-defaults/webhooks";
7
9
  import { EmailAdapter, StubTemplateRenderer } from "@headless-lms/adapter-defaults/email";
8
10
  import { createRootLogger, requestLogContext } from "@headless-lms/adapter-defaults/logging";
9
11
  import { StorageAdapter } from "@headless-lms/adapter-defaults/storage";
10
12
  import { BetterAuth } from "@headless-lms/adapter-auth";
11
- import { ContentServiceImpl } from "@headless-lms/core/content";
13
+ import { ContentService } from "@headless-lms/core/content";
12
14
  import { EntitlementsServiceImpl } from "@headless-lms/core/entitlements";
13
15
  import { ProgressServiceImpl } from "@headless-lms/core/progress";
14
- import { DiscussionServiceImpl } from "@headless-lms/core/discussion";
15
- import { IdentityServiceImpl } from "@headless-lms/core/identity";
16
+ import { ChatServiceImpl, DiscussionServiceImpl, PostServiceImpl } from "@headless-lms/core/discussion";
17
+ import { IdentityServiceImpl, NotificationServiceImpl } from "@headless-lms/core/identity";
16
18
  import { OrganizationServiceImpl, parseRole } from "@headless-lms/core/organizations";
17
- import { AssetsServiceImpl } from "@headless-lms/core/assets";
19
+ import { AssetsServiceImpl, createFileUrls } from "@headless-lms/core/assets";
18
20
  import { IntegrationsServiceImpl } from "@headless-lms/core/integrations";
19
21
  import { AutomationsServiceImpl } from "@headless-lms/core/automations";
20
- import { StudentsReportServiceImpl } from "@headless-lms/core/reporting/students";
21
- import { DashboardReportServiceImpl } from "@headless-lms/core/reporting/dashboard";
22
- import { LearnReportServiceImpl } from "@headless-lms/core/reporting/learn";
22
+ import { AdminReportServiceImpl } from "@headless-lms/core/reporting/admin";
23
+ import { MemberReportServiceImpl } from "@headless-lms/core/reporting/member";
23
24
  import { Mailer } from "@headless-lms/core/shared/mailer";
24
25
  import { SettingsService } from "@headless-lms/core/shared/settings";
25
26
  //#region src/app/container.ts
@@ -54,7 +55,8 @@ async function buildContainer(config, options) {
54
55
  const email = options?.adapters?.email ?? new EmailAdapter(logger.child({ name: "email" }));
55
56
  const storage = options?.adapters?.storage ?? new StorageAdapter(logger.child({ name: "storage" }));
56
57
  const templates = options?.adapters?.templates ?? new StubTemplateRenderer(logger.child({ name: "email" }));
57
- const automationEngine = options?.adapters?.workflows ?? new InlineAutomationEngine();
58
+ const workflows = options?.adapters?.workflows;
59
+ const automationEngine = typeof workflows === "function" ? workflows(automationsLogger) : workflows ?? new InlineAutomationEngine(automationsLogger);
58
60
  const mailer = new Mailer(templates, email, {
59
61
  ...config.emailBranding ?? {
60
62
  brandName: "Headless LMS",
@@ -62,6 +64,19 @@ async function buildContainer(config, options) {
62
64
  },
63
65
  studentPortalUrl: config.studentPortalUrl
64
66
  });
67
+ const fileUrls = createFileUrls(config.authBaseURL);
68
+ const assets = new AssetsServiceImpl({
69
+ storage,
70
+ repo: new DrizzleAssetsRepository(db, assetsLogger),
71
+ uow: new DrizzleUnitOfWork(db, (tx) => ({
72
+ assets: new DrizzleAssetsRepository(tx, assetsLogger),
73
+ outbox: new DrizzleOutboxAppender(tx, outboxLogger)
74
+ })),
75
+ memberImageMaxBytes: config.memberImageMaxBytes,
76
+ downloadLinkExpirySeconds: config.deliveryExpirySeconds,
77
+ fileUrls,
78
+ logger: assetsLogger
79
+ });
65
80
  const identityUoW = new DrizzleUnitOfWork(db, (tx) => ({
66
81
  identity: new DrizzleIdentityRepository(tx, logger.child({ name: "identity" })),
67
82
  outbox: new DrizzleOutboxAppender(tx, logger.child({ name: "identity" }))
@@ -70,17 +85,29 @@ async function buildContainer(config, options) {
70
85
  repo: new DrizzleIdentityRepository(db, logger.child({ name: "identity_repo" })),
71
86
  logger: logger.child({ name: "identity" }),
72
87
  uow: identityUoW,
73
- mailer
88
+ mailer,
89
+ images: assets,
90
+ fileUrls
74
91
  });
92
+ const notifications = new NotificationServiceImpl({
93
+ repo: new DrizzleNotificationsRepository(db),
94
+ logger: logger.child({ name: "notifications" })
95
+ });
96
+ const organizationsRepo = new DrizzleOrganizationsRepository(db, logger.child({ name: "org" }));
75
97
  const organizationsUow = new DrizzleUnitOfWork(db, (tx) => ({
76
98
  organizations: new DrizzleOrganizationsRepository(tx, logger.child({ name: "org" })),
77
99
  outbox: new DrizzleOutboxAppender(tx, logger.child({ name: "org" }))
78
100
  }));
101
+ const orgAdminRef = { current: void 0 };
79
102
  const organizations = new OrganizationServiceImpl({
80
- repo: new DrizzleOrganizationsRepository(db, logger.child({ name: "org" })),
103
+ repo: organizationsRepo,
81
104
  membersRepo: new DrizzleMembersRepository(db, logger.child({ name: "org" })),
82
105
  people: identity,
83
106
  uow: organizationsUow,
107
+ orgAdmin: () => {
108
+ if (!orgAdminRef.current) throw new Error("orgAdmin not initialised");
109
+ return orgAdminRef.current;
110
+ },
84
111
  logger: logger.child({ name: "org" }),
85
112
  mailer,
86
113
  inviteUrls: {
@@ -93,18 +120,31 @@ async function buildContainer(config, options) {
93
120
  content: new DrizzleContentRepository(tx, contentLogger),
94
121
  outbox: new DrizzleOutboxAppender(tx, outboxLogger)
95
122
  }));
96
- const content = new ContentServiceImpl({
123
+ const content = new ContentService({
97
124
  repo: new DrizzleContentRepository(db, contentLogger),
98
125
  uow: contentUow,
99
- settings,
126
+ libraryAssets: assets,
127
+ files: assets,
128
+ fileUrls,
100
129
  logger: contentLogger
101
130
  });
102
131
  const entitlementsUow = new DrizzleUnitOfWork(db, (tx) => ({
103
132
  entitlements: new DrizzleEntitlementsRepository(tx, entitlementsLogger),
133
+ spaces: new DrizzleContentRepository(tx, contentLogger),
104
134
  outbox: new DrizzleOutboxAppender(tx, outboxLogger)
105
135
  }));
136
+ const authors = {
137
+ roleOf: async (orgId, orgUserId) => {
138
+ return (await organizationsRepo.findOrgUserById(orgId, orgUserId))?.role ?? null;
139
+ },
140
+ rolesOf: async (orgId, orgUserIds) => {
141
+ const rows = await organizationsRepo.findOrgUsersByIds(orgId, orgUserIds);
142
+ return Object.fromEntries(rows.map((row) => [row.id, row.role]));
143
+ }
144
+ };
106
145
  const entitlements = new EntitlementsServiceImpl({
107
146
  repo: new DrizzleEntitlementsRepository(db, entitlementsLogger),
147
+ content,
108
148
  uow: entitlementsUow,
109
149
  logger: entitlementsLogger
110
150
  });
@@ -115,45 +155,68 @@ async function buildContainer(config, options) {
115
155
  const progress = new ProgressServiceImpl({
116
156
  repo: new DrizzleProgressRepository(db, progressLogger),
117
157
  content,
158
+ access: entitlements,
118
159
  uow: progressUow,
119
160
  logger: progressLogger
120
161
  });
121
- const discussion = new DiscussionServiceImpl({
122
- repo: new DrizzleDiscussionRepository(db, discussionLogger),
162
+ const postReads = new DrizzlePostRepository(db, discussionLogger);
163
+ const discussionReads = new DrizzleDiscussionRepository(db, discussionLogger);
164
+ const chatReads = new DrizzleChatRepository(db, discussionLogger);
165
+ const posts = new PostServiceImpl({
166
+ repo: postReads,
123
167
  access: entitlements,
124
168
  content,
169
+ authors,
170
+ profiles: discussionReads,
171
+ comments: discussionReads,
172
+ images: assets,
173
+ fileUrls,
125
174
  uow: new DrizzleUnitOfWork(db, (tx) => ({
126
- discussion: new DrizzleDiscussionRepository(tx, discussionLogger),
175
+ posts: new DrizzlePostRepository(tx, discussionLogger),
127
176
  outbox: new DrizzleOutboxAppender(tx, outboxLogger)
128
177
  })),
129
- settings,
130
178
  logger: discussionLogger
131
179
  });
132
- const assets = new AssetsServiceImpl({
133
- storage,
134
- repo: new DrizzleAssetsRepository(db, assetsLogger),
180
+ const chats = new ChatServiceImpl({
181
+ repo: chatReads,
182
+ access: entitlements,
183
+ content,
184
+ authors,
185
+ profiles: discussionReads,
186
+ images: assets,
187
+ fileUrls,
135
188
  uow: new DrizzleUnitOfWork(db, (tx) => ({
136
- assets: new DrizzleAssetsRepository(tx, assetsLogger),
189
+ chats: new DrizzleChatRepository(tx, discussionLogger),
137
190
  outbox: new DrizzleOutboxAppender(tx, outboxLogger)
138
191
  })),
139
- logger: assetsLogger
192
+ logger: discussionLogger
193
+ });
194
+ const discussion = new DiscussionServiceImpl({
195
+ repo: discussionReads,
196
+ access: entitlements,
197
+ content,
198
+ posts,
199
+ communities: { listIds: (orgId) => content.listPublishedCommunityIds(orgId) },
200
+ fileUrls,
201
+ uow: new DrizzleUnitOfWork(db, (tx) => ({
202
+ discussion: new DrizzleDiscussionRepository(tx, discussionLogger),
203
+ outbox: new DrizzleOutboxAppender(tx, outboxLogger)
204
+ })),
205
+ settings,
206
+ logger: discussionLogger
140
207
  });
141
208
  const reporting = {
142
- students: new StudentsReportServiceImpl({
143
- repo: new DrizzleStudentsRepository(db, reportingLogger),
144
- logger: reportingLogger
145
- }),
146
- dashboard: new DashboardReportServiceImpl({
147
- repo: new DrizzleDashboardRepository(db, reportingLogger),
148
- logger: reportingLogger
149
- }),
150
- learn: new LearnReportServiceImpl({
151
- reader: new DrizzleLearnRepository(db, reportingLogger),
209
+ admin: new AdminReportServiceImpl({ repo: new DrizzleAdminRepository(db, reportingLogger) }),
210
+ member: new MemberReportServiceImpl({
211
+ repo: new DrizzleMemberRepository(db, reportingLogger),
152
212
  content,
153
213
  progress,
154
- assets,
155
- deliveryExpirySeconds: config.deliveryExpirySeconds,
156
- logger: reportingLogger
214
+ files: assets,
215
+ fileUrls,
216
+ access: entitlements,
217
+ notifications,
218
+ posts,
219
+ chats
157
220
  })
158
221
  };
159
222
  const credentialStore = new DrizzleCredentialStore(db, config.credentialStoreKey, integrationsLogger);
@@ -173,58 +236,111 @@ async function buildContainer(config, options) {
173
236
  const automationsUow = new DrizzleUnitOfWork(db, (tx) => ({
174
237
  automations: new DrizzleAutomationsRepository(tx, automationsLogger),
175
238
  runs: new DrizzleAutomationRunsRepository(tx, automationsLogger),
239
+ credentials: new DrizzleCredentialStore(tx, config.credentialStoreKey, automationsLogger),
176
240
  outbox: new DrizzleOutboxAppender(tx, automationsLogger)
177
241
  }));
178
242
  const mailerLookups = {
179
- orgUserEmail: async (orgId, orgUserId) => (await reporting.students.get(orgId, orgUserId))?.email ?? null,
243
+ orgUserPerson: async (orgId, orgUserId) => {
244
+ const orgUser = await organizationsRepo.findOrgUserById(orgId, orgUserId);
245
+ const user = orgUser ? await identity.getUserById(orgUser.userId) : null;
246
+ return user ? {
247
+ userId: user.id,
248
+ email: user.email
249
+ } : null;
250
+ },
180
251
  contentInfo: async (orgId, contentId) => {
181
252
  const course = await content.getCourse(orgId, contentId);
182
253
  if (course) return {
183
254
  id: course.id,
184
- title: course.title
255
+ title: course.title,
256
+ type: course.type
185
257
  };
186
258
  const download = await content.getDownload(orgId, contentId);
187
- return download ? {
259
+ if (download) return {
188
260
  id: download.id,
189
- title: download.title
261
+ title: download.title,
262
+ type: download.type
263
+ };
264
+ const community = await content.getCommunity(orgId, contentId);
265
+ return community ? {
266
+ id: community.id,
267
+ title: community.title,
268
+ type: community.type
190
269
  } : null;
191
270
  }
192
271
  };
272
+ const runsRepo = new DrizzleAutomationRunsRepository(db, automationsLogger);
273
+ const wired = communityMail({
274
+ content,
275
+ entitlements,
276
+ organizations,
277
+ identity,
278
+ posts,
279
+ discussion,
280
+ chats,
281
+ assets
282
+ });
193
283
  const automations = new AutomationsServiceImpl({
194
284
  repo: new DrizzleAutomationsRepository(db, automationsLogger),
195
- runsRepo: new DrizzleAutomationRunsRepository(db, automationsLogger),
285
+ runsRepo,
196
286
  uow: automationsUow,
197
287
  engine: automationEngine,
198
288
  mailer,
199
289
  lookups: mailerLookups,
200
290
  integrations,
291
+ credentials: credentialStore,
292
+ webhooks: options?.adapters?.webhooks ?? new FetchWebhookSender({ allowPrivateNetwork: config.webhookAllowPrivateNetwork }),
293
+ mail: wired.mail,
294
+ grants: wired.grants,
295
+ desk: notifications,
296
+ scheduledWork: wired.scheduledWork,
201
297
  logger: automationsLogger
202
298
  });
203
299
  automationEngine.register(automations);
204
300
  const eventBus = new InMemoryEventBus();
205
- registerNotificationSubscribers(eventBus, mailer, mailerLookups);
206
301
  eventBus.subscribeAll((event) => automations.handle(event));
207
302
  const outboxConfig = resolveOutboxConfig(config.outbox);
208
303
  const outboxRelay = new PollingOutboxRelay(new DrizzleOutboxStore(db, outboxLogger), eventBus, outboxConfig, outboxLogger);
304
+ const authLogger = logger.child({ name: "auth" });
305
+ const authHook = (name, fn) => async (...args) => {
306
+ try {
307
+ return await fn(...args);
308
+ } catch (err) {
309
+ authLogger.error(`auth hook ${name} failed`, { err });
310
+ throw err;
311
+ }
312
+ };
313
+ const mcp = mcpEndpoints(config);
314
+ if (!mcp.oauthEnabled) authLogger.warn("MCP OAuth is off: BETTER_AUTH_URL must be https or a loopback host; /mcp accepts API keys only", { authBaseURL: config.authBaseURL });
209
315
  const auth = new BetterAuth({
210
316
  db,
211
317
  baseUrl: config.authBaseURL,
318
+ mcp: {
319
+ oauthEnabled: mcp.oauthEnabled,
320
+ resource: mcp.resource,
321
+ loginPage: mcp.loginPage,
322
+ consentPage: mcp.consentPage,
323
+ orgPickerPage: mcp.orgPickerPage
324
+ },
212
325
  secret: config.authSecret,
213
326
  trustedOrigins: config.trustedOrigins,
214
327
  hooks: {
215
- sendResetPassword: async (data) => {
328
+ sendResetPassword: async ({ user, url }) => {
216
329
  await identity.sendPasswordReset({
217
- email: data.user.email,
218
- url: ""
330
+ email: user.email,
331
+ url
219
332
  });
220
333
  },
221
- sendMagicLink: async ({ email, url }) => {
334
+ onPasswordReset: async ({ user }) => {
335
+ await identity.sendPasswordChanged({ email: user.email });
336
+ },
337
+ sendMagicLink: authHook("sendMagicLink", async ({ email, url }) => {
222
338
  await identity.sendMagicLink({
223
339
  email,
224
340
  url
225
341
  });
226
- },
227
- beforeUserCreate: async (user) => {
342
+ }),
343
+ beforeUserCreate: authHook("beforeUserCreate", async (user) => {
228
344
  const { email, name } = user;
229
345
  const [firstName, lastName] = name.split(" ");
230
346
  const domainUser = await identity.linkOrCreateUser({
@@ -236,8 +352,8 @@ async function buildContainer(config, options) {
236
352
  ...user,
237
353
  id: domainUser.id
238
354
  } };
239
- },
240
- beforeCreateSession: async (session) => {
355
+ }),
356
+ beforeCreateSession: authHook("beforeCreateSession", async (session) => {
241
357
  const person = await identity.getUserByExternalId(session.userId);
242
358
  if (!person) return;
243
359
  const orgs = await organizations.getOrgUsersForUser(person.id);
@@ -248,8 +364,8 @@ async function buildContainer(config, options) {
248
364
  ...session,
249
365
  activeOrganizationId: org.id
250
366
  } };
251
- },
252
- beforeCreateOrganization: async ({ organization: org, user: baUser }) => {
367
+ }),
368
+ beforeCreateOrganization: authHook("beforeCreateOrganization", async ({ organization: org, user: baUser }) => {
253
369
  const domainOrg = await organizations.createOrganization({
254
370
  ownerId: baUser.id,
255
371
  ...org,
@@ -259,37 +375,45 @@ async function buildContainer(config, options) {
259
375
  ...org,
260
376
  id: domainOrg.id
261
377
  } };
262
- },
263
- beforeUpdateOrganization: async ({ organization }) => {
378
+ }),
379
+ beforeUpdateOrganization: authHook("beforeUpdateOrganization", async ({ organization }) => {
264
380
  await organizations.updateOrganization(organization.id, organization);
265
- },
266
- beforeDeleteOrganization: async ({ organization }) => {
381
+ }),
382
+ beforeDeleteOrganization: authHook("beforeDeleteOrganization", async ({ organization }) => {
267
383
  await organizations.deleteOrganization(organization.id);
268
- },
269
- beforeAddMember: async ({ member }) => {
384
+ }),
385
+ beforeAddMember: authHook("beforeAddMember", async ({ member }) => {
270
386
  await organizations.addOrgUser({
271
387
  orgId: member.organizationId,
272
388
  userId: member.userId,
273
389
  role: parseRole(member.role)
274
390
  });
275
- },
276
- beforeRemoveMember: async ({ member }) => {
391
+ }),
392
+ beforeUpdateMemberRole: authHook("beforeUpdateMemberRole", async ({ member, newRole }) => {
393
+ await organizations.updateOrgUser(member.organizationId, member.userId, { role: parseRole(newRole) });
394
+ }),
395
+ beforeRemoveMember: authHook("beforeRemoveMember", async ({ member }) => {
277
396
  await organizations.removeOrgUser(member.organizationId, member.userId);
278
- }
397
+ })
279
398
  },
280
- logger: logger.child({ name: "auth" }),
399
+ logger: authLogger,
281
400
  cookieDomain: config.cookieDomain,
282
401
  secureCookies: config.secureCookies
283
402
  });
403
+ orgAdminRef.current = auth;
284
404
  return {
285
405
  auth,
406
+ mcp,
286
407
  identity,
408
+ notifications,
287
409
  organizations,
288
410
  orgProvider: auth,
289
411
  content,
290
412
  entitlements,
291
413
  progress,
292
414
  discussion,
415
+ posts,
416
+ chats,
293
417
  assets,
294
418
  integrations,
295
419
  automations,
@@ -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 DrizzleDashboardRepository,\n DrizzleDiscussionRepository,\n DrizzleEntitlementsRepository,\n DrizzleIdentityRepository,\n DrizzleLearnRepository,\n DrizzleMembersRepository,\n DrizzleOrganizationsRepository,\n DrizzleOutboxAppender,\n DrizzleOutboxStore,\n DrizzleProgressRepository,\n DrizzleSettingsRepository,\n DrizzleStudentsRepository,\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 { 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 { ContentServiceImpl } from '@headless-lms/core/content';\nimport { EntitlementsServiceImpl } from '@headless-lms/core/entitlements';\nimport { ProgressServiceImpl } from '@headless-lms/core/progress';\nimport { DiscussionServiceImpl } from '@headless-lms/core/discussion';\nimport { IdentityServiceImpl, type SessionAdmin } from '@headless-lms/core/identity';\nimport { type OrgAdmin, OrganizationServiceImpl, parseRole } from '@headless-lms/core/organizations';\nimport { AssetsServiceImpl } from '@headless-lms/core/assets';\nimport { IntegrationsServiceImpl } from '@headless-lms/core/integrations';\nimport { AutomationsServiceImpl } from '@headless-lms/core/automations';\nimport { loadIntegrations } from './integrations.js';\nimport { registerNotificationSubscribers } from './notifications.js';\nimport { StudentsReportServiceImpl } from '@headless-lms/core/reporting/students';\nimport { DashboardReportServiceImpl } from '@headless-lms/core/reporting/dashboard';\nimport { LearnReportServiceImpl } from '@headless-lms/core/reporting/learn';\nimport { Mailer, type MailerLookups } from '@headless-lms/core/shared/mailer';\nimport { SettingsService } from '@headless-lms/core/shared/settings';\n\nimport type {\n CredentialStore,\n EmailSender,\n Logger,\n ObjectStorage,\n OutboxRelay,\n TemplateContext,\n TemplateRenderer,\n} from '@headless-lms/core/shared/ports';\nimport type { AutomationEngine } 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. Absent → InlineAutomationEngine (in-process, one attempt per action). */\n workflows?: AutomationEngine;\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 /** Presigned URL lifetime for entitled download delivery, seconds. Mirrors\n * ServerConfig.deliveryExpirySeconds — createContainer threads it through. */\n deliveryExpirySeconds: number;\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}\n\nexport const OUTBOX_DEFAULTS: PollingOutboxRelayConfig = {\n enabled: true,\n pollIntervalMs: 1000,\n batchSize: 100,\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 // Org Provider\n orgProvider: OrgAdmin;\n\n // Domains\n identity: IdentityServiceImpl;\n organizations: OrganizationServiceImpl;\n content: ContentServiceImpl;\n entitlements: EntitlementsServiceImpl;\n progress: ProgressServiceImpl;\n discussion: DiscussionServiceImpl;\n assets: AssetsServiceImpl;\n integrations: IntegrationsServiceImpl;\n automations: AutomationsServiceImpl;\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 students: StudentsReportServiceImpl;\n dashboard: DashboardReportServiceImpl;\n learn: LearnReportServiceImpl;\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 /** 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 automationEngine: AutomationEngine =\n options?.adapters?.workflows ?? new InlineAutomationEngine();\n const mailer = new Mailer(templates, email, {\n ...(config.emailBranding ?? { brandName: 'Headless LMS', baseUrl: config.adminAppUrl }),\n studentPortalUrl: config.studentPortalUrl,\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 });\n\n /*\n * Organization\n */\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 organizations = new OrganizationServiceImpl({\n repo: new DrizzleOrganizationsRepository(db, logger.child({ name: 'org' })),\n membersRepo: new DrizzleMembersRepository(db, logger.child({ name: 'org' })),\n people: identity,\n uow: organizationsUow,\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 ContentServiceImpl({\n repo: new DrizzleContentRepository(db, contentLogger),\n uow: contentUow,\n settings,\n logger: contentLogger,\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 outbox: new DrizzleOutboxAppender(tx, outboxLogger),\n }));\n const entitlements = new EntitlementsServiceImpl({\n repo: new DrizzleEntitlementsRepository(db, entitlementsLogger),\n uow: entitlementsUow,\n logger: entitlementsLogger,\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 uow: progressUow,\n logger: progressLogger,\n });\n\n const discussion = new DiscussionServiceImpl({\n repo: new DrizzleDiscussionRepository(db, discussionLogger),\n access: entitlements,\n content: content,\n uow: new DrizzleUnitOfWork(db, (tx) => ({\n discussion: new DrizzleDiscussionRepository(tx, discussionLogger),\n outbox: new DrizzleOutboxAppender(tx, outboxLogger),\n })),\n settings,\n logger: discussionLogger,\n });\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 logger: assetsLogger,\n });\n\n const reporting = {\n students: new StudentsReportServiceImpl({\n repo: new DrizzleStudentsRepository(db, reportingLogger),\n logger: reportingLogger,\n }),\n dashboard: new DashboardReportServiceImpl({\n repo: new DrizzleDashboardRepository(db, reportingLogger),\n logger: reportingLogger,\n }),\n learn: new LearnReportServiceImpl({\n reader: new DrizzleLearnRepository(db, reportingLogger),\n content,\n progress,\n assets,\n deliveryExpirySeconds: config.deliveryExpirySeconds,\n logger: reportingLogger,\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 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 orgUserEmail: async (orgId, orgUserId) =>\n (await reporting.students.get(orgId, orgUserId))?.email ?? null,\n contentInfo: async (orgId, contentId) => {\n const course = await content.getCourse(orgId, contentId);\n if (course) {\n return { id: course.id, title: course.title };\n }\n const download = await content.getDownload(orgId, contentId);\n return download ? { id: download.id, title: download.title } : null;\n },\n };\n\n const automations = new AutomationsServiceImpl({\n repo: new DrizzleAutomationsRepository(db, automationsLogger),\n runsRepo: new DrizzleAutomationRunsRepository(db, automationsLogger),\n uow: automationsUow,\n engine: automationEngine,\n mailer,\n lookups: mailerLookups,\n integrations,\n logger: automationsLogger,\n });\n automationEngine.register(automations);\n\n const eventBus = new InMemoryEventBus();\n registerNotificationSubscribers(eventBus, mailer, mailerLookups);\n // handle() never throws — the EventBus fans out to every subscriber\n // sequentially, and a rejection here would break sibling subscribers.\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 auth = new BetterAuth({\n db,\n baseUrl: config.authBaseURL,\n secret: config.authSecret,\n trustedOrigins: config.trustedOrigins,\n hooks: {\n sendResetPassword: async (data) => {\n await identity.sendPasswordReset({\n email: data.user.email,\n url: '',\n });\n },\n sendMagicLink: async ({ email, url }) => {\n await identity.sendMagicLink({\n email,\n url,\n });\n },\n 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: 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: 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 beforeUpdateOrganization: async ({ organization }) => {\n await organizations.updateOrganization(organization.id, organization);\n },\n beforeDeleteOrganization: async ({ organization }) => {\n await organizations.deleteOrganization(organization.id);\n },\n beforeAddMember: async ({ member }) => {\n await organizations.addOrgUser({\n orgId: member.organizationId,\n userId: member.userId,\n role: parseRole(member.role),\n });\n },\n beforeRemoveMember: async ({ member }) => {\n await organizations.removeOrgUser(member.organizationId, member.userId);\n },\n },\n logger: logger.child({ name: 'auth' }),\n cookieDomain: config.cookieDomain,\n secureCookies: config.secureCookies,\n });\n\n return {\n auth,\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 assets,\n integrations,\n automations,\n settings,\n reporting,\n storage,\n mailer,\n credentials: credentialStore,\n outboxRelay,\n 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 DrizzleAssetsRepository,\n DrizzleAutomationRunsRepository,\n DrizzleAutomationsRepository,\n DrizzleConnectionsRepository,\n DrizzleContentRepository,\n DrizzleCredentialStore,\n DrizzleAdminRepository,\n DrizzleDiscussionRepository,\n DrizzlePostRepository,\n DrizzleChatRepository,\n DrizzleEntitlementsRepository,\n DrizzleIdentityRepository,\n DrizzleMemberRepository,\n DrizzleMembersRepository,\n DrizzleNotificationsRepository,\n DrizzleOrganizationsRepository,\n DrizzleOutboxAppender,\n DrizzleOutboxStore,\n DrizzleProgressRepository,\n DrizzleSettingsRepository,\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 { ContentService } from '@headless-lms/core/content';\nimport { EntitlementsServiceImpl } from '@headless-lms/core/entitlements';\nimport { ProgressServiceImpl } from '@headless-lms/core/progress';\nimport {\n ChatServiceImpl,\n DiscussionServiceImpl,\n PostServiceImpl,\n} from '@headless-lms/core/discussion';\nimport {\n IdentityServiceImpl,\n NotificationServiceImpl,\n type SessionAdmin,\n} from '@headless-lms/core/identity';\nimport {\n type OrgAdmin,\n OrganizationServiceImpl,\n parseRole,\n} from '@headless-lms/core/organizations';\nimport { AssetsServiceImpl, createFileUrls } from '@headless-lms/core/assets';\nimport { IntegrationsServiceImpl } from '@headless-lms/core/integrations';\nimport { AutomationsServiceImpl } from '@headless-lms/core/automations';\nimport { loadIntegrations } from './integrations.js';\nimport { mcpEndpoints, type McpEndpoints } from './mcp-endpoints.js';\nimport { communityMail } from './community-mail.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 CredentialStore,\n EmailSender,\n Logger,\n ObjectStorage,\n OutboxRelay,\n TemplateContext,\n TemplateRenderer,\n} from '@headless-lms/core/shared/ports';\nimport type { AutomationEngine, 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 | ((logger: Logger) => AutomationEngine);\n /** Outbound HTTP for webhook deliveries. Absent → FetchWebhookSender (public addresses only, 10s timeout). */\n webhooks?: WebhookSender;\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}\n\nexport const OUTBOX_DEFAULTS: PollingOutboxRelayConfig = {\n enabled: true,\n pollIntervalMs: 1000,\n batchSize: 100,\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 notifications: NotificationServiceImpl;\n organizations: OrganizationServiceImpl;\n content: ContentService;\n entitlements: EntitlementsServiceImpl;\n progress: ProgressServiceImpl;\n discussion: DiscussionServiceImpl;\n posts: PostServiceImpl;\n chats: ChatServiceImpl;\n assets: AssetsServiceImpl;\n integrations: IntegrationsServiceImpl;\n automations: AutomationsServiceImpl;\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 /** 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 =\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 const notifications = new NotificationServiceImpl({\n repo: new DrizzleNotificationsRepository(db),\n logger: logger.child({ name: 'notifications' }),\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 // 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 // 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 posts = new PostServiceImpl({\n repo: postReads,\n access: entitlements,\n content,\n authors,\n profiles: discussionReads,\n comments: discussionReads,\n images: assets,\n fileUrls,\n uow: new DrizzleUnitOfWork(db, (tx) => ({\n posts: new DrizzlePostRepository(tx, discussionLogger),\n outbox: new DrizzleOutboxAppender(tx, outboxLogger),\n })),\n logger: discussionLogger,\n });\n const chats = new ChatServiceImpl({\n repo: chatReads,\n access: entitlements,\n content,\n authors,\n profiles: discussionReads,\n images: assets,\n fileUrls,\n uow: new DrizzleUnitOfWork(db, (tx) => ({\n chats: new DrizzleChatRepository(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 communities: { listIds: (orgId) => content.listPublishedCommunityIds(orgId) },\n fileUrls,\n uow: new DrizzleUnitOfWork(db, (tx) => ({\n discussion: new DrizzleDiscussionRepository(tx, discussionLogger),\n outbox: new DrizzleOutboxAppender(tx, outboxLogger),\n })),\n settings,\n logger: discussionLogger,\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,\n posts,\n chats,\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 discussion,\n chats,\n assets,\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 desk: notifications,\n scheduledWork: wired.scheduledWork,\n logger: automationsLogger,\n });\n automationEngine.register(automations);\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 notifications,\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 assets,\n integrations,\n automations,\n settings,\n reporting,\n storage,\n mailer,\n credentials: credentialStore,\n outboxRelay,\n 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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"}
@@ -0,0 +1,13 @@
1
+ import { McpAuthOptions } from "@headless-lms/adapter-auth";
2
+ //#region src/app/mcp-endpoints.d.ts
3
+ interface McpEndpoints extends McpAuthOptions {
4
+ issuer: string;
5
+ resourceMetadataUrl: string;
6
+ }
7
+ declare function mcpEndpoints(config: {
8
+ authBaseURL: string;
9
+ adminAppUrl: string;
10
+ }): McpEndpoints;
11
+ //#endregion
12
+ export { McpEndpoints, mcpEndpoints };
13
+ //# sourceMappingURL=mcp-endpoints.d.ts.map
@@ -0,0 +1,25 @@
1
+ //#region src/app/mcp-endpoints.ts
2
+ const trimSlash = (url) => url.replace(/\/+$/, "");
3
+ const LOOPBACK_IPV4 = /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
4
+ function oauthEligible(resource) {
5
+ const { protocol, hostname } = new URL(resource);
6
+ if (protocol === "https:") return true;
7
+ return protocol === "http:" && (hostname === "localhost" || hostname === "[::1]" || LOOPBACK_IPV4.test(hostname));
8
+ }
9
+ function mcpEndpoints(config) {
10
+ const api = trimSlash(config.authBaseURL);
11
+ const admin = trimSlash(config.adminAppUrl);
12
+ return {
13
+ oauthEnabled: oauthEligible(`${api}/mcp`),
14
+ resource: `${api}/mcp`,
15
+ resourceMetadataUrl: `${api}/.well-known/oauth-protected-resource/mcp`,
16
+ issuer: `${api}/api/auth`,
17
+ loginPage: `${admin}/login`,
18
+ consentPage: `${admin}/oauth/consent`,
19
+ orgPickerPage: `${admin}/onboarding/select-organization`
20
+ };
21
+ }
22
+ //#endregion
23
+ export { mcpEndpoints };
24
+
25
+ //# sourceMappingURL=mcp-endpoints.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-endpoints.js","names":[],"sources":["../../src/app/mcp-endpoints.ts"],"sourcesContent":["import type { McpAuthOptions } from '@headless-lms/adapter-auth';\n\nexport interface McpEndpoints extends McpAuthOptions {\n issuer: string;\n resourceMetadataUrl: string;\n}\n\nconst trimSlash = (url: string) => url.replace(/\\/+$/, '');\n\nconst LOOPBACK_IPV4 = /^127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$/;\n\nfunction oauthEligible(resource: string): boolean {\n const { protocol, hostname } = new URL(resource);\n if (protocol === 'https:') {\n return true;\n }\n return (\n protocol === 'http:' &&\n (hostname === 'localhost' || hostname === '[::1]' || LOOPBACK_IPV4.test(hostname))\n );\n}\n\nexport function mcpEndpoints(config: { authBaseURL: string; adminAppUrl: string }): McpEndpoints {\n const api = trimSlash(config.authBaseURL);\n const admin = trimSlash(config.adminAppUrl);\n return {\n oauthEnabled: oauthEligible(`${api}/mcp`),\n resource: `${api}/mcp`,\n resourceMetadataUrl: `${api}/.well-known/oauth-protected-resource/mcp`,\n issuer: `${api}/api/auth`,\n loginPage: `${admin}/login`,\n consentPage: `${admin}/oauth/consent`,\n orgPickerPage: `${admin}/onboarding/select-organization`,\n };\n}\n"],"mappings":";AAOA,MAAM,aAAa,QAAgB,IAAI,QAAQ,QAAQ,EAAE;AAEzD,MAAM,gBAAgB;AAEtB,SAAS,cAAc,UAA2B;CAChD,MAAM,EAAE,UAAU,aAAa,IAAI,IAAI,QAAQ;CAC/C,IAAI,aAAa,UACf,OAAO;CAET,OACE,aAAa,YACZ,aAAa,eAAe,aAAa,WAAW,cAAc,KAAK,QAAQ;AAEpF;AAEA,SAAgB,aAAa,QAAoE;CAC/F,MAAM,MAAM,UAAU,OAAO,WAAW;CACxC,MAAM,QAAQ,UAAU,OAAO,WAAW;CAC1C,OAAO;EACL,cAAc,cAAc,GAAG,IAAI,KAAK;EACxC,UAAU,GAAG,IAAI;EACjB,qBAAqB,GAAG,IAAI;EAC5B,QAAQ,GAAG,IAAI;EACf,WAAW,GAAG,MAAM;EACpB,aAAa,GAAG,MAAM;EACtB,eAAe,GAAG,MAAM;CAC1B;AACF"}
@@ -13,10 +13,11 @@ interface ServerConfig {
13
13
  * Unset → on. Turn off for a quiet dev console; app-level logs are
14
14
  * unaffected either way. */
15
15
  requestLogging?: boolean;
16
- /** Presigned URL lifetime for entitled download delivery, seconds. Separate
16
+ /** Lifetime of every signed file URL a response carries, seconds. Separate
17
17
  * from the storage adapter's general download expiry so raising that one
18
18
  * can't silently extend paywalled link lifetimes. */
19
19
  deliveryExpirySeconds: number;
20
+ memberImageMaxBytes: number;
20
21
  /** Everything the composition container needs to wire adapters + services. */
21
22
  container: Config;
22
23
  }
@@ -0,0 +1,14 @@
1
+ //#region src/http/errors.d.ts
2
+ type Refusal = {
3
+ status: number;
4
+ error: string;
5
+ message: string;
6
+ issues?: {
7
+ path: string;
8
+ message: string;
9
+ }[];
10
+ };
11
+ declare function classifyError(error: unknown): Refusal | undefined;
12
+ //#endregion
13
+ export { Refusal, classifyError };
14
+ //# sourceMappingURL=errors.d.ts.map