@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
@@ -0,0 +1,126 @@
1
+ import type { ZodType } from 'zod'
2
+
3
+ import type { Logger } from './logger.ts'
4
+
5
+ /**
6
+ * The port core runs background work through.
7
+ *
8
+ * A module declares a handle at register time with `context.jobs.define(...)`
9
+ * and enqueues against it from a service. The insert lives inside the same
10
+ * Drizzle transaction as the write it belongs to, so a rolled-back write
11
+ * never leaves an orphan job behind. Handlers run out of process — on the
12
+ * worker entry point, or inline when the API runs its own worker — and see
13
+ * the same payload the caller passed, validated against the module's Zod
14
+ * schema before it reaches user code.
15
+ *
16
+ * The port itself knows nothing about pg-boss. `runtime/jobs.ts` binds it to
17
+ * a real provider at boot; tests bind it to a stub. Nothing outside the
18
+ * runtime imports the provider directly.
19
+ *
20
+ * Why a handle rather than a global name map: consumers of `@kelpie/server`
21
+ * installed from npm lost the `KelpieEventMap` augmentation until every
22
+ * catalog was pulled in from the entry point (see
23
+ * `modules/eventCatalogs.ts`). A handle keeps the payload type on the value
24
+ * the module exports, so `enqueue(handle, data)` typechecks without
25
+ * declaration merging across the package boundary.
26
+ */
27
+
28
+ /**
29
+ * Marker property carrying the handle's payload type. Never assigned at
30
+ * runtime; the `?` on the property makes an untagged plain object satisfy the
31
+ * shape at read time, so a stub handle in a test can be built without
32
+ * `unsafeCast`.
33
+ */
34
+ declare const JobHandleDataMarker: unique symbol
35
+
36
+ /** A typed reference a module exports after `define()`. */
37
+ export interface JobHandle<Data> {
38
+ readonly name: string
39
+ readonly [JobHandleDataMarker]?: Data
40
+ }
41
+
42
+ /**
43
+ * Defaults every enqueue on this queue inherits, unless the caller overrides
44
+ * on a per-`enqueue` basis. `localConcurrency` sizes the work loop on the
45
+ * worker; the rest match pg-boss's `QueueOptions` names.
46
+ *
47
+ * `retryDelay` is in seconds. pg-boss's default is 0 (immediate retry).
48
+ * `retryLimit` counts the retries pg-boss does after the first run; a job
49
+ * with `retryLimit: 2` runs at most three times.
50
+ */
51
+ export interface JobDefaults {
52
+ readonly retryLimit?: number
53
+ readonly retryDelay?: number
54
+ readonly retryBackoff?: boolean
55
+ readonly retryDelayMax?: number
56
+ readonly expireInSeconds?: number
57
+ readonly localConcurrency?: number
58
+ }
59
+
60
+ /** Passed to a job handler when a worker picks the job up. */
61
+ export interface JobContext<Data> {
62
+ readonly id: string
63
+ readonly data: Data
64
+ readonly log: Logger
65
+ /** Fires when pg-boss decides the job has expired. Long-running handlers should honour it. */
66
+ readonly signal: AbortSignal
67
+ }
68
+
69
+ export type JobHandler<Data> = (context: JobContext<Data>) => Promise<void>
70
+
71
+ export interface JobDefinition<Data> {
72
+ /**
73
+ * The queue name pg-boss stores against. Must be unique across the
74
+ * assembly; a second `define()` with the same name fails boot.
75
+ *
76
+ * Prefix with the module id — `webhooks.deliver`, `agent-tasks.dispatch` —
77
+ * so operators reading `pgboss.job` see who owns it.
78
+ */
79
+ readonly name: string
80
+ /**
81
+ * Parses the payload at handle time before it reaches user code. A job
82
+ * whose payload no longer matches fails, retries, and eventually
83
+ * dead-letters like any other failure.
84
+ */
85
+ readonly schema: ZodType<Data>
86
+ readonly handler: JobHandler<Data>
87
+ readonly defaults?: JobDefaults
88
+ }
89
+
90
+ /**
91
+ * The registration surface exposed to a module through `context.jobs`. Modules
92
+ * hand a definition in and receive a typed handle to enqueue against.
93
+ */
94
+ export interface JobRegistry {
95
+ define<Data>(definition: JobDefinition<Data>): JobHandle<Data>
96
+ }
97
+
98
+ /**
99
+ * Options a caller may override per `enqueue`. Deliberately narrow: features
100
+ * beyond retries, a singleton key, and a deferred start are out of scope until
101
+ * a consumer needs them (`crm-brief.md` cost/maintainability trade-offs).
102
+ */
103
+ export interface EnqueueOptions {
104
+ readonly retryLimit?: number
105
+ readonly retryBackoff?: boolean
106
+ readonly singletonKey?: string
107
+ readonly startAfter?: number | string | Date
108
+ }
109
+
110
+ /**
111
+ * Exposed on the transaction context next to `events`. `enqueue` runs its
112
+ * insert inside the caller's transaction via pg-boss's `fromDrizzle` adapter,
113
+ * so a rollback discards the job with the write.
114
+ */
115
+ export interface TransactionJobs {
116
+ enqueue<Data>(
117
+ handle: JobHandle<Data>,
118
+ data: Data,
119
+ options?: EnqueueOptions,
120
+ ): Promise<string | null>
121
+ }
122
+
123
+ /** The name pg-boss stores the dead-letter queue under, given a handle name. */
124
+ export function deadLetterQueueName(jobName: string): string {
125
+ return `${jobName}.dead`
126
+ }
@@ -1,6 +1,17 @@
1
+ import type { PublicRegion } from '@kelpie/schemas'
2
+ import { publicRegionSchema } from '@kelpie/schemas'
3
+
1
4
  import type { KelpieModule } from '../runtime/module.ts'
