@kelpie/server 0.5.2 → 0.6.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 (343) hide show
  1. package/README.md +5 -2
  2. package/dist/index.d.ts +17 -8
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +8 -4
  5. package/dist/index.js.map +1 -1
  6. package/dist/lib/actor.d.ts +9 -1
  7. package/dist/lib/actor.d.ts.map +1 -1
  8. package/dist/lib/actor.js +13 -0
  9. package/dist/lib/actor.js.map +1 -1
  10. package/dist/lib/config.d.ts +44 -6
  11. package/dist/lib/config.d.ts.map +1 -1
  12. package/dist/lib/config.js +9 -5
  13. package/dist/lib/config.js.map +1 -1
  14. package/dist/lib/email.d.ts +15 -63
  15. package/dist/lib/email.d.ts.map +1 -1
  16. package/dist/lib/email.js +13 -65
  17. package/dist/lib/email.js.map +1 -1
  18. package/dist/lib/fromEnv.d.ts +60 -0
  19. package/dist/lib/fromEnv.d.ts.map +1 -0
  20. package/dist/lib/fromEnv.js +94 -0
  21. package/dist/lib/fromEnv.js.map +1 -0
  22. package/dist/lib/ids.d.ts +6 -0
  23. package/dist/lib/ids.d.ts.map +1 -1
  24. package/dist/lib/ids.js +6 -0
  25. package/dist/lib/ids.js.map +1 -1
  26. package/dist/lib/kelpieConfigFile.d.ts +123 -0
  27. package/dist/lib/kelpieConfigFile.d.ts.map +1 -0
  28. package/dist/lib/kelpieConfigFile.js +111 -0
  29. package/dist/lib/kelpieConfigFile.js.map +1 -0
  30. package/dist/lib/logger.d.ts +38 -7
  31. package/dist/lib/logger.d.ts.map +1 -1
  32. package/dist/lib/logger.js +58 -20
  33. package/dist/lib/logger.js.map +1 -1
  34. package/dist/modules/agent-tasks/index.d.ts.map +1 -1
  35. package/dist/modules/agent-tasks/index.js +4 -1
  36. package/dist/modules/agent-tasks/index.js.map +1 -1
  37. package/dist/modules/agent-tasks/prompt.d.ts +36 -6
  38. package/dist/modules/agent-tasks/prompt.d.ts.map +1 -1
  39. package/dist/modules/agent-tasks/prompt.js +33 -4
  40. package/dist/modules/agent-tasks/prompt.js.map +1 -1
  41. package/dist/modules/agent-tasks/service.d.ts.map +1 -1
  42. package/dist/modules/agent-tasks/service.js +3 -2
  43. package/dist/modules/agent-tasks/service.js.map +1 -1
  44. package/dist/modules/agent-tasks/wire.d.ts +11 -0
  45. package/dist/modules/agent-tasks/wire.d.ts.map +1 -1
  46. package/dist/modules/agent-tasks/wire.js +1 -0
  47. package/dist/modules/agent-tasks/wire.js.map +1 -1
  48. package/dist/modules/auth/index.d.ts.map +1 -1
  49. package/dist/modules/auth/index.js +8 -7
  50. package/dist/modules/auth/index.js.map +1 -1
  51. package/dist/modules/companies/events.d.ts +22 -0
  52. package/dist/modules/companies/events.d.ts.map +1 -0
  53. package/dist/modules/companies/events.js +8 -0
  54. package/dist/modules/companies/events.js.map +1 -0
  55. package/dist/modules/companies/index.d.ts.map +1 -1
  56. package/dist/modules/companies/index.js +2 -0
  57. package/dist/modules/companies/index.js.map +1 -1
  58. package/dist/modules/companies/service.d.ts +1 -0
  59. package/dist/modules/companies/service.d.ts.map +1 -1
  60. package/dist/modules/companies/service.js +8 -11
  61. package/dist/modules/companies/service.js.map +1 -1
  62. package/dist/modules/core.d.ts.map +1 -1
  63. package/dist/modules/core.js +4 -0
  64. package/dist/modules/core.js.map +1 -1
  65. package/dist/modules/deals/events.d.ts +31 -0
  66. package/dist/modules/deals/events.d.ts.map +1 -0
  67. package/dist/modules/deals/events.js +12 -0
  68. package/dist/modules/deals/events.js.map +1 -0
  69. package/dist/modules/deals/index.d.ts.map +1 -1
  70. package/dist/modules/deals/index.js +2 -0
  71. package/dist/modules/deals/index.js.map +1 -1
  72. package/dist/modules/deals/service.d.ts +1 -0
  73. package/dist/modules/deals/service.d.ts.map +1 -1
  74. package/dist/modules/deals/service.js +9 -18
  75. package/dist/modules/deals/service.js.map +1 -1
  76. package/dist/modules/decisions/events.d.ts +21 -0
  77. package/dist/modules/decisions/events.d.ts.map +1 -0
  78. package/dist/modules/decisions/events.js +11 -0
  79. package/dist/modules/decisions/events.js.map +1 -0
  80. package/dist/modules/decisions/index.d.ts.map +1 -1
  81. package/dist/modules/decisions/index.js +2 -0
  82. package/dist/modules/decisions/index.js.map +1 -1
  83. package/dist/modules/decisions/service.d.ts +1 -0
  84. package/dist/modules/decisions/service.d.ts.map +1 -1
  85. package/dist/modules/decisions/service.js +4 -7
  86. package/dist/modules/decisions/service.js.map +1 -1
  87. package/dist/modules/forms/events.d.ts +37 -0
  88. package/dist/modules/forms/events.d.ts.map +1 -0
  89. package/dist/modules/forms/events.js +15 -0
  90. package/dist/modules/forms/events.js.map +1 -0
  91. package/dist/modules/forms/index.d.ts.map +1 -1
  92. package/dist/modules/forms/index.js +2 -0
  93. package/dist/modules/forms/index.js.map +1 -1
  94. package/dist/modules/forms/service.d.ts +1 -0
  95. package/dist/modules/forms/service.d.ts.map +1 -1
  96. package/dist/modules/forms/service.js +8 -11
  97. package/dist/modules/forms/service.js.map +1 -1
  98. package/dist/modules/forms/submission.d.ts +5 -0
  99. package/dist/modules/forms/submission.d.ts.map +1 -1
  100. package/dist/modules/forms/submission.js +54 -28
  101. package/dist/modules/forms/submission.js.map +1 -1
  102. package/dist/modules/handbook/events.d.ts +28 -0
  103. package/dist/modules/handbook/events.d.ts.map +1 -0
  104. package/dist/modules/handbook/events.js +14 -0
  105. package/dist/modules/handbook/events.js.map +1 -0
  106. package/dist/modules/handbook/index.d.ts.map +1 -1
  107. package/dist/modules/handbook/index.js +2 -0
  108. package/dist/modules/handbook/index.js.map +1 -1
  109. package/dist/modules/handbook/service.d.ts +1 -0
  110. package/dist/modules/handbook/service.d.ts.map +1 -1
  111. package/dist/modules/handbook/service.js +8 -15
  112. package/dist/modules/handbook/service.js.map +1 -1
  113. package/dist/modules/hiring/candidates.d.ts +1 -0
  114. package/dist/modules/hiring/candidates.d.ts.map +1 -1
  115. package/dist/modules/hiring/candidates.js +8 -11
  116. package/dist/modules/hiring/candidates.js.map +1 -1
  117. package/dist/modules/hiring/events.d.ts +35 -0
  118. package/dist/modules/hiring/events.d.ts.map +1 -0
  119. package/dist/modules/hiring/events.js +11 -0
  120. package/dist/modules/hiring/events.js.map +1 -0
  121. package/dist/modules/hiring/index.d.ts.map +1 -1
  122. package/dist/modules/hiring/index.js +2 -0
  123. package/dist/modules/hiring/index.js.map +1 -1
  124. package/dist/modules/hiring/roles.d.ts +1 -0
  125. package/dist/modules/hiring/roles.d.ts.map +1 -1
  126. package/dist/modules/hiring/roles.js +8 -11
  127. package/dist/modules/hiring/roles.js.map +1 -1
  128. package/dist/modules/import-export/events.d.ts +22 -0
  129. package/dist/modules/import-export/events.d.ts.map +1 -0
  130. package/dist/modules/import-export/events.js +12 -0
  131. package/dist/modules/import-export/events.js.map +1 -0
  132. package/dist/modules/import-export/index.d.ts.map +1 -1
  133. package/dist/modules/import-export/index.js +2 -0
  134. package/dist/modules/import-export/index.js.map +1 -1
  135. package/dist/modules/import-export/service.d.ts +5 -0
  136. package/dist/modules/import-export/service.d.ts.map +1 -1
  137. package/dist/modules/import-export/service.js +53 -14
  138. package/dist/modules/import-export/service.js.map +1 -1
  139. package/dist/modules/notes/events.d.ts +22 -0
  140. package/dist/modules/notes/events.d.ts.map +1 -0
  141. package/dist/modules/notes/events.js +12 -0
  142. package/dist/modules/notes/events.js.map +1 -0
  143. package/dist/modules/notes/index.d.ts.map +1 -1
  144. package/dist/modules/notes/index.js +2 -0
  145. package/dist/modules/notes/index.js.map +1 -1
  146. package/dist/modules/notes/service.d.ts +1 -0
  147. package/dist/modules/notes/service.d.ts.map +1 -1
  148. package/dist/modules/notes/service.js +4 -7
  149. package/dist/modules/notes/service.js.map +1 -1
  150. package/dist/modules/opportunities/events.d.ts +31 -0
  151. package/dist/modules/opportunities/events.d.ts.map +1 -0
  152. package/dist/modules/opportunities/events.js +12 -0
  153. package/dist/modules/opportunities/events.js.map +1 -0
  154. package/dist/modules/opportunities/index.d.ts.map +1 -1
  155. package/dist/modules/opportunities/index.js +2 -0
  156. package/dist/modules/opportunities/index.js.map +1 -1
  157. package/dist/modules/opportunities/service.d.ts +1 -0
  158. package/dist/modules/opportunities/service.d.ts.map +1 -1
  159. package/dist/modules/opportunities/service.js +9 -18
  160. package/dist/modules/opportunities/service.js.map +1 -1
  161. package/dist/modules/partnerships/events.d.ts +31 -0
  162. package/dist/modules/partnerships/events.d.ts.map +1 -0
  163. package/dist/modules/partnerships/events.js +12 -0
  164. package/dist/modules/partnerships/events.js.map +1 -0
  165. package/dist/modules/partnerships/index.d.ts.map +1 -1
  166. package/dist/modules/partnerships/index.js +2 -0
  167. package/dist/modules/partnerships/index.js.map +1 -1
  168. package/dist/modules/partnerships/service.d.ts +1 -0
  169. package/dist/modules/partnerships/service.d.ts.map +1 -1
  170. package/dist/modules/partnerships/service.js +9 -18
  171. package/dist/modules/partnerships/service.js.map +1 -1
  172. package/dist/modules/people/events.d.ts +28 -0
  173. package/dist/modules/people/events.d.ts.map +1 -0
  174. package/dist/modules/people/events.js +18 -0
  175. package/dist/modules/people/events.js.map +1 -0
  176. package/dist/modules/people/index.d.ts.map +1 -1
  177. package/dist/modules/people/index.js +2 -0
  178. package/dist/modules/people/index.js.map +1 -1
  179. package/dist/modules/people/service.d.ts +1 -0
  180. package/dist/modules/people/service.d.ts.map +1 -1
  181. package/dist/modules/people/service.js +8 -11
  182. package/dist/modules/people/service.js.map +1 -1
  183. package/dist/modules/pipelines/service.d.ts +4 -0
  184. package/dist/modules/pipelines/service.d.ts.map +1 -1
  185. package/dist/modules/pipelines/service.js +31 -8
  186. package/dist/modules/pipelines/service.js.map +1 -1
  187. package/dist/modules/plans/events.d.ts +21 -0
  188. package/dist/modules/plans/events.d.ts.map +1 -0
  189. package/dist/modules/plans/events.js +11 -0
  190. package/dist/modules/plans/events.js.map +1 -0
  191. package/dist/modules/plans/index.d.ts.map +1 -1
  192. package/dist/modules/plans/index.js +2 -0
  193. package/dist/modules/plans/index.js.map +1 -1
  194. package/dist/modules/plans/service.d.ts +1 -0
  195. package/dist/modules/plans/service.d.ts.map +1 -1
  196. package/dist/modules/plans/service.js +6 -14
  197. package/dist/modules/plans/service.js.map +1 -1
  198. package/dist/modules/positions/events.d.ts +22 -0
  199. package/dist/modules/positions/events.d.ts.map +1 -0
  200. package/dist/modules/positions/events.js +8 -0
  201. package/dist/modules/positions/events.js.map +1 -0
  202. package/dist/modules/positions/index.d.ts.map +1 -1
  203. package/dist/modules/positions/index.js +2 -0
  204. package/dist/modules/positions/index.js.map +1 -1
  205. package/dist/modules/positions/service.d.ts +1 -0
  206. package/dist/modules/positions/service.d.ts.map +1 -1
  207. package/dist/modules/positions/service.js +8 -11
  208. package/dist/modules/positions/service.js.map +1 -1
  209. package/dist/modules/raises/events.d.ts +31 -0
  210. package/dist/modules/raises/events.d.ts.map +1 -0
  211. package/dist/modules/raises/events.js +12 -0
  212. package/dist/modules/raises/events.js.map +1 -0
  213. package/dist/modules/raises/index.d.ts.map +1 -1
  214. package/dist/modules/raises/index.js +2 -0
  215. package/dist/modules/raises/index.js.map +1 -1
  216. package/dist/modules/raises/service.d.ts +1 -0
  217. package/dist/modules/raises/service.d.ts.map +1 -1
  218. package/dist/modules/raises/service.js +9 -18
  219. package/dist/modules/raises/service.js.map +1 -1
  220. package/dist/modules/reseal.d.ts +55 -0
  221. package/dist/modules/reseal.d.ts.map +1 -1
  222. package/dist/modules/reseal.js +90 -1
  223. package/dist/modules/reseal.js.map +1 -1
  224. package/dist/modules/smtp-email/index.d.ts +69 -0
  225. package/dist/modules/smtp-email/index.d.ts.map +1 -0
  226. package/dist/modules/smtp-email/index.js +82 -0
  227. package/dist/modules/smtp-email/index.js.map +1 -0
  228. package/dist/modules/webhooks/index.d.ts.map +1 -1
  229. package/dist/modules/webhooks/index.js +5 -1
  230. package/dist/modules/webhooks/index.js.map +1 -1
  231. package/dist/modules/webhooks/payloads.d.ts +22 -7
  232. package/dist/modules/webhooks/payloads.d.ts.map +1 -1
  233. package/dist/modules/webhooks/payloads.js +63 -44
  234. package/dist/modules/webhooks/payloads.js.map +1 -1
  235. package/dist/modules/workspace/events.d.ts +52 -0
  236. package/dist/modules/workspace/events.d.ts.map +1 -0
  237. package/dist/modules/workspace/events.js +16 -0
  238. package/dist/modules/workspace/events.js.map +1 -0
  239. package/dist/modules/workspace/index.d.ts.map +1 -1
  240. package/dist/modules/workspace/index.js +6 -2
  241. package/dist/modules/workspace/index.js.map +1 -1
  242. package/dist/modules/workspace/service.d.ts +1 -0
  243. package/dist/modules/workspace/service.d.ts.map +1 -1
  244. package/dist/modules/workspace/service.js +13 -15
  245. package/dist/modules/workspace/service.js.map +1 -1
  246. package/dist/runtime/events.d.ts +86 -108
  247. package/dist/runtime/events.d.ts.map +1 -1
  248. package/dist/runtime/events.js +123 -52
  249. package/dist/runtime/events.js.map +1 -1
  250. package/dist/runtime/module.d.ts +54 -2
  251. package/dist/runtime/module.d.ts.map +1 -1
  252. package/dist/runtime/registry.d.ts +23 -0
  253. package/dist/runtime/registry.d.ts.map +1 -1
  254. package/dist/runtime/registry.js +102 -2
  255. package/dist/runtime/registry.js.map +1 -1
  256. package/dist/runtime/transaction.d.ts +29 -4
  257. package/dist/runtime/transaction.d.ts.map +1 -1
  258. package/dist/runtime/transaction.js +48 -7
  259. package/dist/runtime/transaction.js.map +1 -1
  260. package/dist/testing/app.d.ts.map +1 -1
  261. package/dist/testing/app.js +11 -3
  262. package/dist/testing/app.js.map +1 -1
  263. package/dist/testing/database.d.ts.map +1 -1
  264. package/dist/testing/database.js +6 -3
  265. package/dist/testing/database.js.map +1 -1
  266. package/dist/testing/services.d.ts +21 -1
  267. package/dist/testing/services.d.ts.map +1 -1
  268. package/dist/testing/services.js +20 -5
  269. package/dist/testing/services.js.map +1 -1
  270. package/package.json +5 -2
  271. package/src/index.ts +44 -9
  272. package/src/lib/actor.ts +15 -1
  273. package/src/lib/config.ts +56 -7
  274. package/src/lib/email.ts +22 -92
  275. package/src/lib/fromEnv.ts +143 -0
  276. package/src/lib/ids.ts +6 -0
  277. package/src/lib/kelpieConfigFile.ts +283 -0
  278. package/src/lib/logger.ts +92 -34
  279. package/src/modules/agent-tasks/index.ts +4 -1
  280. package/src/modules/agent-tasks/prompt.ts +57 -9
  281. package/src/modules/agent-tasks/service.ts +3 -2
  282. package/src/modules/agent-tasks/wire.ts +12 -0
  283. package/src/modules/auth/index.ts +11 -9
  284. package/src/modules/companies/events.ts +25 -0
  285. package/src/modules/companies/index.ts +2 -0
  286. package/src/modules/companies/service.ts +8 -11
  287. package/src/modules/core.ts +4 -0
  288. package/src/modules/deals/events.ts +34 -0
  289. package/src/modules/deals/index.ts +2 -0
  290. package/src/modules/deals/service.ts +17 -18
  291. package/src/modules/decisions/events.ts +24 -0
  292. package/src/modules/decisions/index.ts +2 -0
  293. package/src/modules/decisions/service.ts +8 -7
  294. package/src/modules/forms/events.ts +37 -0
  295. package/src/modules/forms/index.ts +2 -0
  296. package/src/modules/forms/service.ts +12 -11
  297. package/src/modules/forms/submission.ts +80 -29
  298. package/src/modules/handbook/events.ts +31 -0
  299. package/src/modules/handbook/index.ts +2 -0
  300. package/src/modules/handbook/service.ts +8 -15
  301. package/src/modules/hiring/candidates.ts +8 -11
  302. package/src/modules/hiring/events.ts +36 -0
  303. package/src/modules/hiring/index.ts +2 -0
  304. package/src/modules/hiring/roles.ts +8 -11
  305. package/src/modules/import-export/events.ts +25 -0
  306. package/src/modules/import-export/index.ts +2 -0
  307. package/src/modules/import-export/service.ts +83 -15
  308. package/src/modules/notes/events.ts +25 -0
  309. package/src/modules/notes/index.ts +2 -0
  310. package/src/modules/notes/service.ts +8 -7
  311. package/src/modules/opportunities/events.ts +34 -0
  312. package/src/modules/opportunities/index.ts +2 -0
  313. package/src/modules/opportunities/service.ts +17 -18
  314. package/src/modules/partnerships/events.ts +34 -0
  315. package/src/modules/partnerships/index.ts +2 -0
  316. package/src/modules/partnerships/service.ts +17 -18
  317. package/src/modules/people/events.ts +36 -0
  318. package/src/modules/people/index.ts +2 -0
  319. package/src/modules/people/service.ts +97 -87
  320. package/src/modules/pipelines/service.ts +47 -8
  321. package/src/modules/plans/events.ts +24 -0
  322. package/src/modules/plans/index.ts +2 -0
  323. package/src/modules/plans/service.ts +14 -14
  324. package/src/modules/positions/events.ts +25 -0
  325. package/src/modules/positions/index.ts +2 -0
  326. package/src/modules/positions/service.ts +8 -11
  327. package/src/modules/raises/events.ts +34 -0
  328. package/src/modules/raises/index.ts +2 -0
  329. package/src/modules/raises/service.ts +17 -18
  330. package/src/modules/reseal.ts +144 -1
  331. package/src/modules/smtp-email/index.ts +119 -0
  332. package/src/modules/webhooks/index.ts +5 -1
  333. package/src/modules/webhooks/payloads.ts +94 -48
  334. package/src/modules/workspace/events.ts +46 -0
  335. package/src/modules/workspace/index.ts +6 -2
  336. package/src/modules/workspace/service.ts +37 -15
  337. package/src/runtime/events.ts +268 -107
  338. package/src/runtime/module.ts +55 -2
  339. package/src/runtime/registry.ts +169 -1
  340. package/src/runtime/transaction.ts +94 -17
  341. package/src/testing/app.ts +11 -3
  342. package/src/testing/database.ts +6 -3
  343. package/src/testing/services.ts +34 -5
