@kelpie/server 0.10.0 → 0.12.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 (485) hide show
  1. package/dist/app.d.ts.map +1 -1
  2. package/dist/app.js +2 -0
  3. package/dist/app.js.map +1 -1
  4. package/dist/index.d.ts +3 -3
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +1 -1
  7. package/dist/index.js.map +1 -1
  8. package/dist/lib/actor.d.ts +3 -1
  9. package/dist/lib/actor.d.ts.map +1 -1
  10. package/dist/lib/actor.js.map +1 -1
  11. package/dist/lib/apiKeyScopes.d.ts +8 -0
  12. package/dist/lib/apiKeyScopes.d.ts.map +1 -0
  13. package/dist/lib/apiKeyScopes.js +166 -0
  14. package/dist/lib/apiKeyScopes.js.map +1 -0
  15. package/dist/lib/errors.d.ts +18 -0
  16. package/dist/lib/errors.d.ts.map +1 -1
  17. package/dist/lib/errors.js +17 -0
  18. package/dist/lib/errors.js.map +1 -1
  19. package/dist/modules/activities/repository.d.ts +2 -0
  20. package/dist/modules/activities/repository.d.ts.map +1 -1
  21. package/dist/modules/activities/repository.js +9 -0
  22. package/dist/modules/activities/repository.js.map +1 -1
  23. package/dist/modules/agent-tasks/repository.d.ts +2 -0
  24. package/dist/modules/agent-tasks/repository.d.ts.map +1 -1
  25. package/dist/modules/agent-tasks/repository.js +9 -0
  26. package/dist/modules/agent-tasks/repository.js.map +1 -1
  27. package/dist/modules/api-keys/routes.d.ts.map +1 -1
  28. package/dist/modules/api-keys/routes.js +4 -1
  29. package/dist/modules/api-keys/routes.js.map +1 -1
  30. package/dist/modules/api-keys/schema.d.ts +43 -0
  31. package/dist/modules/api-keys/schema.d.ts.map +1 -1
  32. package/dist/modules/api-keys/schema.js +2 -0
  33. package/dist/modules/api-keys/schema.js.map +1 -1
  34. package/dist/modules/api-keys/scopeMiddleware.d.ts +8 -0
  35. package/dist/modules/api-keys/scopeMiddleware.d.ts.map +1 -0
  36. package/dist/modules/api-keys/scopeMiddleware.js +21 -0
  37. package/dist/modules/api-keys/scopeMiddleware.js.map +1 -0
  38. package/dist/modules/api-keys/service.d.ts +3 -1
  39. package/dist/modules/api-keys/service.d.ts.map +1 -1
  40. package/dist/modules/api-keys/service.js +26 -1
  41. package/dist/modules/api-keys/service.js.map +1 -1
  42. package/dist/modules/auth/credentials.d.ts.map +1 -1
  43. package/dist/modules/auth/credentials.js +2 -0
  44. package/dist/modules/auth/credentials.js.map +1 -1
  45. package/dist/modules/auth/index.d.ts.map +1 -1
  46. package/dist/modules/auth/index.js +21 -4
  47. package/dist/modules/auth/index.js.map +1 -1
  48. package/dist/modules/auth/repository.d.ts +2 -1
  49. package/dist/modules/auth/repository.d.ts.map +1 -1
  50. package/dist/modules/auth/repository.js +1 -0
  51. package/dist/modules/auth/repository.js.map +1 -1
  52. package/dist/modules/auth/routes.d.ts.map +1 -1
  53. package/dist/modules/auth/routes.js +2 -6
  54. package/dist/modules/auth/routes.js.map +1 -1
  55. package/dist/modules/auth/schema.d.ts +18 -1
  56. package/dist/modules/auth/schema.d.ts.map +1 -1
  57. package/dist/modules/auth/schema.js +12 -1
  58. package/dist/modules/auth/schema.js.map +1 -1
  59. package/dist/modules/auth/service.d.ts +18 -0
  60. package/dist/modules/auth/service.d.ts.map +1 -1
  61. package/dist/modules/auth/service.js +95 -6
  62. package/dist/modules/auth/service.js.map +1 -1
  63. package/dist/modules/auth/session.d.ts +12 -0
  64. package/dist/modules/auth/session.d.ts.map +1 -1
  65. package/dist/modules/auth/session.js +12 -0
  66. package/dist/modules/auth/session.js.map +1 -1
  67. package/dist/modules/consent-purposes/index.d.ts +1 -1
  68. package/dist/modules/consent-purposes/index.js +1 -1
  69. package/dist/modules/consent-purposes/schema.d.ts +3 -3
  70. package/dist/modules/consent-purposes/schema.js +3 -3
  71. package/dist/modules/consent-purposes/service.d.ts +1 -1
  72. package/dist/modules/consent-purposes/service.d.ts.map +1 -1
  73. package/dist/modules/consent-purposes/service.js +7 -16
  74. package/dist/modules/consent-purposes/service.js.map +1 -1
  75. package/dist/modules/consent-purposes/starters.d.ts +1 -1
  76. package/dist/modules/consent-purposes/tools.js +1 -1
  77. package/dist/modules/consent-purposes/tools.js.map +1 -1
  78. package/dist/modules/conversions/clearPointers.d.ts +10 -0
  79. package/dist/modules/conversions/clearPointers.d.ts.map +1 -0
  80. package/dist/modules/conversions/clearPointers.js +40 -0
  81. package/dist/modules/conversions/clearPointers.js.map +1 -0
  82. package/dist/modules/conversions/factory.d.ts +5 -0
  83. package/dist/modules/conversions/factory.d.ts.map +1 -0
  84. package/dist/modules/conversions/factory.js +18 -0
  85. package/dist/modules/conversions/factory.js.map +1 -0
  86. package/dist/modules/conversions/fieldMap.d.ts +55 -0
  87. package/dist/modules/conversions/fieldMap.d.ts.map +1 -0
  88. package/dist/modules/conversions/fieldMap.js +398 -0
  89. package/dist/modules/conversions/fieldMap.js.map +1 -0
  90. package/dist/modules/conversions/index.d.ts +7 -0
  91. package/dist/modules/conversions/index.d.ts.map +1 -0
  92. package/dist/modules/conversions/index.js +6 -0
  93. package/dist/modules/conversions/index.js.map +1 -0
  94. package/dist/modules/conversions/mcp.d.ts +7 -0
  95. package/dist/modules/conversions/mcp.d.ts.map +1 -0
  96. package/dist/modules/conversions/mcp.js +47 -0
  97. package/dist/modules/conversions/mcp.js.map +1 -0
  98. package/dist/modules/conversions/repoint.d.ts +19 -0
  99. package/dist/modules/conversions/repoint.d.ts.map +1 -0
  100. package/dist/modules/conversions/repoint.js +51 -0
  101. package/dist/modules/conversions/repoint.js.map +1 -0
  102. package/dist/modules/conversions/routes.d.ts +26 -0
  103. package/dist/modules/conversions/routes.d.ts.map +1 -0
  104. package/dist/modules/conversions/routes.js +23 -0
  105. package/dist/modules/conversions/routes.js.map +1 -0
  106. package/dist/modules/conversions/service.d.ts +28 -0
  107. package/dist/modules/conversions/service.d.ts.map +1 -0
  108. package/dist/modules/conversions/service.js +170 -0
  109. package/dist/modules/conversions/service.js.map +1 -0
  110. package/dist/modules/conversions/wire.d.ts +4 -0
  111. package/dist/modules/conversions/wire.d.ts.map +1 -0
  112. package/dist/modules/conversions/wire.js +28 -0
  113. package/dist/modules/conversions/wire.js.map +1 -0
  114. package/dist/modules/deals/events.d.ts +9 -0
  115. package/dist/modules/deals/events.d.ts.map +1 -1
  116. package/dist/modules/deals/events.js +4 -0
  117. package/dist/modules/deals/events.js.map +1 -1
  118. package/dist/modules/deals/index.d.ts.map +1 -1
  119. package/dist/modules/deals/index.js +4 -2
  120. package/dist/modules/deals/index.js.map +1 -1
  121. package/dist/modules/deals/routes.d.ts +2 -0
  122. package/dist/modules/deals/routes.d.ts.map +1 -1
  123. package/dist/modules/deals/routes.js +8 -1
  124. package/dist/modules/deals/routes.js.map +1 -1
  125. package/dist/modules/deals/schema.d.ts +34 -0
  126. package/dist/modules/deals/schema.d.ts.map +1 -1
  127. package/dist/modules/deals/schema.js +7 -2
  128. package/dist/modules/deals/schema.js.map +1 -1
  129. package/dist/modules/deals/service.d.ts.map +1 -1
  130. package/dist/modules/deals/service.js +2 -0
  131. package/dist/modules/deals/service.js.map +1 -1
  132. package/dist/modules/deals/tools.d.ts +2 -1
  133. package/dist/modules/deals/tools.d.ts.map +1 -1
  134. package/dist/modules/deals/tools.js +3 -1
  135. package/dist/modules/deals/tools.js.map +1 -1
  136. package/dist/modules/decisions/repository.d.ts +2 -1
  137. package/dist/modules/decisions/repository.d.ts.map +1 -1
  138. package/dist/modules/decisions/repository.js +9 -1
  139. package/dist/modules/decisions/repository.js.map +1 -1
  140. package/dist/modules/enquiries/events.d.ts +6 -2
  141. package/dist/modules/enquiries/events.d.ts.map +1 -1
  142. package/dist/modules/enquiries/events.js +5 -1
  143. package/dist/modules/enquiries/events.js.map +1 -1
  144. package/dist/modules/enquiries/index.d.ts.map +1 -1
  145. package/dist/modules/enquiries/index.js +4 -2
  146. package/dist/modules/enquiries/index.js.map +1 -1
  147. package/dist/modules/enquiries/routes.d.ts +2 -0
  148. package/dist/modules/enquiries/routes.d.ts.map +1 -1
  149. package/dist/modules/enquiries/routes.js +9 -9
  150. package/dist/modules/enquiries/routes.js.map +1 -1
  151. package/dist/modules/enquiries/schema.d.ts +36 -1
  152. package/dist/modules/enquiries/schema.d.ts.map +1 -1
  153. package/dist/modules/enquiries/schema.js +9 -3
  154. package/dist/modules/enquiries/schema.js.map +1 -1
  155. package/dist/modules/enquiries/service.d.ts +0 -8
  156. package/dist/modules/enquiries/service.d.ts.map +1 -1
  157. package/dist/modules/enquiries/service.js +3 -95
  158. package/dist/modules/enquiries/service.js.map +1 -1
  159. package/dist/modules/enquiries/tools.d.ts +2 -1
  160. package/dist/modules/enquiries/tools.d.ts.map +1 -1
  161. package/dist/modules/enquiries/tools.js +4 -16
  162. package/dist/modules/enquiries/tools.js.map +1 -1
  163. package/dist/modules/forms/applyMappedFields.d.ts +20 -0
  164. package/dist/modules/forms/applyMappedFields.d.ts.map +1 -0
  165. package/dist/modules/forms/applyMappedFields.js +169 -0
  166. package/dist/modules/forms/applyMappedFields.js.map +1 -0
  167. package/dist/modules/forms/applySubmissionFields.d.ts +18 -0
  168. package/dist/modules/forms/applySubmissionFields.d.ts.map +1 -0
  169. package/dist/modules/forms/applySubmissionFields.js +230 -0
  170. package/dist/modules/forms/applySubmissionFields.js.map +1 -0
  171. package/dist/modules/forms/fields.d.ts +7 -2
  172. package/dist/modules/forms/fields.d.ts.map +1 -1
  173. package/dist/modules/forms/fields.js +17 -6
  174. package/dist/modules/forms/fields.js.map +1 -1
  175. package/dist/modules/forms/mapping.d.ts +8 -2
  176. package/dist/modules/forms/mapping.d.ts.map +1 -1
  177. package/dist/modules/forms/mapping.js +11 -0
  178. package/dist/modules/forms/mapping.js.map +1 -1
  179. package/dist/modules/forms/repository.d.ts +0 -5
  180. package/dist/modules/forms/repository.d.ts.map +1 -1
  181. package/dist/modules/forms/repository.js +0 -2
  182. package/dist/modules/forms/repository.js.map +1 -1
  183. package/dist/modules/forms/routes.d.ts +2 -30
  184. package/dist/modules/forms/routes.d.ts.map +1 -1
  185. package/dist/modules/forms/routes.js +2 -2
  186. package/dist/modules/forms/routes.js.map +1 -1
  187. package/dist/modules/forms/schema.d.ts.map +1 -1
  188. package/dist/modules/forms/schema.js +1 -2
  189. package/dist/modules/forms/schema.js.map +1 -1
  190. package/dist/modules/forms/service.d.ts.map +1 -1
  191. package/dist/modules/forms/service.js +18 -3
  192. package/dist/modules/forms/service.js.map +1 -1
  193. package/dist/modules/forms/starters.d.ts +31 -0
  194. package/dist/modules/forms/starters.d.ts.map +1 -0
  195. package/dist/modules/forms/starters.js +94 -0
  196. package/dist/modules/forms/starters.js.map +1 -0
  197. package/dist/modules/forms/submission.d.ts +1 -0
  198. package/dist/modules/forms/submission.d.ts.map +1 -1
  199. package/dist/modules/forms/submission.js +58 -37
  200. package/dist/modules/forms/submission.js.map +1 -1
  201. package/dist/modules/import-export/customFieldCsv.d.ts +9 -0
  202. package/dist/modules/import-export/customFieldCsv.d.ts.map +1 -0
  203. package/dist/modules/import-export/customFieldCsv.js +39 -0
  204. package/dist/modules/import-export/customFieldCsv.js.map +1 -0
  205. package/dist/modules/import-export/customFieldImport.d.ts +19 -0
  206. package/dist/modules/import-export/customFieldImport.d.ts.map +1 -0
  207. package/dist/modules/import-export/customFieldImport.js +76 -0
  208. package/dist/modules/import-export/customFieldImport.js.map +1 -0
  209. package/dist/modules/import-export/drafts.d.ts +57 -3
  210. package/dist/modules/import-export/drafts.d.ts.map +1 -1
  211. package/dist/modules/import-export/drafts.js +65 -3
  212. package/dist/modules/import-export/drafts.js.map +1 -1
  213. package/dist/modules/import-export/exportRows.d.ts +15 -13
  214. package/dist/modules/import-export/exportRows.d.ts.map +1 -1
  215. package/dist/modules/import-export/exportRows.js +99 -26
  216. package/dist/modules/import-export/exportRows.js.map +1 -1
  217. package/dist/modules/import-export/plan.d.ts +42 -11
  218. package/dist/modules/import-export/plan.d.ts.map +1 -1
  219. package/dist/modules/import-export/plan.js +198 -56
  220. package/dist/modules/import-export/plan.js.map +1 -1
  221. package/dist/modules/import-export/repository.d.ts +58 -2
  222. package/dist/modules/import-export/repository.d.ts.map +1 -1
  223. package/dist/modules/import-export/repository.js +201 -12
  224. package/dist/modules/import-export/repository.js.map +1 -1
  225. package/dist/modules/import-export/routes.d.ts.map +1 -1
  226. package/dist/modules/import-export/routes.js +5 -5
  227. package/dist/modules/import-export/routes.js.map +1 -1
  228. package/dist/modules/import-export/service.d.ts +8 -3
  229. package/dist/modules/import-export/service.d.ts.map +1 -1
  230. package/dist/modules/import-export/service.js +133 -40
  231. package/dist/modules/import-export/service.js.map +1 -1
  232. package/dist/modules/import-export/streams.d.ts +2 -2
  233. package/dist/modules/import-export/streams.d.ts.map +1 -1
  234. package/dist/modules/import-export/streams.js +50 -9
  235. package/dist/modules/import-export/streams.js.map +1 -1
  236. package/dist/modules/import-export/tools.d.ts.map +1 -1
  237. package/dist/modules/import-export/tools.js +13 -7
  238. package/dist/modules/import-export/tools.js.map +1 -1
  239. package/dist/modules/import-export/validation.d.ts +5 -1
  240. package/dist/modules/import-export/validation.d.ts.map +1 -1
  241. package/dist/modules/import-export/validation.js +89 -2
  242. package/dist/modules/import-export/validation.js.map +1 -1
  243. package/dist/modules/import-export/writes.d.ts +2 -1
  244. package/dist/modules/import-export/writes.d.ts.map +1 -1
  245. package/dist/modules/import-export/writes.js +358 -25
  246. package/dist/modules/import-export/writes.js.map +1 -1
  247. package/dist/modules/lists/repository.d.ts +0 -1
  248. package/dist/modules/lists/repository.d.ts.map +1 -1
  249. package/dist/modules/lists/repository.js +0 -1
  250. package/dist/modules/lists/repository.js.map +1 -1
  251. package/dist/modules/lists/routes.d.ts +0 -2
  252. package/dist/modules/lists/routes.d.ts.map +1 -1
  253. package/dist/modules/lists/routes.js +0 -8
  254. package/dist/modules/lists/routes.js.map +1 -1
  255. package/dist/modules/lists/schema.d.ts +0 -17
  256. package/dist/modules/lists/schema.d.ts.map +1 -1
  257. package/dist/modules/lists/schema.js +0 -10
  258. package/dist/modules/lists/schema.js.map +1 -1
  259. package/dist/modules/lists/service.d.ts +2 -14
  260. package/dist/modules/lists/service.d.ts.map +1 -1
  261. package/dist/modules/lists/service.js +7 -53
  262. package/dist/modules/lists/service.js.map +1 -1
  263. package/dist/modules/lists/starters.d.ts +12 -0
  264. package/dist/modules/lists/starters.d.ts.map +1 -0
  265. package/dist/modules/lists/starters.js +4 -0
  266. package/dist/modules/lists/starters.js.map +1 -0
  267. package/dist/modules/mcp/protocol.d.ts.map +1 -1
  268. package/dist/modules/mcp/protocol.js +13 -0
  269. package/dist/modules/mcp/protocol.js.map +1 -1
  270. package/dist/modules/notes/repository.d.ts +2 -0
  271. package/dist/modules/notes/repository.d.ts.map +1 -1
  272. package/dist/modules/notes/repository.js +9 -0
  273. package/dist/modules/notes/repository.js.map +1 -1
  274. package/dist/modules/opportunities/events.d.ts +9 -0
  275. package/dist/modules/opportunities/events.d.ts.map +1 -1
  276. package/dist/modules/opportunities/events.js +4 -0
  277. package/dist/modules/opportunities/events.js.map +1 -1
  278. package/dist/modules/opportunities/index.d.ts.map +1 -1
  279. package/dist/modules/opportunities/index.js +4 -2
  280. package/dist/modules/opportunities/index.js.map +1 -1
  281. package/dist/modules/opportunities/routes.d.ts +2 -0
  282. package/dist/modules/opportunities/routes.d.ts.map +1 -1
  283. package/dist/modules/opportunities/routes.js +8 -1
  284. package/dist/modules/opportunities/routes.js.map +1 -1
  285. package/dist/modules/opportunities/schema.d.ts +34 -0
  286. package/dist/modules/opportunities/schema.d.ts.map +1 -1
  287. package/dist/modules/opportunities/schema.js +7 -2
  288. package/dist/modules/opportunities/schema.js.map +1 -1
  289. package/dist/modules/opportunities/service.d.ts.map +1 -1
  290. package/dist/modules/opportunities/service.js +2 -0
  291. package/dist/modules/opportunities/service.js.map +1 -1
  292. package/dist/modules/opportunities/tools.d.ts +2 -1
  293. package/dist/modules/opportunities/tools.d.ts.map +1 -1
  294. package/dist/modules/opportunities/tools.js +3 -1
  295. package/dist/modules/opportunities/tools.js.map +1 -1
  296. package/dist/modules/partnerships/events.d.ts +9 -0
  297. package/dist/modules/partnerships/events.d.ts.map +1 -1
  298. package/dist/modules/partnerships/events.js +4 -0
  299. package/dist/modules/partnerships/events.js.map +1 -1
  300. package/dist/modules/partnerships/index.d.ts.map +1 -1
  301. package/dist/modules/partnerships/index.js +4 -2
  302. package/dist/modules/partnerships/index.js.map +1 -1
  303. package/dist/modules/partnerships/routes.d.ts +2 -0
  304. package/dist/modules/partnerships/routes.d.ts.map +1 -1
  305. package/dist/modules/partnerships/routes.js +8 -1
  306. package/dist/modules/partnerships/routes.js.map +1 -1
  307. package/dist/modules/partnerships/schema.d.ts +34 -0
  308. package/dist/modules/partnerships/schema.d.ts.map +1 -1
  309. package/dist/modules/partnerships/schema.js +7 -2
  310. package/dist/modules/partnerships/schema.js.map +1 -1
  311. package/dist/modules/partnerships/service.d.ts.map +1 -1
  312. package/dist/modules/partnerships/service.js +2 -0
  313. package/dist/modules/partnerships/service.js.map +1 -1
  314. package/dist/modules/partnerships/tools.d.ts +2 -1
  315. package/dist/modules/partnerships/tools.d.ts.map +1 -1
  316. package/dist/modules/partnerships/tools.js +3 -1
  317. package/dist/modules/partnerships/tools.js.map +1 -1
  318. package/dist/modules/people/personConsentWrites.d.ts +1 -1
  319. package/dist/modules/people/schema.js +1 -1
  320. package/dist/modules/people/schema.js.map +1 -1
  321. package/dist/modules/plans/repository.d.ts +2 -0
  322. package/dist/modules/plans/repository.d.ts.map +1 -1
  323. package/dist/modules/plans/repository.js +9 -0
  324. package/dist/modules/plans/repository.js.map +1 -1
  325. package/dist/modules/raises/events.d.ts +9 -0
  326. package/dist/modules/raises/events.d.ts.map +1 -1
  327. package/dist/modules/raises/events.js +4 -0
  328. package/dist/modules/raises/events.js.map +1 -1
  329. package/dist/modules/raises/index.d.ts.map +1 -1
  330. package/dist/modules/raises/index.js +4 -2
  331. package/dist/modules/raises/index.js.map +1 -1
  332. package/dist/modules/raises/routes.d.ts +2 -0
  333. package/dist/modules/raises/routes.d.ts.map +1 -1
  334. package/dist/modules/raises/routes.js +8 -1
  335. package/dist/modules/raises/routes.js.map +1 -1
  336. package/dist/modules/raises/schema.d.ts +34 -0
  337. package/dist/modules/raises/schema.d.ts.map +1 -1
  338. package/dist/modules/raises/schema.js +7 -2
  339. package/dist/modules/raises/schema.js.map +1 -1
  340. package/dist/modules/raises/service.d.ts.map +1 -1
  341. package/dist/modules/raises/service.js +2 -0
  342. package/dist/modules/raises/service.js.map +1 -1
  343. package/dist/modules/raises/tools.d.ts +2 -1
  344. package/dist/modules/raises/tools.d.ts.map +1 -1
  345. package/dist/modules/raises/tools.js +3 -1
  346. package/dist/modules/raises/tools.js.map +1 -1
  347. package/dist/modules/workspace/routes.d.ts +0 -2
  348. package/dist/modules/workspace/routes.d.ts.map +1 -1
  349. package/dist/modules/workspace/routes.js +0 -6
  350. package/dist/modules/workspace/routes.js.map +1 -1
  351. package/dist/modules/workspace/schema.d.ts +0 -34
  352. package/dist/modules/workspace/schema.d.ts.map +1 -1
  353. package/dist/modules/workspace/schema.js +0 -2
  354. package/dist/modules/workspace/schema.js.map +1 -1
  355. package/dist/modules/workspace/seedStarterForms.d.ts +17 -0
  356. package/dist/modules/workspace/seedStarterForms.d.ts.map +1 -0
  357. package/dist/modules/workspace/seedStarterForms.js +71 -0
  358. package/dist/modules/workspace/seedStarterForms.js.map +1 -0
  359. package/dist/modules/workspace/service.d.ts +0 -4
  360. package/dist/modules/workspace/service.d.ts.map +1 -1
  361. package/dist/modules/workspace/service.js +20 -10
  362. package/dist/modules/workspace/service.js.map +1 -1
  363. package/dist/modules/workspace/tools.js +1 -1
  364. package/dist/modules/workspace/tools.js.map +1 -1
  365. package/dist/runtime/module.d.ts +63 -1
  366. package/dist/runtime/module.d.ts.map +1 -1
  367. package/dist/runtime/registry.d.ts.map +1 -1
  368. package/dist/runtime/registry.js +40 -2
  369. package/dist/runtime/registry.js.map +1 -1
  370. package/dist/testing/fixtures.d.ts.map +1 -1
  371. package/dist/testing/fixtures.js +1 -0
  372. package/dist/testing/fixtures.js.map +1 -1
  373. package/migrations/0037_drop_lists_consent_purpose_id.sql +2 -0
  374. package/migrations/0038_smart_white_queen.sql +2 -0
  375. package/migrations/0039_form_map_to_open.sql +1 -0
  376. package/migrations/0040_pipeline_converted_to.sql +20 -0
  377. package/migrations/0041_api_key_scopes.sql +1 -0
  378. package/migrations/0042_drop_workspace_identity.sql +2 -0
  379. package/migrations/0043_external_sign_in.sql +2 -0
  380. package/migrations/meta/0038_snapshot.json +6554 -0
  381. package/migrations/meta/_journal.json +49 -0
  382. package/package.json +2 -2
  383. package/src/app.ts +3 -0
  384. package/src/index.ts +13 -1
  385. package/src/lib/actor.ts +3 -1
  386. package/src/lib/apiKeyScopes.ts +213 -0
  387. package/src/lib/errors.ts +30 -0
  388. package/src/modules/activities/repository.ts +24 -0
  389. package/src/modules/agent-tasks/repository.ts +24 -0
  390. package/src/modules/api-keys/routes.ts +5 -0
  391. package/src/modules/api-keys/schema.ts +2 -0
  392. package/src/modules/api-keys/scopeMiddleware.ts +30 -0
  393. package/src/modules/api-keys/service.ts +36 -2
  394. package/src/modules/auth/credentials.ts +4 -0
  395. package/src/modules/auth/index.ts +25 -4
  396. package/src/modules/auth/repository.ts +2 -1
  397. package/src/modules/auth/routes.ts +2 -8
  398. package/src/modules/auth/schema.ts +12 -1
  399. package/src/modules/auth/service.ts +143 -6
  400. package/src/modules/auth/session.ts +14 -0
  401. package/src/modules/consent-purposes/index.ts +1 -1
  402. package/src/modules/consent-purposes/schema.ts +3 -3
  403. package/src/modules/consent-purposes/service.ts +7 -16
  404. package/src/modules/consent-purposes/starters.ts +1 -1
  405. package/src/modules/consent-purposes/tools.ts +1 -1
  406. package/src/modules/conversions/clearPointers.ts +81 -0
  407. package/src/modules/conversions/factory.ts +20 -0
  408. package/src/modules/conversions/fieldMap.ts +548 -0
  409. package/src/modules/conversions/index.ts +6 -0
  410. package/src/modules/conversions/mcp.ts +71 -0
  411. package/src/modules/conversions/repoint.ts +146 -0
  412. package/src/modules/conversions/routes.ts +68 -0
  413. package/src/modules/conversions/service.ts +310 -0
  414. package/src/modules/conversions/wire.ts +42 -0
  415. package/src/modules/deals/events.ts +9 -0
  416. package/src/modules/deals/index.ts +4 -2
  417. package/src/modules/deals/routes.ts +11 -1
  418. package/src/modules/deals/schema.ts +10 -2
  419. package/src/modules/deals/service.ts +3 -0
  420. package/src/modules/deals/tools.ts +9 -1
  421. package/src/modules/decisions/repository.ts +23 -1
  422. package/src/modules/enquiries/events.ts +8 -2
  423. package/src/modules/enquiries/index.ts +4 -2
  424. package/src/modules/enquiries/routes.ts +11 -13
  425. package/src/modules/enquiries/schema.ts +12 -3
  426. package/src/modules/enquiries/service.ts +3 -150
  427. package/src/modules/enquiries/tools.ts +9 -18
  428. package/src/modules/forms/applyMappedFields.ts +248 -0
  429. package/src/modules/forms/applySubmissionFields.ts +435 -0
  430. package/src/modules/forms/fields.ts +31 -7
  431. package/src/modules/forms/mapping.ts +20 -3
  432. package/src/modules/forms/repository.ts +0 -7
  433. package/src/modules/forms/routes.ts +1 -2
  434. package/src/modules/forms/schema.ts +0 -2
  435. package/src/modules/forms/service.ts +31 -4
  436. package/src/modules/forms/starters.ts +129 -0
  437. package/src/modules/forms/submission.ts +135 -69
  438. package/src/modules/import-export/customFieldCsv.ts +55 -0
  439. package/src/modules/import-export/customFieldImport.ts +111 -0
  440. package/src/modules/import-export/drafts.ts +151 -3
  441. package/src/modules/import-export/exportRows.ts +222 -68
  442. package/src/modules/import-export/plan.ts +321 -67
  443. package/src/modules/import-export/repository.ts +356 -14
  444. package/src/modules/import-export/routes.ts +8 -8
  445. package/src/modules/import-export/service.ts +208 -45
  446. package/src/modules/import-export/streams.ts +95 -13
  447. package/src/modules/import-export/tools.ts +14 -6
  448. package/src/modules/import-export/validation.ts +128 -0
  449. package/src/modules/import-export/writes.ts +559 -27
  450. package/src/modules/lists/repository.ts +0 -2
  451. package/src/modules/lists/routes.ts +0 -8
  452. package/src/modules/lists/schema.ts +0 -10
  453. package/src/modules/lists/service.ts +9 -107
  454. package/src/modules/lists/starters.ts +16 -0
  455. package/src/modules/mcp/protocol.ts +18 -0
  456. package/src/modules/notes/repository.ts +24 -0
  457. package/src/modules/opportunities/events.ts +9 -0
  458. package/src/modules/opportunities/index.ts +4 -2
  459. package/src/modules/opportunities/routes.ts +11 -1
  460. package/src/modules/opportunities/schema.ts +10 -2
  461. package/src/modules/opportunities/service.ts +3 -0
  462. package/src/modules/opportunities/tools.ts +9 -1
  463. package/src/modules/partnerships/events.ts +9 -0
  464. package/src/modules/partnerships/index.ts +4 -2
  465. package/src/modules/partnerships/routes.ts +11 -1
  466. package/src/modules/partnerships/schema.ts +10 -2
  467. package/src/modules/partnerships/service.ts +3 -0
  468. package/src/modules/partnerships/tools.ts +9 -1
  469. package/src/modules/people/personConsentWrites.ts +1 -1
  470. package/src/modules/people/schema.ts +1 -1
  471. package/src/modules/plans/repository.ts +24 -0
  472. package/src/modules/raises/events.ts +9 -0
  473. package/src/modules/raises/index.ts +4 -2
  474. package/src/modules/raises/routes.ts +11 -1
  475. package/src/modules/raises/schema.ts +10 -2
  476. package/src/modules/raises/service.ts +3 -0
  477. package/src/modules/raises/tools.ts +9 -1
  478. package/src/modules/workspace/routes.ts +0 -6
  479. package/src/modules/workspace/schema.ts +0 -2
  480. package/src/modules/workspace/seedStarterForms.ts +98 -0
  481. package/src/modules/workspace/service.ts +23 -15
  482. package/src/modules/workspace/tools.ts +1 -1
  483. package/src/runtime/module.ts +70 -1
  484. package/src/runtime/registry.ts +54 -0
  485. package/src/testing/fixtures.ts +1 -0
