@kelpie/server 0.12.0 → 0.14.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 (420) 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 +9 -0
  6. package/dist/boot.d.ts.map +1 -1
  7. package/dist/boot.js +12 -2
  8. package/dist/boot.js.map +1 -1
  9. package/dist/index.d.ts +6 -1
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +3 -0
  12. package/dist/index.js.map +1 -1
  13. package/dist/lib/apiKeyScopes.d.ts.map +1 -1
  14. package/dist/lib/apiKeyScopes.js +6 -0
  15. package/dist/lib/apiKeyScopes.js.map +1 -1
  16. package/dist/lib/columns.d.ts +6 -0
  17. package/dist/lib/columns.d.ts.map +1 -1
  18. package/dist/lib/columns.js +5 -3
  19. package/dist/lib/columns.js.map +1 -1
  20. package/dist/lib/config.d.ts +17 -0
  21. package/dist/lib/config.d.ts.map +1 -1
  22. package/dist/lib/config.js +3 -0
  23. package/dist/lib/config.js.map +1 -1
  24. package/dist/lib/egress.d.ts.map +1 -1
  25. package/dist/lib/egress.js +8 -4
  26. package/dist/lib/egress.js.map +1 -1
  27. package/dist/lib/errors.d.ts +3 -3
  28. package/dist/lib/errors.d.ts.map +1 -1
  29. package/dist/lib/errors.js +13 -5
  30. package/dist/lib/errors.js.map +1 -1
  31. package/dist/lib/ids.d.ts +11 -0
  32. package/dist/lib/ids.d.ts.map +1 -1
  33. package/dist/lib/ids.js +11 -0
  34. package/dist/lib/ids.js.map +1 -1
  35. package/dist/lib/jobs.d.ts +110 -0
  36. package/dist/lib/jobs.d.ts.map +1 -0
  37. package/dist/lib/jobs.js +5 -0
  38. package/dist/lib/jobs.js.map +1 -0
  39. package/dist/lib/kelpieConfigFile.d.ts +15 -1
  40. package/dist/lib/kelpieConfigFile.d.ts.map +1 -1
  41. package/dist/lib/kelpieConfigFile.js +33 -1
  42. package/dist/lib/kelpieConfigFile.js.map +1 -1
  43. package/dist/modules/agent-tasks/catalog.d.ts.map +1 -1
  44. package/dist/modules/agent-tasks/catalog.js +32 -1
  45. package/dist/modules/agent-tasks/catalog.js.map +1 -1
  46. package/dist/modules/agent-tasks/index.d.ts.map +1 -1
  47. package/dist/modules/agent-tasks/index.js +1 -0
  48. package/dist/modules/agent-tasks/index.js.map +1 -1
  49. package/dist/modules/agent-tasks/resolve.d.ts.map +1 -1
  50. package/dist/modules/agent-tasks/resolve.js +5 -0
  51. package/dist/modules/agent-tasks/resolve.js.map +1 -1
  52. package/dist/modules/agent-tasks/routes.d.ts +2 -0
  53. package/dist/modules/agent-tasks/routes.d.ts.map +1 -1
  54. package/dist/modules/attachedRecords.d.ts +22 -8
  55. package/dist/modules/attachedRecords.d.ts.map +1 -1
  56. package/dist/modules/attachedRecords.js +42 -5
  57. package/dist/modules/attachedRecords.js.map +1 -1
  58. package/dist/modules/auth/index.d.ts.map +1 -1
  59. package/dist/modules/auth/index.js +1 -0
  60. package/dist/modules/auth/index.js.map +1 -1
  61. package/dist/modules/auth/repository.d.ts +14 -0
  62. package/dist/modules/auth/repository.d.ts.map +1 -1
  63. package/dist/modules/auth/repository.js +20 -2
  64. package/dist/modules/auth/repository.js.map +1 -1
  65. package/dist/modules/auth/routes.d.ts.map +1 -1
  66. package/dist/modules/auth/routes.js +30 -0
  67. package/dist/modules/auth/routes.js.map +1 -1
  68. package/dist/modules/auth/service.d.ts +28 -0
  69. package/dist/modules/auth/service.d.ts.map +1 -1
  70. package/dist/modules/auth/service.js +72 -4
  71. package/dist/modules/auth/service.js.map +1 -1
  72. package/dist/modules/companies/repository.d.ts.map +1 -1
  73. package/dist/modules/companies/repository.js +1 -1
  74. package/dist/modules/companies/repository.js.map +1 -1
  75. package/dist/modules/companies/routes.d.ts +2 -4
  76. package/dist/modules/companies/routes.d.ts.map +1 -1
  77. package/dist/modules/companies/routes.js +6 -11
  78. package/dist/modules/companies/routes.js.map +1 -1
  79. package/dist/modules/companies/schema.d.ts +13 -28
  80. package/dist/modules/companies/schema.d.ts.map +1 -1
  81. package/dist/modules/companies/schema.js +2 -2
  82. package/dist/modules/companies/schema.js.map +1 -1
  83. package/dist/modules/companies/service.d.ts +2 -3
  84. package/dist/modules/companies/service.d.ts.map +1 -1
  85. package/dist/modules/companies/service.js +4 -7
  86. package/dist/modules/companies/service.js.map +1 -1
  87. package/dist/modules/core.d.ts.map +1 -1
  88. package/dist/modules/core.js +2 -0
  89. package/dist/modules/core.js.map +1 -1
  90. package/dist/modules/custom-fields/repository.d.ts.map +1 -1
  91. package/dist/modules/custom-fields/repository.js +2 -0
  92. package/dist/modules/custom-fields/repository.js.map +1 -1
  93. package/dist/modules/custom-fields/routes.d.ts +1 -0
  94. package/dist/modules/custom-fields/routes.d.ts.map +1 -1
  95. package/dist/modules/dashboard/index.d.ts.map +1 -1
  96. package/dist/modules/dashboard/index.js +1 -0
  97. package/dist/modules/dashboard/index.js.map +1 -1
  98. package/dist/modules/dashboard/repository.d.ts +13 -0
  99. package/dist/modules/dashboard/repository.d.ts.map +1 -1
  100. package/dist/modules/dashboard/repository.js +36 -0
  101. package/dist/modules/dashboard/repository.js.map +1 -1
  102. package/dist/modules/dashboard/routes.d.ts.map +1 -1
  103. package/dist/modules/dashboard/routes.js +13 -0
  104. package/dist/modules/dashboard/routes.js.map +1 -1
  105. package/dist/modules/dashboard/service.d.ts +11 -0
  106. package/dist/modules/dashboard/service.d.ts.map +1 -1
  107. package/dist/modules/dashboard/service.js +24 -1
  108. package/dist/modules/dashboard/service.js.map +1 -1
  109. package/dist/modules/decisions/repository.d.ts.map +1 -1
  110. package/dist/modules/decisions/repository.js +2 -0
  111. package/dist/modules/decisions/repository.js.map +1 -1
  112. package/dist/modules/decisions/routes.d.ts +2 -0
  113. package/dist/modules/decisions/routes.d.ts.map +1 -1
  114. package/dist/modules/events/attendances.d.ts +35 -0
  115. package/dist/modules/events/attendances.d.ts.map +1 -0
  116. package/dist/modules/events/attendances.js +157 -0
  117. package/dist/modules/events/attendances.js.map +1 -0
  118. package/dist/modules/events/catalog.d.ts +35 -0
  119. package/dist/modules/events/catalog.d.ts.map +1 -0
  120. package/dist/modules/events/catalog.js +11 -0
  121. package/dist/modules/events/catalog.js.map +1 -0
  122. package/dist/modules/events/index.d.ts +10 -0
  123. package/dist/modules/events/index.d.ts.map +1 -0
  124. package/dist/modules/events/index.js +47 -0
  125. package/dist/modules/events/index.js.map +1 -0
  126. package/dist/modules/events/repository.d.ts +67 -0
  127. package/dist/modules/events/repository.d.ts.map +1 -0
  128. package/dist/modules/events/repository.js +212 -0
  129. package/dist/modules/events/repository.js.map +1 -0
  130. package/dist/modules/events/routes.d.ts +136 -0
  131. package/dist/modules/events/routes.d.ts.map +1 -0
  132. package/dist/modules/events/routes.js +280 -0
  133. package/dist/modules/events/routes.js.map +1 -0
  134. package/dist/modules/events/schema.d.ts +626 -0
  135. package/dist/modules/events/schema.d.ts.map +1 -0
  136. package/dist/modules/events/schema.js +93 -0
  137. package/dist/modules/events/schema.js.map +1 -0
  138. package/dist/modules/events/service.d.ts +68 -0
  139. package/dist/modules/events/service.d.ts.map +1 -0
  140. package/dist/modules/events/service.js +262 -0
  141. package/dist/modules/events/service.js.map +1 -0
  142. package/dist/modules/events/tools.d.ts +8 -0
  143. package/dist/modules/events/tools.d.ts.map +1 -0
  144. package/dist/modules/events/tools.js +80 -0
  145. package/dist/modules/events/tools.js.map +1 -0
  146. package/dist/modules/forms/applySubmissionFields.d.ts.map +1 -1
  147. package/dist/modules/forms/applySubmissionFields.js +7 -5
  148. package/dist/modules/forms/applySubmissionFields.js.map +1 -1
  149. package/dist/modules/forms/fillAddresses.d.ts +4 -0
  150. package/dist/modules/forms/fillAddresses.d.ts.map +1 -0
  151. package/dist/modules/forms/fillAddresses.js +93 -0
  152. package/dist/modules/forms/fillAddresses.js.map +1 -0
  153. package/dist/modules/forms/repository.d.ts +2 -2
  154. package/dist/modules/forms/repository.d.ts.map +1 -1
  155. package/dist/modules/forms/repository.js.map +1 -1
  156. package/dist/modules/forms/routes.d.ts +2 -0
  157. package/dist/modules/forms/routes.d.ts.map +1 -1
  158. package/dist/modules/forms/routes.js +2 -2
  159. package/dist/modules/forms/routes.js.map +1 -1
  160. package/dist/modules/forms/schema.js +2 -2
  161. package/dist/modules/forms/schema.js.map +1 -1
  162. package/dist/modules/forms/service.js +2 -2
  163. package/dist/modules/forms/service.js.map +1 -1
  164. package/dist/modules/forms/submission.d.ts +1 -0
  165. package/dist/modules/forms/submission.d.ts.map +1 -1
  166. package/dist/modules/forms/submission.js +27 -4
  167. package/dist/modules/forms/submission.js.map +1 -1
  168. package/dist/modules/hiring/roles.d.ts.map +1 -1
  169. package/dist/modules/hiring/roles.js +2 -1
  170. package/dist/modules/hiring/roles.js.map +1 -1
  171. package/dist/modules/import-export/addresses.d.ts +22 -0
  172. package/dist/modules/import-export/addresses.d.ts.map +1 -0
  173. package/dist/modules/import-export/addresses.js +83 -0
  174. package/dist/modules/import-export/addresses.js.map +1 -0
  175. package/dist/modules/import-export/drafts.d.ts +3 -4
  176. package/dist/modules/import-export/drafts.d.ts.map +1 -1
  177. package/dist/modules/import-export/drafts.js +4 -5
  178. package/dist/modules/import-export/drafts.js.map +1 -1
  179. package/dist/modules/import-export/exportRows.d.ts.map +1 -1
  180. package/dist/modules/import-export/exportRows.js +3 -3
  181. package/dist/modules/import-export/exportRows.js.map +1 -1
  182. package/dist/modules/import-export/plan.d.ts.map +1 -1
  183. package/dist/modules/import-export/plan.js +17 -2
  184. package/dist/modules/import-export/plan.js.map +1 -1
  185. package/dist/modules/import-export/presets.d.ts.map +1 -1
  186. package/dist/modules/import-export/presets.js +52 -7
  187. package/dist/modules/import-export/presets.js.map +1 -1
  188. package/dist/modules/import-export/repository.d.ts +3 -4
  189. package/dist/modules/import-export/repository.d.ts.map +1 -1
  190. package/dist/modules/import-export/repository.js +2 -3
  191. package/dist/modules/import-export/repository.js.map +1 -1
  192. package/dist/modules/import-export/writes.d.ts.map +1 -1
  193. package/dist/modules/import-export/writes.js +8 -4
  194. package/dist/modules/import-export/writes.js.map +1 -1
  195. package/dist/modules/lists/routes.d.ts +4 -0
  196. package/dist/modules/lists/routes.d.ts.map +1 -1
  197. package/dist/modules/notes/routes.d.ts +2 -0
  198. package/dist/modules/notes/routes.d.ts.map +1 -1
  199. package/dist/modules/people/repository.d.ts.map +1 -1
  200. package/dist/modules/people/repository.js +1 -1
  201. package/dist/modules/people/repository.js.map +1 -1
  202. package/dist/modules/people/routes.d.ts +2 -2
  203. package/dist/modules/people/routes.js +6 -6
  204. package/dist/modules/people/routes.js.map +1 -1
  205. package/dist/modules/people/schema.d.ts +17 -14
  206. package/dist/modules/people/schema.d.ts.map +1 -1
  207. package/dist/modules/people/schema.js +6 -4
  208. package/dist/modules/people/schema.js.map +1 -1
  209. package/dist/modules/people/service.d.ts +2 -2
  210. package/dist/modules/people/service.d.ts.map +1 -1
  211. package/dist/modules/people/service.js +5 -4
  212. package/dist/modules/people/service.js.map +1 -1
  213. package/dist/modules/plans/repository.d.ts +2 -2
  214. package/dist/modules/plans/repository.d.ts.map +1 -1
  215. package/dist/modules/plans/routes.d.ts +1 -0
  216. package/dist/modules/plans/routes.d.ts.map +1 -1
  217. package/dist/modules/plans/routes.js +4 -4
  218. package/dist/modules/plans/routes.js.map +1 -1
  219. package/dist/modules/plans/schema.d.ts +2 -2
  220. package/dist/modules/plans/schema.d.ts.map +1 -1
  221. package/dist/modules/plans/schema.js +3 -3
  222. package/dist/modules/plans/schema.js.map +1 -1
  223. package/dist/modules/plans/service.d.ts +2 -2
  224. package/dist/modules/plans/service.d.ts.map +1 -1
  225. package/dist/modules/plans/service.js.map +1 -1
  226. package/dist/modules/plans/tools.js +2 -2
  227. package/dist/modules/plans/tools.js.map +1 -1
  228. package/dist/modules/recordTargets.d.ts.map +1 -1
  229. package/dist/modules/recordTargets.js +11 -0
  230. package/dist/modules/recordTargets.js.map +1 -1
  231. package/dist/modules/references.d.ts.map +1 -1
  232. package/dist/modules/references.js +2 -0
  233. package/dist/modules/references.js.map +1 -1
  234. package/dist/modules/sample-data/fixture.d.ts +26 -4
  235. package/dist/modules/sample-data/fixture.d.ts.map +1 -1
  236. package/dist/modules/sample-data/fixture.js +157 -23
  237. package/dist/modules/sample-data/fixture.js.map +1 -1
  238. package/dist/modules/sample-data/index.d.ts +5 -2
  239. package/dist/modules/sample-data/index.d.ts.map +1 -1
  240. package/dist/modules/sample-data/index.js +7 -2
  241. package/dist/modules/sample-data/index.js.map +1 -1
  242. package/dist/modules/sample-data/routes.d.ts.map +1 -1
  243. package/dist/modules/sample-data/routes.js +2 -0
  244. package/dist/modules/sample-data/routes.js.map +1 -1
  245. package/dist/modules/sample-data/service.d.ts +11 -3
  246. package/dist/modules/sample-data/service.d.ts.map +1 -1
  247. package/dist/modules/sample-data/service.js +452 -363
  248. package/dist/modules/sample-data/service.js.map +1 -1
  249. package/dist/modules/sample-data/tools.d.ts.map +1 -1
  250. package/dist/modules/sample-data/tools.js +4 -3
  251. package/dist/modules/sample-data/tools.js.map +1 -1
  252. package/dist/modules/search/index.d.ts +1 -1
  253. package/dist/modules/search/index.d.ts.map +1 -1
  254. package/dist/modules/search/index.js +2 -1
  255. package/dist/modules/search/index.js.map +1 -1
  256. package/dist/modules/search/repository.d.ts +1 -0
  257. package/dist/modules/search/repository.d.ts.map +1 -1
  258. package/dist/modules/search/repository.js +22 -0
  259. package/dist/modules/search/repository.js.map +1 -1
  260. package/dist/modules/search/service.d.ts +2 -2
  261. package/dist/modules/search/service.d.ts.map +1 -1
  262. package/dist/modules/search/service.js +4 -3
  263. package/dist/modules/search/service.js.map +1 -1
  264. package/dist/modules/smtp-email/index.d.ts +12 -4
  265. package/dist/modules/smtp-email/index.d.ts.map +1 -1
  266. package/dist/modules/smtp-email/index.js +25 -5
  267. package/dist/modules/smtp-email/index.js.map +1 -1
  268. package/dist/modules/workspace/repository.d.ts +2 -0
  269. package/dist/modules/workspace/repository.d.ts.map +1 -1
  270. package/dist/modules/workspace/repository.js +12 -0
  271. package/dist/modules/workspace/repository.js.map +1 -1
  272. package/dist/modules/workspace/routes.d.ts.map +1 -1
  273. package/dist/modules/workspace/routes.js +13 -0
  274. package/dist/modules/workspace/routes.js.map +1 -1
  275. package/dist/modules/workspace/service.d.ts +10 -0
  276. package/dist/modules/workspace/service.d.ts.map +1 -1
  277. package/dist/modules/workspace/service.js +34 -10
  278. package/dist/modules/workspace/service.js.map +1 -1
  279. package/dist/modules/workspace/starters.d.ts +17 -2
  280. package/dist/modules/workspace/starters.d.ts.map +1 -1
  281. package/dist/modules/workspace/starters.js +141 -14
  282. package/dist/modules/workspace/starters.js.map +1 -1
  283. package/dist/modules/workspace/workspaceAccessMiddleware.d.ts.map +1 -1
  284. package/dist/modules/workspace/workspaceAccessMiddleware.js +3 -4
  285. package/dist/modules/workspace/workspaceAccessMiddleware.js.map +1 -1
  286. package/dist/runtime/events.d.ts +1 -1
  287. package/dist/runtime/events.d.ts.map +1 -1
  288. package/dist/runtime/events.js +2 -0
  289. package/dist/runtime/events.js.map +1 -1
  290. package/dist/runtime/jobs.d.ts +63 -0
  291. package/dist/runtime/jobs.d.ts.map +1 -0
  292. package/dist/runtime/jobs.js +231 -0
  293. package/dist/runtime/jobs.js.map +1 -0
  294. package/dist/runtime/module.d.ts +20 -0
  295. package/dist/runtime/module.d.ts.map +1 -1
  296. package/dist/runtime/registry.d.ts +8 -0
  297. package/dist/runtime/registry.d.ts.map +1 -1
  298. package/dist/runtime/registry.js +117 -7
  299. package/dist/runtime/registry.js.map +1 -1
  300. package/dist/runtime/transaction.d.ts +19 -0
  301. package/dist/runtime/transaction.d.ts.map +1 -1
  302. package/dist/runtime/transaction.js +7 -0
  303. package/dist/runtime/transaction.js.map +1 -1
  304. package/dist/schema/index.d.ts +1 -0
  305. package/dist/schema/index.d.ts.map +1 -1
  306. package/dist/schema/index.js +1 -0
  307. package/dist/schema/index.js.map +1 -1
  308. package/dist/testing/app.d.ts +15 -0
  309. package/dist/testing/app.d.ts.map +1 -1
  310. package/dist/testing/app.js +7 -1
  311. package/dist/testing/app.js.map +1 -1
  312. package/dist/testing/client.d.ts.map +1 -1
  313. package/dist/testing/client.js +9 -1
  314. package/dist/testing/client.js.map +1 -1
  315. package/dist/testing/database.d.ts +7 -0
  316. package/dist/testing/database.d.ts.map +1 -1
  317. package/dist/testing/database.js +26 -2
  318. package/dist/testing/database.js.map +1 -1
  319. package/dist/testing/services.d.ts +7 -0
  320. package/dist/testing/services.d.ts.map +1 -1
  321. package/dist/testing/services.js +8 -1
  322. package/dist/testing/services.js.map +1 -1
  323. package/dist/worker.d.ts +15 -0
  324. package/dist/worker.d.ts.map +1 -0
  325. package/dist/worker.js +19 -0
  326. package/dist/worker.js.map +1 -0
  327. package/migrations/0044_events.sql +79 -0
  328. package/migrations/0045_addresses.sql +66 -0
  329. package/migrations/0046_drop_company_website.sql +66 -0
  330. package/migrations/meta/_journal.json +21 -0
  331. package/package.json +4 -3
  332. package/src/app.ts +25 -1
  333. package/src/boot.ts +21 -2
  334. package/src/index.ts +17 -0
  335. package/src/lib/apiKeyScopes.ts +6 -0
  336. package/src/lib/columns.ts +12 -3
  337. package/src/lib/config.ts +21 -0
  338. package/src/lib/egress.ts +8 -4
  339. package/src/lib/errors.ts +16 -9
  340. package/src/lib/ids.ts +11 -0
  341. package/src/lib/jobs.ts +126 -0
  342. package/src/lib/kelpieConfigFile.ts +69 -1
  343. package/src/modules/agent-tasks/catalog.ts +33 -1
  344. package/src/modules/agent-tasks/index.ts +1 -0
  345. package/src/modules/agent-tasks/resolve.ts +11 -0
  346. package/src/modules/attachedRecords.ts +85 -13
  347. package/src/modules/auth/index.ts +1 -0
  348. package/src/modules/auth/repository.ts +34 -3
  349. package/src/modules/auth/routes.ts +44 -1
  350. package/src/modules/auth/service.ts +118 -4
  351. package/src/modules/companies/repository.ts +1 -0
  352. package/src/modules/companies/routes.ts +6 -11
  353. package/src/modules/companies/schema.ts +3 -3
  354. package/src/modules/companies/service.ts +6 -10
  355. package/src/modules/core.ts +2 -0
  356. package/src/modules/custom-fields/repository.ts +2 -0
  357. package/src/modules/dashboard/index.ts +1 -0
  358. package/src/modules/dashboard/repository.ts +64 -0
  359. package/src/modules/dashboard/routes.ts +15 -0
  360. package/src/modules/dashboard/service.ts +48 -0
  361. package/src/modules/decisions/repository.ts +2 -0
  362. package/src/modules/events/attendances.ts +245 -0
  363. package/src/modules/events/catalog.ts +36 -0
  364. package/src/modules/events/index.ts +54 -0
  365. package/src/modules/events/repository.ts +396 -0
  366. package/src/modules/events/routes.ts +405 -0
  367. package/src/modules/events/schema.ts +148 -0
  368. package/src/modules/events/service.ts +443 -0
  369. package/src/modules/events/tools.ts +118 -0
  370. package/src/modules/forms/applySubmissionFields.ts +7 -5
  371. package/src/modules/forms/fillAddresses.ts +185 -0
  372. package/src/modules/forms/repository.ts +3 -3
  373. package/src/modules/forms/routes.ts +2 -2
  374. package/src/modules/forms/schema.ts +2 -2
  375. package/src/modules/forms/service.ts +2 -2
  376. package/src/modules/forms/submission.ts +34 -4
  377. package/src/modules/hiring/roles.ts +2 -1
  378. package/src/modules/import-export/addresses.ts +150 -0
  379. package/src/modules/import-export/drafts.ts +7 -9
  380. package/src/modules/import-export/exportRows.ts +3 -3
  381. package/src/modules/import-export/plan.ts +30 -2
  382. package/src/modules/import-export/presets.ts +55 -7
  383. package/src/modules/import-export/repository.ts +5 -7
  384. package/src/modules/import-export/writes.ts +8 -4
  385. package/src/modules/people/repository.ts +1 -0
  386. package/src/modules/people/routes.ts +6 -6
  387. package/src/modules/people/schema.ts +7 -5
  388. package/src/modules/people/service.ts +7 -5
  389. package/src/modules/plans/repository.ts +2 -2
  390. package/src/modules/plans/routes.ts +6 -6
  391. package/src/modules/plans/schema.ts +4 -4
  392. package/src/modules/plans/service.ts +3 -3
  393. package/src/modules/plans/tools.ts +2 -2
  394. package/src/modules/recordTargets.ts +14 -0
  395. package/src/modules/references.ts +2 -0
  396. package/src/modules/sample-data/fixture.ts +208 -27
  397. package/src/modules/sample-data/index.ts +7 -2
  398. package/src/modules/sample-data/routes.ts +2 -0
  399. package/src/modules/sample-data/service.ts +166 -49
  400. package/src/modules/sample-data/tools.ts +4 -3
  401. package/src/modules/search/index.ts +2 -1
  402. package/src/modules/search/repository.ts +32 -1
  403. package/src/modules/search/service.ts +6 -4
  404. package/src/modules/smtp-email/index.ts +32 -10
  405. package/src/modules/workspace/repository.ts +18 -0
  406. package/src/modules/workspace/routes.ts +16 -0
  407. package/src/modules/workspace/service.ts +59 -14
  408. package/src/modules/workspace/starters.ts +275 -14
  409. package/src/modules/workspace/workspaceAccessMiddleware.ts +3 -4
  410. package/src/runtime/events.ts +2 -0
  411. package/src/runtime/jobs.ts +358 -0
  412. package/src/runtime/module.ts +20 -0
  413. package/src/runtime/registry.ts +146 -8
  414. package/src/runtime/transaction.ts +43 -0
  415. package/src/schema/index.ts +1 -0
  416. package/src/testing/app.ts +22 -1
  417. package/src/testing/client.ts +11 -1
  418. package/src/testing/database.ts +35 -2
  419. package/src/testing/services.ts +15 -1
  420. package/src/worker.ts +20 -0