@@ -0,0 +1,36 @@
1
+ import { z } from 'zod'
2
+
3
+ import type { ModuleEventCatalog } from '../../runtime/module.ts'
4
+
5
+ /**
6
+ * Domain events published by the people module.
7
+ *
8
+ * The runtime catalog (below) is the authority; the `KelpieEventMap`
9
+ * augmentation gives autocomplete at every emit and subscribe site.
10
+ */
11
+
12
+ export const PEOPLE_EVENT_NAMES = [
13
+ 'people.person.created',
14
+ 'people.person.updated',
15
+ 'people.person.deleted',
16
+ ] as const
17
+
18
+ export const peopleEvents = {
19
+ 'people.person.created': z.object({}).strict(),
20
+ 'people.person.updated': z.object({ changed: z.array(z.string()).readonly() }),
21
+ 'people.person.deleted': z.object({}).strict(),
22
+ } satisfies ModuleEventCatalog
23
+
24
+ export type PersonCreatedData = Record<string, never>
25
+ export interface PersonUpdatedData {
26
+ readonly changed: readonly string[]
27
+ }
28
+ export type PersonDeletedData = Record<string, never>
29
+
30
+ declare module '../../runtime/events.ts' {
31
+ interface KelpieEventMap {
32
+ 'people.person.created': PersonCreatedData
33
+ 'people.person.updated': PersonUpdatedData
34
+ 'people.person.deleted': PersonDeletedData
35
+ }
36
+ }
@@ -1,5 +1,6 @@
1
1
  import type { KelpieModule } from '../../runtime/module.ts'