2
- import { ConfigurationError, type Environment, type KelpieConfig, type LogLevel, type RuntimeMode } from './config.ts'
5
+ import {
6
+ ConfigurationError,
7
+ type Environment,
8
+ type KelpieConfig,
9
+ type LogLevel,
10
+ type RuntimeMode,
11
+ type SignupsMode,
12
+ } from './config.ts'
3
13
  import type { EmailConfig } from './email.ts'
14
+ import { describeValidationIssue } from './errors.ts'
4
15
  import { type ConfigValue, resolveMarkers } from './fromEnv.ts'
5
16
  import type { LoggingDestination } from './logger.ts'
6
17
  import type { RateLimitConfig } from './rateLimit.ts'
@@ -35,6 +46,19 @@ export interface KelpieConfigInput {
35
46
  readonly siteName?: ConfigValue<string | undefined>
36
47
  /** Optional; defaults to 0 (no proxy in front). */
37
48
  readonly trustedProxyHopCount?: ConfigValue<number>
49
+ /**
50
+ * Whether the instance accepts a brand-new account. Optional; defaults to
51
+ * `'open'`, matching `loadConfig`'s default, so an assembly that never sets
52
+ * this sees no change in behaviour.
53
+ */
54
+ readonly signups?: ConfigValue<SignupsMode>
55
+ /**
56
+ * Origins a multi-region assembly advertises on signed-out pages. Optional;
57
+ * omitted or empty hides the switcher, matching a self-hosted install.
58
+ * Two or more draws it. Each `id` and `origin` must be unique, and each
59
+ * `origin` must be an absolute `http:` or `https:` origin.
60
+ */
61
+ readonly regions?: ConfigValue<readonly PublicRegion[]>
38
62
  /**
39
63
  * The deployment's base URL. Every emailed link (password reset, email
40
64
  * verification, invite) is built from it. Required by the workspace and auth
@@ -164,6 +188,9 @@ export function resolveKelpieConfig(input: KelpieConfigInput, environment: Envir
164
188
  const rateLimit = buildRateLimitConfig(resolved.rateLimit)
165
189
  const env = mergeEnv(environment, resolved.env)
166
190
  const secretEncryption = buildSecretEncryptionConfig(resolved.secretEncryption)
191
+ const regions = resolved.regions ?? []
192
+
193
+ problems.push(...describeRegionProblems(regions))
167
194
 
168
195
  if (problems.length > 0) {
169
196
  throw new ConfigurationError(problems)
@@ -186,6 +213,8 @@ export function resolveKelpieConfig(input: KelpieConfigInput, environment: Envir
186
213
  env,
187
214
  appBaseUrl: resolved.appBaseUrl,
188
215
  secretEncryption,
216
+ signups: resolved.signups ?? 'open',
217
+ regions,
189
218
  }
190
219
  }
191
220
 
@@ -217,6 +246,8 @@ interface ResolvedInput {
217
246
  readonly moduleConfigPath?: string | undefined
218
247
  readonly siteName?: string | undefined
219
248
  readonly trustedProxyHopCount?: number
249
+ readonly signups?: SignupsMode
250
+ readonly regions?: readonly PublicRegion[]
220
251
  readonly appBaseUrl?: string
221
252
  readonly secretEncryption?: ResolvedSecretEncryption
222
253
  readonly email: ResolvedEmail
@@ -242,6 +273,43 @@ interface ResolvedRateLimit {
242
273
  readonly api?: { readonly limit?: number; readonly windowSeconds?: number }
243
274
  }
244
275
 
276
+ function describeRegionProblems(regions: readonly unknown[]): string[] {
277
+ const problems: string[] = []
278
+ const ids = new Set<string>()
279
+ const origins = new Set<string>()
280
+
281
+ for (const [index, region] of regions.entries()) {
282
+ const parsed = publicRegionSchema.safeParse(region)
283
+
284
+ if (!parsed.success) {
285
+ for (const issue of parsed.error.issues) {
286
+ problems.push(
287
+ describeValidationIssue({
288
+ path: ['regions', index, ...issue.path],
289
+ message: issue.message,
290
+ }),
291
+ )
292
+ }
293
+
294
+ continue
295
+ }
296
+
297
+ if (ids.has(parsed.data.id)) {
298
+ problems.push(`regions.${String(index)}.id: duplicate id '${parsed.data.id}'`)
299
+ }
300
+
301
+ ids.add(parsed.data.id)
302
+
303
+ if (origins.has(parsed.data.origin)) {
304
+ problems.push(`regions.${String(index)}.origin: duplicate origin '${parsed.data.origin}'`)
305
+ }
306
+
307
+ origins.add(parsed.data.origin)
308
+ }
309
+
310
+ return problems
311
+ }
312
+
245
313
  function buildEmailConfig(resolved: ResolvedEmail | undefined): EmailConfig {
246
314
  // Missing `email.provider` or `email.from` is already recorded by the
247
315
  // walker; return a placeholder so `resolveKelpieConfig` reaches its throw
@@ -128,7 +128,7 @@ Append a Note, update summary only when clearly supported, propose Plan items on
128
128
  targetTypes: ['company'],
129
129
  placement: 'primary',
130
130
  handbookSlugs: ['ideal-customer-profile', 'agent-faq'],
131
- instructions: `Research this Company. Update description, stage, sizeBand, techStack, website/linkedin when evidenced, tags, and summary.
131
+ instructions: `Research this Company. Update description, stage, sizeBand, techStack, domain when evidenced, tags, and summary.
132
132
  Append a Note listing sources. Prefer notes over guesses for uncertain facts.`,
133
133
  }),
134
134
  task({
@@ -623,6 +623,28 @@ Ask before setting stage to passed.`,
623
623
  instructions: `The human will paste notes after this prompt. Append a Note; propose Plan items; extract Decisions.`,
624
624
  }),
625
625
 
626
+ // Event
627
+ task({
628
+ id: 'event.brief',
629
+ label: 'Event brief',
630
+ description: 'Agenda, who is registered, linked records, open Plans.',
631
+ targetTypes: ['event'],
632
+ placement: 'primary',
633
+ handbookSlugs: ['agent-faq'],
634
+ instructions: `Prepare a brief for this Event: agenda from details, who is registered, linked records, and open Plan items.
635
+ Do not invent attendees. Prefer pinned notes and Decisions.`,
636
+ }),
637
+ task({
638
+ id: 'event.follow_up',
639
+ label: 'Follow up',
640
+ description: 'Draft follow-up from attended vs no-show Attendances.',
641
+ targetTypes: ['event'],
642
+ placement: 'primary',
643
+ handbookSlugs: ['voice-and-tone', 'agent-faq'],
644
+ instructions: `Draft follow-up from this Event's Attendances. Separate attended from no-show.
645
+ Save drafts as Notes. Do not send email.`,
646
+ }),
647
+
626
648
  // Candidate
627
649
  task({
628
650
  id: 'candidate.score',
@@ -753,6 +775,16 @@ Suggest Draft outreach or Plan items. Do not spam everyone.`,
753
775
  handbookSlugs: ['how-we-sell', 'agent-faq'],
754
776
  instructions: `Propose a week of Plan items across open Deals, Opportunities, Raises, and Partnerships.
755
777
  Create Plan items only when clearly useful; otherwise list proposals for confirmation.`,
778
+ }),
779
+ task({
780
+ id: 'workspace.upcoming_events',
781
+ label: 'Upcoming events',
782
+ description: 'Scheduled Events in the next week and their attendee counts.',
783
+ targetTypes: ['workspace'],
784
+ placement: 'primary',
785
+ handbookSlugs: ['agent-faq'],
786
+ instructions: `List scheduled Events in the next week with attendee counts.
787
+ Use GET /v1/dashboard upcoming_events. Suggest follow-up or brief tasks where useful.`,
756
788
  }),
757
789
  task({
758
790
  id: 'workspace.empty_field_sweep',
@@ -38,6 +38,7 @@ export function createAgentTasksModule(
38
38
  'partnerships',
39
39
  'raises',
40
40
  'hiring',
41
+ 'events',
41
42
  'plans',
42
43
  'decisions',
43
44
  'notes',
@@ -9,6 +9,7 @@ import { companies } from '../companies/schema.ts'
9
9
  import { deals } from '../deals/schema.ts'
10
10
  import { decisions } from '../decisions/schema.ts'
11
11
  import { enquiries } from '../enquiries/schema.ts'
12
+ import { attendances } from '../events/schema.ts'
12
13
  import { handbookPages } from '../handbook/schema.ts'
13
14
  import { candidates, roles } from '../hiring/schema.ts'
14
15
  import { notes } from '../notes/schema.ts'
@@ -443,6 +444,16 @@ async function collectRelated(
443
444
  ),
444
445
  }
445
446
 
447
+ case 'event':
448
+ return {
449
+ person_ids: await relatedFrom(
450
+ db,
451
+ attendances,
452
+ attendances.personId,
453
+ and(eq(attendances.workspaceId, workspaceId), eq(attendances.eventId, targetId)) as SQL,
454
+ ),
455
+ }
456
+
446
457
  case 'handbook':
447
458
  case 'workspace':
448
459
  return {}
@@ -1,10 +1,16 @@
1
- import { PIPELINE_KINDS } from '@kelpie/schemas'
1
+ import {
2
+ EVENT_ASSOCIATION_TARGET_TYPES,
3
+ FORM_ATTACH_TARGET_TYPES,
4
+ PIPELINE_KINDS,
5
+ PLAN_ITEM_TARGET_TYPES,
6
+ } from '@kelpie/schemas'
2
7
  import type { PipelineKind } from '@kelpie/schemas'
3
8
  import { and, eq } from 'drizzle-orm'
4
9
 
5
10
  import type { Queryable } from '../runtime/transaction.ts'
6
11
  import * as activityRepository from './activities/repository.ts'
7
12
  import * as decisionRepository from './decisions/repository.ts'
13
+ import { attendances, eventAssociations } from './events/schema.ts'
8
14
  import { formAttachTargets } from './forms/schema.ts'
9
15
  import { candidates } from './hiring/schema.ts'
10
16
  import * as listRepository from './lists/repository.ts'
@@ -20,19 +26,26 @@ import * as planRepository from './plans/repository.ts'
20
26
  * the target deletes these, in the same transaction, or the rows outlive the
21
27
  * record they described and reappear the day an id is reused.
22
28
  *
23
- * `plan_items` attach the same way but their check constraint allows only the
24
- * four pipeline types, so they are removed only for those targets. `person_links`
25
- * follows the same rule: it holds a person's involvement in a pipeline record
26
- * and its check constraint allows only the four pipeline types, so a deleted
27
- * deal/opportunity/raise/partnership sheds its people here. `form_attach_targets`
28
- * is the same shape: forms pinning submitters into a pre-existing pipeline
29
- * record shed the mapping when the target goes.
29
+ * `plan_items` attach the same way but their check constraint allows the
30
+ * pipeline types plus Event, so they are removed only for those targets.
31
+ * `person_links` stays pipeline-only. `form_attach_targets` follows the plan
32
+ * set (pipelines plus Event). `event_associations` is Event-only: deleting the
33
+ * other record strips rows that pointed at it.
30
34
  */
31
35
 
32
36
  /** Target types that can own attached records here. */
33
- export type AttachableTargetType = 'person' | 'company' | 'candidate' | PipelineKind
37
+ export type AttachableTargetType =
38
+ | 'person'
39
+ | 'company'
40
+ | 'candidate'
41
+ | 'event'
42
+ | 'attendance'
43
+ | PipelineKind
34
44
 
35
- const PLAN_TARGET_TYPES: ReadonlySet<string> = new Set(PIPELINE_KINDS)
45
+ const PLAN_TARGET_TYPES: ReadonlySet<string> = new Set(PLAN_ITEM_TARGET_TYPES)
46
+ const LINK_TARGET_TYPES: ReadonlySet<string> = new Set(PIPELINE_KINDS)
47
+ const FORM_ATTACH_TYPES: ReadonlySet<string> = new Set(FORM_ATTACH_TARGET_TYPES)
48
+ const ASSOCIATION_TARGET_TYPES: ReadonlySet<string> = new Set(EVENT_ASSOCIATION_TARGET_TYPES)
36
49
 
37
50
  /**
38
51
  * @param db Must be the caller's transaction. Called on its own, this deletes the
@@ -53,13 +66,13 @@ export async function deleteRecordsAttachedTo(
53
66
  const plans = PLAN_TARGET_TYPES.has(targetType)
54
67
  ? await planRepository.deleteForTarget(db, workspaceId, targetType, targetId)
55
68
  : 0
56
- const links = PLAN_TARGET_TYPES.has(targetType)
69
+ const links = LINK_TARGET_TYPES.has(targetType)
57
70
  ? await personLinks.deleteLinksForTarget(db, workspaceId, {
58
71
  targetType: targetType as PipelineKind,
59
72
  targetId,
60
73
  })
61
74
  : 0
62
- const attachTargets = PLAN_TARGET_TYPES.has(targetType)
75
+ const attachTargets = FORM_ATTACH_TYPES.has(targetType)
63
76
  ? (
64
77
  await db
65
78
  .delete(formAttachTargets)
@@ -79,8 +92,36 @@ export async function deleteRecordsAttachedTo(
79
92
  targetType,
80
93
  targetId,
81
94
  )
95
+ const associations = ASSOCIATION_TARGET_TYPES.has(targetType)
96
+ ? await deleteEventAssociationsForTarget(db, workspaceId, targetType, targetId)
97
+ : 0
98
+
99
+ return notes + activities + decisions + plans + links + attachTargets + memberships + associations
100
+ }
101
+
102
+ /**
103
+ * Event↔record rows have no FK to the target, so the target's delete must strip
104
+ * them. Event delete itself cascades `event_id`. Role is not an attachable
105
+ * notes target, so its service calls this helper directly.
106
+ */
107
+ export async function deleteEventAssociationsForTarget(
108
+ db: Queryable,
109
+ workspaceId: string,
110
+ targetType: string,
111
+ targetId: string,
112
+ ): Promise<number> {
113
+ const deleted = await db
114
+ .delete(eventAssociations)
115
+ .where(
116
+ and(
117
+ eq(eventAssociations.workspaceId, workspaceId),
118
+ eq(eventAssociations.targetType, targetType),
119
+ eq(eventAssociations.targetId, targetId),
120
+ ),
121
+ )
122
+ .returning({ id: eventAssociations.id })
82
123
 
83
- return notes + activities + decisions + plans + links + attachTargets + memberships
124
+ return deleted.length
84
125
  }
85
126
 
86
127
  /**
@@ -117,3 +158,34 @@ export async function deleteRecordsAttachedToCandidaciesOf(
117
158
 
118
159
  return removed
119
160
  }
161
+
162
+ /**
163
+ * The same cleanup for Attendances a person's (or Event's) delete takes with
164
+ * them. `attendances.person_id` / `event_id` cascade, so notes on those rows
165
+ * would otherwise outlive the Attendance.
166
+ */
167
+ export async function deleteRecordsAttachedToAttendancesOf(
168
+ db: Queryable,
169
+ workspaceId: string,
170
+ filter: { readonly personId: string } | { readonly eventId: string },
171
+ ): Promise<number> {
172
+ const held = await db
173
+ .select({ id: attendances.id })
174
+ .from(attendances)
175
+ .where(
176
+ and(
177
+ eq(attendances.workspaceId, workspaceId),
178
+ 'personId' in filter
179
+ ? eq(attendances.personId, filter.personId)
180
+ : eq(attendances.eventId, filter.eventId),
181
+ ),
182
+ )
183
+
184
+ let removed = 0
185
+
186
+ for (const attendance of held) {
187
+ removed += await deleteRecordsAttachedTo(db, workspaceId, 'attendance', attendance.id)
188
+ }
189
+
190
+ return removed
191
+ }
@@ -37,6 +37,7 @@ export function createAuthModule(migrationsDirectory: string): KelpieModule {
37
37
  createId: context.createId,
38
38
  now: context.now,
39
39
  appBaseUrl,
40
+ signupsEnabled: context.signupsEnabled ?? true,
40
41
  })
