@kelpie/server 0.11.0 → 0.13.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 (399) hide show
  1. package/dist/app.d.ts +13 -0
  2. package/dist/app.d.ts.map +1 -1
  3. package/dist/app.js +8 -1
  4. package/dist/app.js.map +1 -1
  5. package/dist/boot.d.ts.map +1 -1
  6. package/dist/boot.js +1 -0
  7. package/dist/boot.js.map +1 -1
  8. package/dist/index.d.ts +3 -3
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +1 -1
  11. package/dist/index.js.map +1 -1
  12. package/dist/lib/apiKeyScopes.d.ts.map +1 -1
  13. package/dist/lib/apiKeyScopes.js +6 -0
  14. package/dist/lib/apiKeyScopes.js.map +1 -1
  15. package/dist/lib/columns.d.ts +6 -0
  16. package/dist/lib/columns.d.ts.map +1 -1
  17. package/dist/lib/columns.js +5 -3
  18. package/dist/lib/columns.js.map +1 -1
  19. package/dist/lib/config.d.ts +17 -0
  20. package/dist/lib/config.d.ts.map +1 -1
  21. package/dist/lib/config.js +3 -0
  22. package/dist/lib/config.js.map +1 -1
  23. package/dist/lib/egress.d.ts.map +1 -1
  24. package/dist/lib/egress.js +8 -4
  25. package/dist/lib/egress.js.map +1 -1
  26. package/dist/lib/errors.d.ts +18 -0
  27. package/dist/lib/errors.d.ts.map +1 -1
  28. package/dist/lib/errors.js +25 -0
  29. package/dist/lib/errors.js.map +1 -1
  30. package/dist/lib/ids.d.ts +11 -0
  31. package/dist/lib/ids.d.ts.map +1 -1
  32. package/dist/lib/ids.js +11 -0
  33. package/dist/lib/ids.js.map +1 -1
  34. package/dist/lib/kelpieConfigFile.d.ts +15 -1
  35. package/dist/lib/kelpieConfigFile.d.ts.map +1 -1
  36. package/dist/lib/kelpieConfigFile.js +33 -1
  37. package/dist/lib/kelpieConfigFile.js.map +1 -1
  38. package/dist/modules/agent-tasks/catalog.d.ts.map +1 -1
  39. package/dist/modules/agent-tasks/catalog.js +32 -1
  40. package/dist/modules/agent-tasks/catalog.js.map +1 -1
  41. package/dist/modules/agent-tasks/index.d.ts.map +1 -1
  42. package/dist/modules/agent-tasks/index.js +1 -0
  43. package/dist/modules/agent-tasks/index.js.map +1 -1
  44. package/dist/modules/agent-tasks/resolve.d.ts.map +1 -1
  45. package/dist/modules/agent-tasks/resolve.js +5 -0
  46. package/dist/modules/agent-tasks/resolve.js.map +1 -1
  47. package/dist/modules/agent-tasks/routes.d.ts +2 -0
  48. package/dist/modules/agent-tasks/routes.d.ts.map +1 -1
  49. package/dist/modules/attachedRecords.d.ts +22 -8
  50. package/dist/modules/attachedRecords.d.ts.map +1 -1
  51. package/dist/modules/attachedRecords.js +42 -5
  52. package/dist/modules/attachedRecords.js.map +1 -1
  53. package/dist/modules/auth/index.d.ts.map +1 -1
  54. package/dist/modules/auth/index.js +22 -4
  55. package/dist/modules/auth/index.js.map +1 -1
  56. package/dist/modules/auth/repository.d.ts +16 -1
  57. package/dist/modules/auth/repository.d.ts.map +1 -1
  58. package/dist/modules/auth/repository.js +21 -2
  59. package/dist/modules/auth/repository.js.map +1 -1
  60. package/dist/modules/auth/routes.d.ts.map +1 -1
  61. package/dist/modules/auth/routes.js +32 -6
  62. package/dist/modules/auth/routes.js.map +1 -1
  63. package/dist/modules/auth/schema.d.ts +18 -1
  64. package/dist/modules/auth/schema.d.ts.map +1 -1
  65. package/dist/modules/auth/schema.js +12 -1
  66. package/dist/modules/auth/schema.js.map +1 -1
  67. package/dist/modules/auth/service.d.ts +46 -0
  68. package/dist/modules/auth/service.d.ts.map +1 -1
  69. package/dist/modules/auth/service.js +167 -10
  70. package/dist/modules/auth/service.js.map +1 -1
  71. package/dist/modules/auth/session.d.ts +12 -0
  72. package/dist/modules/auth/session.d.ts.map +1 -1
  73. package/dist/modules/auth/session.js +12 -0
  74. package/dist/modules/auth/session.js.map +1 -1
  75. package/dist/modules/companies/repository.d.ts.map +1 -1
  76. package/dist/modules/companies/repository.js +1 -1
  77. package/dist/modules/companies/repository.js.map +1 -1
  78. package/dist/modules/companies/routes.d.ts +2 -4
  79. package/dist/modules/companies/routes.d.ts.map +1 -1
  80. package/dist/modules/companies/routes.js +6 -11
  81. package/dist/modules/companies/routes.js.map +1 -1
  82. package/dist/modules/companies/schema.d.ts +13 -28
  83. package/dist/modules/companies/schema.d.ts.map +1 -1
  84. package/dist/modules/companies/schema.js +2 -2
  85. package/dist/modules/companies/schema.js.map +1 -1
  86. package/dist/modules/companies/service.d.ts +2 -3
  87. package/dist/modules/companies/service.d.ts.map +1 -1
  88. package/dist/modules/companies/service.js +4 -7
  89. package/dist/modules/companies/service.js.map +1 -1
  90. package/dist/modules/core.d.ts.map +1 -1
  91. package/dist/modules/core.js +2 -0
  92. package/dist/modules/core.js.map +1 -1
  93. package/dist/modules/custom-fields/repository.d.ts.map +1 -1
  94. package/dist/modules/custom-fields/repository.js +2 -0
  95. package/dist/modules/custom-fields/repository.js.map +1 -1
  96. package/dist/modules/custom-fields/routes.d.ts +1 -0
  97. package/dist/modules/custom-fields/routes.d.ts.map +1 -1
  98. package/dist/modules/dashboard/index.d.ts.map +1 -1
  99. package/dist/modules/dashboard/index.js +1 -0
  100. package/dist/modules/dashboard/index.js.map +1 -1
  101. package/dist/modules/dashboard/repository.d.ts +13 -0
  102. package/dist/modules/dashboard/repository.d.ts.map +1 -1
  103. package/dist/modules/dashboard/repository.js +36 -0
  104. package/dist/modules/dashboard/repository.js.map +1 -1
  105. package/dist/modules/dashboard/routes.d.ts.map +1 -1
  106. package/dist/modules/dashboard/routes.js +13 -0
  107. package/dist/modules/dashboard/routes.js.map +1 -1
  108. package/dist/modules/dashboard/service.d.ts +11 -0
  109. package/dist/modules/dashboard/service.d.ts.map +1 -1
  110. package/dist/modules/dashboard/service.js +24 -1
  111. package/dist/modules/dashboard/service.js.map +1 -1
  112. package/dist/modules/decisions/repository.d.ts.map +1 -1
  113. package/dist/modules/decisions/repository.js +2 -0
  114. package/dist/modules/decisions/repository.js.map +1 -1
  115. package/dist/modules/decisions/routes.d.ts +2 -0
  116. package/dist/modules/decisions/routes.d.ts.map +1 -1
  117. package/dist/modules/events/attendances.d.ts +35 -0
  118. package/dist/modules/events/attendances.d.ts.map +1 -0
  119. package/dist/modules/events/attendances.js +157 -0
  120. package/dist/modules/events/attendances.js.map +1 -0
  121. package/dist/modules/events/catalog.d.ts +35 -0
  122. package/dist/modules/events/catalog.d.ts.map +1 -0
  123. package/dist/modules/events/catalog.js +11 -0
  124. package/dist/modules/events/catalog.js.map +1 -0
  125. package/dist/modules/events/index.d.ts +10 -0
  126. package/dist/modules/events/index.d.ts.map +1 -0
  127. package/dist/modules/events/index.js +47 -0
  128. package/dist/modules/events/index.js.map +1 -0
  129. package/dist/modules/events/repository.d.ts +67 -0
  130. package/dist/modules/events/repository.d.ts.map +1 -0
  131. package/dist/modules/events/repository.js +212 -0
  132. package/dist/modules/events/repository.js.map +1 -0
  133. package/dist/modules/events/routes.d.ts +136 -0
  134. package/dist/modules/events/routes.d.ts.map +1 -0
  135. package/dist/modules/events/routes.js +280 -0
  136. package/dist/modules/events/routes.js.map +1 -0
  137. package/dist/modules/events/schema.d.ts +626 -0
  138. package/dist/modules/events/schema.d.ts.map +1 -0
  139. package/dist/modules/events/schema.js +93 -0
  140. package/dist/modules/events/schema.js.map +1 -0
  141. package/dist/modules/events/service.d.ts +68 -0
  142. package/dist/modules/events/service.d.ts.map +1 -0
  143. package/dist/modules/events/service.js +262 -0
  144. package/dist/modules/events/service.js.map +1 -0
  145. package/dist/modules/events/tools.d.ts +8 -0
  146. package/dist/modules/events/tools.d.ts.map +1 -0
  147. package/dist/modules/events/tools.js +80 -0
  148. package/dist/modules/events/tools.js.map +1 -0
  149. package/dist/modules/forms/applySubmissionFields.d.ts.map +1 -1
  150. package/dist/modules/forms/applySubmissionFields.js +7 -5
  151. package/dist/modules/forms/applySubmissionFields.js.map +1 -1
  152. package/dist/modules/forms/fillAddresses.d.ts +4 -0
  153. package/dist/modules/forms/fillAddresses.d.ts.map +1 -0
  154. package/dist/modules/forms/fillAddresses.js +93 -0
  155. package/dist/modules/forms/fillAddresses.js.map +1 -0
  156. package/dist/modules/forms/repository.d.ts +2 -2
  157. package/dist/modules/forms/repository.d.ts.map +1 -1
  158. package/dist/modules/forms/repository.js.map +1 -1
  159. package/dist/modules/forms/routes.d.ts +2 -0
  160. package/dist/modules/forms/routes.d.ts.map +1 -1
  161. package/dist/modules/forms/routes.js +2 -2
  162. package/dist/modules/forms/routes.js.map +1 -1
  163. package/dist/modules/forms/schema.js +2 -2
  164. package/dist/modules/forms/schema.js.map +1 -1
  165. package/dist/modules/forms/service.js +2 -2
  166. package/dist/modules/forms/service.js.map +1 -1
  167. package/dist/modules/forms/submission.d.ts +1 -0
  168. package/dist/modules/forms/submission.d.ts.map +1 -1
  169. package/dist/modules/forms/submission.js +27 -4
  170. package/dist/modules/forms/submission.js.map +1 -1
  171. package/dist/modules/hiring/roles.d.ts.map +1 -1
  172. package/dist/modules/hiring/roles.js +2 -1
  173. package/dist/modules/hiring/roles.js.map +1 -1
  174. package/dist/modules/import-export/addresses.d.ts +22 -0
  175. package/dist/modules/import-export/addresses.d.ts.map +1 -0
  176. package/dist/modules/import-export/addresses.js +83 -0
  177. package/dist/modules/import-export/addresses.js.map +1 -0
  178. package/dist/modules/import-export/drafts.d.ts +3 -4
  179. package/dist/modules/import-export/drafts.d.ts.map +1 -1
  180. package/dist/modules/import-export/drafts.js +4 -5
  181. package/dist/modules/import-export/drafts.js.map +1 -1
  182. package/dist/modules/import-export/exportRows.d.ts.map +1 -1
  183. package/dist/modules/import-export/exportRows.js +3 -3
  184. package/dist/modules/import-export/exportRows.js.map +1 -1
  185. package/dist/modules/import-export/plan.d.ts.map +1 -1
  186. package/dist/modules/import-export/plan.js +17 -2
  187. package/dist/modules/import-export/plan.js.map +1 -1
  188. package/dist/modules/import-export/presets.d.ts.map +1 -1
  189. package/dist/modules/import-export/presets.js +52 -7
  190. package/dist/modules/import-export/presets.js.map +1 -1
  191. package/dist/modules/import-export/repository.d.ts +3 -4
  192. package/dist/modules/import-export/repository.d.ts.map +1 -1
  193. package/dist/modules/import-export/repository.js +2 -3
  194. package/dist/modules/import-export/repository.js.map +1 -1
  195. package/dist/modules/import-export/writes.d.ts.map +1 -1
  196. package/dist/modules/import-export/writes.js +8 -4
  197. package/dist/modules/import-export/writes.js.map +1 -1
  198. package/dist/modules/lists/routes.d.ts +4 -0
  199. package/dist/modules/lists/routes.d.ts.map +1 -1
  200. package/dist/modules/notes/routes.d.ts +2 -0
  201. package/dist/modules/notes/routes.d.ts.map +1 -1
  202. package/dist/modules/people/repository.d.ts.map +1 -1
  203. package/dist/modules/people/repository.js +1 -1
  204. package/dist/modules/people/repository.js.map +1 -1
  205. package/dist/modules/people/routes.d.ts +2 -2
  206. package/dist/modules/people/routes.js +6 -6
  207. package/dist/modules/people/routes.js.map +1 -1
  208. package/dist/modules/people/schema.d.ts +17 -14
  209. package/dist/modules/people/schema.d.ts.map +1 -1
  210. package/dist/modules/people/schema.js +6 -4
  211. package/dist/modules/people/schema.js.map +1 -1
  212. package/dist/modules/people/service.d.ts +2 -2
  213. package/dist/modules/people/service.d.ts.map +1 -1
  214. package/dist/modules/people/service.js +5 -4
  215. package/dist/modules/people/service.js.map +1 -1
  216. package/dist/modules/plans/repository.d.ts +2 -2
  217. package/dist/modules/plans/repository.d.ts.map +1 -1
  218. package/dist/modules/plans/routes.d.ts +1 -0
  219. package/dist/modules/plans/routes.d.ts.map +1 -1
  220. package/dist/modules/plans/routes.js +4 -4
  221. package/dist/modules/plans/routes.js.map +1 -1
  222. package/dist/modules/plans/schema.d.ts +2 -2
  223. package/dist/modules/plans/schema.d.ts.map +1 -1
  224. package/dist/modules/plans/schema.js +3 -3
  225. package/dist/modules/plans/schema.js.map +1 -1
  226. package/dist/modules/plans/service.d.ts +2 -2
  227. package/dist/modules/plans/service.d.ts.map +1 -1
  228. package/dist/modules/plans/service.js.map +1 -1
  229. package/dist/modules/plans/tools.js +2 -2
  230. package/dist/modules/plans/tools.js.map +1 -1
  231. package/dist/modules/recordTargets.d.ts.map +1 -1
  232. package/dist/modules/recordTargets.js +11 -0
  233. package/dist/modules/recordTargets.js.map +1 -1
  234. package/dist/modules/references.d.ts.map +1 -1
  235. package/dist/modules/references.js +2 -0
  236. package/dist/modules/references.js.map +1 -1
  237. package/dist/modules/sample-data/fixture.d.ts +26 -4
  238. package/dist/modules/sample-data/fixture.d.ts.map +1 -1
  239. package/dist/modules/sample-data/fixture.js +157 -23
  240. package/dist/modules/sample-data/fixture.js.map +1 -1
  241. package/dist/modules/sample-data/index.d.ts +5 -2
  242. package/dist/modules/sample-data/index.d.ts.map +1 -1
  243. package/dist/modules/sample-data/index.js +7 -2
  244. package/dist/modules/sample-data/index.js.map +1 -1
  245. package/dist/modules/sample-data/routes.d.ts.map +1 -1
  246. package/dist/modules/sample-data/routes.js +2 -0
  247. package/dist/modules/sample-data/routes.js.map +1 -1
  248. package/dist/modules/sample-data/service.d.ts +11 -3
  249. package/dist/modules/sample-data/service.d.ts.map +1 -1
  250. package/dist/modules/sample-data/service.js +452 -363
  251. package/dist/modules/sample-data/service.js.map +1 -1
  252. package/dist/modules/sample-data/tools.d.ts.map +1 -1
  253. package/dist/modules/sample-data/tools.js +4 -3
  254. package/dist/modules/sample-data/tools.js.map +1 -1
  255. package/dist/modules/search/index.d.ts +1 -1
  256. package/dist/modules/search/index.d.ts.map +1 -1
  257. package/dist/modules/search/index.js +2 -1
  258. package/dist/modules/search/index.js.map +1 -1
  259. package/dist/modules/search/repository.d.ts +1 -0
  260. package/dist/modules/search/repository.d.ts.map +1 -1
  261. package/dist/modules/search/repository.js +22 -0
  262. package/dist/modules/search/repository.js.map +1 -1
  263. package/dist/modules/search/service.d.ts +2 -2
  264. package/dist/modules/search/service.d.ts.map +1 -1
  265. package/dist/modules/search/service.js +4 -3
  266. package/dist/modules/search/service.js.map +1 -1
  267. package/dist/modules/smtp-email/index.d.ts +12 -4
  268. package/dist/modules/smtp-email/index.d.ts.map +1 -1
  269. package/dist/modules/smtp-email/index.js +25 -5
  270. package/dist/modules/smtp-email/index.js.map +1 -1
  271. package/dist/modules/workspace/repository.d.ts +2 -0
  272. package/dist/modules/workspace/repository.d.ts.map +1 -1
  273. package/dist/modules/workspace/repository.js +12 -0
  274. package/dist/modules/workspace/repository.js.map +1 -1
  275. package/dist/modules/workspace/routes.d.ts.map +1 -1
  276. package/dist/modules/workspace/routes.js +13 -0
  277. package/dist/modules/workspace/routes.js.map +1 -1
  278. package/dist/modules/workspace/service.d.ts +10 -0
  279. package/dist/modules/workspace/service.d.ts.map +1 -1
  280. package/dist/modules/workspace/service.js +34 -10
  281. package/dist/modules/workspace/service.js.map +1 -1
  282. package/dist/modules/workspace/starters.d.ts +17 -2
  283. package/dist/modules/workspace/starters.d.ts.map +1 -1
  284. package/dist/modules/workspace/starters.js +141 -14
  285. package/dist/modules/workspace/starters.js.map +1 -1
  286. package/dist/modules/workspace/workspaceAccessMiddleware.d.ts.map +1 -1
  287. package/dist/modules/workspace/workspaceAccessMiddleware.js +3 -4
  288. package/dist/modules/workspace/workspaceAccessMiddleware.js.map +1 -1
  289. package/dist/runtime/events.d.ts +1 -1
  290. package/dist/runtime/events.d.ts.map +1 -1
  291. package/dist/runtime/events.js +2 -0
  292. package/dist/runtime/events.js.map +1 -1
  293. package/dist/runtime/module.d.ts +71 -1
  294. package/dist/runtime/module.d.ts.map +1 -1
  295. package/dist/runtime/registry.d.ts.map +1 -1
  296. package/dist/runtime/registry.js +142 -9
  297. package/dist/runtime/registry.js.map +1 -1
  298. package/dist/schema/index.d.ts +1 -0
  299. package/dist/schema/index.d.ts.map +1 -1
  300. package/dist/schema/index.js +1 -0
  301. package/dist/schema/index.js.map +1 -1
  302. package/dist/testing/app.d.ts +8 -0
  303. package/dist/testing/app.d.ts.map +1 -1
  304. package/dist/testing/app.js +6 -1
  305. package/dist/testing/app.js.map +1 -1
  306. package/dist/testing/client.d.ts.map +1 -1
  307. package/dist/testing/client.js +9 -1
  308. package/dist/testing/client.js.map +1 -1
  309. package/migrations/0043_external_sign_in.sql +2 -0
  310. package/migrations/0044_events.sql +79 -0
  311. package/migrations/0045_addresses.sql +66 -0
  312. package/migrations/0046_drop_company_website.sql +66 -0
  313. package/migrations/meta/_journal.json +28 -0
  314. package/package.json +3 -3
  315. package/src/app.ts +25 -1
  316. package/src/boot.ts +1 -0
  317. package/src/index.ts +13 -1
  318. package/src/lib/apiKeyScopes.ts +6 -0
  319. package/src/lib/columns.ts +12 -3
  320. package/src/lib/config.ts +21 -0
  321. package/src/lib/egress.ts +8 -4
  322. package/src/lib/errors.ts +37 -0
  323. package/src/lib/ids.ts +11 -0
  324. package/src/lib/kelpieConfigFile.ts +69 -1
  325. package/src/modules/agent-tasks/catalog.ts +33 -1
  326. package/src/modules/agent-tasks/index.ts +1 -0
  327. package/src/modules/agent-tasks/resolve.ts +11 -0
  328. package/src/modules/attachedRecords.ts +85 -13
  329. package/src/modules/auth/index.ts +26 -4
  330. package/src/modules/auth/repository.ts +36 -4
  331. package/src/modules/auth/routes.ts +46 -9
  332. package/src/modules/auth/schema.ts +12 -1
  333. package/src/modules/auth/service.ts +261 -10
  334. package/src/modules/auth/session.ts +14 -0
  335. package/src/modules/companies/repository.ts +1 -0
  336. package/src/modules/companies/routes.ts +6 -11
  337. package/src/modules/companies/schema.ts +3 -3
  338. package/src/modules/companies/service.ts +6 -10
  339. package/src/modules/core.ts +2 -0
  340. package/src/modules/custom-fields/repository.ts +2 -0
  341. package/src/modules/dashboard/index.ts +1 -0
  342. package/src/modules/dashboard/repository.ts +64 -0
  343. package/src/modules/dashboard/routes.ts +15 -0
  344. package/src/modules/dashboard/service.ts +48 -0
  345. package/src/modules/decisions/repository.ts +2 -0
  346. package/src/modules/events/attendances.ts +245 -0
  347. package/src/modules/events/catalog.ts +36 -0
  348. package/src/modules/events/index.ts +54 -0
  349. package/src/modules/events/repository.ts +396 -0
  350. package/src/modules/events/routes.ts +405 -0
  351. package/src/modules/events/schema.ts +148 -0
  352. package/src/modules/events/service.ts +443 -0
  353. package/src/modules/events/tools.ts +118 -0
  354. package/src/modules/forms/applySubmissionFields.ts +7 -5
  355. package/src/modules/forms/fillAddresses.ts +185 -0
  356. package/src/modules/forms/repository.ts +3 -3
  357. package/src/modules/forms/routes.ts +2 -2
  358. package/src/modules/forms/schema.ts +2 -2
  359. package/src/modules/forms/service.ts +2 -2
  360. package/src/modules/forms/submission.ts +34 -4
  361. package/src/modules/hiring/roles.ts +2 -1
  362. package/src/modules/import-export/addresses.ts +150 -0
  363. package/src/modules/import-export/drafts.ts +7 -9
  364. package/src/modules/import-export/exportRows.ts +3 -3
  365. package/src/modules/import-export/plan.ts +30 -2
  366. package/src/modules/import-export/presets.ts +55 -7
  367. package/src/modules/import-export/repository.ts +5 -7
  368. package/src/modules/import-export/writes.ts +8 -4
  369. package/src/modules/people/repository.ts +1 -0
  370. package/src/modules/people/routes.ts +6 -6
  371. package/src/modules/people/schema.ts +7 -5
  372. package/src/modules/people/service.ts +7 -5
  373. package/src/modules/plans/repository.ts +2 -2
  374. package/src/modules/plans/routes.ts +6 -6
  375. package/src/modules/plans/schema.ts +4 -4
  376. package/src/modules/plans/service.ts +3 -3
  377. package/src/modules/plans/tools.ts +2 -2
  378. package/src/modules/recordTargets.ts +14 -0
  379. package/src/modules/references.ts +2 -0
  380. package/src/modules/sample-data/fixture.ts +208 -27
  381. package/src/modules/sample-data/index.ts +7 -2
  382. package/src/modules/sample-data/routes.ts +2 -0
  383. package/src/modules/sample-data/service.ts +166 -49
  384. package/src/modules/sample-data/tools.ts +4 -3
  385. package/src/modules/search/index.ts +2 -1
  386. package/src/modules/search/repository.ts +32 -1
  387. package/src/modules/search/service.ts +6 -4
  388. package/src/modules/smtp-email/index.ts +32 -10
  389. package/src/modules/workspace/repository.ts +18 -0
  390. package/src/modules/workspace/routes.ts +16 -0
  391. package/src/modules/workspace/service.ts +59 -14
  392. package/src/modules/workspace/starters.ts +275 -14
  393. package/src/modules/workspace/workspaceAccessMiddleware.ts +3 -4
  394. package/src/runtime/events.ts +2 -0
  395. package/src/runtime/module.ts +78 -1
  396. package/src/runtime/registry.ts +176 -8
  397. package/src/schema/index.ts +1 -0
  398. package/src/testing/app.ts +14 -1
  399. package/src/testing/client.ts +11 -1