2
2
  import { createActivityRecorder } from '../activities/index.ts'
3
+ import { peopleEvents } from './events.ts'
3
4
  import { mountPeopleRoutes } from './routes.ts'
4
5
  import * as schema from './schema.ts'
5
6
  import { createPeopleService } from './service.ts'
@@ -16,6 +17,7 @@ export function createPeopleModule(migrationsDirectory: string): KelpieModule {
16
17
  id: 'people',
17
18
  requires: ['workspace', 'activities'],
18
19
  structural: true,
20
+ events: peopleEvents,
19
21
 
20
22
  register(context) {
21
23
  const service = createPeopleService({
@@ -10,8 +10,10 @@ import type { TransactionScope } from '../../runtime/transaction.ts'
10
10
  import type { ActivityRecorder } from '../activities/recorder.ts'
11
11
  import { describeCreation, describeUpdate } from '../activities/wording.ts'
12
12
  import type { FieldLabels } from '../activities/wording.ts'
13
+ import { toEventActor } from '../../lib/actor.ts'
13
14
  import type { Actor } from '../auth/actor.ts'
14
15
  import { requireWorkspaceId } from '../auth/actor.ts'
16
+ import './events.ts'
15
17
  import {
16
18
  deleteRecordsAttachedTo,
17
19
  deleteRecordsAttachedToCandidaciesOf,
@@ -160,45 +162,48 @@ export function createPeopleService(dependencies: PeopleDependencies): PeopleSer
160
162
  const workspaceId = requireWorkspaceId(actor)
161
163
  const id = dependencies.createId('person')
162
164
 
163
- return dependencies.transaction(async ({ tx, events }) => {
164
- let created: PersonRecord
165
-
166
- try {
167
- created = await repository.insertPerson(tx, {
168
- id,
169
- workspaceId,
170
- name: input.name,
171
- email: input.email === null ? null : normaliseEmail(input.email),
172
- phones: input.phones,
173
- socialProfiles: input.socialProfiles,
174
- timezone: input.timezone,
175
- location: input.location,
176
- preferredChannel: input.preferredChannel,
177
- influence: input.influence,
178
- relationship: input.relationship,
179
- summary: input.summary,
180
- tags: [...input.tags],
181
- lastContactedAt: input.lastContactedAt,
182
- })
183
- } catch (error: unknown) {
184
- if (postgresErrorCode(error) === UNIQUE_VIOLATION) {
185
- throw duplicateEmail()
165
+ return dependencies.transaction(
166
+ async ({ tx, events }) => {
167
+ let created: PersonRecord
168
+
169
+ try {
170
+ created = await repository.insertPerson(tx, {
171
+ id,
172
+ workspaceId,
173
+ name: input.name,
174
+ email: input.email === null ? null : normaliseEmail(input.email),
175
+ phones: input.phones,
176
+ socialProfiles: input.socialProfiles,
177
+ timezone: input.timezone,
178
+ location: input.location,
179
+ preferredChannel: input.preferredChannel,
180
+ influence: input.influence,
181
+ relationship: input.relationship,
182
+ summary: input.summary,
183
+ tags: [...input.tags],
184
+ lastContactedAt: input.lastContactedAt,
185
+ })
186
+ } catch (error: unknown) {
187
+ if (postgresErrorCode(error) === UNIQUE_VIOLATION) {
188
+ throw duplicateEmail()
189
+ }
190
+
191
+ throw error
186
192
  }
187
193
 
188
- throw error
189
- }
190
-
191
- await dependencies.recordActivity(tx, workspaceId, actor, {
192
- targetType: 'person',
193
- targetId: created.id,
194
- kind: 'created',
195
- ...describeCreation('Person'),
196
- })
194
+ await dependencies.recordActivity(tx, workspaceId, actor, {
195
+ targetType: 'person',
196
+ targetId: created.id,
197
+ kind: 'created',
198
+ ...describeCreation('Person'),
199
+ })
197
200
 
198
- events.emit('record.created', { workspaceId, objectType: 'person', recordId: created.id })
201
+ events.emit('people.person.created', { type: 'person', id: created.id }, {})
199
202
 
200
- return toView(created)
201
- })
203
+ return toView(created)
204
+ },
205
+ { workspaceId, actor: toEventActor(actor) },
206
+ )
202
207
  },
203
208
 
204
209
  async update(actor, id, changes) {
@@ -214,71 +219,76 @@ export function createPeopleService(dependencies: PeopleDependencies): PeopleSer
214
219
  return toView(existing)
215
220
  }
216
221
 
217
- return dependencies.transaction(async ({ tx, events }) => {
218
- let updated: PersonRecord | undefined
222
+ return dependencies.transaction(
223
+ async ({ tx, events }) => {
224
+ let updated: PersonRecord | undefined
225
+
226
+ try {
227
+ updated = await repository.updatePerson(tx, workspaceId, id, {
228
+ ...columns,
229
+ updatedAt: dependencies.now(),
230
+ })
231
+ } catch (error: unknown) {
232
+ if (postgresErrorCode(error) === UNIQUE_VIOLATION) {
233
+ throw duplicateEmail()
234
+ }
235
+
236
+ throw error
237
+ }
219
238
 
220
- try {
221
- updated = await repository.updatePerson(tx, workspaceId, id, {
222
- ...columns,
223
- updatedAt: dependencies.now(),
224
- })
225
- } catch (error: unknown) {
226
- if (postgresErrorCode(error) === UNIQUE_VIOLATION) {
227
- throw duplicateEmail()
239
+ if (updated === undefined) {
240
+ throw AppError.notFound('Person not found')
228
241
  }
229
242
 
230
- throw error
231
- }
232
-
233
- if (updated === undefined) {
234
- throw AppError.notFound('Person not found')
235
- }
236
-
237
- await dependencies.recordActivity(tx, workspaceId, actor, {
238
- targetType: 'person',
239
- targetId: id,
240
- kind: 'updated',
241
- ...describeUpdate(changed, PERSON_FIELD_LABELS, existing, columns),
242
- })
243
-
244
- events.emit('record.updated', {
245
- workspaceId,
246
- objectType: 'person',
247
- recordId: id,
248
- changedFields: changed,
249
- })
250
-
251
- return toView(updated)
252
- })
243
+ await dependencies.recordActivity(tx, workspaceId, actor, {
244
+ targetType: 'person',
245
+ targetId: id,
246
+ kind: 'updated',
247
+ ...describeUpdate(changed, PERSON_FIELD_LABELS, existing, columns),
248
+ })
249
+
250
+ events.emit(
251
+ 'people.person.updated',
252
+ { type: 'person', id },
253
+ { changed },
254
+ )
255
+
256
+ return toView(updated)
257
+ },
258
+ { workspaceId, actor: toEventActor(actor) },
259
+ )
253
260
  },
254
261
 
255
262
  async remove(actor, id) {
256
263
  const workspaceId = requireWorkspaceId(actor)
257
264
 
258
- await dependencies.transaction(async ({ tx, events }) => {
259
- await require(workspaceId, id)
265
+ await dependencies.transaction(
266
+ async ({ tx, events }) => {
267
+ await require(workspaceId, id)
260
268
 
261
- // Notes, activities and decisions carry no foreign key to their target,
262
- // so nothing in the database removes them. Same transaction: if the
263
- // delete below is refused, these come back with it.
264
- await deleteRecordsAttachedTo(tx, workspaceId, 'person', id)
269
+ // Notes, activities and decisions carry no foreign key to their target,
270
+ // so nothing in the database removes them. Same transaction: if the
271
+ // delete below is refused, these come back with it.
272
+ await deleteRecordsAttachedTo(tx, workspaceId, 'person', id)
265
273
 
266
- // The candidacy rows themselves cascade, which is why their interview
267
- // notes have to be taken here: no hiring service sees that delete.
268
- await deleteRecordsAttachedToCandidaciesOf(tx, workspaceId, id)
274
+ // The candidacy rows themselves cascade, which is why their interview
275
+ // notes have to be taken here: no hiring service sees that delete.
276
+ await deleteRecordsAttachedToCandidaciesOf(tx, workspaceId, id)
269
277
 
270
- try {
271
- await repository.deletePerson(tx, workspaceId, id)
272
- } catch (error: unknown) {
273
- if (isReferenceViolation(error)) {
274
- throw referencedElsewhere(error, 'person')
275
- }
278
+ try {
279
+ await repository.deletePerson(tx, workspaceId, id)
280
+ } catch (error: unknown) {
281
+ if (isReferenceViolation(error)) {
282
+ throw referencedElsewhere(error, 'person')
283
+ }
276
284
 
277
- throw error
278
- }
285
+ throw error
286
+ }
279
287
 
280
- events.emit('record.deleted', { workspaceId, objectType: 'person', recordId: id })
281
- })
288
+ events.emit('people.person.deleted', { type: 'person', id }, {})
289
+ },
290
+ { workspaceId, actor: toEventActor(actor) },
291
+ )
282
292
  },
283
293
  }
284
294
  }
@@ -8,8 +8,15 @@ import type { ListQueryParameters, Page } from '../../lib/pagination.ts'
8
8
  import type { Transaction, TransactionScope } from '../../runtime/transaction.ts'
9
9
  import type { ActivityRecorder } from '../activities/recorder.ts'
10
10
  import { describeStageChange } from '../activities/wording.ts'
11
+ import { toEventActor } from '../../lib/actor.ts'
11
12
  import type { Actor } from '../auth/actor.ts'
12
13
  import { requireWorkspaceId } from '../auth/actor.ts'
14
+ // The four owning modules register their `<kind>.<obj>.stage_changed` events;
15
+ // pipelines fires them from cascade reassignments.
16
+ import '../deals/events.ts'
17
+ import '../opportunities/events.ts'
18
+ import '../partnerships/events.ts'
19
+ import '../raises/events.ts'
13
20
  import { referencedElsewhere } from '../references.ts'
14
21
  import { reassignStagedRecords } from '../stagedRecords.ts'
15
22
  import * as repository from './repository.ts'
@@ -310,13 +317,7 @@ export function createPipelineStagesService(
310
317
  kind: 'stage_changed',
311
318
  ...describeStageChange(stage.label, moveTo.label),
312
319
  })
313
- events.emit('stage.changed', {
314
- workspaceId,
315
- objectType: kind,
316
- recordId,
317
- fromStageId: stage.id,
318
- toStageId: moveTo.id,
319
- })
320
+ emitStageChangedFor(events, kind, recordId, stage.id, moveTo.id)
320
321
  }
321
322
  }
