@kelpie/server 0.5.2 → 0.7.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 (406) hide show
  1. package/README.md +5 -2
  2. package/dist/app.d.ts +13 -0
  3. package/dist/app.d.ts.map +1 -1
  4. package/dist/app.js +9 -0
  5. package/dist/app.js.map +1 -1
  6. package/dist/boot.d.ts +42 -0
  7. package/dist/boot.d.ts.map +1 -0
  8. package/dist/boot.js +57 -0
  9. package/dist/boot.js.map +1 -0
  10. package/dist/index.d.ts +20 -8
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +13 -4
  13. package/dist/index.js.map +1 -1
  14. package/dist/lib/actor.d.ts +9 -1
  15. package/dist/lib/actor.d.ts.map +1 -1
  16. package/dist/lib/actor.js +13 -0
  17. package/dist/lib/actor.js.map +1 -1
  18. package/dist/lib/config.d.ts +51 -6
  19. package/dist/lib/config.d.ts.map +1 -1
  20. package/dist/lib/config.js +11 -5
  21. package/dist/lib/config.js.map +1 -1
  22. package/dist/lib/email.d.ts +15 -63
  23. package/dist/lib/email.d.ts.map +1 -1
  24. package/dist/lib/email.js +13 -65
  25. package/dist/lib/email.js.map +1 -1
  26. package/dist/lib/fromEnv.d.ts +60 -0
  27. package/dist/lib/fromEnv.d.ts.map +1 -0
  28. package/dist/lib/fromEnv.js +94 -0
  29. package/dist/lib/fromEnv.js.map +1 -0
  30. package/dist/lib/ids.d.ts +6 -0
  31. package/dist/lib/ids.d.ts.map +1 -1
  32. package/dist/lib/ids.js +6 -0
  33. package/dist/lib/ids.js.map +1 -1
  34. package/dist/lib/kelpieConfigFile.d.ts +129 -0
  35. package/dist/lib/kelpieConfigFile.d.ts.map +1 -0
  36. package/dist/lib/kelpieConfigFile.js +112 -0
  37. package/dist/lib/kelpieConfigFile.js.map +1 -0
  38. package/dist/lib/logger.d.ts +38 -7
  39. package/dist/lib/logger.d.ts.map +1 -1
  40. package/dist/lib/logger.js +58 -20
  41. package/dist/lib/logger.js.map +1 -1
  42. package/dist/modules/agent-tasks/index.d.ts.map +1 -1
  43. package/dist/modules/agent-tasks/index.js +4 -1
  44. package/dist/modules/agent-tasks/index.js.map +1 -1
  45. package/dist/modules/agent-tasks/prompt.d.ts +36 -6
  46. package/dist/modules/agent-tasks/prompt.d.ts.map +1 -1
  47. package/dist/modules/agent-tasks/prompt.js +33 -4
  48. package/dist/modules/agent-tasks/prompt.js.map +1 -1
  49. package/dist/modules/agent-tasks/service.d.ts.map +1 -1
  50. package/dist/modules/agent-tasks/service.js +3 -2
  51. package/dist/modules/agent-tasks/service.js.map +1 -1
  52. package/dist/modules/agent-tasks/wire.d.ts +11 -0
  53. package/dist/modules/agent-tasks/wire.d.ts.map +1 -1
  54. package/dist/modules/agent-tasks/wire.js +1 -0
  55. package/dist/modules/agent-tasks/wire.js.map +1 -1
  56. package/dist/modules/auth/index.d.ts.map +1 -1
  57. package/dist/modules/auth/index.js +8 -7
  58. package/dist/modules/auth/index.js.map +1 -1
  59. package/dist/modules/companies/events.d.ts +22 -0
  60. package/dist/modules/companies/events.d.ts.map +1 -0
  61. package/dist/modules/companies/events.js +8 -0
  62. package/dist/modules/companies/events.js.map +1 -0
  63. package/dist/modules/companies/index.d.ts.map +1 -1
  64. package/dist/modules/companies/index.js +2 -0
  65. package/dist/modules/companies/index.js.map +1 -1
  66. package/dist/modules/companies/service.d.ts +1 -0
  67. package/dist/modules/companies/service.d.ts.map +1 -1
  68. package/dist/modules/companies/service.js +8 -11
  69. package/dist/modules/companies/service.js.map +1 -1
  70. package/dist/modules/core.d.ts.map +1 -1
  71. package/dist/modules/core.js +4 -0
  72. package/dist/modules/core.js.map +1 -1
  73. package/dist/modules/deals/events.d.ts +31 -0
  74. package/dist/modules/deals/events.d.ts.map +1 -0
  75. package/dist/modules/deals/events.js +12 -0
  76. package/dist/modules/deals/events.js.map +1 -0
  77. package/dist/modules/deals/index.d.ts.map +1 -1
  78. package/dist/modules/deals/index.js +2 -0
  79. package/dist/modules/deals/index.js.map +1 -1
  80. package/dist/modules/deals/service.d.ts +1 -0
  81. package/dist/modules/deals/service.d.ts.map +1 -1
  82. package/dist/modules/deals/service.js +9 -18
  83. package/dist/modules/deals/service.js.map +1 -1
  84. package/dist/modules/decisions/events.d.ts +21 -0
  85. package/dist/modules/decisions/events.d.ts.map +1 -0
  86. package/dist/modules/decisions/events.js +11 -0
  87. package/dist/modules/decisions/events.js.map +1 -0
  88. package/dist/modules/decisions/index.d.ts.map +1 -1
  89. package/dist/modules/decisions/index.js +2 -0
  90. package/dist/modules/decisions/index.js.map +1 -1
  91. package/dist/modules/decisions/service.d.ts +1 -0
  92. package/dist/modules/decisions/service.d.ts.map +1 -1
  93. package/dist/modules/decisions/service.js +4 -7
  94. package/dist/modules/decisions/service.js.map +1 -1
  95. package/dist/modules/eventCatalogs.d.ts +35 -0
  96. package/dist/modules/eventCatalogs.d.ts.map +1 -0
  97. package/dist/modules/eventCatalogs.js +35 -0
  98. package/dist/modules/eventCatalogs.js.map +1 -0
  99. package/dist/modules/forms/events.d.ts +37 -0
  100. package/dist/modules/forms/events.d.ts.map +1 -0
  101. package/dist/modules/forms/events.js +15 -0
  102. package/dist/modules/forms/events.js.map +1 -0
  103. package/dist/modules/forms/index.d.ts.map +1 -1
  104. package/dist/modules/forms/index.js +2 -0
  105. package/dist/modules/forms/index.js.map +1 -1
  106. package/dist/modules/forms/service.d.ts +1 -0
  107. package/dist/modules/forms/service.d.ts.map +1 -1
  108. package/dist/modules/forms/service.js +8 -11
  109. package/dist/modules/forms/service.js.map +1 -1
  110. package/dist/modules/forms/submission.d.ts +5 -0
  111. package/dist/modules/forms/submission.d.ts.map +1 -1
  112. package/dist/modules/forms/submission.js +54 -28
  113. package/dist/modules/forms/submission.js.map +1 -1
  114. package/dist/modules/handbook/events.d.ts +28 -0
  115. package/dist/modules/handbook/events.d.ts.map +1 -0
  116. package/dist/modules/handbook/events.js +14 -0
  117. package/dist/modules/handbook/events.js.map +1 -0
  118. package/dist/modules/handbook/index.d.ts.map +1 -1
  119. package/dist/modules/handbook/index.js +2 -0
  120. package/dist/modules/handbook/index.js.map +1 -1
  121. package/dist/modules/handbook/service.d.ts +1 -0
  122. package/dist/modules/handbook/service.d.ts.map +1 -1
  123. package/dist/modules/handbook/service.js +8 -15
  124. package/dist/modules/handbook/service.js.map +1 -1
  125. package/dist/modules/hiring/candidates.d.ts +1 -0
  126. package/dist/modules/hiring/candidates.d.ts.map +1 -1
  127. package/dist/modules/hiring/candidates.js +8 -11
  128. package/dist/modules/hiring/candidates.js.map +1 -1
  129. package/dist/modules/hiring/events.d.ts +35 -0
  130. package/dist/modules/hiring/events.d.ts.map +1 -0
  131. package/dist/modules/hiring/events.js +11 -0
  132. package/dist/modules/hiring/events.js.map +1 -0
  133. package/dist/modules/hiring/index.d.ts.map +1 -1
  134. package/dist/modules/hiring/index.js +2 -0
  135. package/dist/modules/hiring/index.js.map +1 -1
  136. package/dist/modules/hiring/roles.d.ts +1 -0
  137. package/dist/modules/hiring/roles.d.ts.map +1 -1
  138. package/dist/modules/hiring/roles.js +8 -11
  139. package/dist/modules/hiring/roles.js.map +1 -1
  140. package/dist/modules/import-export/drafts.d.ts +9 -0
  141. package/dist/modules/import-export/drafts.d.ts.map +1 -1
  142. package/dist/modules/import-export/drafts.js +25 -0
  143. package/dist/modules/import-export/drafts.js.map +1 -1
  144. package/dist/modules/import-export/events.d.ts +22 -0
  145. package/dist/modules/import-export/events.d.ts.map +1 -0
  146. package/dist/modules/import-export/events.js +12 -0
  147. package/dist/modules/import-export/events.js.map +1 -0
  148. package/dist/modules/import-export/exportRows.d.ts +9 -0
  149. package/dist/modules/import-export/exportRows.d.ts.map +1 -1
  150. package/dist/modules/import-export/exportRows.js +10 -1
  151. package/dist/modules/import-export/exportRows.js.map +1 -1
  152. package/dist/modules/import-export/index.d.ts.map +1 -1
  153. package/dist/modules/import-export/index.js +2 -0
  154. package/dist/modules/import-export/index.js.map +1 -1
  155. package/dist/modules/import-export/plan.d.ts +23 -1
  156. package/dist/modules/import-export/plan.d.ts.map +1 -1
  157. package/dist/modules/import-export/plan.js +62 -3
  158. package/dist/modules/import-export/plan.js.map +1 -1
  159. package/dist/modules/import-export/presets.d.ts.map +1 -1
  160. package/dist/modules/import-export/presets.js +25 -0
  161. package/dist/modules/import-export/presets.js.map +1 -1
  162. package/dist/modules/import-export/repository.d.ts +17 -0
  163. package/dist/modules/import-export/repository.d.ts.map +1 -1
  164. package/dist/modules/import-export/repository.js +21 -1
  165. package/dist/modules/import-export/repository.js.map +1 -1
  166. package/dist/modules/import-export/routes.d.ts.map +1 -1
  167. package/dist/modules/import-export/routes.js +5 -1
  168. package/dist/modules/import-export/routes.js.map +1 -1
  169. package/dist/modules/import-export/schema.d.ts +55 -0
  170. package/dist/modules/import-export/schema.d.ts.map +1 -1
  171. package/dist/modules/import-export/schema.js +13 -1
  172. package/dist/modules/import-export/schema.js.map +1 -1
  173. package/dist/modules/import-export/service.d.ts +9 -1
  174. package/dist/modules/import-export/service.d.ts.map +1 -1
  175. package/dist/modules/import-export/service.js +104 -17
  176. package/dist/modules/import-export/service.js.map +1 -1
  177. package/dist/modules/import-export/tools.d.ts.map +1 -1
  178. package/dist/modules/import-export/tools.js +7 -1
  179. package/dist/modules/import-export/tools.js.map +1 -1
  180. package/dist/modules/import-export/writes.d.ts +16 -0
  181. package/dist/modules/import-export/writes.d.ts.map +1 -1
  182. package/dist/modules/import-export/writes.js +77 -2
  183. package/dist/modules/import-export/writes.js.map +1 -1
  184. package/dist/modules/notes/events.d.ts +22 -0
  185. package/dist/modules/notes/events.d.ts.map +1 -0
  186. package/dist/modules/notes/events.js +12 -0
  187. package/dist/modules/notes/events.js.map +1 -0
  188. package/dist/modules/notes/index.d.ts.map +1 -1
  189. package/dist/modules/notes/index.js +2 -0
  190. package/dist/modules/notes/index.js.map +1 -1
  191. package/dist/modules/notes/service.d.ts +1 -0
  192. package/dist/modules/notes/service.d.ts.map +1 -1
  193. package/dist/modules/notes/service.js +4 -7
  194. package/dist/modules/notes/service.js.map +1 -1
  195. package/dist/modules/opportunities/events.d.ts +31 -0
  196. package/dist/modules/opportunities/events.d.ts.map +1 -0
  197. package/dist/modules/opportunities/events.js +12 -0
  198. package/dist/modules/opportunities/events.js.map +1 -0
  199. package/dist/modules/opportunities/index.d.ts.map +1 -1
  200. package/dist/modules/opportunities/index.js +2 -0
  201. package/dist/modules/opportunities/index.js.map +1 -1
  202. package/dist/modules/opportunities/service.d.ts +1 -0
  203. package/dist/modules/opportunities/service.d.ts.map +1 -1
  204. package/dist/modules/opportunities/service.js +9 -18
  205. package/dist/modules/opportunities/service.js.map +1 -1
  206. package/dist/modules/partnerships/events.d.ts +31 -0
  207. package/dist/modules/partnerships/events.d.ts.map +1 -0
  208. package/dist/modules/partnerships/events.js +12 -0
  209. package/dist/modules/partnerships/events.js.map +1 -0
  210. package/dist/modules/partnerships/index.d.ts.map +1 -1
  211. package/dist/modules/partnerships/index.js +2 -0
  212. package/dist/modules/partnerships/index.js.map +1 -1
  213. package/dist/modules/partnerships/service.d.ts +1 -0
  214. package/dist/modules/partnerships/service.d.ts.map +1 -1
  215. package/dist/modules/partnerships/service.js +9 -18
  216. package/dist/modules/partnerships/service.js.map +1 -1
  217. package/dist/modules/people/events.d.ts +28 -0
  218. package/dist/modules/people/events.d.ts.map +1 -0
  219. package/dist/modules/people/events.js +18 -0
  220. package/dist/modules/people/events.js.map +1 -0
  221. package/dist/modules/people/index.d.ts.map +1 -1
  222. package/dist/modules/people/index.js +2 -0
  223. package/dist/modules/people/index.js.map +1 -1
  224. package/dist/modules/people/service.d.ts +1 -0
  225. package/dist/modules/people/service.d.ts.map +1 -1
  226. package/dist/modules/people/service.js +8 -11
  227. package/dist/modules/people/service.js.map +1 -1
  228. package/dist/modules/pipelines/service.d.ts +4 -0
  229. package/dist/modules/pipelines/service.d.ts.map +1 -1
  230. package/dist/modules/pipelines/service.js +31 -8
  231. package/dist/modules/pipelines/service.js.map +1 -1
  232. package/dist/modules/plans/events.d.ts +21 -0
  233. package/dist/modules/plans/events.d.ts.map +1 -0
  234. package/dist/modules/plans/events.js +11 -0
  235. package/dist/modules/plans/events.js.map +1 -0
  236. package/dist/modules/plans/index.d.ts.map +1 -1
  237. package/dist/modules/plans/index.js +2 -0
  238. package/dist/modules/plans/index.js.map +1 -1
  239. package/dist/modules/plans/service.d.ts +1 -0
  240. package/dist/modules/plans/service.d.ts.map +1 -1
  241. package/dist/modules/plans/service.js +6 -14
  242. package/dist/modules/plans/service.js.map +1 -1
  243. package/dist/modules/positions/events.d.ts +22 -0
  244. package/dist/modules/positions/events.d.ts.map +1 -0
  245. package/dist/modules/positions/events.js +8 -0
  246. package/dist/modules/positions/events.js.map +1 -0
  247. package/dist/modules/positions/index.d.ts.map +1 -1
  248. package/dist/modules/positions/index.js +2 -0
  249. package/dist/modules/positions/index.js.map +1 -1
  250. package/dist/modules/positions/service.d.ts +1 -0
  251. package/dist/modules/positions/service.d.ts.map +1 -1
  252. package/dist/modules/positions/service.js +8 -11
  253. package/dist/modules/positions/service.js.map +1 -1
  254. package/dist/modules/raises/events.d.ts +31 -0
  255. package/dist/modules/raises/events.d.ts.map +1 -0
  256. package/dist/modules/raises/events.js +12 -0
  257. package/dist/modules/raises/events.js.map +1 -0
  258. package/dist/modules/raises/index.d.ts.map +1 -1
  259. package/dist/modules/raises/index.js +2 -0
  260. package/dist/modules/raises/index.js.map +1 -1
  261. package/dist/modules/raises/service.d.ts +1 -0
  262. package/dist/modules/raises/service.d.ts.map +1 -1
  263. package/dist/modules/raises/service.js +9 -18
  264. package/dist/modules/raises/service.js.map +1 -1
  265. package/dist/modules/reseal.d.ts +55 -0
  266. package/dist/modules/reseal.d.ts.map +1 -1
  267. package/dist/modules/reseal.js +90 -1
  268. package/dist/modules/reseal.js.map +1 -1
  269. package/dist/modules/smtp-email/index.d.ts +69 -0
  270. package/dist/modules/smtp-email/index.d.ts.map +1 -0
  271. package/dist/modules/smtp-email/index.js +82 -0
  272. package/dist/modules/smtp-email/index.js.map +1 -0
  273. package/dist/modules/webhooks/index.d.ts.map +1 -1
  274. package/dist/modules/webhooks/index.js +5 -1
  275. package/dist/modules/webhooks/index.js.map +1 -1
  276. package/dist/modules/webhooks/payloads.d.ts +22 -7
  277. package/dist/modules/webhooks/payloads.d.ts.map +1 -1
  278. package/dist/modules/webhooks/payloads.js +63 -44
  279. package/dist/modules/webhooks/payloads.js.map +1 -1
  280. package/dist/modules/workspace/events.d.ts +52 -0
  281. package/dist/modules/workspace/events.d.ts.map +1 -0
  282. package/dist/modules/workspace/events.js +16 -0
  283. package/dist/modules/workspace/events.js.map +1 -0
  284. package/dist/modules/workspace/index.d.ts.map +1 -1
  285. package/dist/modules/workspace/index.js +6 -2
  286. package/dist/modules/workspace/index.js.map +1 -1
  287. package/dist/modules/workspace/service.d.ts +1 -0
  288. package/dist/modules/workspace/service.d.ts.map +1 -1
  289. package/dist/modules/workspace/service.js +13 -15
  290. package/dist/modules/workspace/service.js.map +1 -1
  291. package/dist/runtime/events.d.ts +86 -108
  292. package/dist/runtime/events.d.ts.map +1 -1
  293. package/dist/runtime/events.js +123 -52
  294. package/dist/runtime/events.js.map +1 -1
  295. package/dist/runtime/module.d.ts +54 -2
  296. package/dist/runtime/module.d.ts.map +1 -1
  297. package/dist/runtime/registry.d.ts +23 -0
  298. package/dist/runtime/registry.d.ts.map +1 -1
  299. package/dist/runtime/registry.js +102 -2
  300. package/dist/runtime/registry.js.map +1 -1
  301. package/dist/runtime/transaction.d.ts +29 -4
  302. package/dist/runtime/transaction.d.ts.map +1 -1
  303. package/dist/runtime/transaction.js +48 -7
  304. package/dist/runtime/transaction.js.map +1 -1
  305. package/dist/testing/app.d.ts +5 -1
  306. package/dist/testing/app.d.ts.map +1 -1
  307. package/dist/testing/app.js +13 -3
  308. package/dist/testing/app.js.map +1 -1
  309. package/dist/testing/database.d.ts.map +1 -1
  310. package/dist/testing/database.js +6 -3
  311. package/dist/testing/database.js.map +1 -1
  312. package/dist/testing/services.d.ts +21 -1
  313. package/dist/testing/services.d.ts.map +1 -1
  314. package/dist/testing/services.js +20 -5
  315. package/dist/testing/services.js.map +1 -1
  316. package/migrations/0019_sharp_red_skull.sql +4 -0
  317. package/migrations/0020_early_nemesis.sql +2 -0
  318. package/migrations/meta/0019_snapshot.json +5138 -0
  319. package/migrations/meta/0020_snapshot.json +5138 -0
  320. package/migrations/meta/_journal.json +14 -0
  321. package/package.json +5 -2
  322. package/src/app.ts +26 -0
  323. package/src/boot.ts +89 -0
  324. package/src/index.ts +52 -9
  325. package/src/lib/actor.ts +15 -1
  326. package/src/lib/config.ts +65 -7
  327. package/src/lib/email.ts +22 -92
  328. package/src/lib/fromEnv.ts +143 -0
  329. package/src/lib/ids.ts +6 -0
  330. package/src/lib/kelpieConfigFile.ts +291 -0
  331. package/src/lib/logger.ts +92 -34
  332. package/src/modules/agent-tasks/index.ts +4 -1
  333. package/src/modules/agent-tasks/prompt.ts +57 -9
  334. package/src/modules/agent-tasks/service.ts +3 -2
  335. package/src/modules/agent-tasks/wire.ts +12 -0
  336. package/src/modules/auth/index.ts +11 -9
  337. package/src/modules/companies/events.ts +25 -0
  338. package/src/modules/companies/index.ts +2 -0
  339. package/src/modules/companies/service.ts +8 -11
  340. package/src/modules/core.ts +4 -0
  341. package/src/modules/deals/events.ts +34 -0
  342. package/src/modules/deals/index.ts +2 -0
  343. package/src/modules/deals/service.ts +17 -18
  344. package/src/modules/decisions/events.ts +24 -0
  345. package/src/modules/decisions/index.ts +2 -0
  346. package/src/modules/decisions/service.ts +8 -7
  347. package/src/modules/eventCatalogs.ts +35 -0
  348. package/src/modules/forms/events.ts +37 -0
  349. package/src/modules/forms/index.ts +2 -0
  350. package/src/modules/forms/service.ts +12 -11
  351. package/src/modules/forms/submission.ts +80 -29
  352. package/src/modules/handbook/events.ts +31 -0
  353. package/src/modules/handbook/index.ts +2 -0
  354. package/src/modules/handbook/service.ts +8 -15
  355. package/src/modules/hiring/candidates.ts +8 -11
  356. package/src/modules/hiring/events.ts +36 -0
  357. package/src/modules/hiring/index.ts +2 -0
  358. package/src/modules/hiring/roles.ts +8 -11
  359. package/src/modules/import-export/drafts.ts +27 -0
  360. package/src/modules/import-export/events.ts +25 -0
  361. package/src/modules/import-export/exportRows.ts +10 -1
  362. package/src/modules/import-export/index.ts +2 -0
  363. package/src/modules/import-export/plan.ts +107 -5
  364. package/src/modules/import-export/presets.ts +25 -0
  365. package/src/modules/import-export/repository.ts +35 -1
  366. package/src/modules/import-export/routes.ts +5 -0
  367. package/src/modules/import-export/schema.ts +13 -0
  368. package/src/modules/import-export/service.ts +153 -19
  369. package/src/modules/import-export/tools.ts +9 -0
  370. package/src/modules/import-export/writes.ts +116 -3
  371. package/src/modules/notes/events.ts +25 -0
  372. package/src/modules/notes/index.ts +2 -0
  373. package/src/modules/notes/service.ts +8 -7
  374. package/src/modules/opportunities/events.ts +34 -0
  375. package/src/modules/opportunities/index.ts +2 -0
  376. package/src/modules/opportunities/service.ts +17 -18
  377. package/src/modules/partnerships/events.ts +34 -0
  378. package/src/modules/partnerships/index.ts +2 -0
  379. package/src/modules/partnerships/service.ts +17 -18
  380. package/src/modules/people/events.ts +36 -0
  381. package/src/modules/people/index.ts +2 -0
  382. package/src/modules/people/service.ts +97 -87
  383. package/src/modules/pipelines/service.ts +47 -8
  384. package/src/modules/plans/events.ts +24 -0
  385. package/src/modules/plans/index.ts +2 -0
  386. package/src/modules/plans/service.ts +14 -14
  387. package/src/modules/positions/events.ts +25 -0
  388. package/src/modules/positions/index.ts +2 -0
  389. package/src/modules/positions/service.ts +8 -11
  390. package/src/modules/raises/events.ts +34 -0
  391. package/src/modules/raises/index.ts +2 -0
  392. package/src/modules/raises/service.ts +17 -18
  393. package/src/modules/reseal.ts +144 -1
  394. package/src/modules/smtp-email/index.ts +119 -0
  395. package/src/modules/webhooks/index.ts +5 -1
  396. package/src/modules/webhooks/payloads.ts +94 -48
  397. package/src/modules/workspace/events.ts +46 -0
  398. package/src/modules/workspace/index.ts +6 -2
  399. package/src/modules/workspace/service.ts +37 -15
  400. package/src/runtime/events.ts +268 -107
  401. package/src/runtime/module.ts +55 -2
  402. package/src/runtime/registry.ts +169 -1
  403. package/src/runtime/transaction.ts +94 -17
  404. package/src/testing/app.ts +18 -4
  405. package/src/testing/database.ts +6 -3
  406. package/src/testing/services.ts +34 -5
