@headless-lms/core 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (307) hide show
  1. package/dist/assets/events.d.ts +42 -24
  2. package/dist/assets/events.js +1 -1
  3. package/dist/assets/events.js.map +1 -1
  4. package/dist/assets/file-urls.d.ts +20 -0
  5. package/dist/assets/file-urls.js +25 -0
  6. package/dist/assets/file-urls.js.map +1 -0
  7. package/dist/assets/index.d.ts +6 -4
  8. package/dist/assets/index.js +4 -2
  9. package/dist/assets/jobs.d.ts +15 -0
  10. package/dist/assets/jobs.js +30 -0
  11. package/dist/assets/jobs.js.map +1 -0
  12. package/dist/assets/model.d.ts +2 -2
  13. package/dist/assets/ports.d.ts +46 -15
  14. package/dist/assets/service.d.ts +38 -7
  15. package/dist/assets/service.js +287 -38
  16. package/dist/assets/service.js.map +1 -1
  17. package/dist/automations/actions.d.ts +13 -24
  18. package/dist/automations/actions.js +106 -42
  19. package/dist/automations/actions.js.map +1 -1
  20. package/dist/automations/catalog.js +226 -3
  21. package/dist/automations/catalog.js.map +1 -1
  22. package/dist/automations/community-derivations.d.ts +14 -0
  23. package/dist/automations/community-derivations.js +439 -0
  24. package/dist/automations/community-derivations.js.map +1 -0
  25. package/dist/automations/community-mail.d.ts +98 -0
  26. package/dist/automations/community-mail.js +70 -0
  27. package/dist/automations/community-mail.js.map +1 -0
  28. package/dist/automations/events.d.ts +18 -0
  29. package/dist/automations/index.d.ts +13 -4
  30. package/dist/automations/index.js +10 -2
  31. package/dist/automations/model.d.ts +10 -3
  32. package/dist/automations/model.js +15 -2
  33. package/dist/automations/model.js.map +1 -1
  34. package/dist/automations/notifications/debounce.d.ts +25 -0
  35. package/dist/automations/notifications/debounce.js +93 -0
  36. package/dist/automations/notifications/debounce.js.map +1 -0
  37. package/dist/automations/notifications/defaults.d.ts +8 -0
  38. package/dist/automations/notifications/defaults.js +316 -0
  39. package/dist/automations/notifications/defaults.js.map +1 -0
  40. package/dist/automations/notifications/delivery.d.ts +28 -0
  41. package/dist/automations/notifications/delivery.js +88 -0
  42. package/dist/automations/notifications/delivery.js.map +1 -0
  43. package/dist/automations/notifications/processor.d.ts +64 -0
  44. package/dist/automations/notifications/processor.js +270 -0
  45. package/dist/automations/notifications/processor.js.map +1 -0
  46. package/dist/automations/notifications/rules/content.d.ts +6 -0
  47. package/dist/automations/notifications/rules/content.js +39 -0
  48. package/dist/automations/notifications/rules/content.js.map +1 -0
  49. package/dist/automations/notifications/rules/discussion.d.ts +7 -0
  50. package/dist/automations/notifications/rules/discussion.js +678 -0
  51. package/dist/automations/notifications/rules/discussion.js.map +1 -0
  52. package/dist/automations/notifications/rules/entitlements.d.ts +6 -0
  53. package/dist/automations/notifications/rules/entitlements.js +249 -0
  54. package/dist/automations/notifications/rules/entitlements.js.map +1 -0
  55. package/dist/automations/notifications/rules/index.d.ts +6 -0
  56. package/dist/automations/notifications/rules/index.js +15 -0
  57. package/dist/automations/notifications/rules/index.js.map +1 -0
  58. package/dist/automations/notifications/rules/organizations.d.ts +6 -0
  59. package/dist/automations/notifications/rules/organizations.js +28 -0
  60. package/dist/automations/notifications/rules/organizations.js.map +1 -0
  61. package/dist/automations/notifications/rules/recipients.d.ts +32 -0
  62. package/dist/automations/notifications/rules/recipients.js +99 -0
  63. package/dist/automations/notifications/rules/recipients.js.map +1 -0
  64. package/dist/automations/notifications/service.d.ts +27 -0
  65. package/dist/automations/notifications/service.js +45 -0
  66. package/dist/automations/notifications/service.js.map +1 -0
  67. package/dist/automations/notifications/types.d.ts +7 -0
  68. package/dist/automations/notifications/types.js +473 -0
  69. package/dist/automations/notifications/types.js.map +1 -0
  70. package/dist/automations/ports.d.ts +121 -3
  71. package/dist/automations/schedule.d.ts +6 -0
  72. package/dist/automations/schedule.js +41 -0
  73. package/dist/automations/schedule.js.map +1 -0
  74. package/dist/automations/service.d.ts +20 -4
  75. package/dist/automations/service.js +246 -45
  76. package/dist/automations/service.js.map +1 -1
  77. package/dist/content/events.d.ts +712 -50
  78. package/dist/content/events.js +55 -1
  79. package/dist/content/events.js.map +1 -1
  80. package/dist/content/index.d.ts +6 -4
  81. package/dist/content/index.js +3 -1
  82. package/dist/content/model.d.ts +2 -2
  83. package/dist/content/notification-categories.d.ts +36 -0
  84. package/dist/content/notification-categories.js +64 -0
  85. package/dist/content/notification-categories.js.map +1 -0
  86. package/dist/content/ports.d.ts +90 -18
  87. package/dist/content/service.d.ts +57 -18
  88. package/dist/content/service.js +520 -25
  89. package/dist/content/service.js.map +1 -1
  90. package/dist/content/titles.d.ts +22 -0
  91. package/dist/content/titles.js +69 -0
  92. package/dist/content/titles.js.map +1 -0
  93. package/dist/content/types.d.ts +2 -2
  94. package/dist/discussion/authors.d.ts +10 -0
  95. package/dist/discussion/authors.js +23 -0
  96. package/dist/discussion/authors.js.map +1 -0
  97. package/dist/discussion/chats.d.ts +109 -0
  98. package/dist/discussion/chats.js +831 -0
  99. package/dist/discussion/chats.js.map +1 -0
  100. package/dist/discussion/digests.d.ts +24 -0
  101. package/dist/discussion/digests.js +104 -0
  102. package/dist/discussion/digests.js.map +1 -0
  103. package/dist/discussion/events.d.ts +785 -56
  104. package/dist/discussion/events.js +165 -5
  105. package/dist/discussion/events.js.map +1 -1
  106. package/dist/discussion/index.d.ts +12 -3
  107. package/dist/discussion/index.js +9 -1
  108. package/dist/discussion/jobs.d.ts +16 -0
  109. package/dist/discussion/jobs.js +40 -0
  110. package/dist/discussion/jobs.js.map +1 -0
  111. package/dist/discussion/mentions.d.ts +11 -0
  112. package/dist/discussion/mentions.js +35 -0
  113. package/dist/discussion/mentions.js.map +1 -0
  114. package/dist/discussion/ports.d.ts +372 -28
  115. package/dist/discussion/ports.js +6 -1
  116. package/dist/discussion/ports.js.map +1 -0
  117. package/dist/discussion/posts.d.ts +113 -0
  118. package/dist/discussion/posts.js +775 -0
  119. package/dist/discussion/posts.js.map +1 -0
  120. package/dist/discussion/service.d.ts +42 -11
  121. package/dist/discussion/service.js +364 -85
  122. package/dist/discussion/service.js.map +1 -1
  123. package/dist/discussion/subscriptions.d.ts +37 -0
  124. package/dist/discussion/subscriptions.js +99 -0
  125. package/dist/discussion/subscriptions.js.map +1 -0
  126. package/dist/discussion/titles.d.ts +26 -0
  127. package/dist/discussion/titles.js +84 -0
  128. package/dist/discussion/titles.js.map +1 -0
  129. package/dist/entitlements/events.d.ts +178 -11
  130. package/dist/entitlements/events.js +28 -1
  131. package/dist/entitlements/events.js.map +1 -1
  132. package/dist/entitlements/index.d.ts +6 -4
  133. package/dist/entitlements/index.js +3 -1
  134. package/dist/entitlements/jobs.d.ts +12 -0
  135. package/dist/entitlements/jobs.js +18 -0
  136. package/dist/entitlements/jobs.js.map +1 -0
  137. package/dist/entitlements/model.d.ts +3 -2
  138. package/dist/entitlements/ports.d.ts +128 -11
  139. package/dist/entitlements/rules.d.ts +12 -0
  140. package/dist/entitlements/rules.js +17 -0
  141. package/dist/entitlements/rules.js.map +1 -0
  142. package/dist/entitlements/service.d.ts +57 -3
  143. package/dist/entitlements/service.js +492 -2
  144. package/dist/entitlements/service.js.map +1 -1
  145. package/dist/identity/access.d.ts +6 -0
  146. package/dist/identity/access.js +7 -0
  147. package/dist/identity/access.js.map +1 -0
  148. package/dist/identity/devices.d.ts +30 -0
  149. package/dist/identity/devices.js +107 -0
  150. package/dist/identity/devices.js.map +1 -0
  151. package/dist/identity/events.d.ts +2 -2
  152. package/dist/identity/index.d.ts +7 -4
  153. package/dist/identity/index.js +4 -1
  154. package/dist/identity/model.d.ts +2 -1
  155. package/dist/identity/notification-settings.d.ts +28 -0
  156. package/dist/identity/notification-settings.js +113 -0
  157. package/dist/identity/notification-settings.js.map +1 -0
  158. package/dist/identity/ports.d.ts +97 -3
  159. package/dist/identity/service.d.ts +14 -3
  160. package/dist/identity/service.js +62 -5
  161. package/dist/identity/service.js.map +1 -1
  162. package/dist/integrations/events.d.ts +3 -3
  163. package/dist/integrations/model.js +1 -1
  164. package/dist/integrations/model.js.map +1 -1
  165. package/dist/integrations/service.js +2 -2
  166. package/dist/integrations/service.js.map +1 -1
  167. package/dist/organizations/access-scopes.d.ts +19 -0
  168. package/dist/organizations/access-scopes.js +89 -0
  169. package/dist/organizations/access-scopes.js.map +1 -0
  170. package/dist/organizations/events.d.ts +75 -33
  171. package/dist/organizations/events.js +6 -0
  172. package/dist/organizations/events.js.map +1 -1
  173. package/dist/organizations/index.d.ts +8 -6
  174. package/dist/organizations/index.js +5 -3
  175. package/dist/organizations/members.d.ts +8 -5
  176. package/dist/organizations/members.js.map +1 -1
  177. package/dist/organizations/notification-categories.d.ts +32 -0
  178. package/dist/organizations/notification-categories.js +51 -0
  179. package/dist/organizations/notification-categories.js.map +1 -0
  180. package/dist/organizations/ports.d.ts +17 -11
  181. package/dist/organizations/roles.d.ts +6 -10
  182. package/dist/organizations/roles.js +19 -7
  183. package/dist/organizations/roles.js.map +1 -1
  184. package/dist/organizations/service.d.ts +22 -11
  185. package/dist/organizations/service.js +164 -47
  186. package/dist/organizations/service.js.map +1 -1
  187. package/dist/organizations/types.d.ts +1 -10
  188. package/dist/progress/events.d.ts +14 -14
  189. package/dist/progress/index.d.ts +2 -2
  190. package/dist/progress/ports.d.ts +6 -1
  191. package/dist/progress/service.d.ts +3 -1
  192. package/dist/progress/service.js +6 -2
  193. package/dist/progress/service.js.map +1 -1
  194. package/dist/reporting/admin/index.d.ts +4 -0
  195. package/dist/reporting/admin/index.js +2 -0
  196. package/dist/reporting/admin/model.d.ts +108 -0
  197. package/dist/reporting/admin/ports.d.ts +27 -0
  198. package/dist/reporting/admin/service.d.ts +22 -0
  199. package/dist/reporting/admin/service.js +38 -0
  200. package/dist/reporting/admin/service.js.map +1 -0
  201. package/dist/reporting/member/index.d.ts +5 -0
  202. package/dist/reporting/member/index.js +2 -0
  203. package/dist/reporting/member/model.d.ts +157 -0
  204. package/dist/reporting/member/ports.d.ts +48 -0
  205. package/dist/reporting/member/service.d.ts +92 -0
  206. package/dist/reporting/member/service.js +364 -0
  207. package/dist/reporting/member/service.js.map +1 -0
  208. package/dist/shared/actor.d.ts +9 -0
  209. package/dist/shared/errors.d.ts +19 -1
  210. package/dist/shared/errors.js +27 -1
  211. package/dist/shared/errors.js.map +1 -1
  212. package/dist/shared/id.d.ts +14 -0
  213. package/dist/shared/id.js +15 -1
  214. package/dist/shared/id.js.map +1 -1
  215. package/dist/shared/mailer.d.ts +15 -2
  216. package/dist/shared/mailer.js +32 -5
  217. package/dist/shared/mailer.js.map +1 -1
  218. package/dist/shared/network.d.ts +10 -0
  219. package/dist/shared/network.js +211 -0
  220. package/dist/shared/network.js.map +1 -0
  221. package/dist/shared/notification-categories.d.ts +16 -0
  222. package/dist/shared/notification-categories.js +94 -0
  223. package/dist/shared/notification-categories.js.map +1 -0
  224. package/dist/shared/notification-settings.d.ts +14 -0
  225. package/dist/shared/notification-settings.js +61 -0
  226. package/dist/shared/notification-settings.js.map +1 -0
  227. package/dist/shared/ports.d.ts +48 -2
  228. package/dist/shared/settings.d.ts +2 -1
  229. package/dist/shared/settings.js +1 -0
  230. package/dist/shared/settings.js.map +1 -1
  231. package/dist/types/assets.d.ts +2 -2
  232. package/dist/types/automations.d.ts +15 -1
  233. package/dist/types/content.d.ts +2 -2
  234. package/dist/types/discussion.d.ts +11 -3
  235. package/dist/types/email-templates.d.ts +216 -1
  236. package/dist/types/entitlements.d.ts +2 -2
  237. package/dist/types/identity.d.ts +48 -2
  238. package/dist/types/index.d.ts +9 -8
  239. package/dist/types/notifications.d.ts +157 -0
  240. package/dist/types/ports.d.ts +6 -0
  241. package/dist/types/schemas/assets.d.ts +65 -37
  242. package/dist/types/schemas/assets.js +17 -12
  243. package/dist/types/schemas/assets.js.map +1 -1
  244. package/dist/types/schemas/automations.d.ts +9 -1
  245. package/dist/types/schemas/automations.js +13 -6
  246. package/dist/types/schemas/automations.js.map +1 -1
  247. package/dist/types/schemas/content.d.ts +396 -3
  248. package/dist/types/schemas/content.js +161 -3
  249. package/dist/types/schemas/content.js.map +1 -1
  250. package/dist/types/schemas/discussion.d.ts +443 -3
  251. package/dist/types/schemas/discussion.js +233 -4
  252. package/dist/types/schemas/discussion.js.map +1 -1
  253. package/dist/types/schemas/entitlements.d.ts +36 -2
  254. package/dist/types/schemas/entitlements.js +23 -1
  255. package/dist/types/schemas/entitlements.js.map +1 -1
  256. package/dist/types/schemas/identity.d.ts +27 -1
  257. package/dist/types/schemas/identity.js +14 -2
  258. package/dist/types/schemas/identity.js.map +1 -1
  259. package/dist/types/schemas/index.d.ts +9 -8
  260. package/dist/types/schemas/index.js +9 -8
  261. package/dist/types/schemas/notifications.d.ts +95 -0
  262. package/dist/types/schemas/notifications.js +56 -0
  263. package/dist/types/schemas/notifications.js.map +1 -0
  264. package/dist/types/schemas/organizations.d.ts +8 -0
  265. package/dist/types/schemas/organizations.js +2 -0
  266. package/dist/types/schemas/organizations.js.map +1 -1
  267. package/dist/types/schemas/progress.d.ts +3 -3
  268. package/dist/types/schemas/shared.d.ts +3 -1
  269. package/dist/types/schemas/shared.js +5 -1
  270. package/dist/types/schemas/shared.js.map +1 -1
  271. package/package.json +2 -2
  272. package/dist/reporting/courses/index.d.ts +0 -4
  273. package/dist/reporting/courses/index.js +0 -2
  274. package/dist/reporting/courses/model.d.ts +0 -24
  275. package/dist/reporting/courses/ports.d.ts +0 -13
  276. package/dist/reporting/courses/service.d.ts +0 -19
  277. package/dist/reporting/courses/service.js +0 -20
  278. package/dist/reporting/courses/service.js.map +0 -1
  279. package/dist/reporting/dashboard/index.d.ts +0 -4
  280. package/dist/reporting/dashboard/index.js +0 -2
  281. package/dist/reporting/dashboard/model.d.ts +0 -14
  282. package/dist/reporting/dashboard/ports.d.ts +0 -13
  283. package/dist/reporting/dashboard/service.d.ts +0 -19
  284. package/dist/reporting/dashboard/service.js +0 -20
  285. package/dist/reporting/dashboard/service.js.map +0 -1
  286. package/dist/reporting/learn/index.d.ts +0 -5
  287. package/dist/reporting/learn/index.js +0 -2
  288. package/dist/reporting/learn/model.d.ts +0 -23
  289. package/dist/reporting/learn/ports.d.ts +0 -43
  290. package/dist/reporting/learn/service.d.ts +0 -49
  291. package/dist/reporting/learn/service.js +0 -116
  292. package/dist/reporting/learn/service.js.map +0 -1
  293. package/dist/reporting/students/index.d.ts +0 -4
  294. package/dist/reporting/students/index.js +0 -2
  295. package/dist/reporting/students/model.d.ts +0 -51
  296. package/dist/reporting/students/model.js +0 -1
  297. package/dist/reporting/students/ports.d.ts +0 -15
  298. package/dist/reporting/students/ports.js +0 -1
  299. package/dist/reporting/students/service.d.ts +0 -20
  300. package/dist/reporting/students/service.js +0 -23
  301. package/dist/reporting/students/service.js.map +0 -1
  302. /package/dist/reporting/{courses → admin}/model.js +0 -0
  303. /package/dist/reporting/{courses → admin}/ports.js +0 -0
  304. /package/dist/reporting/{dashboard → member}/model.js +0 -0
  305. /package/dist/reporting/{dashboard → member}/ports.js +0 -0
  306. /package/dist/{reporting/learn/model.js → shared/actor.js} +0 -0
  307. /package/dist/{reporting/learn/ports.js → types/notifications.js} +0 -0