322
323
 
@@ -333,7 +334,45 @@ export function createPipelineStagesService(
333
334
  }
334
335
 
335
336
  await renumber(tx, workspaceId, rows.filter((row) => row.id !== id))
336
- })
337
+ }, { workspaceId, actor: toEventActor(actor) })
337
338
  },
338
339
  }
339
340
  }
341
+
342
+ /**
343
+ * Dispatches the stage-changed envelope for a moved record. Each of the four
344
+ * staged object types has its own event name in its owning module's catalog;
345
+ * pipelines is the emitter, not the owner.
346
+ */
347
+ function emitStageChangedFor(
348
+ events: import('../../runtime/transaction.ts').BufferedEvents,
349
+ kind: PipelineKind,
350
+ recordId: string,
351
+ fromStageId: string,
352
+ toStageId: string,
353
+ ): void {
354
+ const data = { fromStageId, toStageId } as const
355
+
356
+ switch (kind) {
357
+ case 'deal':
358
+ events.emit('deals.deal.stage_changed', { type: 'deal', id: recordId }, data)
359
+ break
360
+ case 'opportunity':
361
+ events.emit(
362
+ 'opportunities.opportunity.stage_changed',
363
+ { type: 'opportunity', id: recordId },
364
+ data,
365
+ )
366
+ break
367
+ case 'partnership':
368
+ events.emit(
369
+ 'partnerships.partnership.stage_changed',
370
+ { type: 'partnership', id: recordId },
371
+ data,
372
+ )
373
+ break
374
+ case 'raise':
375
+ events.emit('raises.raise.stage_changed', { type: 'raise', id: recordId }, data)
376
+ break
377
+ }
378
+ }
@@ -0,0 +1,24 @@
1
+ import { z } from 'zod'
2
+
3
+ import type { ModuleEventCatalog } from '../../runtime/module.ts'
4
+
5
+ /**
6
+ * Domain events published by the plans module.
7
+ *
8
+ * `target` is the record the plan item attaches to; the plan item's own id
9
+ * lives in `data`.
10
+ */
11
+
12
+ export const plansEvents = {
13
+ 'plans.plan_item.completed': z.object({ planItemId: z.string() }),
14
+ } satisfies ModuleEventCatalog
15
+
16
+ export interface PlanItemCompletedData {
17
+ readonly planItemId: string
18
+ }
19
+
20
+ declare module '../../runtime/events.ts' {
21
+ interface KelpieEventMap {
22
+ 'plans.plan_item.completed': PlanItemCompletedData
23
+ }
24
+ }
@@ -1,4 +1,5 @@
1
1
  import type { KelpieModule } from '../../runtime/module.ts'