@@ -1,7 +1,11 @@
1
1
  import { eq } from 'drizzle-orm'
2
2
 
3
+ import { ConfigurationError } from '../lib/config.ts'
4
+ import type { KelpieConfig } from '../lib/config.ts'
5
+ import { connectDatabase } from '../lib/database.ts'
3
6
  import type { Database } from '../lib/database.ts'
4
- import { SecretDecryptionError } from '../lib/secrets.ts'
7
+ import type { Logger } from '../lib/logger.ts'
8
+ import { createSecretCipher, secretEncryptionConfigSchema, SecretDecryptionError } from '../lib/secrets.ts'
5
9
  import type { SecretCipher } from '../lib/secrets.ts'
6
10
  import { agentRegistrations } from './agent-tasks/schema.ts'
7
11
  import { webhooks } from './webhooks/schema.ts'
@@ -164,3 +168,142 @@ export async function resealStoredSecrets(
164
168
  unreadable: columns.reduce((total, column) => total + column.unreadable.length, 0),
165
169
  }
166
170
  }
171
+
172
+ /**
173
+ * One reseal pass over some set of columns. Core's own pass is one; a module
174
+ * with its own sealed table exports one too, and the assembly wires it in as
175
+ * an `extraPasses` entry.
176
+ *
177
+ * The shape matches `resealStoredSecrets` on purpose, so a pass covering a
178
+ * single column returns a one-element `columns` and `runReseal` can print
179
+ * every pass the same way.
180
+ */
181
+ export type ResealPass = (db: Database, cipher: SecretCipher) => Promise<ResealOutcome>
182
+
183
+ export interface RunResealOptions {
184
+ readonly config: KelpieConfig
185
+ readonly logger: Logger
186
+ /**
187
+ * Extra passes to run after core's own. An assembly wires its modules'
188
+ * sealed columns in here; each pass covers columns core cannot see.
189
+ */
190
+ readonly extraPasses?: readonly ResealPass[]
191
+ /** Where the per-column report goes. Defaults to stdout. */
192
+ readonly report?: (message: string) => void
193
+ /** Where unreadable-row ids and the trailing failure summary go. Defaults to stderr. */
194
+ readonly reportFatal?: (message: string) => void
195
+ }
196
+
197
+ function defaultReport(message: string): void {
198
+ process.stdout.write(`${message}\n`)
199
+ }
200
+
201
+ function defaultReportFatal(message: string): void {
202
+ process.stderr.write(`${message}\n`)
203
+ }
204
+
205
+ /**
206
+ * Runs every reseal pass under the current `SECRET_ENCRYPTION_KEY` and
207
+ * prints a report. Returns the exit code a script should return: 0 if every
208
+ * examined value is now under the current key, 1 if any row was unreadable.
209
+ *
210
+ * The rotation procedure, in full:
211
+ *
212
+ * 1. Keep the current key. Add the new one as `SECRET_ENCRYPTION_KEY` and
213
+ * move the old value to `SECRET_ENCRYPTION_KEY_PREVIOUS`.
214
+ * 2. Deploy. Deliveries keep signing: new secrets seal under the new key,
215
+ * and existing ones still open under the previous one.
216
+ * 3. Run the reseal script. It rewrites every row still sealed under the
217
+ * old key.
218
+ * 4. Remove `SECRET_ENCRYPTION_KEY_PREVIOUS` and deploy again.
219
+ *
220
+ * Safe to run at any point, including with no previous key set, where it
221
+ * reports that everything is already current and writes nothing.
222
+ *
223
+ * Prefers `config.secretEncryption` (the top-level field an assembly's
224
+ * `kelpie.config.ts` declares). Falls back to parsing `config.env` for
225
+ * older assemblies and for `loadConfig`-based callers that never populate
226
+ * the top-level field. Either path validates before the pass opens a row,
227
+ * so a mistyped key throws `ConfigurationError` at boot rather than
228
+ * reporting every row as unreadable.
229
+ *
230
+ * @throws ConfigurationError if neither `config.secretEncryption` nor the
231
+ * fallback env parse produces a valid `SecretEncryptionConfig`. The
232
+ * database is closed before the throw.
233
+ */
234
+ export async function runReseal(options: RunResealOptions): Promise<number> {
235
+ const report = options.report ?? defaultReport
236
+ const reportFatal = options.reportFatal ?? defaultReportFatal
237
+ const database = connectDatabase(options.config.databaseUrl, options.logger)
238
+
239
+ let secretConfig = options.config.secretEncryption
240
+
241
+ if (secretConfig === undefined) {
242
+ const parsed = secretEncryptionConfigSchema.safeParse(options.config.env)
243
+
244
+ if (!parsed.success) {
245
+ await database.close()
246
+
247
+ throw new ConfigurationError(
248
+ parsed.error.issues.map((issue) => `${issue.path.join('.')}: ${issue.message}`),
249
+ )
250
+ }
251
+
252
+ secretConfig = parsed.data
253
+ }
254
+
255
+ const hasPrevious =
256
+ secretConfig.SECRET_ENCRYPTION_KEY_PREVIOUS !== undefined &&
257
+ secretConfig.SECRET_ENCRYPTION_KEY_PREVIOUS.trim().length > 0
258
+
259
+ if (!hasPrevious) {
260
+ report('SECRET_ENCRYPTION_KEY_PREVIOUS is not set. Nothing sealed under an older key can be read.')
261
+ }
262
+
263
+ const cipher = createSecretCipher(secretConfig)
264
+ const passes: readonly ResealPass[] = [resealStoredSecrets, ...(options.extraPasses ?? [])]
265
+
266
+ try {
267
+ // Sequential rather than `Promise.all`, so two full-table passes never
268
+ // contend for the pool at once. Each pass is idempotent; a run that
269
+ // dies halfway leaves the finished rows current and re-running
270
+ // completes the rest.
271
+ const outcomes: ResealOutcome[] = []
272
+ for (const pass of passes) {
273
+ outcomes.push(await pass(database.db, cipher))
274
+ }
275
+
276
+ const columns = outcomes.flatMap((outcome) => outcome.columns)
277
+ const examined = outcomes.reduce((total, outcome) => total + outcome.examined, 0)
278
+ const resealed = outcomes.reduce((total, outcome) => total + outcome.resealed, 0)
279
+ const unreadable = outcomes.reduce((total, outcome) => total + outcome.unreadable, 0)
280
+
281
+ for (const column of columns) {
282
+ report(
283
+ `${column.label}: ${String(column.examined)} examined, ${String(column.resealed)} re-sealed, ${String(column.unreadable.length)} unreadable`,
284
+ )
285
+
286
+ for (const id of column.unreadable) {
287
+ reportFatal(` unreadable: ${id}`)
288
+ }
289
+ }
290
+
291
+ if (unreadable > 0) {
292
+ reportFatal(
293
+ `\n${String(unreadable)} value(s) opened under neither key. They were sealed under a key that is not configured, or the rows have been altered. Nothing here can recover them: set SECRET_ENCRYPTION_KEY_PREVIOUS to the key they were sealed with and run this again, or have those records re-created.`,
294
+ )
295
+
296
+ return 1
297
+ }
298
+
299
+ report(
300
+ resealed === 0
301
+ ? `\nNothing to do: all ${String(examined)} value(s) are already sealed under the current key.`
302
+ : `\nRe-sealed ${String(resealed)} of ${String(examined)} value(s). You can now remove SECRET_ENCRYPTION_KEY_PREVIOUS.`,
303
+ )
304
+
305
+ return 0
306
+ } finally {
307
+ await database.close()
308
+ }
309
+ }
@@ -0,0 +1,119 @@
1
+ import nodemailer from 'nodemailer'
2
+ import { z } from 'zod'
3
+
4
+ import { describeThrown } from '../../lib/errors.ts'
5
+ import type { EmailMessage, EmailSender } from '../../lib/email.ts'
6
+ import type { Logger } from '../../lib/logger.ts'
7
+ import type { KelpieModule } from '../../runtime/module.ts'
8
+
9
+ /**
10
+ * The built-in SMTP email module.
11
+ *
12
+ * Registers a provider named `'smtp'` with core's email runtime. An assembly
13
+ * that sets `email.provider: 'smtp'` in its `kelpie.config.ts` uses this
14
+ * sender; any other value picks a different provider (`'log'` for the built-in
15
+ * or another provider module's name). Reads `EMAIL_FROM` and `SMTP_*` from the
16
+ * environment; missing or malformed values fail boot.
17
+ *
18
+ * A commercial email integration (Resend, Postmark) belongs in its own module
19
+ * following the same shape, registered under a different name.
20
+ */
21
+
22
+ /**
23
+ * The name this module registers under. An assembly puts `provider: 'smtp'`
24
+ * in its `email` block to pick it.
25
+ */
26
+ export const SMTP_EMAIL_PROVIDER = 'smtp'
27
+
28
+ export type { EmailMessage } from '../../lib/email.ts'
29
+
30
+ const smtpEmailConfigSchema = z.object({
31
+ EMAIL_FROM: z.string().min(1),
32
+ SMTP_HOST: z.string().min(1),
33
+ SMTP_PORT: z.coerce.number().int().positive().max(65535),
34
+ SMTP_SECURE: z.enum(['true', 'false']).transform((value) => value === 'true'),
35
+ SMTP_USER: z.string().min(1),
36
+ SMTP_PASSWORD: z.string().min(1),
37
+ })
38
+
39
+ export type SmtpEmailConfig = z.infer<typeof smtpEmailConfigSchema>
40
+
41
+ /**
42
+ * What `createSmtpEmailSender` sends a message through. A real deployment gets
43
+ * one backed by `nodemailer`; a test injects one that records calls, so the
44
+ * sender is verifiable without a container running a real SMTP conversation.
45
+ */
46
+ export interface SmtpTransport {
47
+ sendMail(message: { from: string; to: string; subject: string; text: string }): Promise<unknown>
48
+ }
49
+
50
+ function createNodemailerTransport(config: SmtpEmailConfig): SmtpTransport {
51
+ return nodemailer.createTransport({
52
+ host: config.SMTP_HOST,
53
+ port: config.SMTP_PORT,
54
+ secure: config.SMTP_SECURE,
55
+ auth: { user: config.SMTP_USER, pass: config.SMTP_PASSWORD },
56
+ })
57
+ }
58
+
59
+ /** Sends over SMTP. `transport` defaults to a real connection built from `config`. */
60
+ export function createSmtpEmailSender(
61
+ config: SmtpEmailConfig,
62
+ logger: Logger,
63
+ transport: SmtpTransport = createNodemailerTransport(config),
64
+ ): EmailSender {
65
+ return {
66
+ async send(message: EmailMessage) {
67
+ try {
68
+ await transport.sendMail({
69
+ from: config.EMAIL_FROM,
70
+ to: message.to,
71
+ subject: message.subject,
72
+ text: message.body,
73
+ })
74
+ } catch (cause) {
75
+ const reason = describeThrown(cause)
76
+ logger.error('smtp send failed', { to: message.to, reason })
77
+ throw new Error(`Failed to send email to ${message.to} over SMTP: ${reason}`, { cause })
78
+ }
79
+ },
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Options a caller may pass when building the module. `transport` is here for
85
+ * tests; a real deployment omits it and lets the module open a nodemailer
86
+ * connection from the resolved config.
87
+ */
88
+ export interface SmtpEmailModuleOptions {
89
+ readonly transport?: SmtpTransport
90
+ }
91
+
92
+ /**
93
+ * The built-in SMTP module. Registers a factory under `SMTP_EMAIL_PROVIDER`;
94
+ * the factory reads the SMTP environment and builds the nodemailer sender, but
95
+ * only if the assembly's `email.provider` picks this module. Boot fails loudly
96
+ * if any SMTP variable is missing or malformed at that point.
97
+ *
98
+ * The module stays in `coreModules` unconditionally: on any other provider
99
+ * (e.g. `EMAIL_PROVIDER=log`) the factory never runs, so the SMTP environment
100
+ * is not checked.
101
+ */
102
+ export function createSmtpEmailModule(options: SmtpEmailModuleOptions = {}): KelpieModule {
103
+ return {
104
+ id: 'smtp-email',
105
+ structural: true,
106
+
107
+ register(context) {
108
+ context.provideEmailSender(SMTP_EMAIL_PROVIDER, () => {
109
+ const config = context.config(smtpEmailConfigSchema)
110
+
111
+ return options.transport === undefined
112
+ ? createSmtpEmailSender(config, context.log)
113
+ : createSmtpEmailSender(config, context.log, options.transport)
114
+ })
115
+
116
+ return Promise.resolve()
117
+ },
118
+ }
119
+ }
@@ -48,7 +48,11 @@ export function createWebhooksModule(
48
48
  // when a customer's endpoint goes quiet is far too late. The retention
49
49
  // window is validated the same way for the same reason — a bad value
50
50
  // should stop boot, not prune by the wrong window.
51
- const cipher = createSecretCipher(context.config(secretEncryptionConfigSchema))
51
+ //
52
+ // Prefer the top-level `secretEncryption` from the assembly's
53
+ // kelpie.config.ts; fall back to the schema read for older assemblies
54
+ // that don't declare it.
55
+ const cipher = createSecretCipher(context.secretEncryption ?? context.config(secretEncryptionConfigSchema))
52
56
  const retention = context.config(deliveryRetentionConfigSchema)
53
57
  const egress = createEgressGuard(context.config(egressConfigSchema))
54
58
 
@@ -1,13 +1,21 @@
1
- import type { WebhookEvent } from '@kelpie/schemas'
1
+ import type { KelpieEvent, WebhookEvent } from '@kelpie/schemas'
2
2
 
3
- import type { DomainEvents, EventBus } from '../../runtime/events.ts'
3
+ import type { EventBus } from '../../runtime/events.ts'
4
+ import { RECORD_OBJECT_TYPES } from '../../runtime/events.ts'
5
+ import type { RecordObjectType } from '../../runtime/events.ts'
4
6
 
5
7
  /**
6
8
  * The bridge from the internal event catalog to what a webhook receiver sees.
7
9
  *
8
10
  * `modules.md` makes the webhooks engine a consumer of the bus, so this is the
9
11
  * only place that knows both vocabularies. Everything downstream works with a
10
- * `WebhookEventPayload` and never sees a `DomainEvents` key.
12
+ * `WebhookEventPayload` and never sees an internal event key.
13
+ *
14
+ * A single prefix subscribe (`''`) sees every envelope event, and the
15
+ * translator below decides whether the event corresponds to a deliverable wire
16
+ * event. Unrecognised names are dropped, so a module's private events (a
17
+ * `.stage_changed` on a deal, a `.added` on a note) never reach a receiver
18
+ * that never subscribed to them.
11
19
  *
12
20
  * Payload keys are `snake_case`, matching `api.md`, because a receiver has no
13
21
  * reason to expect a different convention from a webhook than from the REST
@@ -24,61 +32,99 @@ export interface WebhookEventPayload {
24
32
  readonly data: Record<string, unknown>
25
33
  }
26
34
 
35
+ const recordObjectTypes = new Set<string>(RECORD_OBJECT_TYPES)
36
+
37
+ interface RecordUpdatedData {
38
+ readonly changed?: readonly string[]
39
+ }
40
+
41
+ interface FormSubmittedData {
42
+ readonly formId?: string
43
+ readonly submissionId?: string
44
+ }
45
+
27
46
  /**
28
- * Payload builders, one per deliverable event.
47
+ * Translates an envelope event into a `WebhookEventPayload`, or `undefined` if
48
+ * the event does not correspond to any deliverable wire event.
29
49
  *
30
- * `satisfies` is what keeps this honest: adding a name to `WEBHOOK_EVENTS`
31
- * without a builder here is a compile error rather than an event that resolves
32
- * to nothing at runtime.
50
+ * The last segment of the event name (`.created`, `.updated`, `.deleted`,
51
+ * `.submitted`) picks the wire event. A `.created`/`.updated`/`.deleted` event
52
+ * is only translated when its target type is one of the CRM record objects
53
+ * (`RECORD_OBJECT_TYPES`), because those are the only ones the wire catalog
54
+ * offers. Everything else is skipped.
33
55
  */
34
- const builders = {
35
- 'record.created': (payload: DomainEvents['record.created']): WebhookEventPayload => ({
36
- workspaceId: payload.workspaceId,
37
- event: 'record.created',
38
- data: { object_type: payload.objectType, record_id: payload.recordId },
39
- }),
40
-
41
- 'record.updated': (payload: DomainEvents['record.updated']): WebhookEventPayload => ({
42
- workspaceId: payload.workspaceId,
43
- event: 'record.updated',
44
- data: {
45
- object_type: payload.objectType,
46
- record_id: payload.recordId,
47
- // The values stay as the emitting service named them, which is camelCase
48
- // (`parentId`, `sortOrder`) even though the keys around them are not.
49
- // Translating them to the wire spelling would mean a second copy of every
50
- // module's column naming, kept in step by hand.
51
- changed_fields: [...payload.changedFields],
52
- },
53
- }),
56
+ export function translateEnvelopeEvent(
57
+ event: KelpieEvent<string, unknown>,
58
+ ): WebhookEventPayload | undefined {
59
+ const suffix = event.name.slice(event.name.lastIndexOf('.') + 1)
60
+ const objectType = event.target.type
61
+
62
+ if (suffix === 'created' || suffix === 'deleted') {
63
+ if (!recordObjectTypes.has(objectType)) {
64
+ return undefined
65
+ }
54
66
 
55
- 'record.deleted': (payload: DomainEvents['record.deleted']): WebhookEventPayload => ({
56
- workspaceId: payload.workspaceId,
57
- event: 'record.deleted',
58
- data: { object_type: payload.objectType, record_id: payload.recordId },
59
- }),
67
+ return {
68
+ workspaceId: event.workspaceId,
69
+ event: suffix === 'created' ? 'record.created' : 'record.deleted',
70
+ data: { object_type: objectType as RecordObjectType, record_id: event.target.id },
71
+ }
72
+ }
60
73
 
61
- 'form.submitted': (payload: DomainEvents['form.submitted']): WebhookEventPayload => ({
62
- workspaceId: payload.workspaceId,
63
- event: 'form.submitted',
64
- data: { form_id: payload.formId, submission_id: payload.submissionId },
65
- }),
66
- } satisfies { [Name in WebhookEvent]: (payload: DomainEvents[Name]) => WebhookEventPayload }
74
+ if (suffix === 'updated') {
75
+ if (!recordObjectTypes.has(objectType)) {
76
+ return undefined
77
+ }
78
+
79
+ const data = event.data as RecordUpdatedData
80
+ return {
81
+ workspaceId: event.workspaceId,
82
+ event: 'record.updated',
83
+ data: {
84
+ object_type: objectType as RecordObjectType,
85
+ record_id: event.target.id,
86
+ changed_fields: data.changed !== undefined ? [...data.changed] : [],
87
+ },
88
+ }
89
+ }
90
+
91
+ if (suffix === 'submitted') {
92
+ // Form submission is the only `.submitted` wire event today. The forms
93
+ // module emits its envelope with `formId` and `submissionId` in `data`.
94
+ const data = event.data as FormSubmittedData
95
+ if (data.formId === undefined || data.submissionId === undefined) {
96
+ return undefined
97
+ }
98
+
99
+ return {
100
+ workspaceId: event.workspaceId,
101
+ event: 'form.submitted',
102
+ data: { form_id: data.formId, submission_id: data.submissionId },
103
+ }
104
+ }
105
+
106
+ return undefined
107
+ }
67
108
 
68
109
  /**
69
- * Subscribes the engine to every deliverable event.
70
- *
71
- * Written out one call at a time rather than looped over `WEBHOOK_EVENTS`,
72
- * because TypeScript cannot correlate a payload type with a generic key: a loop
73
- * would need a cast, and the cast is exactly what would let a wrong payload
74
- * through. A literal key indexes `builders` at its precise signature.
110
+ * Subscribes the engine to every deliverable event through one prefix match on
111
+ * `''`, which every envelope event starts with. The translator inside decides
112
+ * whether the event corresponds to a deliverable wire event; unrecognised
113
+ * names are dropped.
75
114
  */
76
115
  export function subscribeDeliverableEvents(
77
116
  bus: EventBus,
78
117
  deliver: (payload: WebhookEventPayload) => Promise<void>,
79
118
  ): void {
80
- bus.subscribe('record.created', (payload) => deliver(builders['record.created'](payload)))
81
- bus.subscribe('record.updated', (payload) => deliver(builders['record.updated'](payload)))
82
- bus.subscribe('record.deleted', (payload) => deliver(builders['record.deleted'](payload)))
83
- bus.subscribe('form.submitted', (payload) => deliver(builders['form.submitted'](payload)))
119
+ bus.subscribePrefix(
120
+ '',
121
+ async (event) => {
122
+ const built = translateEnvelopeEvent(event)
123
+ if (built === undefined) {
124
+ return
125
+ }
126
+ await deliver(built)
127
+ },
128
+ { label: 'webhooks:envelope-bridge' },
129
+ )
84
130
  }
@@ -0,0 +1,46 @@
1
+ import { z } from 'zod'
2
+
3
+ import type { ModuleEventCatalog } from '../../runtime/module.ts'
4
+
5
+ /**
6
+ * Domain events published by the workspace module.
7
+ *
8
+ * Workspace-level events (`workspace.workspace.*`) target the workspace itself.
9
+ * Membership events target the `workspace_members` row, with the user id in
10
+ * `data` so a consumer that watches identity does not need a second lookup.
11
+ */
12
+
13
+ export const workspaceEvents = {
14
+ 'workspace.workspace.created': z.object({ slug: z.string() }),
15
+ 'workspace.workspace.deleted': z.object({ slug: z.string() }),
16
+ 'workspace.member.invited': z.object({ inviteId: z.string(), email: z.string() }),
17
+ 'workspace.member.joined': z.object({ userId: z.string() }),
18
+ 'workspace.member.removed': z.object({ userId: z.string() }),
19
+ } satisfies ModuleEventCatalog
20
+
21
+ export interface WorkspaceCreatedData {
22
+ readonly slug: string
23
+ }
24
+ export interface WorkspaceDeletedData {
25
+ readonly slug: string
26
+ }
27
+ export interface MemberInvitedData {
28
+ readonly inviteId: string
29
+ readonly email: string
30
+ }
31
+ export interface MemberJoinedData {
32
+ readonly userId: string
33
+ }
34
+ export interface MemberRemovedData {
35
+ readonly userId: string
36
+ }
37
+
38
+ declare module '../../runtime/events.ts' {
39
+ interface KelpieEventMap {
40
+ 'workspace.workspace.created': WorkspaceCreatedData
41
+ 'workspace.workspace.deleted': WorkspaceDeletedData
42
+ 'workspace.member.invited': MemberInvitedData
43
+ 'workspace.member.joined': MemberJoinedData
44
+ 'workspace.member.removed': MemberRemovedData
45
+ }
46
+ }
@@ -2,6 +2,7 @@ import { appUrlConfigSchema } from '../../lib/appUrl.ts'
2
2
  import type { KelpieModule } from '../../runtime/module.ts'
3
3
  import { parseModuleCapability } from '../../runtime/moduleConfig.ts'
4
4
  import { SEATS_LIMIT, WORKSPACE_ACCESS } from './capabilities.ts'
5
+ import { workspaceEvents } from './events.ts'
5
6
  import * as repository from './repository.ts'
6
7
  import { mountWorkspaceRoutes } from './routes.ts'
7
8
  import * as schema from './schema.ts'
@@ -19,9 +20,12 @@ export function createWorkspaceModule(migrationsDirectory: string): KelpieModule
19
20
  id: 'workspace',
20
21
  requires: ['auth'],
21
22
  structural: true,
23
+ events: workspaceEvents,
22
24
 
23
25
  register(context) {
24
- const config = context.config(appUrlConfigSchema)
26
+ // Prefer the top-level `appBaseUrl` from the assembly's kelpie.config.ts;
27
+ // fall back to the schema read for older assemblies that don't declare it.
28
+ const appBaseUrl = context.appBaseUrl ?? context.config(appUrlConfigSchema).APP_BASE_URL
25
29
 
26
30
  context.entitlements.declare(SEATS_LIMIT)
27
31
  context.entitlements.declare(WORKSPACE_ACCESS)
@@ -48,7 +52,7 @@ export function createWorkspaceModule(migrationsDirectory: string): KelpieModule
48
52
  createId: context.createId,
49
53
  now: context.now,
50
54
  entitlements: context.entitlements,
51
- appBaseUrl: config.APP_BASE_URL,
55
+ appBaseUrl,
52
56
  toggleableModuleIds: context.moduleCatalog
53
57
  .filter((entry) => !entry.structural)
54
58
  .map((entry) => entry.id),
@@ -9,7 +9,9 @@ import type { EntitlementRegistry } from '../../runtime/entitlements.ts'
9
9
  import { limitFor } from '../../runtime/entitlements.ts'
10
10
  import { moduleCapabilityName } from '../../runtime/moduleConfig.ts'
11
11
  import type { TransactionScope } from '../../runtime/transaction.ts'
12
+ import { toEventActor } from '../../lib/actor.ts'
12
13
  import type { Actor, SessionActor } from '../auth/actor.ts'
14
+ import './events.ts'
13
15
  import * as authRepository from '../auth/repository.ts'
14
16
  import { SEATS_LIMIT } from './capabilities.ts'
15
17
  import * as repository from './repository.ts'
@@ -460,11 +462,19 @@ export function createWorkspaceService(dependencies: WorkspaceDependencies): Wor
460
462
  // The session that created the workspace should land in it.
461
463
  await authRepository.setActiveWorkspace(tx, actor.sessionId, workspaceId)
462
464
 
463
- events.emit('workspace.created', { workspaceId, slug: workspace.slug })
464
- events.emit('member.joined', { workspaceId, memberId, userId: actor.userId })
465
+ events.emit(
466
+ 'workspace.workspace.created',
467
+ { type: 'workspace', id: workspaceId },
468
+ { slug: workspace.slug },
469
+ )
470
+ events.emit(
471
+ 'workspace.member.joined',
472
+ { type: 'member', id: memberId },
473
+ { userId: actor.userId },
474
+ )
465
475
 
466
476
  return toWorkspaceView(workspace)
467
- })
477
+ }, { workspaceId, actor: toEventActor(actor) })
468
478
  },