@@ -9,6 +9,8 @@ import { MINIMUM_PASSWORD_LENGTH, hashPassword, verifyPassword } from '../../lib
9
9
  import { generateToken, hashToken } from '../../lib/tokens.ts'
10
10
  import type { VerifiedIdentity } from '../../runtime/module.ts'
11
11
  import type { TransactionScope } from '../../runtime/transaction.ts'
12
+ import type { MemberRole } from '../workspace/roles.ts'
13
+ import { parseMemberRole } from '../workspace/roles.ts'
12
14
  import type { SessionActor } from './actor.ts'
13
15
  import { DEFAULT_PREFERENCES, applyPreferenceChanges } from './preferences.ts'
14
16
  import type { PreferenceChanges, PreferenceValues } from './preferences.ts'
@@ -36,6 +38,13 @@ export interface AuthDependencies {
36
38
  readonly now: () => Date
37
39
  /** The deployment's own base URL. Every emailed link is built from it. */
38
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
39
48
  /** Injected only so tests can pin tokens. Production uses the crypto default. */
40
49
  readonly newToken?: () => string
41
50
  }
@@ -70,6 +79,24 @@ export interface SessionView {
70
79
  readonly signedInVia: string | null
71
80
  }
72
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
98
+ }
99
+
73
100
  export interface SignUpInput {
74
101
  readonly email: string
75
102
  readonly name: string
@@ -166,6 +193,10 @@ export interface AuthService {
166
193
  updatePreferences(actor: SessionActor, changes: PreferenceChanges): Promise<PreferenceValues>
167
194
  listSessions(actor: SessionActor): Promise<readonly SessionView[]>
168
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>
169
200
  /** Resolves whether or not the address is registered. */
170
201
  requestPasswordReset(email: string): Promise<void>
171
202
  confirmPasswordReset(token: string, newPassword: string): Promise<void>
@@ -250,6 +281,10 @@ export function createAuthService(dependencies: AuthDependencies): AuthService {
250
281
  return {
251
282
  /** Creates the account only. The first workspace comes from onboarding. */
252
283
  async signUp(input: SignUpInput): Promise<IssuedSession> {
284
+ if (!dependencies.signupsEnabled) {
285
+ throw new AppError('forbidden', 'Signups are closed on this instance')
286
+ }
287
+
253
288
  if (input.password.length < MINIMUM_PASSWORD_LENGTH) {
254
289
  throw AppError.validationFailed('Password is too short', [
255
290
  { field: 'password', message: `Must be at least ${MINIMUM_PASSWORD_LENGTH} characters` },
@@ -372,6 +407,12 @@ export function createAuthService(dependencies: AuthDependencies): AuthService {
372
407
  return dependencies.transaction(({ tx }) => link(tx, existing))
373
408
  }
374
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
+
375
416
  if (input.provision === 'refuse') {
376
417
  throw new ExternalSignInError('unknown_identity')
377
418
  }
@@ -426,15 +467,25 @@ export function createAuthService(dependencies: AuthDependencies): AuthService {
426
467
  * password reset. The current password must verify, every other session
427
468
  * ends, and the address being replaced is told, in case the caller was not
428
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.
429
477
  */
430
478
  async updateAccount(actor: SessionActor, changes: UpdateAccountChanges): Promise<AccountView> {
431
479
  const user = await requireUser(actor.userId)
432
480
 
481
+ const nextEmail =
482
+ changes.email === undefined ? undefined : requireText(normaliseEmail(changes.email), 'email')
483
+ const emailChanging = nextEmail !== undefined && nextEmail !== user.email
484
+
433
485
  const values = {
434
486
  ...(changes.name === undefined ? {} : { name: requireText(changes.name, 'name') }),
435
- ...(changes.email === undefined
436
- ? {}
437
- : { email: requireText(normaliseEmail(changes.email), 'email') }),
487
+ ...(nextEmail === undefined ? {} : { email: nextEmail }),
488
+ ...(emailChanging ? { emailVerifiedAt: null } : {}),
438
489
  }
439
490
 
440
491
  // A passwordless account cannot prove itself this way. It sets a password
@@ -449,10 +500,12 @@ export function createAuthService(dependencies: AuthDependencies): AuthService {
449
500
  }
450
501
 
451
502
  const previousEmail = user.email
503
+ const now = dependencies.now()
504
+ const verificationToken = emailChanging ? newToken() : undefined
452
505
 
453
506
  try {
454
507
  const updated = await dependencies.transaction(async ({ tx }) => {
455
- const saved = await repository.updateUserProfile(tx, actor.userId, values, dependencies.now())
508
+ const saved = await repository.updateUserProfile(tx, actor.userId, values, now)
456
509
 
457
510
  if (saved === undefined) {
458
511
  throw AppError.unauthorized('This session no longer belongs to an account')
@@ -462,6 +515,15 @@ export function createAuthService(dependencies: AuthDependencies): AuthService {
462
515
  await repository.deleteOtherSessionsForUser(tx, actor.userId, actor.sessionId)
463
516
  }
464
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
+
465
527
  return saved
466
528
  })
467
529
 
@@ -481,6 +543,13 @@ export function createAuthService(dependencies: AuthDependencies): AuthService {
481
543
  })
482
544
  }
483
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
+
484
553
  return toAccountView(updated)
485
554
  } catch (error: unknown) {
486
555
  if (postgresErrorCode(error) === UNIQUE_VIOLATION) {
@@ -551,6 +620,51 @@ export function createAuthService(dependencies: AuthDependencies): AuthService {
551
620
  }
552
621
  },
553
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
+
554
668
  /**
555
669
  * Always resolves, whether or not the address is registered. Telling a
556
670
  * stranger which addresses have accounts is the whole attack.
@@ -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',
@@ -9,6 +9,7 @@ import { activities } from '../activities/schema.ts'
9
9
  import { decisions } from '../decisions/schema.ts'
10
10
  import { deals } from '../deals/schema.ts'
11
11
  import { enquiries } from '../enquiries/schema.ts'
12
+ import { attendances, events } from '../events/schema.ts'
12
13
  import { notes } from '../notes/schema.ts'
13
14
  import { opportunities } from '../opportunities/schema.ts'
14
15
  import { partnerships } from '../partnerships/schema.ts'
@@ -242,6 +243,69 @@ export async function listPartnershipTouchpoints(
242
243
  )
243
244
  }
244
245
 
246
+ /** A scheduled Event whose start is inside `[from, to)`. */
247
+ export interface UpcomingEventRecord {
248
+ readonly id: string
249
+ readonly name: string
250
+ readonly startsAt: Date
251
+ readonly endsAt: Date
252
+ readonly format: string
253
+ readonly location: string
254
+ readonly attendeeCount: number
255
+ readonly ownerId: string | null
256
+ }
257
+
258
+ function upcomingEventConditions(workspaceId: string, from: Date, to: Date): (SQL | undefined)[] {
259
+ return [
260
+ eq(events.workspaceId, workspaceId),
261
+ eq(events.status, 'scheduled'),
262
+ gte(events.startsAt, from),
263
+ lt(events.startsAt, to),
264
+ ]
265
+ }
266
+
267
+ export async function countUpcomingEvents(
268
+ db: Queryable,
269
+ workspaceId: string,
270
+ from: Date,
271
+ to: Date,
272
+ ): Promise<number> {
273
+ const [row] = await db
274
+ .select({ total: count() })
275
+ .from(events)
276
+ .where(and(...upcomingEventConditions(workspaceId, from, to)))
277
+
278
+ return row?.total ?? 0
279
+ }
280
+
281
+ export async function listUpcomingEvents(
282
+ db: Queryable,
283
+ workspaceId: string,
284
+ from: Date,
285
+ to: Date,
286
+ limit: number,
287
+ ): Promise<readonly UpcomingEventRecord[]> {
288
+ const rows = await db
289
+ .select({
290
+ id: events.id,
291
+ name: events.name,
292
+ startsAt: events.startsAt,
293
+ endsAt: events.endsAt,
294
+ format: events.format,
295
+ location: events.location,
296
+ ownerId: events.ownerId,
297
+ attendeeCount: sql<number>`coalesce(count(${attendances.id}) filter (where ${attendances.status} <> 'cancelled'), 0)::int`,
298
+ })
299
+ .from(events)
300
+ .leftJoin(attendances, eq(attendances.eventId, events.id))
301
+ .where(and(...upcomingEventConditions(workspaceId, from, to)))
302
+ .groupBy(events.id)
303
+ .orderBy(asc(events.startsAt), asc(events.id))
304
+ .limit(limit)
305
+
306
+ return rows
307
+ }
308
+
245
309
  /**
246
310
  * The calendar day a contact happened on, in the workspace's zone.
247
311
  *
@@ -15,6 +15,7 @@ import type {
15
15
  SignalList,
16
16
  StaleContactSignal,
17
17
  TouchpointSignal,
18
+ UpcomingEventSignal,
18
19
  } from './service.ts'
19
20
 
20
21
  /**
@@ -83,6 +84,19 @@ function touchpointBody(touchpoint: TouchpointSignal): Record<string, unknown> {
83
84
  }
84
85
  }
85
86
 
87
+ function upcomingEventBody(event: UpcomingEventSignal): Record<string, unknown> {
88
+ return {
89
+ id: event.id,
90
+ name: event.name,
91
+ starts_at: event.startsAt.toISOString(),
92
+ ends_at: event.endsAt.toISOString(),
93
+ format: event.format,
94
+ location: event.location,
95
+ attendee_count: event.attendeeCount,
96
+ owner_id: event.ownerId,
97
+ }
98
+ }
99
+
86
100
  function staleContactBody(contact: StaleContactSignal): Record<string, unknown> {
87
101
  return {
88
102
  id: contact.id,
@@ -147,6 +161,7 @@ export function dashboardResponse(snapshot: DashboardSnapshot): Record<string, u
147
161
  overdue_plan_items: signalListBody(snapshot.overduePlanItems, planItemBody),
148
162
  due_soon_plan_items: signalListBody(snapshot.dueSoonPlanItems, planItemBody),
149
163
  partnership_touchpoints: signalListBody(snapshot.partnershipTouchpoints, touchpointBody),
164
+ upcoming_events: signalListBody(snapshot.upcomingEvents, upcomingEventBody),
150
165
  stale_contacts: signalListBody(snapshot.staleContacts, staleContactBody),
151
166
  recent_activity: snapshot.recentActivity.map(activityBody),
152
167
  recent_notes: snapshot.recentNotes.map(noteBody),
@@ -117,6 +117,17 @@ export interface DecisionSignal extends TargetRef {
117
117
  readonly dueAt: Date | null
118
118
  }
119
119
 
120
+ export interface UpcomingEventSignal {
121
+ readonly id: string
122
+ readonly name: string
123
+ readonly startsAt: Date
124
+ readonly endsAt: Date
125
+ readonly format: string
126
+ readonly location: string
127
+ readonly attendeeCount: number
128
+ readonly ownerId: string | null
129
+ }
130
+
120
131
  export interface DashboardSnapshot {
121
132
  readonly generatedAt: Date
122
133
  /** The day everything below was computed against, `YYYY-MM-DD`. */
@@ -129,6 +140,7 @@ export interface DashboardSnapshot {
129
140
  readonly overduePlanItems: SignalList<PlanItemSignal>
130
141
  readonly dueSoonPlanItems: SignalList<PlanItemSignal>
131
142
  readonly partnershipTouchpoints: SignalList<TouchpointSignal>
143
+ readonly upcomingEvents: SignalList<UpcomingEventSignal>
132
144
  readonly staleContacts: SignalList<StaleContactSignal>
133
145
  readonly recentActivity: readonly ActivitySignal[]
134
146
  readonly recentNotes: readonly NoteSignal[]
@@ -155,6 +167,10 @@ interface DayBounds {
155
167
  readonly through: string
156
168
  /** The first day a contact still counts as fresh. */
157
169
  readonly staleCutoff: string
170
+ /** Instant the upcoming Events window starts (now). */
171
+ readonly fromInstant: Date
172
+ /** Instant the upcoming Events window ends (now plus `UPCOMING_DAYS`). */
173
+ readonly throughInstant: Date
158
174
  }
159
175
 
160
176
  /** Everything read from the database, before any of it is named or shaped. */
@@ -163,6 +179,7 @@ interface SignalRows {
163
179
  readonly overduePlanItems: SignalList<repository.PlanItemRecord>
164
180
  readonly dueSoonPlanItems: SignalList<repository.PlanItemRecord>
165
181
  readonly partnershipTouchpoints: SignalList<repository.TouchpointRecord>
182
+ readonly upcomingEvents: SignalList<repository.UpcomingEventRecord>
166
183
  readonly staleContacts: SignalList<repository.StaleContactRecord>
167
184
  readonly recentActivity: readonly repository.ActivityRecord[]
168
185
  readonly recentNotes: readonly repository.NoteRecord[]
@@ -201,6 +218,19 @@ async function readTouchpoints(
201
218
  }
202
219
  }
203
220
 
221
+ async function readUpcomingEvents(
222
+ db: Database,
223
+ workspaceId: string,
224
+ from: Date,
225
+ to: Date,
226
+ limit: number,
227
+ ): Promise<SignalList<repository.UpcomingEventRecord>> {
228
+ return {
229
+ total: await repository.countUpcomingEvents(db, workspaceId, from, to),
230
+ items: await repository.listUpcomingEvents(db, workspaceId, from, to, limit),
231
+ }
232
+ }
233
+
204
234
  async function readStaleContacts(
205
235
  db: Database,
206
236
  workspaceId: string,
@@ -236,6 +266,7 @@ async function readSignals(
236
266
  overduePlanItems,
237
267
  dueSoonPlanItems,
238
268
  partnershipTouchpoints,
269
+ upcomingEvents,
239
270
  staleContacts,
240
271
  recentActivity,
241
272
  recentNotes,
@@ -245,6 +276,7 @@ async function readSignals(
245
276
  readPlanItems(db, workspaceId, { before: days.today }, limit),
246
277
  readPlanItems(db, workspaceId, { from: days.today, to: days.through }, limit),
247
278
  readTouchpoints(db, workspaceId, days.through, limit),
279
+ readUpcomingEvents(db, workspaceId, days.fromInstant, days.throughInstant, limit),
248
280
  readStaleContacts(db, workspaceId, timezone, days.staleCutoff, limit),
249
281
  repository.listRecentActivity(db, workspaceId, limit),
250
282
  repository.listRecentNotes(db, workspaceId, limit),
@@ -256,6 +288,7 @@ async function readSignals(
256
288
  overduePlanItems,
257
289
  dueSoonPlanItems,
258
290
  partnershipTouchpoints,
291
+ upcomingEvents,
259
292
  staleContacts,
260
293
  recentActivity,
261
294
  recentNotes,
@@ -376,6 +409,19 @@ function toSnapshot(
376
409
  overduePlanItems: toPlanItemSignal(rows.overduePlanItems, nameOf),
377
410
  dueSoonPlanItems: toPlanItemSignal(rows.dueSoonPlanItems, nameOf),
378
411
  partnershipTouchpoints: toTouchpointSignal(rows.partnershipTouchpoints, days.today),
412
+ upcomingEvents: {
413
+ total: rows.upcomingEvents.total,
414
+ items: rows.upcomingEvents.items.map((row) => ({
415
+ id: row.id,
416
+ name: row.name,
417
+ startsAt: row.startsAt,
418
+ endsAt: row.endsAt,
419
+ format: row.format,
420
+ location: row.location,
421
+ attendeeCount: row.attendeeCount,
422
+ ownerId: row.ownerId,
423
+ })),
424
+ },
379
425
  staleContacts: toStaleContactSignal(rows.staleContacts, days.today),
380
426
  recentActivity: rows.recentActivity.map((row) => ({
381
427
  id: row.id,
@@ -436,6 +482,8 @@ export function createDashboardService(dependencies: DashboardDependencies): Das
436
482
  today,
437
483
  through: upcomingEnd(today),
438
484
  staleCutoff: staleContactCutoff(today),
485
+ fromInstant: generatedAt,
486
+ throughInstant: new Date(generatedAt.getTime() + UPCOMING_DAYS * 24 * 60 * 60 * 1000),
439
487
  }
440
488
 
441
489
  const rows = await readSignals(dependencies.db, workspaceId, timezone, days, limit)
@@ -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'
@@ -57,6 +58,7 @@ const NAMED_TARGET_TABLES = [
57
58
  { table: partnerships, id: partnerships.id, workspaceId: partnerships.workspaceId, name: partnerships.name },
58
59
  { table: raises, id: raises.id, workspaceId: raises.workspaceId, name: raises.name },
59
60
  { table: enquiries, id: enquiries.id, workspaceId: enquiries.workspaceId, name: enquiries.name },
61
+ { table: events, id: events.id, workspaceId: events.workspaceId, name: events.name },
60
62
  ]
61
63
 
62
64
  /**