2
+ import { plansEvents } from './events.ts'
2
3
  import { mountPlansRoutes } from './routes.ts'
3
4
  import * as schema from './schema.ts'
4
5
  import { createPlansService } from './service.ts'
@@ -16,6 +17,7 @@ export function createPlansModule(migrationsDirectory: string): KelpieModule {
16
17
  id: 'plans',
17
18
  requires: ['workspace'],
18
19
  structural: true,
20
+ events: plansEvents,
19
21
 
20
22
  register(context) {
21
23
  const service = createPlansService({
@@ -5,8 +5,10 @@ import type { IdFactory } from '../../lib/ids.ts'
5
5
  import { mapPage, readListWindow, toPage } from '../../lib/pagination.ts'
6
6
  import type { ListQueryParameters, Page } from '../../lib/pagination.ts'
7
7
  import type { TransactionScope } from '../../runtime/transaction.ts'
8
+ import { toEventActor } from '../../lib/actor.ts'
8
9
  import type { Actor } from '../auth/actor.ts'
9
10
  import { requireWorkspaceId } from '../auth/actor.ts'
11
+ import './events.ts'
10
12
  import { targetExists } from '../recordTargets.ts'
11
13
  import * as repository from './repository.ts'
12
14
  import { DEFAULT_PLAN_ITEM_SORT, PLAN_ITEM_SORTS } from './repository.ts'
@@ -149,16 +151,15 @@ export function createPlansService(dependencies: PlansDependencies): PlansServic
149
151
  // as already finished. A consumer watching for completed work cares that
150
152
  // the work is done, not whether it passed through `todo` on the way.
151
153
  if (created.status === 'done') {
152
- events.emit('plan.completed', {
153
- workspaceId,
154
- planItemId: created.id,
155
- targetType: created.targetType,
156
- targetId: created.targetId,
157
- })
154
+ events.emit(
155
+ 'plans.plan_item.completed',
156
+ { type: created.targetType, id: created.targetId },
157
+ { planItemId: created.id },
158
+ )
158
159
  }
159
160
 
160
161
  return toView(created)
161
- })
162
+ }, { workspaceId, actor: toEventActor(actor) })
162
163
  },