@@ -1,13 +1,16 @@
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'
12
+ import type { MemberRole } from '../workspace/roles.ts'
13
+ import { parseMemberRole } from '../workspace/roles.ts'
11
14
  import type { SessionActor } from './actor.ts'
12
15
  import { DEFAULT_PREFERENCES, applyPreferenceChanges } from './preferences.ts'
13
16
  import type { PreferenceChanges, PreferenceValues } from './preferences.ts'
@@ -35,6 +38,13 @@ export interface AuthDependencies {
35
38
  readonly now: () => Date
36
39
  /** The deployment's own base URL. Every emailed link is built from it. */
37
40
  readonly appBaseUrl: string
41
+ /**
42
+ * Whether a brand-new account may be created, through `signUp` or
43
+ * `completeExternalSignIn` provisioning. Joining an existing workspace via
44
+ * invite, and workspace creation by an already-verified account, are
45
+ * unaffected either way — this only gates the account-creation step itself.
46
+ */
47
+ readonly signupsEnabled: boolean
38
48
  /** Injected only so tests can pin tokens. Production uses the crypto default. */
39
49
  readonly newToken?: () => string
40
50
  }
@@ -54,12 +64,37 @@ export interface IssuedSession {
54
64
  readonly activeWorkspaceId: string | null
55
65
  }