@@ -5,6 +5,7 @@ import type { KelpieModule } from '../../runtime/module.ts'
5
5
  import { mountAuthRoutes } from './routes.ts'
6
6
  import * as schema from './schema.ts'
7
7
  import { createAuthService } from './service.ts'
8
+ import { describeClient, writeSessionCookie } from './session.ts'
8
9
 
9
10
  /**
10
11
  * Accounts, sessions, and password recovery.
@@ -38,6 +39,11 @@ export function createAuthModule(migrationsDirectory: string): KelpieModule {
38
39
  appBaseUrl,
39
40
  })
40
41
 
42
+ // `Secure` everywhere except development. A test host reaches the API
43
+ // over http through a test client, which stores the cookie regardless of
44
+ // the flag, so test is not excluded.
45
+ const cookie = { secure: NODE_ENV !== 'development' }
46
+
41
47
  context.schema(schema, migrationsDirectory)
42
48
 
43
49
  context.routes((router) => {
@@ -45,11 +51,26 @@ export function createAuthModule(migrationsDirectory: string): KelpieModule {
45
51
  db: context.db,
46
52
  now: context.now,
47
53
  service,
48
- // `Secure` everywhere except development. A test host reaches the API
49
- // over http through a test client, which stores the cookie regardless
50
- // of the flag, so test is not excluded.
51
- cookie: { secure: NODE_ENV !== 'development' },
54
+ cookie,
55
+ })
56
+ })
57
+
58
+ // How a module signs a browser in. It verifies an identity its own way
59
+ // and hands it here; everything from the cookie onwards is what a
60
+ // password sign-in does, through the same helper with the same flags.
61
+ context.provideExternalSignIn(async (honoContext, identity) => {
62
+ const issued = await service.completeExternalSignIn({
63
+ ...identity,
64
+ ...describeClient(honoContext),
52
65
  })
66
+
67
+ writeSessionCookie(honoContext, issued.sessionToken, cookie)
68
+
69
+ return {
70
+ account: issued.account,
71
+ created: issued.created,
72
+ activeWorkspaceId: issued.activeWorkspaceId,
73
+ }
53
74
  })
54
75
 
55
76
  return Promise.resolve()
@@ -48,10 +48,11 @@ export async function insertUser(db: Queryable, values: typeof users.$inferInser
48
48
  return created
49
49
  }
50
50
 
51
+ /** `null` clears the password, leaving the account reachable only through a module or a reset. */
51
52
  export async function updateUserPassword(
52
53
  db: Queryable,
53
54
  userId: string,
54
- passwordHash: string,
55
+ passwordHash: string | null,
55
56
  now: Date,
56
57
  ): Promise<void> {
57
58
  await db
@@ -11,7 +11,7 @@ import { resolveActorFrom } from './credentials.ts'
11
11
  import type { CredentialDependencies } from './credentials.ts'
12
12
  import type { PreferenceValues } from './preferences.ts'
13
13
  import type { AccountView, AuthService, IssuedSession, SessionView } from './service.ts'
14
- import { clearSessionCookie, writeSessionCookie } from './session.ts'
14
+ import { clearSessionCookie, describeClient, writeSessionCookie } from './session.ts'
15
15
  import type { SessionCookieOptions } from './session.ts'
16
16
 
17
17
  /**
@@ -108,13 +108,6 @@ async function readBody<T>(context: Context, schema: z.ZodType<T>): Promise<T> {
108
108
  return parsed.data
109
109
  }
110
110
 
111
- /** The device and location shown on the Security page come from the request itself. */
112
- function describeClient(context: Context): { device?: string; location?: string } {
113
- const userAgent = context.req.header('User-Agent')
114
-
115
- return userAgent === undefined ? {} : { device: userAgent }
116
- }
117
-
118
111
  function accountResponse(issued: IssuedSession): Record<string, unknown> {
119
112
  return {
120
113
  account: accountBody(issued.account),
@@ -149,6 +142,7 @@ function sessionResponse(session: SessionView): Record<string, unknown> {
149
142
  location: session.location,
150
143
  last_active_at: session.lastActiveAt.toISOString(),
151
144
  current: session.current,
145
+ signed_in_via: session.signedInVia,
152
146
  }
153
147
  }
154
148
 
@@ -18,7 +18,12 @@ export const users = pgTable('users', {
18
18
  id: primaryId(),
19
19
  email: citext('email').notNull().unique(),
20
20
  name: text('name').notNull(),
21
- passwordHash: text('password_hash').notNull(),
21
+ /**
22
+ * Null for an account that has only ever signed in through an identity
23
+ * module. Password sign-in is refused while it is null; a password is set
24
+ * through the reset flow, whose emailed link proves control of the address.
25
+ */
26
+ passwordHash: text('password_hash'),
22
27
  /** Null until the address is verified, either by confirming a token or by accepting a workspace invite. */
23
28
  emailVerifiedAt: moment('email_verified_at'),
24
29
  createdAt: createdAt(),
@@ -34,6 +39,12 @@ export const sessions = pgTable('sessions', {
34
39
  tokenHash: text('token_hash').notNull().unique(),
35
40
  device: text('device'),
36
41
  location: text('location'),
42
+ /**
43
+ * Which module completed this sign-in (`sign-in:google`), null for a password
44
+ * one. The Security page lists sessions, and a person should be able to tell
45
+ * the two apart.
46
+ */
47
+ signedInVia: text('signed_in_via'),
37
48
  lastActiveAt: moment('last_active_at').notNull().defaultNow(),
38
49
  expiresAt: moment('expires_at').notNull(),
39
50
  createdAt: createdAt(),
@@ -1,12 +1,13 @@
1
1
  import { APP_LINK_PATHS, buildAppLink } from '../../lib/appUrl.ts'
2
2
  import type { EmailSender } from '../../lib/email.ts'
3
3
  import { renderEmail } from '../../lib/emailContent.ts'
4
- import { AppError } from '../../lib/errors.ts'
4
+ import { AppError, ExternalSignInError } from '../../lib/errors.ts'
5
5
  import type { IdFactory } from '../../lib/ids.ts'
6
6
  import { UNIQUE_VIOLATION, postgresErrorCode } from '../../lib/database.ts'
7
7
  import type { Database } from '../../lib/database.ts'
8
8
  import { MINIMUM_PASSWORD_LENGTH, hashPassword, verifyPassword } from '../../lib/passwords.ts'
9
9
  import { generateToken, hashToken } from '../../lib/tokens.ts'
10
+ import type { VerifiedIdentity } from '../../runtime/module.ts'
10
11
  import type { TransactionScope } from '../../runtime/transaction.ts'
11
12
  import type { SessionActor } from './actor.ts'
12
13
  import { DEFAULT_PREFERENCES, applyPreferenceChanges } from './preferences.ts'
@@ -54,12 +55,19 @@ export interface IssuedSession {
54
55
  readonly activeWorkspaceId: string | null
55
56
  }
56
57
 
58
+ /** What `completeExternalSignIn` returns: an issued session plus whether it made the account. */
59
+ export interface ExternalIssuedSession extends IssuedSession {
60
+ readonly created: boolean
61
+ }
62
+
57
63
  export interface SessionView {
58
64
  readonly id: string
59
65
  readonly device: string | null
60
66
  readonly location: string | null
61
67
  readonly lastActiveAt: Date
62
68
  readonly current: boolean
69
+ /** The module that signed this session in, or null for a password sign-in. */
70
+ readonly signedInVia: string | null
63
71
  }
64
72
 
65
73
  export interface SignUpInput {
@@ -85,6 +93,12 @@ export interface LogInInput {
85
93
  readonly location?: string
86
94
  }
87
95
 
96
+ /** A `VerifiedIdentity` plus what the request itself says about the client. */
97
+ export interface ExternalSignInInput extends VerifiedIdentity {
98
+ readonly device?: string
99
+ readonly location?: string
100
+ }
101
+
88
102
  function toAccountView(user: repository.UserRecord): AccountView {
89
103
  return { id: user.id, email: user.email, name: user.name, emailVerified: user.emailVerifiedAt !== null }
90
104
  }
@@ -105,6 +119,16 @@ function normaliseEmail(email: string): string {
105
119
  return email.trim().toLowerCase()
106
120
  }
107
121
 
122
+ /**
123
+ * The name to use when a provider sends none. `users.name` is not nullable and
124
+ * an address is the only other thing an identity is guaranteed to carry.
125
+ */
126
+ function localPartOf(email: string): string {
127
+ const [local] = email.split('@')
128
+
129
+ return local === undefined || local.length === 0 ? email : local
130
+ }
131
+
108
132
  /**
109
133
  * Rejects a field that is only whitespace.
110
134
  *
@@ -128,6 +152,12 @@ export interface AuthService {
128
152
  /** Creates the account only. The first workspace comes from onboarding. */
129
153
  signUp(input: SignUpInput): Promise<IssuedSession>
130
154
  logIn(input: LogInInput): Promise<IssuedSession>
155
+ /**
156
+ * Signs in an identity a module verified elsewhere, provisioning the account
157
+ * when the module asked for it. Never sends a verification email: the
158
+ * provider already proved control of the address.
159
+ */
160
+ completeExternalSignIn(input: ExternalSignInInput): Promise<ExternalIssuedSession>
131
161
  logOut(actor: SessionActor): Promise<void>
132
162
  getAccount(actor: SessionActor): Promise<AccountView>
133
163
  updateAccount(actor: SessionActor, changes: UpdateAccountChanges): Promise<AccountView>
@@ -167,6 +197,8 @@ export function createAuthService(dependencies: AuthDependencies): AuthService {
167
197
  user: repository.UserRecord,
168
198
  device: string | undefined,
169
199
  location: string | undefined,
200
+ /** The module that verified the identity, or null for a password sign-in. */
201
+ signedInVia: string | null = null,
170
202
  ): Promise<IssuedSession> {
171
203
  const now = dependencies.now()
172
204
  const token = newToken()
@@ -180,6 +212,7 @@ export function createAuthService(dependencies: AuthDependencies): AuthService {
180
212
  tokenHash: hashToken(token),
181
213
  device: device ?? null,
182
214
  location: location ?? null,
215
+ signedInVia,
183
216
  lastActiveAt: now,
184
217
  expiresAt,
185
218
  })
@@ -269,17 +302,113 @@ export function createAuthService(dependencies: AuthDependencies): AuthService {
269
302
  async logIn(input: LogInInput): Promise<IssuedSession> {
270
303
  const user = await repository.findUserByEmail(dependencies.db, normaliseEmail(input.email))
271
304
 
272
- // Hash a throwaway password when the user is unknown, so a missing account
273
- // and a wrong password take the same time to answer.
305
+ // Hash a throwaway password when the account is unknown or has no
306
+ // password, so all three cases take the same time to answer.
274
307
  const storedHash = user?.passwordHash ?? (await hashPassword('not-a-real-password-placeholder'))
275
-
276
- if (!(await verifyPassword(storedHash, input.password)) || user === undefined) {
308
+ // Always run the verify, even when the hash is the placeholder: skipping
309
+ // it would make a passwordless account answer faster than a wrong one.
310
+ const matches = await verifyPassword(storedHash, input.password)
311
+
312
+ // The null check is the guard, not the placeholder hash. Without it,
313
+ // whoever guessed the placeholder string would sign in to every account
314
+ // that has no password.
315
+ if (user === undefined || user.passwordHash === null || !matches) {
277
316
  throw AppError.unauthorized('Email or password is incorrect')
278
317
  }
279
318
 
280
319
  return dependencies.transaction(({ tx }) => issueSession(tx, user, input.device, input.location))
281
320
  },
282
321
 
322
+ /**
323
+ * Signs in an identity a module already verified.
324
+ *
325
+ * Core does not redo the verification and never learns the protocol. What
326
+ * it owns is everything downstream: finding or provisioning the account,
327
+ * issuing the session, and recording which module vouched for it.
328
+ */
329
+ async completeExternalSignIn(input: ExternalSignInInput): Promise<ExternalIssuedSession> {
330
+ // An unverified address is not an identity. A provider that does not say
331
+ // it checked is treated as not having checked.
332
+ if (!input.emailVerified) {
333
+ throw new ExternalSignInError('email_unverified')
334
+ }
335
+
336
+ const email = requireText(normaliseEmail(input.email), 'email')
337
+ const name = requireText(input.name ?? localPartOf(email), 'name')
338
+ const now = dependencies.now()
339
+
340
+ const link = async (
341
+ tx: repository.Queryable,
342
+ user: repository.UserRecord,
343
+ ): Promise<ExternalIssuedSession> => {
344
+ // Signing in through a provider on an account whose address was never
345
+ // confirmed is the pre-registration takeover case: somebody registered
346
+ // this address with a password and never proved they own it. The
347
+ // provider just proved the opposite. Drop that password and every
348
+ // session it opened; the real owner sets a new one through a reset.
349
+ if (user.emailVerifiedAt === null) {
350
+ await repository.updateUserPassword(tx, user.id, null, now)
351
+ await repository.deleteAllSessionsForUser(tx, user.id)
352
+ }
353
+
354
+ // The provider proved control of the address, which is the same thing
355
+ // accepting an invite proves.
356
+ await repository.markEmailVerified(tx, user.id, now)
357
+
358
+ const issued = await issueSession(
359
+ tx,
360
+ { ...user, emailVerifiedAt: user.emailVerifiedAt ?? now },
361
+ input.device,
362
+ input.location,
363
+ input.verifiedBy,
364
+ )
365
+
366
+ return { ...issued, created: false }
367
+ }
368
+
369
+ const existing = await repository.findUserByEmail(dependencies.db, email)
370
+
371
+ if (existing !== undefined) {
372
+ return dependencies.transaction(({ tx }) => link(tx, existing))
373
+ }
374
+
375
+ if (input.provision === 'refuse') {
376
+ throw new ExternalSignInError('unknown_identity')
377
+ }
378
+
379
+ return dependencies.transaction(async ({ tx }) => {
380
+ let user: repository.UserRecord
381
+
382
+ try {
383
+ user = await repository.insertUser(tx, {
384
+ id: dependencies.createId('user'),
385
+ email,
386
+ name,
387
+ passwordHash: null,
388
+ emailVerifiedAt: now,
389
+ })
390
+ } catch (error: unknown) {
391
+ if (postgresErrorCode(error) !== UNIQUE_VIOLATION) {
392
+ throw error
393
+ }
394
+
395
+ // Two first sign-ins for the same address at once. The other one won;
396
+ // this is now an ordinary link.
397
+ const raced = await repository.findUserByEmail(tx, email)
398
+
399
+ if (raced === undefined) {
400
+ throw error
401
+ }
402
+
403
+ return link(tx, raced)
404
+ }
405
+
406
+ const issued = await issueSession(tx, user, input.device, input.location, input.verifiedBy)
407
+
408
+ return { ...issued, created: true }
409
+ })
410
+ },
411
+
283
412
  async logOut(actor: SessionActor): Promise<void> {
284
413
  await repository.deleteSession(dependencies.db, actor.userId, actor.sessionId)
285
414
  },
@@ -308,9 +437,12 @@ export function createAuthService(dependencies: AuthDependencies): AuthService {
308
437
  : { email: requireText(normaliseEmail(changes.email), 'email') }),
309
438
  }
310
439
 
440
+ // A passwordless account cannot prove itself this way. It sets a password
441
+ // through the reset flow first, which is the same bar this check is.
311
442
  if (
312
443
  changes.email !== undefined &&
313
444
  (changes.currentPassword === undefined ||
445
+ user.passwordHash === null ||
314
446
  !(await verifyPassword(user.passwordHash, changes.currentPassword)))
315
447
  ) {
316
448
  throw AppError.unauthorized('Current password is incorrect')
@@ -405,6 +537,7 @@ export function createAuthService(dependencies: AuthDependencies): AuthService {
405
537
  location: record.location,
406
538
  lastActiveAt: record.lastActiveAt,
407
539
  current: record.id === actor.sessionId,
540
+ signedInVia: record.signedInVia,
408
541
  }))
409
542
  },
410
543
 
@@ -494,7 +627,11 @@ export function createAuthService(dependencies: AuthDependencies): AuthService {
494
627
 
495
628
  const user = await repository.findUserById(dependencies.db, actor.userId)
496
629
 
497
- if (user === undefined || !(await verifyPassword(user.passwordHash, currentPassword))) {
630
+ if (
631
+ user === undefined ||
632
+ user.passwordHash === null ||
633
+ !(await verifyPassword(user.passwordHash, currentPassword))
634
+ ) {
498
635
  throw AppError.unauthorized('Current password is incorrect')
499
636
  }
500
637
 
@@ -11,6 +11,20 @@ import type { Context } from 'hono'
11
11
 
12
12
  export const SESSION_COOKIE = 'kelpie_session'
13
13
 
14
+ /**
15
+ * The device and location shown on the Security page come from the request
16
+ * itself.
17
+ *
18
+ * Here rather than in `routes.ts` because the external sign-in handler needs
19
+ * the same description, and a session created by a module should look the same
20
+ * on that page as one created by the login form.
21
+ */
22
+ export function describeClient(context: Context): { device?: string; location?: string } {
23
+ const userAgent = context.req.header('User-Agent')
24
+
25
+ return userAgent === undefined ? {} : { device: userAgent }
26
+ }
27
+
14
28
  /** Thirty days, matching the session row's expiry. */
15
29
  const COOKIE_MAX_AGE_SECONDS = 30 * 24 * 60 * 60
16
30
 
@@ -10,7 +10,7 @@ import { registerConsentPurposesTools } from './tools.ts'
10
10
  * Consent purposes: the workspace catalog every capture site names.
11
11
  *
12
12
  * Structural because Person consent rows carry `purpose_id`, and forms /
13
- * lists / imports each reference one; a disabled module would leave those
13
+ * forms / imports each reference one; a disabled module would leave those
14
14
  * references without a definition to resolve.
15
15
  *
16
16
  * Registers before people, forms, lists, and import-export so their `_fk`
@@ -7,9 +7,9 @@ import { workspaces } from '../workspace/schema.ts'
7
7
  /**
8
8
  * Workspace-defined consent purposes.
9
9
  *
10
- * Every capture site — a form's consent field, a list, an import job, the
11
- * manual override on a Person — names one of these. `slug` is set at create
12
- * and immutable after: a rename would strand every form/list/import that
10
+ * Every capture site — a form's consent field, an import job, the manual
11
+ * override on a Person — names one of these. `slug` is set at create and
12
+ * immutable after: a rename would strand every form/import that names it and
13
13
  * names it and every `person_consents` row that carries it (identified by
14
14
  * the purpose id). The strict PATCH body naturally refuses either as a `422`.
15
15
  *
@@ -20,7 +20,6 @@ import type { Transaction, TransactionScope } from '../../runtime/transaction.ts
20
20
  import type { Actor } from '../auth/actor.ts'
21
21
  import { requireWorkspaceId } from '../auth/actor.ts'
22
22
  import { formFields } from '../forms/schema.ts'
23
- import { lists } from '../lists/schema.ts'
24
23
  import { roleAllows } from '../workspace/roles.ts'
25
24
  import { CONSENT_PURPOSES_LIMIT } from './capabilities.ts'
26
25
  import './events.ts'
@@ -39,7 +38,7 @@ import type {
39
38
  *
40
39
  * Reads are open to any workspace member — every capture site needs to list
41
40
  * them to render its purpose picker. Writes need the admin role: a purpose
42
- * change touches every form, list, and import job that names it, and it is
41
+ * change touches every form and import job that names it, and it is
43
42
  * team-wide config rather than a per-user preference.
44
43
  */
45
44
 
@@ -143,17 +142,16 @@ function validateDefaultStatus(status: string): void {
143
142
  }
144
143
 
145
144
  /**
146
- * A purpose still named by a form's consent field or a list's
147
- * `consent_purpose_id` cannot be deleted. Counted here so the 409 lists both
148
- * types; the FK on either side would refuse the delete on its own but only
149
- * name one.
145
+ * A purpose still named by a form's consent field cannot be deleted. Counted
146
+ * here so the 409 names the referencing type; the FK on either side would
147
+ * refuse the delete on its own but only name one.
150
148
  */
151
149
  async function referencingTypes(
152
150
  db: Database | Transaction,
153
151
  workspaceId: string,
154
152
  id: string,
155
153
  ): Promise<readonly string[]> {
156
- const [fieldRow, listRow] = await Promise.all([
154
+ const [fieldRow] = await Promise.all([
157
155
  db
158
156
  .select({ count: sql<number>`count(*)::int` })
159
157
  .from(formFields)
@@ -165,19 +163,12 @@ async function referencingTypes(
165
163
  sql`${id} = ANY(${formFields.consentPurposeIds})`,
166
164
  ),
167
165
  ),
168
- db
169
- .select({ count: sql<number>`count(*)::int` })
170
- .from(lists)
171
- .where(and(eq(lists.workspaceId, workspaceId), eq(lists.consentPurposeId, id))),
172
166
  ])
173
167
 
174
168
  const referencing: string[] = []
175
169
  if (Number(fieldRow[0]?.count ?? 0) > 0) {
176
170
  referencing.push('form_field')
177
171
  }
178
- if (Number(listRow[0]?.count ?? 0) > 0) {
179
- referencing.push('list')
180
- }
181
172
  return referencing
182
173
  }
183
174
 
@@ -390,7 +381,7 @@ export function createConsentPurposesService(
390
381
  const referencing = await referencingTypes(tx, workspaceId, id)
391
382
  if (referencing.length > 0) {
392
383
  throw AppError.conflict(
393
- 'Remove this purpose from every form and list that names it before deleting it',
384
+ 'Remove this purpose from every form field that names it before deleting it',
394
385
  referencing.map((type) => ({ field: type, message: 'still references this purpose' })),
395
386
  )
396
387
  }
@@ -400,7 +391,7 @@ export function createConsentPurposesService(
400
391
  } catch (error: unknown) {
401
392
  if (postgresErrorCode(error) === FOREIGN_KEY_VIOLATION) {
402
393
  throw AppError.conflict(
403
- 'Remove this purpose from every form and list that names it before deleting it',
394
+ 'Remove this purpose from every form field that names it before deleting it',
404
395
  [{ field: 'id', message: 'still referenced' }],
405
396
  )
406
397
  }
@@ -5,7 +5,7 @@ import type { ConsentPurposeStatus } from '@kelpie/schemas'
5
5
  * owner may rename, reorder, or delete them, and add any others they need.
6
6
  *
7
7
  * Both starters default to `unknown` — silence about a person's wishes is not
8
- * a grant, and every capture site (forms, lists, imports, manual override)
8
+ * a grant, and every capture site (forms, imports, manual override)
9
9
  * writes an explicit `person_consents` row when consent is actually given.
10
10
  */
11
11
 
@@ -31,7 +31,7 @@ export function registerConsentPurposesTools(
31
31
  subject: 'consent purpose',
32
32
  about:
33
33
  'A workspace-defined consent purpose. Read these before writing a person ' +
34
- 'consent or setting a form/list/import purpose_id.',
34
+ 'consent or setting a form/import purpose_id.',
35
35
  service,
36
36
  render: consentPurposeResponse,
37
37
  listArgs,
@@ -0,0 +1,81 @@
1
+ import { and, eq } from 'drizzle-orm'
2
+ import type { PipelineKind } from '@kelpie/schemas'
3
+
4
+ import type { Queryable } from '../../runtime/transaction.ts'
5
+ import { deals } from '../deals/schema.ts'
6
+ import { enquiries } from '../enquiries/schema.ts'
7
+ import { opportunities } from '../opportunities/schema.ts'
8
+ import { partnerships } from '../partnerships/schema.ts'
9
+ import { raises } from '../raises/schema.ts'
10
+
11
+ /**
12
+ * Clears `converted_target_*` on every pipeline record that pointed at a target
13
+ * being deleted. Enquiries also lose `converted_deal_id` when the target is a
14
+ * deal, matching the legacy FK `on delete set null` behaviour and allowing a
15
+ * fresh conversion once the created record is gone.
16
+ */
17
+ export async function clearConversionPointersToTarget(
18
+ tx: Queryable,
19
+ workspaceId: string,
20
+ targetKind: PipelineKind,
21
+ targetId: string,
22
+ ): Promise<void> {
23
+ const match = and(
24
+ eq(deals.workspaceId, workspaceId),
25
+ eq(deals.convertedTargetType, targetKind),
26
+ eq(deals.convertedTargetId, targetId),
27
+ )
28
+
29
+ await tx
30
+ .update(enquiries)
31
+ .set({
32
+ convertedTargetType: null,
33
+ convertedTargetId: null,
34
+ ...(targetKind === 'deal' ? { convertedDealId: null } : {}),
35
+ })
36
+ .where(
37
+ and(
38
+ eq(enquiries.workspaceId, workspaceId),
39
+ eq(enquiries.convertedTargetType, targetKind),
40
+ eq(enquiries.convertedTargetId, targetId),
41
+ ),
42
+ )
43
+
44
+ await tx
45
+ .update(deals)
46
+ .set({ convertedTargetType: null, convertedTargetId: null })
47
+ .where(match)
48
+
49
+ await tx
50
+ .update(opportunities)
51
+ .set({ convertedTargetType: null, convertedTargetId: null })
52
+ .where(
53
+ and(
54
+ eq(opportunities.workspaceId, workspaceId),
55
+ eq(opportunities.convertedTargetType, targetKind),
56
+ eq(opportunities.convertedTargetId, targetId),
57
+ ),
58
+ )
59
+
60
+ await tx
61
+ .update(raises)
62
+ .set({ convertedTargetType: null, convertedTargetId: null })
63
+ .where(
64
+ and(
65
+ eq(raises.workspaceId, workspaceId),
66
+ eq(raises.convertedTargetType, targetKind),
67
+ eq(raises.convertedTargetId, targetId),
68
+ ),
69
+ )
70
+
71
+ await tx
72
+ .update(partnerships)
73
+ .set({ convertedTargetType: null, convertedTargetId: null })
74
+ .where(
75
+ and(
76
+ eq(partnerships.workspaceId, workspaceId),
77
+ eq(partnerships.convertedTargetType, targetKind),
78
+ eq(partnerships.convertedTargetId, targetId),
79
+ ),
80
+ )
81
+ }
@@ -0,0 +1,20 @@
1
+ import type { ModuleContext } from '../../runtime/module.ts'
2
+ import { createActivityRecorder } from '../activities/index.ts'
3
+ import { createCustomFieldValues } from '../custom-fields/index.ts'
4
+ import { createConversionsService } from './service.ts'
5
+ import type { ConversionsService } from './service.ts'
6
+
7
+ /** Build the shared conversion service from a module registration context. */
8
+ export function createConversionsFromContext(context: ModuleContext): ConversionsService {
9
+ return createConversionsService({
10
+ db: context.db,
11
+ transaction: context.transaction,
12
+ createId: context.createId,
13
+ now: context.now,
14
+ recordActivity: createActivityRecorder({
15
+ createId: context.createId,
16
+ now: context.now,
17
+ }),
18
+ customFields: createCustomFieldValues({ db: context.db }),
19
+ })
20
+ }