163
164
 
164
165
  async update(actor, id, changes) {
@@ -196,16 +197,15 @@ export function createPlansService(dependencies: PlansDependencies): PlansServic
196
197
  // On the transition only. Re-sending `done` on a finished item changes
197
198
  // nothing, and it is caught above before it reaches here.
198
199
  if (changed.includes('status') && updated.status === 'done') {
199
- events.emit('plan.completed', {
200
- workspaceId,
201
- planItemId: updated.id,
202
- targetType: updated.targetType,
203
- targetId: updated.targetId,
204
- })
200
+ events.emit(
201
+ 'plans.plan_item.completed',
202
+ { type: updated.targetType, id: updated.targetId },
203
+ { planItemId: updated.id },
204
+ )
205
205
  }
206
206
 
207
207
  return toView(updated)
208
- })
208
+ }, { workspaceId, actor: toEventActor(actor) })
209
209
  },
210
210
 
211
211
  async remove(actor, id) {
@@ -0,0 +1,25 @@
1
+ import { z } from 'zod'
2
+
3
+ import type { ModuleEventCatalog } from '../../runtime/module.ts'
4
+
5
+ /** Domain events published by the positions module. */
6
+
7
+ export const positionsEvents = {
8
+ 'positions.position.created': z.object({}).strict(),
9
+ 'positions.position.updated': z.object({ changed: z.array(z.string()).readonly() }),
10
+ 'positions.position.deleted': z.object({}).strict(),
11
+ } satisfies ModuleEventCatalog
12
+
13
+ export type PositionCreatedData = Record<string, never>
14
+ export interface PositionUpdatedData {
15
+ readonly changed: readonly string[]
16
+ }
17
+ export type PositionDeletedData = Record<string, never>
18
+
19
+ declare module '../../runtime/events.ts' {
20
+ interface KelpieEventMap {
21
+ 'positions.position.created': PositionCreatedData
22
+ 'positions.position.updated': PositionUpdatedData
23
+ 'positions.position.deleted': PositionDeletedData
24
+ }
25
+ }
@@ -1,5 +1,6 @@
1
1
  import type { KelpieModule } from '../../runtime/module.ts'
2
2
  import { createActivityRecorder } from '../activities/index.ts'
3
+ import { positionsEvents } from './events.ts'
3
4
  import { mountPositionsRoutes } from './routes.ts'
4
5
  import * as schema from './schema.ts'
5
6
  import { createPositionsService } from './service.ts'
@@ -15,6 +16,7 @@ export function createPositionsModule(migrationsDirectory: string): KelpieModule
15
16
  return {
16
17
  id: 'positions',
17
18
  requires: ['people', 'companies', 'activities'],
19
+ events: positionsEvents,
18
20
 
19
21
  register(context) {
20
22
  const service = createPositionsService({
@@ -7,8 +7,10 @@ import type { ListQueryParameters, Page } from '../../lib/pagination.ts'
7
7
  import type { TransactionScope } from '../../runtime/transaction.ts'
8
8
  import type { ActivityRecorder } from '../activities/recorder.ts'
9
9
  import { describeLink, describeUnlink } from '../activities/wording.ts'
10
+ import { toEventActor } from '../../lib/actor.ts'
10
11
  import type { Actor } from '../auth/actor.ts'
11
12
  import { requireWorkspaceId } from '../auth/actor.ts'
13
+ import './events.ts'
12
14
  import * as companyRepository from '../companies/repository.ts'
13
15
  import * as personRepository from '../people/repository.ts'
14
16
  import * as repository from './repository.ts'
@@ -174,10 +176,10 @@ export function createPositionsService(dependencies: PositionsDependencies): Pos
174
176
  ...describeLink('person', ends.personName),
175
177
  })
176
178
 
177
- events.emit('record.created', { workspaceId, objectType: 'position', recordId: created.id })
179
+ events.emit('positions.position.created', { type: 'position', id: created.id }, {})
178
180
 
179
181
  return toView(created)
180
- })
182
+ }, { workspaceId, actor: toEventActor(actor) })
181
183
  },