56
66
 
67
+ /** What `completeExternalSignIn` returns: an issued session plus whether it made the account. */
68
+ export interface ExternalIssuedSession extends IssuedSession {
69
+ readonly created: boolean
70
+ }
71
+
57
72
  export interface SessionView {
58
73
  readonly id: string
59
74
  readonly device: string | null
60
75
  readonly location: string | null
61
76
  readonly lastActiveAt: Date
62
77
  readonly current: boolean
78
+ /** The module that signed this session in, or null for a password sign-in. */
79
+ readonly signedInVia: string | null
80
+ }
81
+
82
+ /** What `GET /v1/auth/me` and `POST /v1/auth/workspace` answer with. */
83
+ export interface MeView {
84
+ readonly userId: string
85
+ readonly sessionId: string
86
+ readonly workspaceId: string | null
87
+ readonly role: MemberRole | null
88
+ readonly emailVerified: boolean
89
+ }
90
+
91
+ /** One membership on `GET /v1/auth/workspaces`. */
92
+ export interface AccountWorkspaceView {
93
+ readonly id: string
94
+ readonly name: string
95
+ readonly slug: string
96
+ readonly timezone: string
97
+ readonly role: MemberRole
63
98
  }
64
99
 
65
100
  export interface SignUpInput {
@@ -85,6 +120,12 @@ export interface LogInInput {
85
120
  readonly location?: string
86
121
  }
87
122
 
123
+ /** A `VerifiedIdentity` plus what the request itself says about the client. */
124
+ export interface ExternalSignInInput extends VerifiedIdentity {
125
+ readonly device?: string
126
+ readonly location?: string
127
+ }
128
+
88
129
  function toAccountView(user: repository.UserRecord): AccountView {
89
130
  return { id: user.id, email: user.email, name: user.name, emailVerified: user.emailVerifiedAt !== null }
90
131
  }
@@ -105,6 +146,16 @@ function normaliseEmail(email: string): string {
105
146
  return email.trim().toLowerCase()
106
147
  }
107
148
 
149
+ /**
150
+ * The name to use when a provider sends none. `users.name` is not nullable and
151
+ * an address is the only other thing an identity is guaranteed to carry.
152
+ */
153
+ function localPartOf(email: string): string {
154
+ const [local] = email.split('@')
155
+
156
+ return local === undefined || local.length === 0 ? email : local
157
+ }
158
+
108
159
  /**
109
160
  * Rejects a field that is only whitespace.
110
161
  *
@@ -128,6 +179,12 @@ export interface AuthService {
128
179
  /** Creates the account only. The first workspace comes from onboarding. */
129
180
  signUp(input: SignUpInput): Promise<IssuedSession>
130
181
  logIn(input: LogInInput): Promise<IssuedSession>
182
+ /**
183
+ * Signs in an identity a module verified elsewhere, provisioning the account
184
+ * when the module asked for it. Never sends a verification email: the
185
+ * provider already proved control of the address.
186
+ */
187
+ completeExternalSignIn(input: ExternalSignInInput): Promise<ExternalIssuedSession>
131
188
  logOut(actor: SessionActor): Promise<void>
132
189
  getAccount(actor: SessionActor): Promise<AccountView>
133
190
  updateAccount(actor: SessionActor, changes: UpdateAccountChanges): Promise<AccountView>
@@ -136,6 +193,10 @@ export interface AuthService {
136
193
  updatePreferences(actor: SessionActor, changes: PreferenceChanges): Promise<PreferenceValues>
137
194
  listSessions(actor: SessionActor): Promise<readonly SessionView[]>
138
195
  revokeSession(actor: SessionActor, sessionId: string): Promise<void>
196
+ /** Workspaces this account belongs to. Oldest membership first. */
197
+ listWorkspaces(actor: SessionActor): Promise<readonly AccountWorkspaceView[]>
198
+ /** Moves this session into a membership. Answers the same shape as `GET /v1/auth/me`. */
199
+ switchWorkspace(actor: SessionActor, workspaceId: string): Promise<MeView>
139
200
  /** Resolves whether or not the address is registered. */
140
201
  requestPasswordReset(email: string): Promise<void>
141
202
  confirmPasswordReset(token: string, newPassword: string): Promise<void>
@@ -167,6 +228,8 @@ export function createAuthService(dependencies: AuthDependencies): AuthService {
167
228
  user: repository.UserRecord,
168
229
  device: string | undefined,
169
230
  location: string | undefined,
231
+ /** The module that verified the identity, or null for a password sign-in. */
232
+ signedInVia: string | null = null,
170
233
  ): Promise<IssuedSession> {
171
234
  const now = dependencies.now()
172
235
  const token = newToken()
@@ -180,6 +243,7 @@ export function createAuthService(dependencies: AuthDependencies): AuthService {
180
243
  tokenHash: hashToken(token),
181
244
  device: device ?? null,
182
245
  location: location ?? null,
246
+ signedInVia,
183
247
  lastActiveAt: now,
184
248
  expiresAt,
185
249
  })
@@ -217,6 +281,10 @@ export function createAuthService(dependencies: AuthDependencies): AuthService {
217
281
  return {
218
282
  /** Creates the account only. The first workspace comes from onboarding. */
219
283
  async signUp(input: SignUpInput): Promise<IssuedSession> {
284
+ if (!dependencies.signupsEnabled) {
285
+ throw new AppError('forbidden', 'Signups are closed on this instance')
286
+ }
287
+
220
288
  if (input.password.length < MINIMUM_PASSWORD_LENGTH) {
221
289
  throw AppError.validationFailed('Password is too short', [
222
290
  { field: 'password', message: `Must be at least ${MINIMUM_PASSWORD_LENGTH} characters` },
@@ -269,17 +337,119 @@ export function createAuthService(dependencies: AuthDependencies): AuthService {
269
337
  async logIn(input: LogInInput): Promise<IssuedSession> {
270
338
  const user = await repository.findUserByEmail(dependencies.db, normaliseEmail(input.email))
271
339
 
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.
340
+ // Hash a throwaway password when the account is unknown or has no
341
+ // password, so all three cases take the same time to answer.
274
342
  const storedHash = user?.passwordHash ?? (await hashPassword('not-a-real-password-placeholder'))
275
-
276
- if (!(await verifyPassword(storedHash, input.password)) || user === undefined) {
343
+ // Always run the verify, even when the hash is the placeholder: skipping
344
+ // it would make a passwordless account answer faster than a wrong one.
345
+ const matches = await verifyPassword(storedHash, input.password)
346
+
347
+ // The null check is the guard, not the placeholder hash. Without it,
348
+ // whoever guessed the placeholder string would sign in to every account
349
+ // that has no password.
350
+ if (user === undefined || user.passwordHash === null || !matches) {
277
351
  throw AppError.unauthorized('Email or password is incorrect')
278
352
  }
279
353
 
280
354
  return dependencies.transaction(({ tx }) => issueSession(tx, user, input.device, input.location))
281
355
  },
282
356
 
357
+ /**
358
+ * Signs in an identity a module already verified.
359
+ *
360
+ * Core does not redo the verification and never learns the protocol. What
361
+ * it owns is everything downstream: finding or provisioning the account,
362
+ * issuing the session, and recording which module vouched for it.
363
+ */
364
+ async completeExternalSignIn(input: ExternalSignInInput): Promise<ExternalIssuedSession> {
365
+ // An unverified address is not an identity. A provider that does not say
366
+ // it checked is treated as not having checked.
367
+ if (!input.emailVerified) {
368
+ throw new ExternalSignInError('email_unverified')
369
+ }
370
+
371
+ const email = requireText(normaliseEmail(input.email), 'email')
372
+ const name = requireText(input.name ?? localPartOf(email), 'name')
373
+ const now = dependencies.now()
374
+
375
+ const link = async (
376
+ tx: repository.Queryable,
377
+ user: repository.UserRecord,
378
+ ): Promise<ExternalIssuedSession> => {
379
+ // Signing in through a provider on an account whose address was never
380
+ // confirmed is the pre-registration takeover case: somebody registered
381
+ // this address with a password and never proved they own it. The
382
+ // provider just proved the opposite. Drop that password and every
383
+ // session it opened; the real owner sets a new one through a reset.
384
+ if (user.emailVerifiedAt === null) {
385
+ await repository.updateUserPassword(tx, user.id, null, now)
386
+ await repository.deleteAllSessionsForUser(tx, user.id)
387
+ }
388
+
389
+ // The provider proved control of the address, which is the same thing
390
+ // accepting an invite proves.
391
+ await repository.markEmailVerified(tx, user.id, now)
392
+
393
+ const issued = await issueSession(
394
+ tx,
395
+ { ...user, emailVerifiedAt: user.emailVerifiedAt ?? now },
396
+ input.device,
397
+ input.location,
398
+ input.verifiedBy,
399
+ )
400
+
401
+ return { ...issued, created: false }
402
+ }
403
+
404
+ const existing = await repository.findUserByEmail(dependencies.db, email)
405
+
406
+ if (existing !== undefined) {
407
+ return dependencies.transaction(({ tx }) => link(tx, existing))
408
+ }
409
+
410
+ // The instance-level gate applies before the module's own policy: a
411
+ // module that would provision does not get to override it.
412
+ if (!dependencies.signupsEnabled) {
413
+ throw new ExternalSignInError('signups_closed')
414
+ }
415
+
416
+ if (input.provision === 'refuse') {
417
+ throw new ExternalSignInError('unknown_identity')
418
+ }
419
+
420
+ return dependencies.transaction(async ({ tx }) => {
421
+ let user: repository.UserRecord
422
+
423
+ try {
424
+ user = await repository.insertUser(tx, {
425
+ id: dependencies.createId('user'),
426
+ email,
427
+ name,
428
+ passwordHash: null,
429
+ emailVerifiedAt: now,
430
+ })
431
+ } catch (error: unknown) {
432
+ if (postgresErrorCode(error) !== UNIQUE_VIOLATION) {
433
+ throw error
434
+ }
435
+
436
+ // Two first sign-ins for the same address at once. The other one won;
437
+ // this is now an ordinary link.
438
+ const raced = await repository.findUserByEmail(tx, email)
439
+
440
+ if (raced === undefined) {
441
+ throw error
442
+ }
443
+
444
+ return link(tx, raced)
445
+ }
446
+
447
+ const issued = await issueSession(tx, user, input.device, input.location, input.verifiedBy)
448
+
449
+ return { ...issued, created: true }
450
+ })
451
+ },
452
+
283
453
  async logOut(actor: SessionActor): Promise<void> {
284
454
  await repository.deleteSession(dependencies.db, actor.userId, actor.sessionId)
285
455
  },
@@ -297,30 +467,45 @@ export function createAuthService(dependencies: AuthDependencies): AuthService {
297
467
  * password reset. The current password must verify, every other session
298
468
  * ends, and the address being replaced is told, in case the caller was not
299
469
  * its owner.
470
+ *
471
+ * A real change of address also resets verification and sends a fresh
472
+ * token to the new one. Verifying the old address proves nothing about the
473
+ * new one, and leaving the flag set would let anyone who can rename an
474
+ * unregistered account onto a given address inherit "verified" for free —
475
+ * exactly the mailbox-ownership proof signup's own token exists to require.
476
+ * Submitting the address already on file is not a change and resets nothing.
300
477
  */
301
478
  async updateAccount(actor: SessionActor, changes: UpdateAccountChanges): Promise<AccountView> {
302
479
  const user = await requireUser(actor.userId)
303
480
 
481
+ const nextEmail =
482
+ changes.email === undefined ? undefined : requireText(normaliseEmail(changes.email), 'email')
483
+ const emailChanging = nextEmail !== undefined && nextEmail !== user.email
484
+
304
485
  const values = {
305
486
  ...(changes.name === undefined ? {} : { name: requireText(changes.name, 'name') }),
306
- ...(changes.email === undefined
307
- ? {}
308
- : { email: requireText(normaliseEmail(changes.email), 'email') }),
487
+ ...(nextEmail === undefined ? {} : { email: nextEmail }),
488
+ ...(emailChanging ? { emailVerifiedAt: null } : {}),
309
489
  }
310
490
 
491
+ // A passwordless account cannot prove itself this way. It sets a password
492
+ // through the reset flow first, which is the same bar this check is.
311
493
  if (
312
494
  changes.email !== undefined &&
313
495
  (changes.currentPassword === undefined ||
496
+ user.passwordHash === null ||
314
497
  !(await verifyPassword(user.passwordHash, changes.currentPassword)))
315
498
  ) {
316
499
  throw AppError.unauthorized('Current password is incorrect')
317
500
  }
318
501
 
319
502
  const previousEmail = user.email
503
+ const now = dependencies.now()
504
+ const verificationToken = emailChanging ? newToken() : undefined
320
505
 
321
506
  try {
322
507
  const updated = await dependencies.transaction(async ({ tx }) => {
323
- const saved = await repository.updateUserProfile(tx, actor.userId, values, dependencies.now())
508
+ const saved = await repository.updateUserProfile(tx, actor.userId, values, now)
324
509
 
325
510
  if (saved === undefined) {
326
511
  throw AppError.unauthorized('This session no longer belongs to an account')
@@ -330,6 +515,15 @@ export function createAuthService(dependencies: AuthDependencies): AuthService {
330
515
  await repository.deleteOtherSessionsForUser(tx, actor.userId, actor.sessionId)
331
516
  }
332
517
 
518
+ if (verificationToken !== undefined) {
519
+ await repository.insertEmailVerificationToken(tx, {
520
+ id: dependencies.createId('emailVerificationToken'),
521
+ userId: actor.userId,
522
+ tokenHash: hashToken(verificationToken),
523
+ expiresAt: new Date(now.getTime() + EMAIL_VERIFICATION_TOKEN_LIFETIME_MS),
524
+ })
525
+ }
526
+
333
527
  return saved
334
528
  })
335
529
 
@@ -349,6 +543,13 @@ export function createAuthService(dependencies: AuthDependencies): AuthService {
349
543
  })
350
544
  }
351
545
 
546
+ // Sent after commit, same as signup: a rolled-back change never emails
547
+ // a token, and this token is only ever minted alongside a committed
548
+ // `emailVerifiedAt: null` write.
549
+ if (verificationToken !== undefined) {
550
+ await sendVerificationEmail(updated.email, verificationToken)
551
+ }
552
+
352
553
  return toAccountView(updated)
353
554
  } catch (error: unknown) {
354
555
  if (postgresErrorCode(error) === UNIQUE_VIOLATION) {
@@ -405,6 +606,7 @@ export function createAuthService(dependencies: AuthDependencies): AuthService {
405
606
  location: record.location,
406
607
  lastActiveAt: record.lastActiveAt,
407
608
  current: record.id === actor.sessionId,
609
+ signedInVia: record.signedInVia,
408
610
  }))
409
611
  },
410
612
 
@@ -418,6 +620,51 @@ export function createAuthService(dependencies: AuthDependencies): AuthService {
418
620
  }
419
621
  },
420
622
 
623
+ async listWorkspaces(actor: SessionActor): Promise<readonly AccountWorkspaceView[]> {
624
+ const rows = await repository.listWorkspacesForUser(dependencies.db, actor.userId)
625
+
626
+ return rows.map((row) => {
627
+ const role = parseMemberRole(row.role)
628
+
629
+ if (role === undefined) {
630
+ throw new Error(`workspace_members.role holds "${row.role}", which its check constraint forbids`)
631
+ }
632
+
633
+ return {
634
+ id: row.id,
635
+ name: row.name,
636
+ slug: row.slug,
637
+ timezone: row.timezone,
638
+ role,
639
+ }
640
+ })
641
+ },
642
+
643
+ async switchWorkspace(actor: SessionActor, workspaceId: string): Promise<MeView> {
644
+ const membership = await repository.findMembership(dependencies.db, workspaceId, actor.userId)
645
+
646
+ if (membership === undefined) {
647
+ throw AppError.notFound('Workspace not found')
648
+ }
649
+
650
+ const role = parseMemberRole(membership.role)
651
+
652
+ if (role === undefined) {
653
+ throw new Error(`workspace_members.role holds "${membership.role}", which its check constraint forbids`)
654
+ }
655
+
656
+ await repository.setActiveWorkspace(dependencies.db, actor.sessionId, workspaceId)
657
+ const user = await requireUser(actor.userId)
658
+
659
+ return {
660
+ userId: actor.userId,
661
+ sessionId: actor.sessionId,
662
+ workspaceId,
663
+ role,
664
+ emailVerified: user.emailVerifiedAt !== null,
665
+ }
666
+ },
667
+
421
668
  /**
422
669
  * Always resolves, whether or not the address is registered. Telling a
423
670
  * stranger which addresses have accounts is the whole attack.
@@ -494,7 +741,11 @@ export function createAuthService(dependencies: AuthDependencies): AuthService {
494
741
 
495
742
  const user = await repository.findUserById(dependencies.db, actor.userId)
496
743
 
497
- if (user === undefined || !(await verifyPassword(user.passwordHash, currentPassword))) {
744
+ if (
745
+ user === undefined ||
746
+ user.passwordHash === null ||
747
+ !(await verifyPassword(user.passwordHash, currentPassword))
748
+ ) {
498
749
  throw AppError.unauthorized('Current password is incorrect')
499
750
  }
500
751
 
@@ -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
 
@@ -58,6 +58,7 @@ function matchesTerm(term: string): SQL | undefined {
58
58
  ilike(companies.summary, pattern),
59
59
  ilike(companies.accountType, pattern),
60
60
  arrayContainsPattern(companies.tags, pattern),
61
+ sql`kelpie_addresses_search_text(${companies.addresses}) ilike ${pattern}`,
61
62
  )
62
63
  }
63
64
 
@@ -1,4 +1,4 @@
1
- import { customFieldsPatchShape } from '@kelpie/schemas'
1
+ import { addressBody, companyAddressesSchema, customFieldsPatchShape } from '@kelpie/schemas'
2
2
  import type { Context, Hono } from 'hono'
3
3
  import { z } from 'zod'
4
4
 
@@ -25,8 +25,7 @@ const companyShape = {
25
25
  description: z.string(),
26
26
  stage: z.enum(COMPANY_STAGES),
27
27
  size_band: z.enum(SIZE_BANDS),
28
- hq: z.string().nullable(),
29
- website: z.string().nullable(),
28
+ addresses: companyAddressesSchema,
30
29
  account_type: z.enum(ACCOUNT_TYPES),
31
30
  icp_fit: z.enum(ICP_FITS),
32
31
  tech_stack: z.array(z.string().min(1)),
@@ -48,8 +47,7 @@ export const createBody = z.strictObject({
48
47
  description: companyShape.description.default(''),
49
48
  stage: companyShape.stage.default('startup'),
50
49
  size_band: companyShape.size_band.default('1-10'),
51
- hq: companyShape.hq.default(null),
52
- website: companyShape.website.default(null),
50
+ addresses: companyShape.addresses.default([]),
53
51
  account_type: companyShape.account_type.default('prospect'),
54
52
  icp_fit: companyShape.icp_fit.default('unknown'),
55
53
  tech_stack: companyShape.tech_stack.default([]),
@@ -73,8 +71,7 @@ export function toCreateInput(body: z.infer<typeof createBody>): CreateCompanyIn
73
71
  description: body.description,
74
72
  stage: body.stage,
75
73
  sizeBand: body.size_band,
76
- hq: body.hq,
77
- website: body.website,
74
+ addresses: body.addresses,
78
75
  accountType: body.account_type,
79
76
  icpFit: body.icp_fit,
80
77
  techStack: body.tech_stack,
@@ -93,8 +90,7 @@ export function toUpdateInput(body: z.infer<typeof updateBody>): UpdateCompanyIn
93
90
  ...(body.description === undefined ? {} : { description: body.description }),
94
91
  ...(body.stage === undefined ? {} : { stage: body.stage }),
95
92
  ...(body.size_band === undefined ? {} : { sizeBand: body.size_band }),
96
- ...(body.hq === undefined ? {} : { hq: body.hq }),
97
- ...(body.website === undefined ? {} : { website: body.website }),
93
+ ...(body.addresses === undefined ? {} : { addresses: body.addresses }),
98
94
  ...(body.account_type === undefined ? {} : { accountType: body.account_type }),
99
95
  ...(body.icp_fit === undefined ? {} : { icpFit: body.icp_fit }),
100
96
  ...(body.tech_stack === undefined ? {} : { techStack: body.tech_stack }),
@@ -114,8 +110,7 @@ export function companyResponse(company: CompanyView): Record<string, unknown> {
114
110
  description: company.description,
115
111
  stage: company.stage,
116
112
  size_band: company.sizeBand,
117
- hq: company.hq,
118
- website: company.website,
113
+ addresses: company.addresses.map(addressBody),
119
114
  account_type: company.accountType,
120
115
  icp_fit: company.icpFit,
121
116
  tech_stack: company.techStack,
@@ -1,5 +1,5 @@
1
1
  import { ACCOUNT_TYPES, COMPANY_STAGES, ICP_FITS, SIZE_BANDS } from '@kelpie/schemas'
2
- import type { CustomFieldValue } from '@kelpie/schemas'
2
+ import type { CompanyAddress, CustomFieldValue } from '@kelpie/schemas'
3
3
  import { boolean, index, jsonb, pgTable, text, unique } from 'drizzle-orm/pg-core'
4
4
 
5
5
  import {
@@ -36,8 +36,7 @@ export const companies = pgTable(
36
36
  description: text('description').notNull().default(''),
37
37
  stage: text('stage').notNull(),
38
38
  sizeBand: text('size_band').notNull(),
39
- hq: text('hq'),
40
- website: text('website'),
39
+ addresses: jsonb('addresses').$type<readonly CompanyAddress[]>().notNull().default([]),
41
40
  accountType: text('account_type').notNull(),
42
41
  icpFit: text('icp_fit').notNull(),
43
42
  techStack: text('tech_stack').array().notNull().default([]),
@@ -62,6 +61,7 @@ export const companies = pgTable(
62
61
  { column: companies.summary, weight: 'B' },
63
62
  { column: companies.tags, weight: 'C', array: true },
64
63
  { column: companies.techStack, weight: 'C', array: true },
64
+ { column: companies.addresses, weight: 'C', addresses: true },
65
65
  ]),
66
66
  },
67
67
  (table) => [
@@ -1,4 +1,4 @@
1
- import type { CustomFieldWireValue } from '@kelpie/schemas'
1
+ import type { CompanyAddress, CustomFieldWireValue } from '@kelpie/schemas'
2
2
 
3
3
  import { changedKeys } from '../../lib/changes.ts'
4
4
  import { UNIQUE_VIOLATION, isReferenceViolation, postgresErrorCode } from '../../lib/database.ts'
@@ -49,8 +49,7 @@ const COMPANY_FIELD_LABELS: FieldLabels = {
49
49
  description: 'Description',
50
50
  stage: 'Stage',
51
51
  sizeBand: 'Size',
52
- hq: 'Headquarters',
53
- website: 'Website',
52
+ addresses: 'Addresses',
54
53
  accountType: 'Account type',
55
54
  icpFit: 'ICP fit',
56
55
  techStack: 'Tech stack',
@@ -69,8 +68,7 @@ export interface CreateCompanyInput {
69
68
  readonly description: string
70
69
  readonly stage: CompanyStage
71
70
  readonly sizeBand: SizeBand
72
- readonly hq: string | null
73
- readonly website: string | null
71
+ readonly addresses: readonly CompanyAddress[]
74
72
  readonly accountType: AccountType
75
73
  readonly icpFit: IcpFit
76
74
  readonly techStack: readonly string[]
@@ -102,7 +100,7 @@ function toView(record: CompanyRecord): CompanyView {
102
100
  * The stored form of what the caller sent. A domain arrives however someone
103
101
  * pasted it and is reduced to a host; blank becomes null, because the domain is
104
102
  * unique per workspace and a stored `''` would make the second company without a
105
- * website a 409.
103
+ * domain a 409.
106
104
  */
107
105
  function toStoredColumns(input: UpdateCompanyInput): Partial<repository.CompanyColumns> {
108
106
  return {
@@ -114,8 +112,7 @@ function toStoredColumns(input: UpdateCompanyInput): Partial<repository.CompanyC
114
112
  ...(input.description === undefined ? {} : { description: input.description }),
115
113
  ...(input.stage === undefined ? {} : { stage: input.stage }),
116
114
  ...(input.sizeBand === undefined ? {} : { sizeBand: input.sizeBand }),
117
- ...(input.hq === undefined ? {} : { hq: input.hq }),
118
- ...(input.website === undefined ? {} : { website: input.website }),
115
+ ...(input.addresses === undefined ? {} : { addresses: input.addresses }),
119
116
  ...(input.accountType === undefined ? {} : { accountType: input.accountType }),
120
117
  ...(input.icpFit === undefined ? {} : { icpFit: input.icpFit }),
121
118
  ...(input.techStack === undefined ? {} : { techStack: [...input.techStack] }),
@@ -180,8 +177,7 @@ export function createCompaniesService(dependencies: CompaniesDependencies): Com
180
177
  description: input.description,
181
178
  stage: input.stage,
182
179
  sizeBand: input.sizeBand,
183
- hq: input.hq,
184
- website: input.website,
180
+ addresses: input.addresses,
185
181
  accountType: input.accountType,
186
182
  icpFit: input.icpFit,
187
183
  techStack: [...input.techStack],
@@ -13,6 +13,7 @@ import { createDealsModule } from './deals/index.ts'
13
13
  import { createDecisionsModule } from './decisions/index.ts'
14
14
  import { createEmailDomainLinkerModule } from './email-domain-linker/index.ts'
15
15
  import { createEnquiriesModule } from './enquiries/index.ts'
16
+ import { createEventsModule } from './events/index.ts'
16
17
  import { createFormsModule } from './forms/index.ts'
17
18
  import { createHandbookModule } from './handbook/index.ts'
18
19
  import { createHiringModule } from './hiring/index.ts'
@@ -68,6 +69,7 @@ export const coreModules: readonly KelpieModule[] = [
68
69
  createPartnershipsModule(coreMigrationsDirectory),
69
70
  createRaisesModule(coreMigrationsDirectory),
70
71
  createHiringModule(coreMigrationsDirectory),
72
+ createEventsModule(coreMigrationsDirectory),
71
73
  createPlansModule(coreMigrationsDirectory),
72
74
  createDecisionsModule(coreMigrationsDirectory),
73
75
  createListsModule(coreMigrationsDirectory),
@@ -8,6 +8,7 @@ import type { Queryable } from '../../runtime/transaction.ts'
8
8
  import { companies } from '../companies/schema.ts'
9
9
  import { deals } from '../deals/schema.ts'
10
10
  import { enquiries } from '../enquiries/schema.ts'
11
+ import { events } from '../events/schema.ts'
11
12
  import { opportunities } from '../opportunities/schema.ts'
12
13
  import { partnerships } from '../partnerships/schema.ts'
13
14
  import { people } from '../people/schema.ts'
@@ -178,6 +179,7 @@ const TABLES = {
178
179
  partnership: partnerships,
179
180
  raise: raises,
180
181
  enquiry: enquiries,
182
+ event: events,
181
183
  } as const
182
184
 
183
185
  /**
@@ -27,6 +27,7 @@ export function createDashboardModule(): KelpieModule {
27
27
  'raises',
28
28
  'partnerships',
29
29
  'plans',
30
+ 'events',
30
31
  'decisions',
31
32
  'notes',
32
33
  'activities',