469
479
 
470
480
  async get(actor, workspaceId) {
@@ -526,8 +536,12 @@ export function createWorkspaceService(dependencies: WorkspaceDependencies): Wor
526
536
  await dependencies.transaction(async ({ tx, events }) => {
527
537
  await repository.deleteWorkspace(tx, workspaceId)
528
538
 
529
- events.emit('workspace.deleted', { workspaceId, slug: workspace.slug })
530
- })
539
+ events.emit(
540
+ 'workspace.workspace.deleted',
541
+ { type: 'workspace', id: workspaceId },
542
+ { slug: workspace.slug },
543
+ )
544
+ }, { workspaceId, actor: toEventActor(actor) })
531
545
  },
532
546
 
533
547
  async listMembers(actor, workspaceId) {
@@ -602,8 +616,12 @@ export function createWorkspaceService(dependencies: WorkspaceDependencies): Wor
602
616
  await dependencies.transaction(async ({ tx, events }) => {
603
617
  await repository.deleteMember(tx, target.id)
604
618
 
605
- events.emit('member.removed', { workspaceId, memberId: target.id, userId: target.userId })
606
- })
619
+ events.emit(
620
+ 'workspace.member.removed',
621
+ { type: 'member', id: target.id },
622
+ { userId: target.userId },
623
+ )
624
+ }, { workspaceId, actor: toEventActor(actor) })
607
625
  } catch (error: unknown) {
608
626
  // A record assigned to them between the count and this delete. The
609
627
  // constraint answers the same question the count did, so the caller gets
@@ -658,10 +676,14 @@ export function createWorkspaceService(dependencies: WorkspaceDependencies): Wor
658
676
  expiresAt: new Date(now.getTime() + INVITE_LIFETIME_MS),
659
677
  })