182
184
 
183
185
  async update(actor, id, changes) {
@@ -210,15 +212,10 @@ export function createPositionsService(dependencies: PositionsDependencies): Pos
210
212
  throw AppError.notFound('Position not found')
211
213
  }
212
214
 
213
- events.emit('record.updated', {
214
- workspaceId,
215
- objectType: 'position',
216
- recordId: id,
217
- changedFields: ['title'],
218
- })
215
+ events.emit('positions.position.updated', { type: 'position', id }, { changed: ['title'] })
219
216
 
220
217
  return toView(updated)
221
- })
218
+ }, { workspaceId, actor: toEventActor(actor) })
222
219
  },
223
220
 
224
221
  async remove(actor, id) {
@@ -246,8 +243,8 @@ export function createPositionsService(dependencies: PositionsDependencies): Pos
246
243
  ...describeUnlink('person', ends.personName),
247
244
  })
248
245
 
249
- events.emit('record.deleted', { workspaceId, objectType: 'position', recordId: id })
250
- })
246
+ events.emit('positions.position.deleted', { type: 'position', id }, {})
247
+ }, { workspaceId, actor: toEventActor(actor) })
251
248
  },
252
249
  }
253
250
  }
@@ -0,0 +1,34 @@
1
+ import { z } from 'zod'
2
+
3
+ import type { ModuleEventCatalog } from '../../runtime/module.ts'
4
+
5
+ /** Domain events published by the raises module. */
6
+
7
+ export const raisesEvents = {
8
+ 'raises.raise.created': z.object({}).strict(),
9
+ 'raises.raise.updated': z.object({ changed: z.array(z.string()).readonly() }),
10
+ 'raises.raise.deleted': z.object({}).strict(),
11
+ 'raises.raise.stage_changed': z.object({
12
+ fromStageId: z.string().nullable(),
13
+ toStageId: z.string(),
14
+ }),
15
+ } satisfies ModuleEventCatalog
16
+
17
+ export type RaiseCreatedData = Record<string, never>
18
+ export interface RaiseUpdatedData {
19
+ readonly changed: readonly string[]
20
+ }
21
+ export type RaiseDeletedData = Record<string, never>
22
+ export interface RaiseStageChangedData {
23
+ readonly fromStageId: string | null
24
+ readonly toStageId: string
25
+ }
26
+
27
+ declare module '../../runtime/events.ts' {
28
+ interface KelpieEventMap {
29
+ 'raises.raise.created': RaiseCreatedData
30
+ 'raises.raise.updated': RaiseUpdatedData
31
+ 'raises.raise.deleted': RaiseDeletedData
32
+ 'raises.raise.stage_changed': RaiseStageChangedData
33
+ }
34
+ }
@@ -1,5 +1,6 @@
1
1
  import type { KelpieModule } from '../../runtime/module.ts'
2
2
  import { createActivityRecorder } from '../activities/index.ts'
3
+ import { raisesEvents } from './events.ts'
3
4
  import { mountRaisesRoutes } from './routes.ts'
4
5
  import * as schema from './schema.ts'
5
6
  import { createRaisesService } from './service.ts'
@@ -17,6 +18,7 @@ export function createRaisesModule(migrationsDirectory: string): KelpieModule {
17
18
  return {
18
19
  id: 'raises',
19
20
  requires: ['companies', 'people', 'pipelines', 'activities'],
21
+ events: raisesEvents,
20
22
 
21
23
  register(context) {
22
24
  const service = createRaisesService({