@headless-lms/core 0.1.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 (261) hide show
  1. package/LICENSE +21 -0
  2. package/dist/assets/events.d.ts +81 -0
  3. package/dist/assets/events.js +24 -0
  4. package/dist/assets/events.js.map +1 -0
  5. package/dist/assets/index.d.ts +7 -0
  6. package/dist/assets/index.js +3 -0
  7. package/dist/assets/model.d.ts +4 -0
  8. package/dist/assets/model.js +1 -0
  9. package/dist/assets/ports.d.ts +34 -0
  10. package/dist/assets/ports.js +1 -0
  11. package/dist/assets/service.d.ts +29 -0
  12. package/dist/assets/service.js +130 -0
  13. package/dist/assets/service.js.map +1 -0
  14. package/dist/automations/actions.d.ts +24 -0
  15. package/dist/automations/actions.js +59 -0
  16. package/dist/automations/actions.js.map +1 -0
  17. package/dist/automations/catalog.d.ts +12 -0
  18. package/dist/automations/catalog.js +224 -0
  19. package/dist/automations/catalog.js.map +1 -0
  20. package/dist/automations/events.d.ts +264 -0
  21. package/dist/automations/events.js +54 -0
  22. package/dist/automations/events.js.map +1 -0
  23. package/dist/automations/index.d.ts +9 -0
  24. package/dist/automations/index.js +4 -0
  25. package/dist/automations/model.d.ts +12 -0
  26. package/dist/automations/model.js +14 -0
  27. package/dist/automations/model.js.map +1 -0
  28. package/dist/automations/ports.d.ts +56 -0
  29. package/dist/automations/ports.js +1 -0
  30. package/dist/automations/service.d.ts +53 -0
  31. package/dist/automations/service.js +225 -0
  32. package/dist/automations/service.js.map +1 -0
  33. package/dist/automations/types.d.ts +3 -0
  34. package/dist/automations/types.js +1 -0
  35. package/dist/content/events.d.ts +410 -0
  36. package/dist/content/events.js +105 -0
  37. package/dist/content/events.js.map +1 -0
  38. package/dist/content/index.d.ts +8 -0
  39. package/dist/content/index.js +3 -0
  40. package/dist/content/model.d.ts +3 -0
  41. package/dist/content/model.js +1 -0
  42. package/dist/content/ports.d.ts +85 -0
  43. package/dist/content/ports.js +1 -0
  44. package/dist/content/service.d.ts +54 -0
  45. package/dist/content/service.js +479 -0
  46. package/dist/content/service.js.map +1 -0
  47. package/dist/content/types.d.ts +4 -0
  48. package/dist/content/types.js +1 -0
  49. package/dist/discussion/events.d.ts +101 -0
  50. package/dist/discussion/events.js +29 -0
  51. package/dist/discussion/events.js.map +1 -0
  52. package/dist/discussion/index.d.ts +8 -0
  53. package/dist/discussion/index.js +3 -0
  54. package/dist/discussion/model.d.ts +5 -0
  55. package/dist/discussion/model.js +1 -0
  56. package/dist/discussion/ports.d.ts +112 -0
  57. package/dist/discussion/ports.js +1 -0
  58. package/dist/discussion/service.d.ts +67 -0
  59. package/dist/discussion/service.js +343 -0
  60. package/dist/discussion/service.js.map +1 -0
  61. package/dist/discussion/types.d.ts +4 -0
  62. package/dist/discussion/types.js +1 -0
  63. package/dist/entitlements/events.d.ts +89 -0
  64. package/dist/entitlements/events.js +29 -0
  65. package/dist/entitlements/events.js.map +1 -0
  66. package/dist/entitlements/index.d.ts +7 -0
  67. package/dist/entitlements/index.js +3 -0
  68. package/dist/entitlements/model.d.ts +4 -0
  69. package/dist/entitlements/model.js +1 -0
  70. package/dist/entitlements/ports.d.ts +34 -0
  71. package/dist/entitlements/ports.js +1 -0
  72. package/dist/entitlements/service.d.ts +27 -0
  73. package/dist/entitlements/service.js +60 -0
  74. package/dist/entitlements/service.js.map +1 -0
  75. package/dist/identity/events.d.ts +63 -0
  76. package/dist/identity/events.js +24 -0
  77. package/dist/identity/events.js.map +1 -0
  78. package/dist/identity/index.d.ts +8 -0
  79. package/dist/identity/index.js +3 -0
  80. package/dist/identity/model.d.ts +3 -0
  81. package/dist/identity/model.js +1 -0
  82. package/dist/identity/ports.d.ts +58 -0
  83. package/dist/identity/ports.js +1 -0
  84. package/dist/identity/service.d.ts +38 -0
  85. package/dist/identity/service.js +79 -0
  86. package/dist/identity/service.js.map +1 -0
  87. package/dist/identity/types.d.ts +4 -0
  88. package/dist/identity/types.js +1 -0
  89. package/dist/integrations/events.d.ts +70 -0
  90. package/dist/integrations/events.js +24 -0
  91. package/dist/integrations/events.js.map +1 -0
  92. package/dist/integrations/index.d.ts +9 -0
  93. package/dist/integrations/index.js +6 -0
  94. package/dist/integrations/model.d.ts +28 -0
  95. package/dist/integrations/model.js +45 -0
  96. package/dist/integrations/model.js.map +1 -0
  97. package/dist/integrations/ports.d.ts +62 -0
  98. package/dist/integrations/ports.js +1 -0
  99. package/dist/integrations/registry.d.ts +7 -0
  100. package/dist/integrations/registry.js +16 -0
  101. package/dist/integrations/registry.js.map +1 -0
  102. package/dist/integrations/service.d.ts +47 -0
  103. package/dist/integrations/service.js +184 -0
  104. package/dist/integrations/service.js.map +1 -0
  105. package/dist/integrations/zod.d.ts +29 -0
  106. package/dist/integrations/zod.js +40 -0
  107. package/dist/integrations/zod.js.map +1 -0
  108. package/dist/organizations/events.d.ts +197 -0
  109. package/dist/organizations/events.js +59 -0
  110. package/dist/organizations/events.js.map +1 -0
  111. package/dist/organizations/index.d.ts +11 -0
  112. package/dist/organizations/index.js +5 -0
  113. package/dist/organizations/members.d.ts +32 -0
  114. package/dist/organizations/members.js +12 -0
  115. package/dist/organizations/members.js.map +1 -0
  116. package/dist/organizations/model.d.ts +3 -0
  117. package/dist/organizations/model.js +1 -0
  118. package/dist/organizations/ports.d.ts +127 -0
  119. package/dist/organizations/ports.js +1 -0
  120. package/dist/organizations/roles.d.ts +23 -0
  121. package/dist/organizations/roles.js +70 -0
  122. package/dist/organizations/roles.js.map +1 -0
  123. package/dist/organizations/service.d.ts +58 -0
  124. package/dist/organizations/service.js +359 -0
  125. package/dist/organizations/service.js.map +1 -0
  126. package/dist/organizations/types.d.ts +39 -0
  127. package/dist/organizations/types.js +1 -0
  128. package/dist/progress/events.d.ts +55 -0
  129. package/dist/progress/events.js +19 -0
  130. package/dist/progress/events.js.map +1 -0
  131. package/dist/progress/index.d.ts +8 -0
  132. package/dist/progress/index.js +3 -0
  133. package/dist/progress/model.d.ts +3 -0
  134. package/dist/progress/model.js +1 -0
  135. package/dist/progress/ports.d.ts +37 -0
  136. package/dist/progress/ports.js +1 -0
  137. package/dist/progress/service.d.ts +33 -0
  138. package/dist/progress/service.js +181 -0
  139. package/dist/progress/service.js.map +1 -0
  140. package/dist/progress/types.d.ts +4 -0
  141. package/dist/progress/types.js +1 -0
  142. package/dist/reporting/dashboard/index.d.ts +4 -0
  143. package/dist/reporting/dashboard/index.js +2 -0
  144. package/dist/reporting/dashboard/model.d.ts +14 -0
  145. package/dist/reporting/dashboard/model.js +1 -0
  146. package/dist/reporting/dashboard/ports.d.ts +13 -0
  147. package/dist/reporting/dashboard/ports.js +1 -0
  148. package/dist/reporting/dashboard/service.d.ts +19 -0
  149. package/dist/reporting/dashboard/service.js +20 -0
  150. package/dist/reporting/dashboard/service.js.map +1 -0
  151. package/dist/reporting/learn/index.d.ts +5 -0
  152. package/dist/reporting/learn/index.js +2 -0
  153. package/dist/reporting/learn/model.d.ts +23 -0
  154. package/dist/reporting/learn/model.js +1 -0
  155. package/dist/reporting/learn/ports.d.ts +42 -0
  156. package/dist/reporting/learn/ports.js +1 -0
  157. package/dist/reporting/learn/service.d.ts +48 -0
  158. package/dist/reporting/learn/service.js +111 -0
  159. package/dist/reporting/learn/service.js.map +1 -0
  160. package/dist/reporting/students/index.d.ts +4 -0
  161. package/dist/reporting/students/index.js +2 -0
  162. package/dist/reporting/students/model.d.ts +31 -0
  163. package/dist/reporting/students/model.js +1 -0
  164. package/dist/reporting/students/ports.d.ts +13 -0
  165. package/dist/reporting/students/ports.js +1 -0
  166. package/dist/reporting/students/service.d.ts +19 -0
  167. package/dist/reporting/students/service.js +20 -0
  168. package/dist/reporting/students/service.js.map +1 -0
  169. package/dist/shared/errors.d.ts +22 -0
  170. package/dist/shared/errors.js +34 -0
  171. package/dist/shared/errors.js.map +1 -0
  172. package/dist/shared/events.d.ts +31 -0
  173. package/dist/shared/events.js +75 -0
  174. package/dist/shared/events.js.map +1 -0
  175. package/dist/shared/id.d.ts +44 -0
  176. package/dist/shared/id.js +53 -0
  177. package/dist/shared/id.js.map +1 -0
  178. package/dist/shared/invite-token.d.ts +9 -0
  179. package/dist/shared/invite-token.js +16 -0
  180. package/dist/shared/invite-token.js.map +1 -0
  181. package/dist/shared/logger.d.ts +19 -0
  182. package/dist/shared/logger.js +44 -0
  183. package/dist/shared/logger.js.map +1 -0
  184. package/dist/shared/mailer.d.ts +23 -0
  185. package/dist/shared/mailer.js +27 -0
  186. package/dist/shared/mailer.js.map +1 -0
  187. package/dist/shared/ports.d.ts +62 -0
  188. package/dist/shared/ports.js +2 -0
  189. package/dist/shared/settings.d.ts +25 -0
  190. package/dist/shared/settings.js +21 -0
  191. package/dist/shared/settings.js.map +1 -0
  192. package/dist/types/assets.d.ts +2 -0
  193. package/dist/types/assets.js +1 -0
  194. package/dist/types/automations.d.ts +23 -0
  195. package/dist/types/automations.js +1 -0
  196. package/dist/types/content.d.ts +2 -0
  197. package/dist/types/content.js +1 -0
  198. package/dist/types/discussion.d.ts +36 -0
  199. package/dist/types/discussion.js +1 -0
  200. package/dist/types/email-templates.d.ts +57 -0
  201. package/dist/types/email-templates.js +1 -0
  202. package/dist/types/entitlements.d.ts +3 -0
  203. package/dist/types/entitlements.js +1 -0
  204. package/dist/types/events.d.ts +25 -0
  205. package/dist/types/events.js +1 -0
  206. package/dist/types/identity.d.ts +37 -0
  207. package/dist/types/identity.js +1 -0
  208. package/dist/types/index.d.ts +23 -0
  209. package/dist/types/index.js +1 -0
  210. package/dist/types/integrations.d.ts +45 -0
  211. package/dist/types/integrations.js +1 -0
  212. package/dist/types/organizations.d.ts +15 -0
  213. package/dist/types/organizations.js +1 -0
  214. package/dist/types/ports.d.ts +57 -0
  215. package/dist/types/ports.js +1 -0
  216. package/dist/types/progress.d.ts +6 -0
  217. package/dist/types/progress.js +1 -0
  218. package/dist/types/schemas/assets.d.ts +109 -0
  219. package/dist/types/schemas/assets.js +49 -0
  220. package/dist/types/schemas/assets.js.map +1 -0
  221. package/dist/types/schemas/automations.d.ts +118 -0
  222. package/dist/types/schemas/automations.js +73 -0
  223. package/dist/types/schemas/automations.js.map +1 -0
  224. package/dist/types/schemas/content.d.ts +216 -0
  225. package/dist/types/schemas/content.js +138 -0
  226. package/dist/types/schemas/content.js.map +1 -0
  227. package/dist/types/schemas/credentials.d.ts +15 -0
  228. package/dist/types/schemas/credentials.js +15 -0
  229. package/dist/types/schemas/credentials.js.map +1 -0
  230. package/dist/types/schemas/discussion.d.ts +192 -0
  231. package/dist/types/schemas/discussion.js +100 -0
  232. package/dist/types/schemas/discussion.js.map +1 -0
  233. package/dist/types/schemas/entitlements.d.ts +50 -0
  234. package/dist/types/schemas/entitlements.js +38 -0
  235. package/dist/types/schemas/entitlements.js.map +1 -0
  236. package/dist/types/schemas/identity.d.ts +37 -0
  237. package/dist/types/schemas/identity.js +34 -0
  238. package/dist/types/schemas/identity.js.map +1 -0
  239. package/dist/types/schemas/index.d.ts +14 -0
  240. package/dist/types/schemas/index.js +14 -0
  241. package/dist/types/schemas/integrations.d.ts +36 -0
  242. package/dist/types/schemas/integrations.js +30 -0
  243. package/dist/types/schemas/integrations.js.map +1 -0
  244. package/dist/types/schemas/organizations.d.ts +135 -0
  245. package/dist/types/schemas/organizations.js +90 -0
  246. package/dist/types/schemas/organizations.js.map +1 -0
  247. package/dist/types/schemas/outbox.d.ts +19 -0
  248. package/dist/types/schemas/outbox.js +18 -0
  249. package/dist/types/schemas/outbox.js.map +1 -0
  250. package/dist/types/schemas/progress.d.ts +53 -0
  251. package/dist/types/schemas/progress.js +37 -0
  252. package/dist/types/schemas/progress.js.map +1 -0
  253. package/dist/types/schemas/settings.d.ts +16 -0
  254. package/dist/types/schemas/settings.js +15 -0
  255. package/dist/types/schemas/settings.js.map +1 -0
  256. package/dist/types/schemas/shared.d.ts +13 -0
  257. package/dist/types/schemas/shared.js +28 -0
  258. package/dist/types/schemas/shared.js.map +1 -0
  259. package/dist/types/shared.d.ts +25 -0
  260. package/dist/types/shared.js +1 -0
  261. package/package.json +61 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 mdwt
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,81 @@
1
+ import { EventOf, EventOfValues, RuntimeEventDefinition, VersionedDomainEvent } from "../shared/events.js";
2
+ import "../shared/ports.js";
3
+ //#region src/assets/events.d.ts
4
+ declare const assetEvents: {
5
+ assetCreated: RuntimeEventDefinition<VersionedDomainEvent<{
6
+ orgId: string;
7
+ id: string;
8
+ key: string;
9
+ kind: "video" | "download" | "content";
10
+ filename: string;
11
+ contentType: string;
12
+ size: number;
13
+ status: "pending" | "ready";
14
+ uploadedBy: string;
15
+ createdAt: Date;
16
+ }, "asset.created", 1>, {
17
+ orgId: string;
18
+ id: string;
19
+ key: string;
20
+ kind: "video" | "download" | "content";
21
+ filename: string;
22
+ contentType: string;
23
+ size: number;
24
+ status: "pending" | "ready";
25
+ uploadedBy: string;
26
+ createdAt: unknown;
27
+ }>;
28
+ assetReady: RuntimeEventDefinition<VersionedDomainEvent<{
29
+ orgId: string;
30
+ id: string;
31
+ key: string;
32
+ kind: "video" | "download" | "content";
33
+ filename: string;
34
+ contentType: string;
35
+ size: number;
36
+ status: "pending" | "ready";
37
+ uploadedBy: string;
38
+ createdAt: Date;
39
+ }, "asset.ready", 1>, {
40
+ orgId: string;
41
+ id: string;
42
+ key: string;
43
+ kind: "video" | "download" | "content";
44
+ filename: string;
45
+ contentType: string;
46
+ size: number;
47
+ status: "pending" | "ready";
48
+ uploadedBy: string;
49
+ createdAt: unknown;
50
+ }>;
51
+ assetDeleted: RuntimeEventDefinition<VersionedDomainEvent<{
52
+ orgId: string;
53
+ id: string;
54
+ key: string;
55
+ kind: "video" | "download" | "content";
56
+ filename: string;
57
+ contentType: string;
58
+ size: number;
59
+ status: "pending" | "ready";
60
+ uploadedBy: string;
61
+ createdAt: Date;
62
+ }, "asset.deleted", 1>, {
63
+ orgId: string;
64
+ id: string;
65
+ key: string;
66
+ kind: "video" | "download" | "content";
67
+ filename: string;
68
+ contentType: string;
69
+ size: number;
70
+ status: "pending" | "ready";
71
+ uploadedBy: string;
72
+ createdAt: unknown;
73
+ }>;
74
+ };
75
+ type AssetCreated = EventOf<typeof assetEvents.assetCreated>;
76
+ type AssetReady = EventOf<typeof assetEvents.assetReady>;
77
+ type AssetDeleted = EventOf<typeof assetEvents.assetDeleted>;
78
+ type AssetEvent = EventOfValues<typeof assetEvents>;
79
+ //#endregion
80
+ export { AssetCreated, AssetDeleted, AssetEvent, AssetReady, assetEvents };
81
+ //# sourceMappingURL=events.d.ts.map
@@ -0,0 +1,24 @@
1
+ import { assetSchema } from "../types/schemas/assets.js";
2
+ import { defineEvent } from "../shared/events.js";
3
+ //#region src/assets/events.ts
4
+ const assetEvents = {
5
+ assetCreated: defineEvent({
6
+ type: "asset.created",
7
+ version: 1,
8
+ data: assetSchema
9
+ }),
10
+ assetReady: defineEvent({
11
+ type: "asset.ready",
12
+ version: 1,
13
+ data: assetSchema
14
+ }),
15
+ assetDeleted: defineEvent({
16
+ type: "asset.deleted",
17
+ version: 1,
18
+ data: assetSchema
19
+ })
20
+ };
21
+ //#endregion
22
+ export { assetEvents };
23
+
24
+ //# sourceMappingURL=events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.js","names":[],"sources":["../../src/assets/events.ts"],"sourcesContent":["import { assetSchema } from '../types/schemas/index.js';\nimport { defineEvent, type EventOf, type EventOfValues } from '../shared/ports.js';\n\nexport const assetEvents = {\n assetCreated: defineEvent({\n type: 'asset.created',\n version: 1,\n data: assetSchema,\n }),\n assetReady: defineEvent({\n type: 'asset.ready',\n version: 1,\n data: assetSchema,\n }),\n assetDeleted: defineEvent({\n type: 'asset.deleted',\n version: 1,\n data: assetSchema,\n }),\n};\n\nexport type AssetCreated = EventOf<typeof assetEvents.assetCreated>;\nexport type AssetReady = EventOf<typeof assetEvents.assetReady>;\nexport type AssetDeleted = EventOf<typeof assetEvents.assetDeleted>;\nexport type AssetEvent = EventOfValues<typeof assetEvents>;\n"],"mappings":";;;AAGA,MAAa,cAAc;CACzB,cAAc,YAAY;EACxB,MAAM;EACN,SAAS;EACT,MAAM;CACR,CAAC;CACD,YAAY,YAAY;EACtB,MAAM;EACN,SAAS;EACT,MAAM;CACR,CAAC;CACD,cAAc,YAAY;EACxB,MAAM;EACN,SAAS;EACT,MAAM;CACR,CAAC;AACH"}
@@ -0,0 +1,7 @@
1
+ import { Page } from "../types/shared.js";
2
+ import { Asset, AssetKind, AssetStatus, AssetsQuery, DownloadTicket, RequestUploadInput, UploadTicket } from "../types/schemas/assets.js";
3
+ import { AssetCreated, AssetDeleted, AssetEvent, AssetReady, assetEvents } from "./events.js";
4
+ import "./model.js";
5
+ import { AssetsRepository, AssetsService } from "./ports.js";
6
+ import { AssetsServiceImpl } from "./service.js";
7
+ export { type Asset, type AssetCreated, type AssetDeleted, type AssetEvent, type AssetKind, type AssetReady, type AssetStatus, type AssetsQuery, type AssetsRepository, type AssetsService, AssetsServiceImpl, type DownloadTicket, type Page, type RequestUploadInput, type UploadTicket, assetEvents };
@@ -0,0 +1,3 @@
1
+ import { assetEvents } from "./events.js";
2
+ import { AssetsServiceImpl } from "./service.js";
3
+ export { AssetsServiceImpl, assetEvents };
@@ -0,0 +1,4 @@
1
+ import { Page } from "../types/shared.js";
2
+ import { Asset, AssetKind, AssetStatus, AssetsQuery, DownloadTicket, RequestUploadInput, UploadTicket } from "../types/schemas/assets.js";
3
+ import "../types/index.js";
4
+ export type { Asset, AssetKind, AssetStatus, AssetsQuery, DownloadTicket, Page, RequestUploadInput, UploadTicket };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,34 @@
1
+ import { Page } from "../types/shared.js";
2
+ import { Asset, AssetsQuery, DownloadTicket, RequestUploadInput, UploadTicket } from "../types/schemas/assets.js";
3
+ import { OutboxAppender, UnitOfWork } from "../shared/ports.js";
4
+ import "./model.js";
5
+ //#region src/assets/ports.d.ts
6
+ interface AssetsService {
7
+ /** Register an asset and return a presigned URL to upload its bytes. */
8
+ requestUpload(orgId: string, input: RequestUploadInput): Promise<UploadTicket>;
9
+ /** After upload, capture size/content-type from storage and mark ready. */
10
+ confirm(orgId: string, id: string): Promise<Asset | null>;
11
+ list(orgId: string, query: AssetsQuery): Promise<Page<Asset>>;
12
+ get(orgId: string, id: string): Promise<Asset | null>;
13
+ /** Short-lived presigned URL to download/serve the asset. `expiresInSeconds`
14
+ * overrides the adapter default — entitled delivery passes its own. */
15
+ requestDownload(orgId: string, id: string, filename?: string, expiresInSeconds?: number): Promise<DownloadTicket | null>;
16
+ /** Remove the object from storage and the registry. */
17
+ remove(orgId: string, id: string): Promise<boolean>;
18
+ }
19
+ interface AssetsRepository {
20
+ insert(orgId: string, asset: Asset): Promise<Asset>;
21
+ list(orgId: string, query: AssetsQuery): Promise<Page<Asset>>;
22
+ /** Scoped to the org — returns null if the asset belongs to another org. */
23
+ findById(orgId: string, id: string): Promise<Asset | null>;
24
+ update(id: string, patch: Partial<Pick<Asset, 'size' | 'contentType' | 'status'>>): Promise<Asset | null>;
25
+ delete(id: string): Promise<boolean>;
26
+ }
27
+ interface AssetsWriteScope {
28
+ assets: AssetsRepository;
29
+ outbox: OutboxAppender;
30
+ }
31
+ type AssetsUnitOfWork = UnitOfWork<AssetsWriteScope>;
32
+ //#endregion
33
+ export { AssetsRepository, AssetsService, AssetsUnitOfWork, AssetsWriteScope };
34
+ //# sourceMappingURL=ports.d.ts.map
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,29 @@
1
+ import { Page } from "../types/shared.js";
2
+ import { Logger, ObjectStorage } from "../types/ports.js";
3
+ import { Asset, AssetsQuery, DownloadTicket, RequestUploadInput, UploadTicket } from "../types/schemas/assets.js";
4
+ import "../shared/ports.js";
5
+ import "./model.js";
6
+ import { AssetsRepository, AssetsService, AssetsUnitOfWork } from "./ports.js";
7
+ //#region src/assets/service.d.ts
8
+ type AssetsServiceParams = {
9
+ storage: ObjectStorage;
10
+ repo: AssetsRepository;
11
+ uow: AssetsUnitOfWork;
12
+ logger?: Logger;
13
+ };
14
+ declare class AssetsServiceImpl implements AssetsService {
15
+ private readonly storage;
16
+ private readonly repo;
17
+ private readonly uow;
18
+ private readonly logger;
19
+ constructor(params: AssetsServiceParams);
20
+ requestUpload(orgId: string, input: RequestUploadInput): Promise<UploadTicket>;
21
+ confirm(orgId: string, id: string): Promise<Asset | null>;
22
+ list(orgId: string, query: AssetsQuery): Promise<Page<Asset>>;
23
+ get(orgId: string, id: string): Promise<Asset | null>;
24
+ requestDownload(orgId: string, id: string, filename?: string, expiresInSeconds?: number): Promise<DownloadTicket | null>;
25
+ remove(orgId: string, id: string): Promise<boolean>;
26
+ }
27
+ //#endregion
28
+ export { AssetsServiceImpl, AssetsServiceParams };
29
+ //# sourceMappingURL=service.d.ts.map
@@ -0,0 +1,130 @@
1
+ import { assetEvents } from "./events.js";
2
+ import { genId } from "../shared/id.js";
3
+ import { noopLogger } from "../shared/logger.js";
4
+ //#region src/assets/service.ts
5
+ function orgPrefix(orgId) {
6
+ return `org/${orgId}/`;
7
+ }
8
+ function sanitizeFilename(filename) {
9
+ return (filename.split(/[\\/]/).pop() ?? "file").replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 120) || "file";
10
+ }
11
+ var AssetsServiceImpl = class {
12
+ storage;
13
+ repo;
14
+ uow;
15
+ logger;
16
+ constructor(params) {
17
+ this.storage = params.storage;
18
+ this.repo = params.repo;
19
+ this.uow = params.uow;
20
+ this.logger = params.logger ?? noopLogger;
21
+ }
22
+ async requestUpload(orgId, input) {
23
+ const id = genId("asset");
24
+ const key = `${orgPrefix(orgId)}${input.kind}/${id}/${sanitizeFilename(input.filename)}`;
25
+ const presigned = await this.storage.presignUpload({
26
+ key,
27
+ contentType: input.contentType
28
+ });
29
+ const asset = await this.uow.run(async ({ assets, outbox }) => {
30
+ const asset = await assets.insert(orgId, {
31
+ orgId,
32
+ id,
33
+ key,
34
+ kind: input.kind,
35
+ filename: input.filename,
36
+ contentType: input.contentType,
37
+ size: 0,
38
+ status: "pending",
39
+ uploadedBy: input.uploadedBy,
40
+ createdAt: /* @__PURE__ */ new Date()
41
+ });
42
+ await outbox.append([assetEvents.assetCreated.make({
43
+ orgId,
44
+ data: asset
45
+ })]);
46
+ return asset;
47
+ });
48
+ this.logger.info("asset upload requested", {
49
+ orgId,
50
+ assetId: id,
51
+ kind: input.kind
52
+ });
53
+ return {
54
+ asset,
55
+ uploadUrl: presigned.url,
56
+ method: "PUT",
57
+ expiresInSeconds: presigned.expiresInSeconds,
58
+ headers: presigned.headers
59
+ };
60
+ }
61
+ async confirm(orgId, id) {
62
+ const asset = await this.repo.findById(orgId, id);
63
+ if (!asset) return null;
64
+ const stat = await this.storage.stat(asset.key);
65
+ if (!stat) {
66
+ this.logger.debug("asset not yet uploaded", {
67
+ orgId,
68
+ assetId: id
69
+ });
70
+ return asset;
71
+ }
72
+ const updated = await this.uow.run(async ({ assets, outbox }) => {
73
+ const updated = await assets.update(id, {
74
+ size: stat.size,
75
+ contentType: stat.contentType ?? asset.contentType,
76
+ status: "ready"
77
+ });
78
+ if (updated) await outbox.append([assetEvents.assetReady.make({
79
+ orgId,
80
+ data: updated
81
+ })]);
82
+ return updated;
83
+ });
84
+ this.logger.info("asset confirmed", {
85
+ orgId,
86
+ assetId: id
87
+ });
88
+ return updated;
89
+ }
90
+ list(orgId, query) {
91
+ return this.repo.list(orgId, query);
92
+ }
93
+ get(orgId, id) {
94
+ return this.repo.findById(orgId, id);
95
+ }
96
+ async requestDownload(orgId, id, filename, expiresInSeconds) {
97
+ const asset = await this.repo.findById(orgId, id);
98
+ if (!asset) return null;
99
+ return {
100
+ url: await this.storage.presignDownload({
101
+ key: asset.key,
102
+ downloadFilename: filename ?? asset.filename,
103
+ ...expiresInSeconds === void 0 ? {} : { expiresInSeconds }
104
+ }),
105
+ asset
106
+ };
107
+ }
108
+ async remove(orgId, id) {
109
+ const asset = await this.repo.findById(orgId, id);
110
+ if (!asset) return false;
111
+ await this.storage.remove(asset.key);
112
+ const deleted = await this.uow.run(async ({ assets, outbox }) => {
113
+ const deleted = await assets.delete(id);
114
+ if (deleted) await outbox.append([assetEvents.assetDeleted.make({
115
+ orgId,
116
+ data: asset
117
+ })]);
118
+ return deleted;
119
+ });
120
+ this.logger.info("asset removed", {
121
+ orgId,
122
+ assetId: id
123
+ });
124
+ return deleted;
125
+ }
126
+ };
127
+ //#endregion
128
+ export { AssetsServiceImpl };
129
+
130
+ //# sourceMappingURL=service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.js","names":[],"sources":["../../src/assets/service.ts"],"sourcesContent":["// assets context — service. Owns org-scoped key construction and the asset\n// lifecycle (register → upload → confirm → serve → remove); delegates the\n// actual object operations to the ObjectStorage port. Org isolation is enforced\n// by scoping every lookup to the caller's org (cross-org reads return null).\nimport { genId } from '../shared/id.js';\nimport type { Logger, ObjectStorage } from '../shared/ports.js';\nimport type {\n Asset,\n AssetsQuery,\n DownloadTicket,\n Page,\n RequestUploadInput,\n UploadTicket,\n} from './model.js';\nimport type { AssetsRepository, AssetsService, AssetsUnitOfWork } from './ports.js';\nimport { noopLogger } from '../shared/logger.js';\nimport { assetEvents } from './events.js';\n\nfunction orgPrefix(orgId: string): string {\n return `org/${orgId}/`;\n}\n\nfunction sanitizeFilename(filename: string): string {\n const base = filename.split(/[\\\\/]/).pop() ?? 'file';\n return base.replace(/[^a-zA-Z0-9._-]/g, '_').slice(0, 120) || 'file';\n}\n\nexport type AssetsServiceParams = {\n storage: ObjectStorage;\n repo: AssetsRepository;\n uow: AssetsUnitOfWork;\n logger?: Logger;\n};\n\nexport class AssetsServiceImpl implements AssetsService {\n private readonly storage: ObjectStorage;\n private readonly repo: AssetsRepository;\n private readonly uow: AssetsUnitOfWork;\n private readonly logger: Logger;\n\n constructor(params: AssetsServiceParams) {\n this.storage = params.storage;\n this.repo = params.repo;\n this.uow = params.uow;\n this.logger = params.logger ?? noopLogger;\n }\n\n async requestUpload(orgId: string, input: RequestUploadInput): Promise<UploadTicket> {\n const id = genId('asset');\n const key = `${orgPrefix(orgId)}${input.kind}/${id}/${sanitizeFilename(input.filename)}`;\n const presigned = await this.storage.presignUpload({ key, contentType: input.contentType });\n const asset = await this.uow.run(async ({ assets, outbox }) => {\n const asset = await assets.insert(orgId, {\n orgId,\n id,\n key,\n kind: input.kind,\n filename: input.filename,\n contentType: input.contentType,\n size: 0,\n status: 'pending',\n uploadedBy: input.uploadedBy,\n createdAt: new Date(),\n });\n await outbox.append([assetEvents.assetCreated.make({ orgId, data: asset })]);\n return asset;\n });\n this.logger.info('asset upload requested', { orgId, assetId: id, kind: input.kind });\n return {\n asset,\n uploadUrl: presigned.url,\n method: 'PUT',\n expiresInSeconds: presigned.expiresInSeconds,\n headers: presigned.headers,\n };\n }\n\n async confirm(orgId: string, id: string): Promise<Asset | null> {\n const asset = await this.repo.findById(orgId, id);\n if (!asset) {\n return null;\n }\n const stat = await this.storage.stat(asset.key);\n if (!stat) {\n this.logger.debug('asset not yet uploaded', { orgId, assetId: id });\n return asset; // not uploaded yet — stays pending\n }\n const updated = await this.uow.run(async ({ assets, outbox }) => {\n const updated = await assets.update(id, {\n size: stat.size,\n contentType: stat.contentType ?? asset.contentType,\n status: 'ready',\n });\n if (updated) {\n await outbox.append([assetEvents.assetReady.make({ orgId, data: updated })]);\n }\n return updated;\n });\n this.logger.info('asset confirmed', { orgId, assetId: id });\n return updated;\n }\n\n list(orgId: string, query: AssetsQuery): Promise<Page<Asset>> {\n return this.repo.list(orgId, query);\n }\n\n get(orgId: string, id: string): Promise<Asset | null> {\n return this.repo.findById(orgId, id);\n }\n\n async requestDownload(\n orgId: string,\n id: string,\n filename?: string,\n expiresInSeconds?: number,\n ): Promise<DownloadTicket | null> {\n const asset = await this.repo.findById(orgId, id);\n if (!asset) {\n return null;\n }\n const url = await this.storage.presignDownload({\n key: asset.key,\n downloadFilename: filename ?? asset.filename,\n ...(expiresInSeconds === undefined ? {} : { expiresInSeconds }),\n });\n return { url, asset };\n }\n\n async remove(orgId: string, id: string): Promise<boolean> {\n const asset = await this.repo.findById(orgId, id);\n if (!asset) {\n return false;\n }\n await this.storage.remove(asset.key);\n const deleted = await this.uow.run(async ({ assets, outbox }) => {\n const deleted = await assets.delete(id);\n if (deleted) {\n await outbox.append([assetEvents.assetDeleted.make({ orgId, data: asset })]);\n }\n return deleted;\n });\n this.logger.info('asset removed', { orgId, assetId: id });\n return deleted;\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import { DomainEvent } from "../types/shared.js";
2
+ import { EmailTemplateId, EmailTemplateParams } from "../types/email-templates.js";
3
+ import { AutomationAction } from "../types/schemas/automations.js";
4
+ import "../types/index.js";
5
+ import "../shared/ports.js";
6
+ import { Mailer, MailerLookups } from "../shared/mailer.js";
7
+ import "./model.js";
8
+ //#region src/automations/actions.d.ts
9
+ interface SendEmailDerivation<K extends EmailTemplateId> {
10
+ /** The only event type this template's params can be derived from. */
11
+ trigger: string;
12
+ /** Undefined = the recipient or content behind the event's ids cannot be resolved. */
13
+ derive(event: DomainEvent, lookups: MailerLookups): Promise<{
14
+ to: string;
15
+ params: EmailTemplateParams[K];
16
+ } | undefined>;
17
+ }
18
+ type SendEmailDerivations = { [K in EmailTemplateId]?: SendEmailDerivation<K>; };
19
+ declare const SEND_EMAIL_DERIVATIONS: SendEmailDerivations;
20
+ /** Throws on any failure — the engine owns retry policy and failure bookkeeping. */
21
+ declare function executeAction(action: AutomationAction, event: DomainEvent, mailer: Pick<Mailer, 'send'>, lookups: MailerLookups): Promise<void>;
22
+ //#endregion
23
+ export { SEND_EMAIL_DERIVATIONS, executeAction };
24
+ //# sourceMappingURL=actions.d.ts.map
@@ -0,0 +1,59 @@
1
+ import { entitlementEvents } from "../entitlements/events.js";
2
+ import { ALL_EMAIL_TEMPLATE_IDS } from "./catalog.js";
3
+ //#region src/automations/actions.ts
4
+ const SEND_EMAIL_DERIVATIONS = {
5
+ accessGranted: {
6
+ trigger: "entitlement.created",
7
+ derive: async (event, lookups) => {
8
+ const result = entitlementEvents.entitlementCreated.safeParse(event);
9
+ if (!result.success) return;
10
+ const entitlement = result.data.data;
11
+ const [to, content] = await Promise.all([lookups.orgUserEmail(entitlement.orgId, entitlement.orgUserId), lookups.contentInfo(entitlement.orgId, entitlement.contentId)]);
12
+ if (!to || !content) return;
13
+ return {
14
+ to,
15
+ params: {
16
+ contentTitle: content.title,
17
+ contentId: content.id
18
+ }
19
+ };
20
+ }
21
+ },
22
+ accessRevoked: {
23
+ trigger: "entitlement.deleted",
24
+ derive: async (event, lookups) => {
25
+ const result = entitlementEvents.entitlementDeleted.safeParse(event);
26
+ if (!result.success) return;
27
+ const entitlement = result.data.data;
28
+ const [to, content] = await Promise.all([lookups.orgUserEmail(entitlement.orgId, entitlement.orgUserId), lookups.contentInfo(entitlement.orgId, entitlement.contentId)]);
29
+ if (!to || !content) return;
30
+ return {
31
+ to,
32
+ params: { contentTitle: content.title }
33
+ };
34
+ }
35
+ }
36
+ };
37
+ function isEmailTemplateId(value) {
38
+ return typeof value === "string" && value in ALL_EMAIL_TEMPLATE_IDS;
39
+ }
40
+ /** Throws on any failure — the engine owns retry policy and failure bookkeeping. */
41
+ async function executeAction(action, event, mailer, lookups) {
42
+ switch (action.type) {
43
+ case "sendEmail": {
44
+ const template = action.input["template"];
45
+ if (!isEmailTemplateId(template)) throw new Error(`sendEmail: unknown template "${String(template)}"`);
46
+ const derivation = SEND_EMAIL_DERIVATIONS[template];
47
+ if (!derivation || derivation.trigger !== event.type) throw new Error(`sendEmail: template "${template}" cannot be derived from event "${event.type}"`);
48
+ const derived = await derivation.derive(event, lookups);
49
+ if (!derived) throw new Error(`sendEmail: event "${event.type}" is missing the data required to derive template "${template}"`);
50
+ await mailer.send(derived.to, template, derived.params);
51
+ return;
52
+ }
53
+ default: throw new Error(`unknown automation action type "${action.type}"`);
54
+ }
55
+ }
56
+ //#endregion
57
+ export { SEND_EMAIL_DERIVATIONS, executeAction };
58
+
59
+ //# sourceMappingURL=actions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actions.js","names":[],"sources":["../../src/automations/actions.ts"],"sourcesContent":["// automations context — action runners. `executeAction` maps one\n// AutomationAction against the DomainEvent that triggered its run.\n//\n// SEND_EMAIL_DERIVATIONS is the sendEmail runner's internal derivation table:\n// which (trigger, template) pairings are derivable. A template with no entry\n// (e.g. courseCompleted) makes `executeAction` throw a named error, recorded\n// by the engine as a failed action.\nimport type { EmailTemplateId, EmailTemplateParams } from '../types/index.js';\nimport { entitlementEvents } from '../entitlements/index.js';\nimport type { Mailer, MailerLookups } from '../shared/mailer.js';\nimport type { DomainEvent } from '../shared/ports.js';\nimport type { AutomationAction } from './model.js';\nimport { ALL_EMAIL_TEMPLATE_IDS } from './catalog.js';\n\ninterface SendEmailDerivation<K extends EmailTemplateId> {\n /** The only event type this template's params can be derived from. */\n trigger: string;\n /** Undefined = the recipient or content behind the event's ids cannot be resolved. */\n derive(\n event: DomainEvent,\n lookups: MailerLookups,\n ): Promise<{ to: string; params: EmailTemplateParams[K] } | undefined>;\n}\n\ntype SendEmailDerivations = { [K in EmailTemplateId]?: SendEmailDerivation<K> };\n\nexport const SEND_EMAIL_DERIVATIONS: SendEmailDerivations = {\n accessGranted: {\n trigger: 'entitlement.created',\n derive: async (event, lookups) => {\n const result = entitlementEvents.entitlementCreated.safeParse(event);\n if (!result.success) {\n return undefined;\n }\n const entitlement = result.data.data;\n const [to, content] = await Promise.all([\n lookups.orgUserEmail(entitlement.orgId, entitlement.orgUserId),\n lookups.contentInfo(entitlement.orgId, entitlement.contentId),\n ]);\n if (!to || !content) {\n return undefined;\n }\n return {\n to,\n params: { contentTitle: content.title, contentId: content.id },\n };\n },\n },\n accessRevoked: {\n trigger: 'entitlement.deleted',\n derive: async (event, lookups) => {\n const result = entitlementEvents.entitlementDeleted.safeParse(event);\n if (!result.success) {\n return undefined;\n }\n const entitlement = result.data.data;\n const [to, content] = await Promise.all([\n lookups.orgUserEmail(entitlement.orgId, entitlement.orgUserId),\n lookups.contentInfo(entitlement.orgId, entitlement.contentId),\n ]);\n if (!to || !content) {\n return undefined;\n }\n return {\n to,\n params: { contentTitle: content.title },\n };\n },\n },\n};\n\nfunction isEmailTemplateId(value: unknown): value is EmailTemplateId {\n return typeof value === 'string' && value in ALL_EMAIL_TEMPLATE_IDS;\n}\n\n/** Throws on any failure — the engine owns retry policy and failure bookkeeping. */\nexport async function executeAction(\n action: AutomationAction,\n event: DomainEvent,\n mailer: Pick<Mailer, 'send'>,\n lookups: MailerLookups,\n): Promise<void> {\n switch (action.type) {\n case 'sendEmail': {\n const template = action.input['template'];\n if (!isEmailTemplateId(template)) {\n throw new Error(`sendEmail: unknown template \"${String(template)}\"`);\n }\n const derivation = SEND_EMAIL_DERIVATIONS[template];\n if (!derivation || derivation.trigger !== event.type) {\n throw new Error(\n `sendEmail: template \"${template}\" cannot be derived from event \"${event.type}\"`,\n );\n }\n const derived = await derivation.derive(event, lookups);\n if (!derived) {\n throw new Error(\n `sendEmail: event \"${event.type}\" is missing the data required to derive template \"${template}\"`,\n );\n }\n await mailer.send(derived.to, template, derived.params);\n return;\n }\n default: {\n throw new Error(`unknown automation action type \"${action.type}\"`);\n }\n }\n}\n"],"mappings":";;;AA0BA,MAAa,yBAA+C;CAC1D,eAAe;EACb,SAAS;EACT,QAAQ,OAAO,OAAO,YAAY;GAChC,MAAM,SAAS,kBAAkB,mBAAmB,UAAU,KAAK;GACnE,IAAI,CAAC,OAAO,SACV;GAEF,MAAM,cAAc,OAAO,KAAK;GAChC,MAAM,CAAC,IAAI,WAAW,MAAM,QAAQ,IAAI,CACtC,QAAQ,aAAa,YAAY,OAAO,YAAY,SAAS,GAC7D,QAAQ,YAAY,YAAY,OAAO,YAAY,SAAS,CAC9D,CAAC;GACD,IAAI,CAAC,MAAM,CAAC,SACV;GAEF,OAAO;IACL;IACA,QAAQ;KAAE,cAAc,QAAQ;KAAO,WAAW,QAAQ;IAAG;GAC/D;EACF;CACF;CACA,eAAe;EACb,SAAS;EACT,QAAQ,OAAO,OAAO,YAAY;GAChC,MAAM,SAAS,kBAAkB,mBAAmB,UAAU,KAAK;GACnE,IAAI,CAAC,OAAO,SACV;GAEF,MAAM,cAAc,OAAO,KAAK;GAChC,MAAM,CAAC,IAAI,WAAW,MAAM,QAAQ,IAAI,CACtC,QAAQ,aAAa,YAAY,OAAO,YAAY,SAAS,GAC7D,QAAQ,YAAY,YAAY,OAAO,YAAY,SAAS,CAC9D,CAAC;GACD,IAAI,CAAC,MAAM,CAAC,SACV;GAEF,OAAO;IACL;IACA,QAAQ,EAAE,cAAc,QAAQ,MAAM;GACxC;EACF;CACF;AACF;AAEA,SAAS,kBAAkB,OAA0C;CACnE,OAAO,OAAO,UAAU,YAAY,SAAS;AAC/C;;AAGA,eAAsB,cACpB,QACA,OACA,QACA,SACe;CACf,QAAQ,OAAO,MAAf;EACE,KAAK,aAAa;GAChB,MAAM,WAAW,OAAO,MAAM;GAC9B,IAAI,CAAC,kBAAkB,QAAQ,GAC7B,MAAM,IAAI,MAAM,gCAAgC,OAAO,QAAQ,EAAE,EAAE;GAErE,MAAM,aAAa,uBAAuB;GAC1C,IAAI,CAAC,cAAc,WAAW,YAAY,MAAM,MAC9C,MAAM,IAAI,MACR,wBAAwB,SAAS,kCAAkC,MAAM,KAAK,EAChF;GAEF,MAAM,UAAU,MAAM,WAAW,OAAO,OAAO,OAAO;GACtD,IAAI,CAAC,SACH,MAAM,IAAI,MACR,qBAAqB,MAAM,KAAK,qDAAqD,SAAS,EAChG;GAEF,MAAM,OAAO,KAAK,QAAQ,IAAI,UAAU,QAAQ,MAAM;GACtD;EACF;EACA,SACE,MAAM,IAAI,MAAM,mCAAmC,OAAO,KAAK,EAAE;CAErE;AACF"}
@@ -0,0 +1,12 @@
1
+ import { EmailTemplateId } from "../types/email-templates.js";
2
+ import { AvailableActions, AvailableTriggers } from "../types/schemas/automations.js";
3
+ import "../types/index.js";
4
+ import "./types.js";
5
+ //#region src/automations/catalog.d.ts
6
+ /** Every EmailTemplateId — a missing key here is a compile error, kept exhaustive by construction. */
7
+ declare const ALL_EMAIL_TEMPLATE_IDS: Record<EmailTemplateId, true>;
8
+ declare function catalogActions(): AvailableActions;
9
+ declare function catalogTriggers(): AvailableTriggers['triggers'];
10
+ //#endregion
11
+ export { ALL_EMAIL_TEMPLATE_IDS, catalogActions, catalogTriggers };
12
+ //# sourceMappingURL=catalog.d.ts.map