@@ -1,4 +1,26 @@
1
1
  //#region src/shared/mailer.ts
2
+ var EmailRejectedError = class extends Error {
3
+ status;
4
+ constructor(message, status) {
5
+ super(message);
6
+ this.status = status;
7
+ this.name = "EmailRejectedError";
8
+ }
9
+ };
10
+ function isEmailRejected(err) {
11
+ return err instanceof Error && err.name === "EmailRejectedError";
12
+ }
13
+ var EmailRenderError = class extends Error {
14
+ template;
15
+ constructor(template, cause) {
16
+ super(`email template ${template} failed to render: ${cause instanceof Error ? cause.message : String(cause)}`, { cause });
17
+ this.template = template;
18
+ this.name = "EmailRenderError";
19
+ }
20
+ };
21
+ function isEmailRenderError(err) {
22
+ return err instanceof Error && err.name === "EmailRenderError";
23
+ }
2
24
  var Mailer = class {
3
25
  templates;
4
26
  email;
@@ -9,10 +31,15 @@ var Mailer = class {
9
31
  this.ctx = ctx;
10
32
  }
11
33
  async send(to, id, params, ctx) {
12
- const content = await this.templates.render(id, {
13
- ...this.ctx,
14
- ...ctx
15
- }, params);
34
+ let content;
35
+ try {
36
+ content = await this.templates.render(id, {
37
+ ...this.ctx,
38
+ ...ctx
39
+ }, params);
40
+ } catch (err) {
41
+ throw new EmailRenderError(id, err);
42
+ }
16
43
  await this.email.send({
17
44
  to,
18
45
  subject: content.subject,
@@ -22,6 +49,6 @@ var Mailer = class {
22
49
  }
23
50
  };
24
51
  //#endregion
25
- export { Mailer };
52
+ export { EmailRejectedError, EmailRenderError, Mailer, isEmailRejected, isEmailRenderError };
26
53
 
27
54
  //# sourceMappingURL=mailer.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"mailer.js","names":[],"sources":["../../src/shared/mailer.ts"],"sourcesContent":["// Composes a TemplateRenderer with an EmailSender: callers name a template,\n// the mailer resolves content and hands it to the transport.\nimport type {\n EmailSender,\n EmailTemplateId,\n EmailTemplateParams,\n TemplateContext,\n TemplateRenderer,\n} from './ports.js';\nimport type { ContentType } from '../types/schemas/content.js';\n\n/** Resolves the recipient/content details a template needs when the\n * triggering event carries only row ids. Wired by the app layer. */\nexport interface MailerLookups {\n orgUserEmail(orgId: string, orgUserId: string): Promise<string | null>;\n contentInfo(\n orgId: string,\n contentId: string,\n ): Promise<{ id: string; title: string; type: ContentType } | null>;\n}\n\nexport class Mailer {\n constructor(\n private readonly templates: TemplateRenderer,\n private readonly email: EmailSender,\n private readonly ctx: TemplateContext,\n ) {}\n\n async send<K extends EmailTemplateId>(\n to: string,\n id: K,\n params: EmailTemplateParams[K],\n ctx?: Partial<TemplateContext>,\n ): Promise<void> {\n const content = await this.templates.render(id, { ...this.ctx, ...ctx }, params);\n await this.email.send({ to, subject: content.subject, text: content.text, html: content.html });\n }\n}\n"],"mappings":";AAqBA,IAAa,SAAb,MAAoB;CAEC;CACA;CACA;CAHnB,YACE,WACA,OACA,KACA;EAHiB,KAAA,YAAA;EACA,KAAA,QAAA;EACA,KAAA,MAAA;CAChB;CAEH,MAAM,KACJ,IACA,IACA,QACA,KACe;EACf,MAAM,UAAU,MAAM,KAAK,UAAU,OAAO,IAAI;GAAE,GAAG,KAAK;GAAK,GAAG;EAAI,GAAG,MAAM;EAC/E,MAAM,KAAK,MAAM,KAAK;GAAE;GAAI,SAAS,QAAQ;GAAS,MAAM,QAAQ;GAAM,MAAM,QAAQ;EAAK,CAAC;CAChG;AACF"}
1
+ {"version":3,"file":"mailer.js","names":[],"sources":["../../src/shared/mailer.ts"],"sourcesContent":["// Composes a TemplateRenderer with an EmailSender: callers name a template,\n// the mailer resolves content and hands it to the transport.\nimport type {\n EmailSender,\n EmailTemplateId,\n EmailTemplateParams,\n TemplateContext,\n TemplateRenderer,\n} from './ports.js';\nimport type { ContentType } from '../types/schemas/content.js';\n\n/** Resolves the recipient/content details a template needs when the\n * triggering event carries only row ids. Wired by the app layer. */\nexport interface MailerLookups {\n orgUserPerson(\n orgId: string,\n orgUserId: string,\n ): Promise<{ userId: string; email: string } | null>;\n contentInfo(\n orgId: string,\n contentId: string,\n ): Promise<{ id: string; title: string; type: ContentType } | null>;\n}\n\nexport class EmailRejectedError extends Error {\n constructor(\n message: string,\n readonly status?: number,\n ) {\n super(message);\n this.name = 'EmailRejectedError';\n }\n}\n\nexport function isEmailRejected(err: unknown): err is EmailRejectedError {\n return err instanceof Error && err.name === 'EmailRejectedError';\n}\n\nexport class EmailRenderError extends Error {\n constructor(\n readonly template: string,\n cause: unknown,\n ) {\n super(\n `email template ${template} failed to render: ${cause instanceof Error ? cause.message : String(cause)}`,\n { cause },\n );\n this.name = 'EmailRenderError';\n }\n}\n\nexport function isEmailRenderError(err: unknown): err is EmailRenderError {\n return err instanceof Error && err.name === 'EmailRenderError';\n}\n\nexport class Mailer {\n constructor(\n private readonly templates: TemplateRenderer,\n private readonly email: EmailSender,\n private readonly ctx: TemplateContext,\n ) {}\n\n async send<K extends EmailTemplateId>(\n to: string,\n id: K,\n params: EmailTemplateParams[K],\n ctx?: Partial<TemplateContext>,\n ): Promise<void> {\n let content: Awaited<ReturnType<TemplateRenderer['render']>>;\n try {\n content = await this.templates.render(id, { ...this.ctx, ...ctx }, params);\n } catch (err) {\n throw new EmailRenderError(id, err);\n }\n await this.email.send({ to, subject: content.subject, text: content.text, html: content.html });\n }\n}\n"],"mappings":";AAwBA,IAAa,qBAAb,cAAwC,MAAM;CAGjC;CAFX,YACE,SACA,QACA;EACA,MAAM,OAAO;EAFJ,KAAA,SAAA;EAGT,KAAK,OAAO;CACd;AACF;AAEA,SAAgB,gBAAgB,KAAyC;CACvE,OAAO,eAAe,SAAS,IAAI,SAAS;AAC9C;AAEA,IAAa,mBAAb,cAAsC,MAAM;CAE/B;CADX,YACE,UACA,OACA;EACA,MACE,kBAAkB,SAAS,qBAAqB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,KACrG,EAAE,MAAM,CACV;EANS,KAAA,WAAA;EAOT,KAAK,OAAO;CACd;AACF;AAEA,SAAgB,mBAAmB,KAAuC;CACxE,OAAO,eAAe,SAAS,IAAI,SAAS;AAC9C;AAEA,IAAa,SAAb,MAAoB;CAEC;CACA;CACA;CAHnB,YACE,WACA,OACA,KACA;EAHiB,KAAA,YAAA;EACA,KAAA,QAAA;EACA,KAAA,MAAA;CAChB;CAEH,MAAM,KACJ,IACA,IACA,QACA,KACe;EACf,IAAI;EACJ,IAAI;GACF,UAAU,MAAM,KAAK,UAAU,OAAO,IAAI;IAAE,GAAG,KAAK;IAAK,GAAG;GAAI,GAAG,MAAM;EAC3E,SAAS,KAAK;GACZ,MAAM,IAAI,iBAAiB,IAAI,GAAG;EACpC;EACA,MAAM,KAAK,MAAM,KAAK;GAAE;GAAI,SAAS,QAAQ;GAAS,MAAM,QAAQ;GAAM,MAAM,QAAQ;EAAK,CAAC;CAChG;AACF"}
@@ -0,0 +1,10 @@
1
+ //#region src/shared/network.d.ts
2
+ /** True when `address` is an IP literal; brackets and an IPv6 zone are allowed. */
3
+ declare function isIpLiteral(address: string): boolean;
4
+ /** True only for an IP literal routable on the public internet; anything unparseable is not public. */
5
+ declare function isPublicIp(address: string): boolean;
6
+ /** False for localhost names and for IP literals outside the public internet. */
7
+ declare function isPublicHostname(hostname: string): boolean;
8
+ //#endregion
9
+ export { isIpLiteral, isPublicHostname, isPublicIp };
10
+ //# sourceMappingURL=network.d.ts.map
@@ -0,0 +1,211 @@
1
+ //#region src/shared/network.ts
2
+ const NON_PUBLIC_IPV4 = [
3
+ [ipv4Number([
4
+ 0,
5
+ 0,
6
+ 0,
7
+ 0
8
+ ]), 8],
9
+ [ipv4Number([
10
+ 10,
11
+ 0,
12
+ 0,
13
+ 0
14
+ ]), 8],
15
+ [ipv4Number([
16
+ 100,
17
+ 64,
18
+ 0,
19
+ 0
20
+ ]), 10],
21
+ [ipv4Number([
22
+ 127,
23
+ 0,
24
+ 0,
25
+ 0
26
+ ]), 8],
27
+ [ipv4Number([
28
+ 169,
29
+ 254,
30
+ 0,
31
+ 0
32
+ ]), 16],
33
+ [ipv4Number([
34
+ 172,
35
+ 16,
36
+ 0,
37
+ 0
38
+ ]), 12],
39
+ [ipv4Number([
40
+ 192,
41
+ 0,
42
+ 0,
43
+ 0
44
+ ]), 24],
45
+ [ipv4Number([
46
+ 192,
47
+ 0,
48
+ 2,
49
+ 0
50
+ ]), 24],
51
+ [ipv4Number([
52
+ 192,
53
+ 88,
54
+ 99,
55
+ 0
56
+ ]), 24],
57
+ [ipv4Number([
58
+ 192,
59
+ 168,
60
+ 0,
61
+ 0
62
+ ]), 16],
63
+ [ipv4Number([
64
+ 198,
65
+ 18,
66
+ 0,
67
+ 0
68
+ ]), 15],
69
+ [ipv4Number([
70
+ 198,
71
+ 51,
72
+ 100,
73
+ 0
74
+ ]), 24],
75
+ [ipv4Number([
76
+ 203,
77
+ 0,
78
+ 113,
79
+ 0
80
+ ]), 24],
81
+ [ipv4Number([
82
+ 224,
83
+ 0,
84
+ 0,
85
+ 0
86
+ ]), 4],
87
+ [ipv4Number([
88
+ 240,
89
+ 0,
90
+ 0,
91
+ 0
92
+ ]), 4]
93
+ ];
94
+ function ipv4Number(octets) {
95
+ return (octets[0] << 24 | octets[1] << 16 | octets[2] << 8 | octets[3]) >>> 0;
96
+ }
97
+ function parseIpv4(value) {
98
+ const parts = value.split(".");
99
+ if (parts.length !== 4 || !parts.every((part) => /^\d{1,3}$/.test(part))) return null;
100
+ const octets = parts.map(Number);
101
+ return octets.every((octet) => octet <= 255) ? octets : null;
102
+ }
103
+ function parseHextets(part) {
104
+ if (part === "") return [];
105
+ const groups = part.split(":");
106
+ const hextets = [];
107
+ for (const [i, group] of groups.entries()) if (i === groups.length - 1 && group.includes(".")) {
108
+ const octets = parseIpv4(group);
109
+ if (!octets) return null;
110
+ hextets.push(octets[0] << 8 | octets[1], octets[2] << 8 | octets[3]);
111
+ } else if (/^[0-9a-f]{1,4}$/i.test(group)) hextets.push(parseInt(group, 16));
112
+ else return null;
113
+ return hextets;
114
+ }
115
+ function parseIpv6(value) {
116
+ const halves = value.split("%")[0].split("::");
117
+ if (halves.length > 2) return null;
118
+ const head = parseHextets(halves[0]);
119
+ const tail = halves.length === 2 ? parseHextets(halves[1]) : [];
120
+ if (!head || !tail) return null;
121
+ const missing = 8 - head.length - tail.length;
122
+ if (halves.length === 2 ? missing < 1 : missing !== 0) return null;
123
+ return [
124
+ ...head,
125
+ ...new Array(missing).fill(0),
126
+ ...tail
127
+ ].flatMap((hextet) => [hextet >> 8, hextet & 255]);
128
+ }
129
+ function isPublicIpv4(octets) {
130
+ const address = ipv4Number(octets);
131
+ return NON_PUBLIC_IPV4.every(([network, bits]) => {
132
+ const mask = bits === 0 ? 0 : -1 << 32 - bits >>> 0;
133
+ return (address & mask) >>> 0 !== network;
134
+ });
135
+ }
136
+ function startsWith(bytes, prefix) {
137
+ return prefix.every((byte, i) => bytes[i] === byte);
138
+ }
139
+ function isPublicIpv6(bytes) {
140
+ if (bytes.slice(0, 10).every((b) => b === 0)) {
141
+ const mapped = bytes[10] === 255 && bytes[11] === 255;
142
+ const compatible = bytes[10] === 0 && bytes[11] === 0;
143
+ return (mapped || compatible) && isPublicIpv4(bytes.slice(12));
144
+ }
145
+ if (startsWith(bytes, [
146
+ 0,
147
+ 100,
148
+ 255,
149
+ 155,
150
+ 0,
151
+ 0,
152
+ 0,
153
+ 0,
154
+ 0,
155
+ 0,
156
+ 0,
157
+ 0
158
+ ])) return isPublicIpv4(bytes.slice(12));
159
+ if (startsWith(bytes, [32, 2])) return isPublicIpv4(bytes.slice(2, 6));
160
+ const [first = 0, second = 0] = bytes;
161
+ return !((first & 254) === 252 || first === 254 && (second & 128) === 128 || first === 255 || startsWith(bytes, [
162
+ 0,
163
+ 100,
164
+ 255,
165
+ 155,
166
+ 0,
167
+ 1
168
+ ]) || startsWith(bytes, [
169
+ 1,
170
+ 0,
171
+ 0,
172
+ 0,
173
+ 0,
174
+ 0,
175
+ 0,
176
+ 0
177
+ ]) || startsWith(bytes, [
178
+ 32,
179
+ 1,
180
+ 13,
181
+ 184
182
+ ]) || startsWith(bytes, [
183
+ 32,
184
+ 1,
185
+ 0,
186
+ 0
187
+ ]));
188
+ }
189
+ /** True when `address` is an IP literal; brackets and an IPv6 zone are allowed. */
190
+ function isIpLiteral(address) {
191
+ const bare = address.replace(/^\[(.*)\]$/, "$1");
192
+ return parseIpv4(bare) !== null || parseIpv6(bare) !== null;
193
+ }
194
+ /** True only for an IP literal routable on the public internet; anything unparseable is not public. */
195
+ function isPublicIp(address) {
196
+ const bare = address.replace(/^\[(.*)\]$/, "$1");
197
+ const v4 = parseIpv4(bare);
198
+ if (v4) return isPublicIpv4(v4);
199
+ const v6 = parseIpv6(bare);
200
+ return v6 !== null && isPublicIpv6(v6);
201
+ }
202
+ /** False for localhost names and for IP literals outside the public internet. */
203
+ function isPublicHostname(hostname) {
204
+ const host = hostname.toLowerCase().replace(/\.$/, "");
205
+ if (host === "localhost" || host.endsWith(".localhost")) return false;
206
+ return isIpLiteral(host) ? isPublicIp(host) : true;
207
+ }
208
+ //#endregion
209
+ export { isIpLiteral, isPublicHostname, isPublicIp };
210
+
211
+ //# sourceMappingURL=network.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"network.js","names":[],"sources":["../../src/shared/network.ts"],"sourcesContent":["const NON_PUBLIC_IPV4: ReadonlyArray<readonly [number, number]> = [\n [ipv4Number([0, 0, 0, 0]), 8],\n [ipv4Number([10, 0, 0, 0]), 8],\n [ipv4Number([100, 64, 0, 0]), 10],\n [ipv4Number([127, 0, 0, 0]), 8],\n [ipv4Number([169, 254, 0, 0]), 16],\n [ipv4Number([172, 16, 0, 0]), 12],\n [ipv4Number([192, 0, 0, 0]), 24],\n [ipv4Number([192, 0, 2, 0]), 24],\n [ipv4Number([192, 88, 99, 0]), 24],\n [ipv4Number([192, 168, 0, 0]), 16],\n [ipv4Number([198, 18, 0, 0]), 15],\n [ipv4Number([198, 51, 100, 0]), 24],\n [ipv4Number([203, 0, 113, 0]), 24],\n [ipv4Number([224, 0, 0, 0]), 4],\n [ipv4Number([240, 0, 0, 0]), 4],\n];\n\nfunction ipv4Number(octets: number[]): number {\n return ((octets[0]! << 24) | (octets[1]! << 16) | (octets[2]! << 8) | octets[3]!) >>> 0;\n}\n\nfunction parseIpv4(value: string): number[] | null {\n const parts = value.split('.');\n if (parts.length !== 4 || !parts.every((part) => /^\\d{1,3}$/.test(part))) {\n return null;\n }\n const octets = parts.map(Number);\n return octets.every((octet) => octet <= 255) ? octets : null;\n}\n\nfunction parseHextets(part: string): number[] | null {\n if (part === '') {\n return [];\n }\n const groups = part.split(':');\n const hextets: number[] = [];\n for (const [i, group] of groups.entries()) {\n if (i === groups.length - 1 && group.includes('.')) {\n const octets = parseIpv4(group);\n if (!octets) {\n return null;\n }\n hextets.push((octets[0]! << 8) | octets[1]!, (octets[2]! << 8) | octets[3]!);\n } else if (/^[0-9a-f]{1,4}$/i.test(group)) {\n hextets.push(parseInt(group, 16));\n } else {\n return null;\n }\n }\n return hextets;\n}\n\nfunction parseIpv6(value: string): number[] | null {\n const address = value.split('%')[0]!;\n const halves = address.split('::');\n if (halves.length > 2) {\n return null;\n }\n const head = parseHextets(halves[0]!);\n const tail = halves.length === 2 ? parseHextets(halves[1]!) : [];\n if (!head || !tail) {\n return null;\n }\n const missing = 8 - head.length - tail.length;\n if (halves.length === 2 ? missing < 1 : missing !== 0) {\n return null;\n }\n const hextets = [...head, ...new Array<number>(missing).fill(0), ...tail];\n return hextets.flatMap((hextet) => [hextet >> 8, hextet & 0xff]);\n}\n\nfunction isPublicIpv4(octets: number[]): boolean {\n const address = ipv4Number(octets);\n return NON_PUBLIC_IPV4.every(([network, bits]) => {\n const mask = bits === 0 ? 0 : (~0 << (32 - bits)) >>> 0;\n return (address & mask) >>> 0 !== network;\n });\n}\n\nfunction startsWith(bytes: number[], prefix: number[]): boolean {\n return prefix.every((byte, i) => bytes[i] === byte);\n}\n\nfunction isPublicIpv6(bytes: number[]): boolean {\n if (bytes.slice(0, 10).every((b) => b === 0)) {\n const mapped = bytes[10] === 0xff && bytes[11] === 0xff;\n const compatible = bytes[10] === 0 && bytes[11] === 0;\n return (mapped || compatible) && isPublicIpv4(bytes.slice(12));\n }\n if (startsWith(bytes, [0x00, 0x64, 0xff, 0x9b, 0, 0, 0, 0, 0, 0, 0, 0])) {\n return isPublicIpv4(bytes.slice(12));\n }\n if (startsWith(bytes, [0x20, 0x02])) {\n return isPublicIpv4(bytes.slice(2, 6));\n }\n const [first = 0, second = 0] = bytes;\n return !(\n (first & 0xfe) === 0xfc ||\n (first === 0xfe && (second & 0x80) === 0x80) ||\n first === 0xff ||\n startsWith(bytes, [0x00, 0x64, 0xff, 0x9b, 0x00, 0x01]) ||\n startsWith(bytes, [0x01, 0x00, 0, 0, 0, 0, 0, 0]) ||\n startsWith(bytes, [0x20, 0x01, 0x0d, 0xb8]) ||\n startsWith(bytes, [0x20, 0x01, 0x00, 0x00])\n );\n}\n\n/** True when `address` is an IP literal; brackets and an IPv6 zone are allowed. */\nexport function isIpLiteral(address: string): boolean {\n const bare = address.replace(/^\\[(.*)\\]$/, '$1');\n return parseIpv4(bare) !== null || parseIpv6(bare) !== null;\n}\n\n/** True only for an IP literal routable on the public internet; anything unparseable is not public. */\nexport function isPublicIp(address: string): boolean {\n const bare = address.replace(/^\\[(.*)\\]$/, '$1');\n const v4 = parseIpv4(bare);\n if (v4) {\n return isPublicIpv4(v4);\n }\n const v6 = parseIpv6(bare);\n return v6 !== null && isPublicIpv6(v6);\n}\n\n/** False for localhost names and for IP literals outside the public internet. */\nexport function isPublicHostname(hostname: string): boolean {\n const host = hostname.toLowerCase().replace(/\\.$/, '');\n if (host === 'localhost' || host.endsWith('.localhost')) {\n return false;\n }\n return isIpLiteral(host) ? isPublicIp(host) : true;\n}\n"],"mappings":";AAAA,MAAM,kBAA4D;CAChE,CAAC,WAAW;EAAC;EAAG;EAAG;EAAG;CAAC,CAAC,GAAG,CAAC;CAC5B,CAAC,WAAW;EAAC;EAAI;EAAG;EAAG;CAAC,CAAC,GAAG,CAAC;CAC7B,CAAC,WAAW;EAAC;EAAK;EAAI;EAAG;CAAC,CAAC,GAAG,EAAE;CAChC,CAAC,WAAW;EAAC;EAAK;EAAG;EAAG;CAAC,CAAC,GAAG,CAAC;CAC9B,CAAC,WAAW;EAAC;EAAK;EAAK;EAAG;CAAC,CAAC,GAAG,EAAE;CACjC,CAAC,WAAW;EAAC;EAAK;EAAI;EAAG;CAAC,CAAC,GAAG,EAAE;CAChC,CAAC,WAAW;EAAC;EAAK;EAAG;EAAG;CAAC,CAAC,GAAG,EAAE;CAC/B,CAAC,WAAW;EAAC;EAAK;EAAG;EAAG;CAAC,CAAC,GAAG,EAAE;CAC/B,CAAC,WAAW;EAAC;EAAK;EAAI;EAAI;CAAC,CAAC,GAAG,EAAE;CACjC,CAAC,WAAW;EAAC;EAAK;EAAK;EAAG;CAAC,CAAC,GAAG,EAAE;CACjC,CAAC,WAAW;EAAC;EAAK;EAAI;EAAG;CAAC,CAAC,GAAG,EAAE;CAChC,CAAC,WAAW;EAAC;EAAK;EAAI;EAAK;CAAC,CAAC,GAAG,EAAE;CAClC,CAAC,WAAW;EAAC;EAAK;EAAG;EAAK;CAAC,CAAC,GAAG,EAAE;CACjC,CAAC,WAAW;EAAC;EAAK;EAAG;EAAG;CAAC,CAAC,GAAG,CAAC;CAC9B,CAAC,WAAW;EAAC;EAAK;EAAG;EAAG;CAAC,CAAC,GAAG,CAAC;AAChC;AAEA,SAAS,WAAW,QAA0B;CAC5C,QAAS,OAAO,MAAO,KAAO,OAAO,MAAO,KAAO,OAAO,MAAO,IAAK,OAAO,QAAS;AACxF;AAEA,SAAS,UAAU,OAAgC;CACjD,MAAM,QAAQ,MAAM,MAAM,GAAG;CAC7B,IAAI,MAAM,WAAW,KAAK,CAAC,MAAM,OAAO,SAAS,YAAY,KAAK,IAAI,CAAC,GACrE,OAAO;CAET,MAAM,SAAS,MAAM,IAAI,MAAM;CAC/B,OAAO,OAAO,OAAO,UAAU,SAAS,GAAG,IAAI,SAAS;AAC1D;AAEA,SAAS,aAAa,MAA+B;CACnD,IAAI,SAAS,IACX,OAAO,CAAC;CAEV,MAAM,SAAS,KAAK,MAAM,GAAG;CAC7B,MAAM,UAAoB,CAAC;CAC3B,KAAK,MAAM,CAAC,GAAG,UAAU,OAAO,QAAQ,GACtC,IAAI,MAAM,OAAO,SAAS,KAAK,MAAM,SAAS,GAAG,GAAG;EAClD,MAAM,SAAS,UAAU,KAAK;EAC9B,IAAI,CAAC,QACH,OAAO;EAET,QAAQ,KAAM,OAAO,MAAO,IAAK,OAAO,IAAM,OAAO,MAAO,IAAK,OAAO,EAAG;CAC7E,OAAO,IAAI,mBAAmB,KAAK,KAAK,GACtC,QAAQ,KAAK,SAAS,OAAO,EAAE,CAAC;MAEhC,OAAO;CAGX,OAAO;AACT;AAEA,SAAS,UAAU,OAAgC;CAEjD,MAAM,SADU,MAAM,MAAM,GAAG,CAAC,CAAC,EACX,CAAC,MAAM,IAAI;CACjC,IAAI,OAAO,SAAS,GAClB,OAAO;CAET,MAAM,OAAO,aAAa,OAAO,EAAG;CACpC,MAAM,OAAO,OAAO,WAAW,IAAI,aAAa,OAAO,EAAG,IAAI,CAAC;CAC/D,IAAI,CAAC,QAAQ,CAAC,MACZ,OAAO;CAET,MAAM,UAAU,IAAI,KAAK,SAAS,KAAK;CACvC,IAAI,OAAO,WAAW,IAAI,UAAU,IAAI,YAAY,GAClD,OAAO;CAGT,OAAO;EADU,GAAG;EAAM,GAAG,IAAI,MAAc,OAAO,CAAC,CAAC,KAAK,CAAC;EAAG,GAAG;CACvD,CAAC,CAAC,SAAS,WAAW,CAAC,UAAU,GAAG,SAAS,GAAI,CAAC;AACjE;AAEA,SAAS,aAAa,QAA2B;CAC/C,MAAM,UAAU,WAAW,MAAM;CACjC,OAAO,gBAAgB,OAAO,CAAC,SAAS,UAAU;EAChD,MAAM,OAAO,SAAS,IAAI,IAAK,MAAO,KAAK,SAAW;EACtD,QAAQ,UAAU,UAAU,MAAM;CACpC,CAAC;AACH;AAEA,SAAS,WAAW,OAAiB,QAA2B;CAC9D,OAAO,OAAO,OAAO,MAAM,MAAM,MAAM,OAAO,IAAI;AACpD;AAEA,SAAS,aAAa,OAA0B;CAC9C,IAAI,MAAM,MAAM,GAAG,EAAE,CAAC,CAAC,OAAO,MAAM,MAAM,CAAC,GAAG;EAC5C,MAAM,SAAS,MAAM,QAAQ,OAAQ,MAAM,QAAQ;EACnD,MAAM,aAAa,MAAM,QAAQ,KAAK,MAAM,QAAQ;EACpD,QAAQ,UAAU,eAAe,aAAa,MAAM,MAAM,EAAE,CAAC;CAC/D;CACA,IAAI,WAAW,OAAO;EAAC;EAAM;EAAM;EAAM;EAAM;EAAG;EAAG;EAAG;EAAG;EAAG;EAAG;EAAG;CAAC,CAAC,GACpE,OAAO,aAAa,MAAM,MAAM,EAAE,CAAC;CAErC,IAAI,WAAW,OAAO,CAAC,IAAM,CAAI,CAAC,GAChC,OAAO,aAAa,MAAM,MAAM,GAAG,CAAC,CAAC;CAEvC,MAAM,CAAC,QAAQ,GAAG,SAAS,KAAK;CAChC,OAAO,GACJ,QAAQ,SAAU,OAClB,UAAU,QAAS,SAAS,SAAU,OACvC,UAAU,OACV,WAAW,OAAO;EAAC;EAAM;EAAM;EAAM;EAAM;EAAM;CAAI,CAAC,KACtD,WAAW,OAAO;EAAC;EAAM;EAAM;EAAG;EAAG;EAAG;EAAG;EAAG;CAAC,CAAC,KAChD,WAAW,OAAO;EAAC;EAAM;EAAM;EAAM;CAAI,CAAC,KAC1C,WAAW,OAAO;EAAC;EAAM;EAAM;EAAM;CAAI,CAAC;AAE9C;;AAGA,SAAgB,YAAY,SAA0B;CACpD,MAAM,OAAO,QAAQ,QAAQ,cAAc,IAAI;CAC/C,OAAO,UAAU,IAAI,MAAM,QAAQ,UAAU,IAAI,MAAM;AACzD;;AAGA,SAAgB,WAAW,SAA0B;CACnD,MAAM,OAAO,QAAQ,QAAQ,cAAc,IAAI;CAC/C,MAAM,KAAK,UAAU,IAAI;CACzB,IAAI,IACF,OAAO,aAAa,EAAE;CAExB,MAAM,KAAK,UAAU,IAAI;CACzB,OAAO,OAAO,QAAQ,aAAa,EAAE;AACvC;;AAGA,SAAgB,iBAAiB,UAA2B;CAC1D,MAAM,OAAO,SAAS,YAAY,CAAC,CAAC,QAAQ,OAAO,EAAE;CACrD,IAAI,SAAS,eAAe,KAAK,SAAS,YAAY,GACpD,OAAO;CAET,OAAO,YAAY,IAAI,IAAI,WAAW,IAAI,IAAI;AAChD"}
@@ -0,0 +1,16 @@
1
+ import { NotificationCategory, NotificationTypeDeclaration } from "../types/notifications.js";
2
+ import "../types/index.js";
3
+ import { NotificationTypeRegistry } from "./ports.js";
4
+ import { ValidationProblem } from "./errors.js";
5
+ //#region src/shared/notification-categories.d.ts
6
+ type CategoryListScope = 'org' | 'community';
7
+ declare function coversListScope(declaration: Pick<NotificationTypeDeclaration, 'scopes'>, listScope: CategoryListScope): boolean;
8
+ declare function typesForList(types: Pick<NotificationTypeRegistry, 'list'>, listScope: CategoryListScope): Pick<NotificationTypeDeclaration, 'type' | 'scopes' | 'reasons'>[];
9
+ declare function categoryListProblems(categories: NotificationCategory[], listScope: CategoryListScope, types: Pick<NotificationTypeRegistry, 'get'>, configured: string[]): ValidationProblem[];
10
+ declare function assertValidCategoryList(categories: NotificationCategory[], listScope: CategoryListScope, types: Pick<NotificationTypeRegistry, 'get'>, configured: string[]): void;
11
+ declare function channelOrder(configured: string[]): string[];
12
+ declare function offeringConfigured(categories: NotificationCategory[], configured: string[]): NotificationCategory[];
13
+ declare function storedCategoryList(value: unknown): NotificationCategory[] | null;
14
+ //#endregion
15
+ export { CategoryListScope, assertValidCategoryList, categoryListProblems, channelOrder, coversListScope, offeringConfigured, storedCategoryList, typesForList };
16
+ //# sourceMappingURL=notification-categories.d.ts.map
@@ -0,0 +1,94 @@
1
+ import { notificationCategoryListSchema } from "../types/schemas/notifications.js";
2
+ import { ValidationError } from "./errors.js";
3
+ //#region src/shared/notification-categories.ts
4
+ const LIST_SCOPES = {
5
+ org: ["org"],
6
+ community: [
7
+ "community",
8
+ "space",
9
+ "chat"
10
+ ]
11
+ };
12
+ function coversListScope(declaration, listScope) {
13
+ return declaration.scopes.some((scope) => LIST_SCOPES[listScope].includes(scope));
14
+ }
15
+ function typesForList(types, listScope) {
16
+ return types.list().filter((declaration) => coversListScope(declaration, listScope));
17
+ }
18
+ function categoryListProblems(categories, listScope, types, configured) {
19
+ const problems = [];
20
+ const problem = (path, message) => problems.push({
21
+ path,
22
+ message
23
+ });
24
+ const keys = /* @__PURE__ */ new Set();
25
+ const covered = /* @__PURE__ */ new Set();
26
+ categories.forEach((category, index) => {
27
+ const at = `categories[${index}]`;
28
+ if (keys.has(category.key)) problem(`${at}.key`, `the key ${category.key} is used by an earlier category`);
29
+ keys.add(category.key);
30
+ const pairs = [];
31
+ category.covers.forEach((cover, coverIndex) => {
32
+ const coverAt = `${at}.covers[${coverIndex}]`;
33
+ const declaration = types.get(cover.type);
34
+ if (!declaration) {
35
+ problem(`${coverAt}.type`, `${cover.type} is not a notification type`);
36
+ return;
37
+ }
38
+ if (!coversListScope(declaration, listScope)) problem(`${coverAt}.type`, `${cover.type} cannot be in the ${listScope} list`);
39
+ cover.reasons.forEach((reason, reasonIndex) => {
40
+ if (!declaration.reasons.includes(reason)) problem(`${coverAt}.reasons[${reasonIndex}]`, `${cover.type} does not allow the reason ${reason}`);
41
+ pairs.push(`${cover.type}:${reason}`);
42
+ });
43
+ if (category.perSpace === true && !declaration.scopes.includes("space")) problem(`${coverAt}.type`, `${cover.type} cannot occur in a space, so it cannot be per space`);
44
+ });
45
+ if (Object.keys(category.channels).length === 0) problem(`${at}.channels`, "the category offers no channel");
46
+ for (const channel of Object.keys(category.channels)) if (!configured.includes(channel)) problem(`${at}.channels.${channel}`, `the channel ${channel} is not configured`);
47
+ if (category.perSpace === true && listScope === "org") problem(`${at}.perSpace`, "only a community list can have a per-space category");
48
+ if (category.spaceDefaults !== void 0) {
49
+ if (category.perSpace !== true) problem(`${at}.spaceDefaults`, "spaceDefaults needs perSpace");
50
+ for (const [spaceId, channels] of Object.entries(category.spaceDefaults)) for (const channel of Object.keys(channels)) if (!(channel in category.channels)) problem(`${at}.spaceDefaults.${spaceId}.${channel}`, `spaceDefaults for ${spaceId} names the channel ${channel}, which the category does not offer`);
51
+ }
52
+ if (pairs.length > 0 && pairs.every((pair) => covered.has(pair))) problem(`${at}.covers`, "earlier categories already cover everything it covers");
53
+ for (const pair of pairs) covered.add(pair);
54
+ });
55
+ return problems;
56
+ }
57
+ function assertValidCategoryList(categories, listScope, types, configured) {
58
+ const problems = categoryListProblems(categories, listScope, types, configured);
59
+ if (problems.length > 0) throw new ValidationError(problems);
60
+ }
61
+ const LEADING_CHANNELS = [
62
+ "inApp",
63
+ "email",
64
+ "push"
65
+ ];
66
+ function channelOrder(configured) {
67
+ return [...LEADING_CHANNELS.filter((channel) => configured.includes(channel)), ...configured.filter((channel) => !LEADING_CHANNELS.includes(channel))];
68
+ }
69
+ function inOrder(values, order) {
70
+ return Object.fromEntries(order.filter((channel) => channel in values).map((channel) => [channel, values[channel]]));
71
+ }
72
+ function offeringConfigured(categories, configured) {
73
+ const order = channelOrder(configured);
74
+ return categories.flatMap((category) => {
75
+ const channels = inOrder(category.channels, order);
76
+ if (Object.keys(channels).length === 0) return [];
77
+ const { spaceDefaults, ...rest } = category;
78
+ return [{
79
+ ...rest,
80
+ channels,
81
+ ...spaceDefaults !== void 0 && { spaceDefaults: Object.fromEntries(Object.entries(spaceDefaults).map(([spaceId, values]) => [spaceId, inOrder(values, Object.keys(channels))])) }
82
+ }];
83
+ });
84
+ }
85
+ function storedCategoryList(value) {
86
+ if (value === null || value === void 0) return null;
87
+ const parsed = notificationCategoryListSchema.safeParse(value);
88
+ if (!parsed.success) throw new Error(`stored notification categories are invalid: ${parsed.error.message}`);
89
+ return parsed.data;
90
+ }
91
+ //#endregion
92
+ export { assertValidCategoryList, categoryListProblems, channelOrder, coversListScope, offeringConfigured, storedCategoryList, typesForList };
93
+
94
+ //# sourceMappingURL=notification-categories.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-categories.js","names":[],"sources":["../../src/shared/notification-categories.ts"],"sourcesContent":["import type { NotificationCategory, NotificationTypeDeclaration, Scope } from '../types/index.js';\nimport { notificationCategoryListSchema } from '../types/schemas/notifications.js';\nimport { ValidationError, type ValidationProblem } from './errors.js';\nimport type { NotificationTypeRegistry } from './ports.js';\n\nexport type CategoryListScope = 'org' | 'community';\n\nconst LIST_SCOPES: Record<CategoryListScope, Scope[]> = {\n org: ['org'],\n community: ['community', 'space', 'chat'],\n};\n\nexport function coversListScope(\n declaration: Pick<NotificationTypeDeclaration, 'scopes'>,\n listScope: CategoryListScope,\n): boolean {\n return declaration.scopes.some((scope) => LIST_SCOPES[listScope].includes(scope));\n}\n\nexport function typesForList(\n types: Pick<NotificationTypeRegistry, 'list'>,\n listScope: CategoryListScope,\n): Pick<NotificationTypeDeclaration, 'type' | 'scopes' | 'reasons'>[] {\n return types.list().filter((declaration) => coversListScope(declaration, listScope));\n}\n\nexport function categoryListProblems(\n categories: NotificationCategory[],\n listScope: CategoryListScope,\n types: Pick<NotificationTypeRegistry, 'get'>,\n configured: string[],\n): ValidationProblem[] {\n const problems: ValidationProblem[] = [];\n const problem = (path: string, message: string) => problems.push({ path, message });\n const keys = new Set<string>();\n const covered = new Set<string>();\n categories.forEach((category, index) => {\n const at = `categories[${index}]`;\n if (keys.has(category.key)) {\n problem(`${at}.key`, `the key ${category.key} is used by an earlier category`);\n }\n keys.add(category.key);\n const pairs: string[] = [];\n category.covers.forEach((cover, coverIndex) => {\n const coverAt = `${at}.covers[${coverIndex}]`;\n const declaration = types.get(cover.type);\n if (!declaration) {\n problem(`${coverAt}.type`, `${cover.type} is not a notification type`);\n return;\n }\n if (!coversListScope(declaration, listScope)) {\n problem(`${coverAt}.type`, `${cover.type} cannot be in the ${listScope} list`);\n }\n cover.reasons.forEach((reason, reasonIndex) => {\n if (!declaration.reasons.includes(reason)) {\n problem(\n `${coverAt}.reasons[${reasonIndex}]`,\n `${cover.type} does not allow the reason ${reason}`,\n );\n }\n pairs.push(`${cover.type}:${reason}`);\n });\n if (category.perSpace === true && !declaration.scopes.includes('space')) {\n problem(\n `${coverAt}.type`,\n `${cover.type} cannot occur in a space, so it cannot be per space`,\n );\n }\n });\n if (Object.keys(category.channels).length === 0) {\n problem(`${at}.channels`, 'the category offers no channel');\n }\n for (const channel of Object.keys(category.channels)) {\n if (!configured.includes(channel)) {\n problem(`${at}.channels.${channel}`, `the channel ${channel} is not configured`);\n }\n }\n if (category.perSpace === true && listScope === 'org') {\n problem(`${at}.perSpace`, 'only a community list can have a per-space category');\n }\n if (category.spaceDefaults !== undefined) {\n if (category.perSpace !== true) {\n problem(`${at}.spaceDefaults`, 'spaceDefaults needs perSpace');\n }\n for (const [spaceId, channels] of Object.entries(category.spaceDefaults)) {\n for (const channel of Object.keys(channels)) {\n if (!(channel in category.channels)) {\n problem(\n `${at}.spaceDefaults.${spaceId}.${channel}`,\n `spaceDefaults for ${spaceId} names the channel ${channel}, which the category does not offer`,\n );\n }\n }\n }\n }\n if (pairs.length > 0 && pairs.every((pair) => covered.has(pair))) {\n problem(`${at}.covers`, 'earlier categories already cover everything it covers');\n }\n for (const pair of pairs) {\n covered.add(pair);\n }\n });\n return problems;\n}\n\nexport function assertValidCategoryList(\n categories: NotificationCategory[],\n listScope: CategoryListScope,\n types: Pick<NotificationTypeRegistry, 'get'>,\n configured: string[],\n): void {\n const problems = categoryListProblems(categories, listScope, types, configured);\n if (problems.length > 0) {\n throw new ValidationError(problems);\n }\n}\n\nconst LEADING_CHANNELS = ['inApp', 'email', 'push'];\n\nexport function channelOrder(configured: string[]): string[] {\n return [\n ...LEADING_CHANNELS.filter((channel) => configured.includes(channel)),\n ...configured.filter((channel) => !LEADING_CHANNELS.includes(channel)),\n ];\n}\n\nfunction inOrder<T>(values: Record<string, T>, order: string[]): Record<string, T> {\n return Object.fromEntries(\n order.filter((channel) => channel in values).map((channel) => [channel, values[channel]!]),\n );\n}\n\nexport function offeringConfigured(\n categories: NotificationCategory[],\n configured: string[],\n): NotificationCategory[] {\n const order = channelOrder(configured);\n return categories.flatMap((category) => {\n const channels = inOrder(category.channels, order);\n if (Object.keys(channels).length === 0) {\n return [];\n }\n const { spaceDefaults, ...rest } = category;\n return [\n {\n ...rest,\n channels,\n ...(spaceDefaults !== undefined && {\n spaceDefaults: Object.fromEntries(\n Object.entries(spaceDefaults).map(([spaceId, values]) => [\n spaceId,\n inOrder(values, Object.keys(channels)),\n ]),\n ),\n }),\n },\n ];\n });\n}\n\nexport function storedCategoryList(value: unknown): NotificationCategory[] | null {\n if (value === null || value === undefined) {\n return null;\n }\n const parsed = notificationCategoryListSchema.safeParse(value);\n if (!parsed.success) {\n throw new Error(`stored notification categories are invalid: ${parsed.error.message}`);\n }\n return parsed.data;\n}\n"],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import { NotificationCategory, StoredSetting } from "../types/notifications.js";
2
+ import "../types/index.js";
3
+ //#region src/shared/notification-settings.d.ts
4
+ type ChannelSource = 'locked' | 'space' | 'community' | 'spaceDefault' | 'default';
5
+ interface ResolvedChannel {
6
+ channel: string;
7
+ enabled: boolean;
8
+ source: ChannelSource;
9
+ locked: boolean;
10
+ }
11
+ declare function resolveChannels(category: NotificationCategory, settings: StoredSetting[], spaceId: string | null, configured: string[]): ResolvedChannel[];
12
+ //#endregion
13
+ export { ChannelSource, ResolvedChannel, resolveChannels };
14
+ //# sourceMappingURL=notification-settings.d.ts.map
@@ -0,0 +1,61 @@
1
+ //#region src/shared/notification-settings.ts
2
+ function resolveChannels(category, settings, spaceId, configured) {
3
+ const perSpace = category.perSpace === true;
4
+ const ownSettings = settings.filter((s) => s.category === category.key);
5
+ const resolved = [];
6
+ for (const channel of configured) {
7
+ const offered = category.channels[channel];
8
+ if (!offered) continue;
9
+ if (offered.locked) {
10
+ resolved.push({
11
+ channel,
12
+ enabled: offered.default,
13
+ source: "locked",
14
+ locked: true
15
+ });
16
+ continue;
17
+ }
18
+ const forChannel = ownSettings.filter((s) => s.channel === channel);
19
+ const spaceSetting = perSpace && spaceId !== null ? forChannel.find((s) => s.spaceId === spaceId) : void 0;
20
+ if (spaceSetting) {
21
+ resolved.push({
22
+ channel,
23
+ enabled: spaceSetting.enabled,
24
+ source: "space",
25
+ locked: false
26
+ });
27
+ continue;
28
+ }
29
+ const communitySetting = forChannel.find((s) => s.spaceId === null);
30
+ if (communitySetting) {
31
+ resolved.push({
32
+ channel,
33
+ enabled: communitySetting.enabled,
34
+ source: "community",
35
+ locked: false
36
+ });
37
+ continue;
38
+ }
39
+ const spaceDefault = perSpace && spaceId !== null ? category.spaceDefaults?.[spaceId]?.[channel] : void 0;
40
+ if (spaceDefault !== void 0) {
41
+ resolved.push({
42
+ channel,
43
+ enabled: spaceDefault,
44
+ source: "spaceDefault",
45
+ locked: false
46
+ });
47
+ continue;
48
+ }
49
+ resolved.push({
50
+ channel,
51
+ enabled: offered.default,
52
+ source: "default",
53
+ locked: false
54
+ });
55
+ }
56
+ return resolved;
57
+ }
58
+ //#endregion
59
+ export { resolveChannels };
60
+
61
+ //# sourceMappingURL=notification-settings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-settings.js","names":[],"sources":["../../src/shared/notification-settings.ts"],"sourcesContent":["import type { NotificationCategory, StoredSetting } from '../types/index.js';\n\nexport type ChannelSource = 'locked' | 'space' | 'community' | 'spaceDefault' | 'default';\n\nexport interface ResolvedChannel {\n channel: string;\n enabled: boolean;\n source: ChannelSource;\n locked: boolean;\n}\n\nexport function resolveChannels(\n category: NotificationCategory,\n settings: StoredSetting[],\n spaceId: string | null,\n configured: string[],\n): ResolvedChannel[] {\n const perSpace = category.perSpace === true;\n const ownSettings = settings.filter((s) => s.category === category.key);\n const resolved: ResolvedChannel[] = [];\n for (const channel of configured) {\n const offered = category.channels[channel];\n if (!offered) {\n continue;\n }\n if (offered.locked) {\n resolved.push({ channel, enabled: offered.default, source: 'locked', locked: true });\n continue;\n }\n const forChannel = ownSettings.filter((s) => s.channel === channel);\n const spaceSetting =\n perSpace && spaceId !== null ? forChannel.find((s) => s.spaceId === spaceId) : undefined;\n if (spaceSetting) {\n resolved.push({ channel, enabled: spaceSetting.enabled, source: 'space', locked: false });\n continue;\n }\n const communitySetting = forChannel.find((s) => s.spaceId === null);\n if (communitySetting) {\n resolved.push({\n channel,\n enabled: communitySetting.enabled,\n source: 'community',\n locked: false,\n });\n continue;\n }\n const spaceDefault =\n perSpace && spaceId !== null ? category.spaceDefaults?.[spaceId]?.[channel] : undefined;\n if (spaceDefault !== undefined) {\n resolved.push({ channel, enabled: spaceDefault, source: 'spaceDefault', locked: false });\n continue;\n }\n resolved.push({ channel, enabled: offered.default, source: 'default', locked: false });\n }\n return resolved;\n}\n"],"mappings":";AAWA,SAAgB,gBACd,UACA,UACA,SACA,YACmB;CACnB,MAAM,WAAW,SAAS,aAAa;CACvC,MAAM,cAAc,SAAS,QAAQ,MAAM,EAAE,aAAa,SAAS,GAAG;CACtE,MAAM,WAA8B,CAAC;CACrC,KAAK,MAAM,WAAW,YAAY;EAChC,MAAM,UAAU,SAAS,SAAS;EAClC,IAAI,CAAC,SACH;EAEF,IAAI,QAAQ,QAAQ;GAClB,SAAS,KAAK;IAAE;IAAS,SAAS,QAAQ;IAAS,QAAQ;IAAU,QAAQ;GAAK,CAAC;GACnF;EACF;EACA,MAAM,aAAa,YAAY,QAAQ,MAAM,EAAE,YAAY,OAAO;EAClE,MAAM,eACJ,YAAY,YAAY,OAAO,WAAW,MAAM,MAAM,EAAE,YAAY,OAAO,IAAI,KAAA;EACjF,IAAI,cAAc;GAChB,SAAS,KAAK;IAAE;IAAS,SAAS,aAAa;IAAS,QAAQ;IAAS,QAAQ;GAAM,CAAC;GACxF;EACF;EACA,MAAM,mBAAmB,WAAW,MAAM,MAAM,EAAE,YAAY,IAAI;EAClE,IAAI,kBAAkB;GACpB,SAAS,KAAK;IACZ;IACA,SAAS,iBAAiB;IAC1B,QAAQ;IACR,QAAQ;GACV,CAAC;GACD;EACF;EACA,MAAM,eACJ,YAAY,YAAY,OAAO,SAAS,gBAAgB,QAAQ,GAAG,WAAW,KAAA;EAChF,IAAI,iBAAiB,KAAA,GAAW;GAC9B,SAAS,KAAK;IAAE;IAAS,SAAS;IAAc,QAAQ;IAAgB,QAAQ;GAAM,CAAC;GACvF;EACF;EACA,SAAS,KAAK;GAAE;GAAS,SAAS,QAAQ;GAAS,QAAQ;GAAW,QAAQ;EAAM,CAAC;CACvF;CACA,OAAO;AACT"}
@@ -1,7 +1,8 @@
1
- import { DomainEvent, NewDomainEvent } from "../types/shared.js";
1
+ import { DomainEvent, JsonValue, NewDomainEvent } from "../types/shared.js";
2
2
  import { EventDefinition } from "../types/events.js";
3
3
  import { EmailMessage, EmailSender, Logger, ObjectStorage, PresignDownloadInput, PresignedUpload, StoredObjectInfo } from "../types/ports.js";
4
4
  import { EmailContent, EmailTemplateId, EmailTemplateParams, TemplateContext, TemplateRenderer } from "../types/email-templates.js";
5
+ import { ChannelRecipient, DeliveryResult, NotificationCategory, NotificationTypeDeclaration, PushDevice, PushMessage, StoredNotification } from "../types/notifications.js";
5
6
  import "../types/index.js";
6
7
  import { EventOf, EventOfValues, InvalidDomainEventError, RuntimeEventDefinition, defineEvent, eventIdSchema, eventJsonValueSchema, eventMetadataSchema } from "./events.js";
7
8
  //#region src/shared/ports.d.ts
@@ -57,6 +58,51 @@ interface CredentialStore {
57
58
  /** Permanently delete stored secrets (e.g. disconnect). */
58
59
  destroy(orgId: string, ref: string): Promise<void>;
59
60
  }
61
+ interface NotificationTypeRegistry {
62
+ list(): Pick<NotificationTypeDeclaration, 'type' | 'scopes' | 'reasons'>[];
63
+ get(type: string): NotificationTypeDeclaration | undefined;
64
+ defaults(scope: 'org' | 'community'): NotificationCategory[];
65
+ }
66
+ interface NotificationChannel {
67
+ key: string;
68
+ recipient(orgId: string, userId: string): Promise<ChannelRecipient | null>;
69
+ deliver(notification: StoredNotification, to: ChannelRecipient): Promise<DeliveryResult>;
70
+ }
71
+ interface ChannelRegistry {
72
+ list(): string[];
73
+ get(key: string): NotificationChannel | undefined;
74
+ }
75
+ interface NotificationCategoryService {
76
+ get(orgId: string, scopeId: string): Promise<{
77
+ categories: NotificationCategory[];
78
+ source: 'default' | 'stored';
79
+ }>;
80
+ replace(orgId: string, scopeId: string, categories: NotificationCategory[]): Promise<{
81
+ categories: NotificationCategory[];
82
+ source: 'stored';
83
+ }>;
84
+ reset(orgId: string, scopeId: string): Promise<void>;
85
+ defaults(): NotificationCategory[];
86
+ }
87
+ interface PushSender {
88
+ send(devices: PushDevice[], message: PushMessage): Promise<DeliveryResult>;
89
+ }
90
+ interface ScheduledJob {
91
+ job: string;
92
+ run(payload: JsonValue, fire: {
93
+ id: string;
94
+ startedAt: Date;
95
+ }): Promise<void>;
96
+ }
97
+ interface RecurringJob {
98
+ job: string;
99
+ cron: string;
100
+ }
101
+ interface Scheduler {
102
+ registerJobs(handlers: ScheduledJob[], recurring: RecurringJob[]): void;
103
+ at(job: string, runAt: Date, key: string, payload: JsonValue): Promise<void>;
104
+ cancel(job: string, key: string): Promise<void>;
105
+ }
60
106
  //#endregion
61
- export { Clock, CredentialStore, type DomainEvent, type EmailContent, type EmailMessage, type EmailSender, type EmailTemplateId, type EmailTemplateParams, EventBus, type EventDefinition, type EventOf, type EventOfValues, InvalidDomainEventError, type Logger, type NewDomainEvent, type ObjectStorage, OutboxAppender, OutboxMessage, OutboxRelay, OutboxStore, type PresignDownloadInput, type PresignedUpload, type RuntimeEventDefinition, type StoredObjectInfo, type TemplateContext, type TemplateRenderer, UnitOfWork, defineEvent, eventIdSchema, eventJsonValueSchema, eventMetadataSchema };
107
+ export { type ChannelRecipient, ChannelRegistry, Clock, CredentialStore, type DeliveryResult, type DomainEvent, type EmailContent, type EmailMessage, type EmailSender, type EmailTemplateId, type EmailTemplateParams, EventBus, type EventDefinition, type EventOf, type EventOfValues, InvalidDomainEventError, type JsonValue, type Logger, type NewDomainEvent, type NotificationCategory, NotificationCategoryService, NotificationChannel, type NotificationTypeDeclaration, NotificationTypeRegistry, type ObjectStorage, OutboxAppender, OutboxMessage, OutboxRelay, OutboxStore, type PresignDownloadInput, type PresignedUpload, type PushDevice, type PushMessage, PushSender, RecurringJob, type RuntimeEventDefinition, ScheduledJob, Scheduler, type StoredNotification, type StoredObjectInfo, type TemplateContext, type TemplateRenderer, UnitOfWork, defineEvent, eventIdSchema, eventJsonValueSchema, eventMetadataSchema };
62
108
  //# sourceMappingURL=ports.d.ts.map
@@ -5,7 +5,8 @@ interface SettingsRecord {
5
5
  value?: Record<string, unknown>;
6
6
  }
7
7
  declare enum SettingsNamespace {
8
- content = "content"
8
+ content = "content",
9
+ organizations = "organizations"
9
10
  }
10
11
  interface SettingsRepository {
11
12
  /** Rows for the given scopes, optionally narrowed to one namespace. */
@@ -1,6 +1,7 @@
1
1
  //#region src/shared/settings.ts
2
2
  let SettingsNamespace = /* @__PURE__ */ function(SettingsNamespace) {
3
3
  SettingsNamespace["content"] = "content";
4
+ SettingsNamespace["organizations"] = "organizations";
4
5
  return SettingsNamespace;
5
6
  }({});
6
7
  var SettingsService = class {
@@ -1 +1 @@
1
- {"version":3,"file":"settings.js","names":[],"sources":["../../src/shared/settings.ts"],"sourcesContent":["// Cross-cutting settings: storage, scoping and resolution for every domain's\n// configuration. Not a bounded context: it is a leaf every context may import, and it imports\n// no context.\n\nexport interface SettingsRecord {\n namespace: string;\n scopeId: string;\n value?: Record<string, unknown>;\n}\n\nexport enum SettingsNamespace {\n content = 'content',\n}\n\nexport interface SettingsRepository {\n /** Rows for the given scopes, optionally narrowed to one namespace. */\n find(orgId: string, scopeId: string, namespace?: string): Promise<SettingsRecord[]>;\n /** Deep-merges the patch into the row's value, inserting when absent. Nested\n * objects merge key by key; arrays and scalars are replaced outright. */\n patch(orgId: string, namespace: string, scopeId: string, value: unknown): Promise<SettingsRecord>;\n}\n\nexport class SettingsService {\n constructor(private readonly repo: SettingsRepository) {}\n\n async get<T>(\n orgId: string,\n namespace: string,\n scopeId: string,\n defaultIfNull?: T,\n ): Promise<T | undefined> {\n const rows = await this.repo.find(orgId, scopeId, namespace);\n return (rows[0]?.value as T) ?? defaultIfNull;\n }\n\n async patch<T>(orgId: string, namespace: string, scopeId: string, value: unknown): Promise<T> {\n const row = await this.repo.patch(orgId, namespace, scopeId, value);\n return row.value as T;\n }\n}\n"],"mappings":";AAUA,IAAY,oBAAL,yBAAA,mBAAA;CACL,kBAAA,aAAA;;AACF,EAAA,CAAA,CAAA;AAUA,IAAa,kBAAb,MAA6B;CACE;CAA7B,YAAY,MAA2C;EAA1B,KAAA,OAAA;CAA2B;CAExD,MAAM,IACJ,OACA,WACA,SACA,eACwB;EAExB,QAAQ,MADW,KAAK,KAAK,KAAK,OAAO,SAAS,SAAS,EAAA,CAC9C,EAAE,EAAE,SAAe;CAClC;CAEA,MAAM,MAAS,OAAe,WAAmB,SAAiB,OAA4B;EAE5F,QAAO,MADW,KAAK,KAAK,MAAM,OAAO,WAAW,SAAS,KAAK,EAAA,CACvD;CACb;AACF"}
1
+ {"version":3,"file":"settings.js","names":[],"sources":["../../src/shared/settings.ts"],"sourcesContent":["// Cross-cutting settings: storage, scoping and resolution for every domain's\n// configuration. Not a bounded context: it is a leaf every context may import, and it imports\n// no context.\n\nexport interface SettingsRecord {\n namespace: string;\n scopeId: string;\n value?: Record<string, unknown>;\n}\n\nexport enum SettingsNamespace {\n content = 'content',\n organizations = 'organizations',\n}\n\nexport interface SettingsRepository {\n /** Rows for the given scopes, optionally narrowed to one namespace. */\n find(orgId: string, scopeId: string, namespace?: string): Promise<SettingsRecord[]>;\n /** Deep-merges the patch into the row's value, inserting when absent. Nested\n * objects merge key by key; arrays and scalars are replaced outright. */\n patch(orgId: string, namespace: string, scopeId: string, value: unknown): Promise<SettingsRecord>;\n}\n\nexport class SettingsService {\n constructor(private readonly repo: SettingsRepository) {}\n\n async get<T>(\n orgId: string,\n namespace: string,\n scopeId: string,\n defaultIfNull?: T,\n ): Promise<T | undefined> {\n const rows = await this.repo.find(orgId, scopeId, namespace);\n return (rows[0]?.value as T) ?? defaultIfNull;\n }\n\n async patch<T>(orgId: string, namespace: string, scopeId: string, value: unknown): Promise<T> {\n const row = await this.repo.patch(orgId, namespace, scopeId, value);\n return row.value as T;\n }\n}\n"],"mappings":";AAUA,IAAY,oBAAL,yBAAA,mBAAA;CACL,kBAAA,aAAA;CACA,kBAAA,mBAAA;;AACF,EAAA,CAAA,CAAA;AAUA,IAAa,kBAAb,MAA6B;CACE;CAA7B,YAAY,MAA2C;EAA1B,KAAA,OAAA;CAA2B;CAExD,MAAM,IACJ,OACA,WACA,SACA,eACwB;EAExB,QAAQ,MADW,KAAK,KAAK,KAAK,OAAO,SAAS,SAAS,EAAA,CAC9C,EAAE,EAAE,SAAe;CAClC;CAEA,MAAM,MAAS,OAAe,WAAmB,SAAiB,OAA4B;EAE5F,QAAO,MADW,KAAK,KAAK,MAAM,OAAO,WAAW,SAAS,KAAK,EAAA,CACvD;CACb;AACF"}
@@ -1,2 +1,2 @@
1
- import { Asset, AssetKind, AssetStatus, AssetsQuery, DownloadTicket, RequestUploadInput, UploadTicket } from "./schemas/assets.js";
2
- export type { Asset, AssetKind, AssetStatus, AssetsQuery, DownloadTicket, RequestUploadInput, UploadTicket };
1
+ import { Asset, AssetKind, AssetSource, AssetStatus, AssetUrls, AssetView, AssetsQuery, RequestUploadInput, UploadTicket } from "./schemas/assets.js";
2
+ export type { Asset, AssetKind, AssetSource, AssetStatus, AssetUrls, AssetView, AssetsQuery, RequestUploadInput, UploadTicket };