41
42
 
42
43
  // `Secure` everywhere except development. A test host reaches the API
@@ -1,8 +1,8 @@
1
- import { and, desc, eq, gt, isNull, ne } from 'drizzle-orm'
1
+ import { and, asc, desc, eq, gt, isNull, ne } from 'drizzle-orm'
2
2
 
3
3
  import type { Database } from '../../lib/database.ts'
4
4
  import type { Transaction } from '../../runtime/transaction.ts'
5
- import { workspaceMembers } from '../workspace/schema.ts'
5
+ import { workspaceMembers, workspaces } from '../workspace/schema.ts'
6
6
  import {
7
7
  emailVerificationTokens,
8
8
  passwordResetTokens,
@@ -64,11 +64,15 @@ export async function updateUserPassword(
64
64
  /**
65
65
  * Writes name and email. Not the password: that one has its own function because
66
66
  * it also ends sessions, and the two must not be reachable through one call.
67
+ *
68
+ * `emailVerifiedAt` rides along with an email change: the service resets it to
69
+ * null in the same call rather than a second write, so there is no window where
70
+ * the row holds a new address and the old address's verification.
67
71
  */
68
72
  export async function updateUserProfile(
69
73
  db: Queryable,
70
74
  userId: string,
71
- changes: { readonly name?: string; readonly email?: string },
75
+ changes: { readonly name?: string; readonly email?: string; readonly emailVerifiedAt?: Date | null },
72
76
  now: Date,
73
77
  ): Promise<UserRecord | undefined> {
74
78
  const [updated] = await db
@@ -294,3 +298,30 @@ export async function findFirstMembership(
294
298
 
295
299
  return found
296
300
  }
301
+
302
+ /** Every workspace this account belongs to, oldest membership first. */
303
+ export interface AccountWorkspaceRecord {
304
+ readonly id: string
305
+ readonly name: string
306
+ readonly slug: string
307
+ readonly timezone: string
308
+ readonly role: string
309
+ }
310
+
311
+ export async function listWorkspacesForUser(
312
+ db: Queryable,
313
+ userId: string,
314
+ ): Promise<AccountWorkspaceRecord[]> {
315
+ return db
316
+ .select({
317
+ id: workspaces.id,
318
+ name: workspaces.name,
319
+ slug: workspaces.slug,
320
+ timezone: workspaces.timezone,
321
+ role: workspaceMembers.role,
322
+ })
323
+ .from(workspaceMembers)
324
+ .innerJoin(workspaces, eq(workspaces.id, workspaceMembers.workspaceId))
325
+ .where(eq(workspaceMembers.userId, userId))
326
+ .orderBy(asc(workspaceMembers.joinedAt))
327
+ }
@@ -10,7 +10,7 @@ import type { SessionActor } from './actor.ts'
10
10
  import { resolveActorFrom } from './credentials.ts'
11
11
  import type { CredentialDependencies } from './credentials.ts'
12
12
  import type { PreferenceValues } from './preferences.ts'
13
- import type { AccountView, AuthService, IssuedSession, SessionView } from './service.ts'
13
+ import type { AccountView, AuthService, IssuedSession, MeView, SessionView } from './service.ts'
14
14
  import { clearSessionCookie, describeClient, writeSessionCookie } from './session.ts'
15
15
  import type { SessionCookieOptions } from './session.ts'
16
16
 
@@ -44,6 +44,10 @@ const changePasswordBody = z.object({
44
44
  new_password: z.string().min(1),
45
45
  })
46
46
 
47
+ const switchWorkspaceBodySchema = z.object({
48
+ workspace_id: z.string().min(1),
49
+ })
50
+
47
51
  const verifyEmailConfirmBody = z.object({
48
52
  token: z.string().min(1),
49
53
  })
@@ -135,6 +139,32 @@ function preferencesResponse(preferences: PreferenceValues): Record<string, unkn
135
139
  }
136
140
  }
137
141
 
142
+ function sessionMeResponse(session: MeView): Record<string, unknown> {
143
+ return {
144
+ user_id: session.userId,
145
+ session_id: session.sessionId,
146
+ workspace_id: session.workspaceId,
147
+ role: session.role,
148
+ email_verified: session.emailVerified,
149
+ }
150
+ }
151
+
152
+ function accountWorkspaceResponse(workspace: {
153
+ readonly id: string
154
+ readonly name: string
155
+ readonly slug: string
156
+ readonly timezone: string
157
+ readonly role: string
158
+ }): Record<string, unknown> {
159
+ return {
160
+ id: workspace.id,
161
+ name: workspace.name,
162
+ slug: workspace.slug,
163
+ timezone: workspace.timezone,
164
+ role: workspace.role,
165
+ }
166
+ }
167
+
138
168
  function sessionResponse(session: SessionView): Record<string, unknown> {
139
169
  return {
140
170
  id: session.id,
@@ -194,6 +224,19 @@ export function mountAuthRoutes(router: Hono, dependencies: AuthRoutesDependenci
194
224
  })
195
225
  })
196
226
 
227
+ router.get('/auth/workspaces', async (context) => {
228
+ const workspaces = await dependencies.service.listWorkspaces(await requireActor(context))
229
+
230
+ return context.json({ data: workspaces.map(accountWorkspaceResponse), next_cursor: null })
231
+ })
232
+
233
+ router.post('/auth/workspace', async (context) => {
234
+ const body = await readBody(context, switchWorkspaceBodySchema)
235
+ const session = await dependencies.service.switchWorkspace(await requireActor(context), body.workspace_id)
236
+
237
+ return context.json(sessionMeResponse(session))
238
+ })
239
+
197
240
  /**
198
241
  * `/account` rather than `/auth/account`: `/auth/*` is how a browser gets a
199
242
  * session, and this is the person that session belongs to. The auth module