660
678
 
661
- events.emit('member.invited', { workspaceId, inviteId: created.id, email: created.email })
679
+ events.emit(
680
+ 'workspace.member.invited',
681
+ { type: 'invite', id: created.id },
682
+ { inviteId: created.id, email: created.email },
683
+ )
662
684
 
663
685
  return created
664
- })
686
+ }, { workspaceId, actor: toEventActor(actor) })
665
687
 
666
688
  // Sent after commit, so a rolled-back invite never reaches an inbox.
667
689
  await sendInviteEmail(invite.email, token)
@@ -790,14 +812,14 @@ export function createWorkspaceService(dependencies: WorkspaceDependencies): Wor
790
812
  throw AppError.notFound('Workspace not found')
791
813
  }
792
814
 
793
- events.emit('member.joined', {
794
- workspaceId: invite.workspaceId,
795
- memberId,
796
- userId: actor.userId,
797
- })
815
+ events.emit(
816
+ 'workspace.member.joined',
817
+ { type: 'member', id: memberId },
818
+ { userId: actor.userId },
819
+ )
798
820
 
799
821
  return toWorkspaceView(workspace)
800
- })
822
+ }, { workspaceId: invite.workspaceId, actor: toEventActor(actor) })
801
823
  },
802
824
 
803
825
  async listModuleSettings(actor, workspaceId) {