@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,79 @@
1
+ CREATE TABLE "events" (
2
+ "id" text PRIMARY KEY NOT NULL,
3
+ "workspace_id" text NOT NULL,
4
+ "name" text NOT NULL,
5
+ "kind" text DEFAULT '' NOT NULL,
6
+ "starts_at" timestamp with time zone NOT NULL,
7
+ "ends_at" timestamp with time zone NOT NULL,
8
+ "timezone" text NOT NULL,
9
+ "location" text DEFAULT '' NOT NULL,
10
+ "meeting_url" text,
11
+ "format" text NOT NULL,
12
+ "details" text DEFAULT '' NOT NULL,
13
+ "status" text NOT NULL,
14
+ "owner_id" text,
15
+ "summary" text DEFAULT '' NOT NULL,
16
+ "tags" text[] DEFAULT '{}' NOT NULL,
17
+ "custom_fields" jsonb DEFAULT '{}'::jsonb NOT NULL,
18
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
19
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL,
20
+ "search_vector" "tsvector" GENERATED ALWAYS AS (setweight(to_tsvector('english', regexp_replace(coalesce("events"."name"::text, ''), '[^[:alnum:]]+', ' ', 'g')), 'A') || setweight(to_tsvector('english', regexp_replace(coalesce("events"."details"::text, ''), '[^[:alnum:]]+', ' ', 'g')), 'B') || setweight(to_tsvector('english', regexp_replace(coalesce("events"."summary"::text, ''), '[^[:alnum:]]+', ' ', 'g')), 'B') || setweight(to_tsvector('english', regexp_replace(coalesce("events"."kind"::text, ''), '[^[:alnum:]]+', ' ', 'g')), 'C') || setweight(to_tsvector('english', regexp_replace(coalesce("events"."location"::text, ''), '[^[:alnum:]]+', ' ', 'g')), 'C') || setweight(to_tsvector('english', regexp_replace(coalesce(kelpie_text_array_to_string("events"."tags"), ''), '[^[:alnum:]]+', ' ', 'g')), 'C')) STORED
21
+ );
22
+ --> statement-breakpoint
23
+ CREATE TABLE "attendances" (
24
+ "id" text PRIMARY KEY NOT NULL,
25
+ "workspace_id" text NOT NULL,
26
+ "event_id" text NOT NULL,
27
+ "person_id" text NOT NULL,
28
+ "status" text NOT NULL,
29
+ "source" text NOT NULL,
30
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
31
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL
32
+ );
33
+ --> statement-breakpoint
34
+ CREATE TABLE "event_associations" (
35
+ "id" text PRIMARY KEY NOT NULL,
36
+ "workspace_id" text NOT NULL,
37
+ "event_id" text NOT NULL,
38
+ "target_type" text NOT NULL,
39
+ "target_id" text NOT NULL,
40
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL
41
+ );
42
+ --> statement-breakpoint
43
+ ALTER TABLE "notes" DROP CONSTRAINT "notes_target_type_check";--> statement-breakpoint
44
+ ALTER TABLE "activities" DROP CONSTRAINT "activities_target_type_check";--> statement-breakpoint
45
+ ALTER TABLE "decisions" DROP CONSTRAINT "decisions_target_type_check";--> statement-breakpoint
46
+ ALTER TABLE "lists" DROP CONSTRAINT "lists_target_type_check";--> statement-breakpoint
47
+ ALTER TABLE "list_members" DROP CONSTRAINT "list_members_target_type_check";--> statement-breakpoint
48
+ ALTER TABLE "plan_items" DROP CONSTRAINT "plan_items_target_type_check";--> statement-breakpoint
49
+ ALTER TABLE "form_attach_targets" DROP CONSTRAINT "form_attach_targets_target_type_check";--> statement-breakpoint
50
+ ALTER TABLE "custom_field_definitions" DROP CONSTRAINT "custom_field_definitions_object_type_check";--> statement-breakpoint
51
+ ALTER TABLE "events" ADD CONSTRAINT "events_workspace_id_workspaces_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspaces"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
52
+ ALTER TABLE "events" ADD CONSTRAINT "events_owner_id_workspace_members_id_fk" FOREIGN KEY ("owner_id") REFERENCES "public"."workspace_members"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
53
+ ALTER TABLE "events" ADD CONSTRAINT "events_format_check" CHECK ("events"."format" in ('in_person', 'virtual', 'hybrid'));--> statement-breakpoint
54
+ ALTER TABLE "events" ADD CONSTRAINT "events_status_check" CHECK ("events"."status" in ('draft', 'scheduled', 'cancelled'));--> statement-breakpoint
55
+ CREATE INDEX "events_workspace_idx" ON "events" USING btree ("workspace_id");--> statement-breakpoint
56
+ CREATE INDEX "events_starts_idx" ON "events" USING btree ("workspace_id","starts_at");--> statement-breakpoint
57
+ CREATE INDEX "events_search_idx" ON "events" USING gin ("search_vector");--> statement-breakpoint
58
+ ALTER TABLE "attendances" ADD CONSTRAINT "attendances_workspace_id_workspaces_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspaces"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
59
+ ALTER TABLE "attendances" ADD CONSTRAINT "attendances_event_id_events_id_fk" FOREIGN KEY ("event_id") REFERENCES "public"."events"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
60
+ ALTER TABLE "attendances" ADD CONSTRAINT "attendances_person_id_people_id_fk" FOREIGN KEY ("person_id") REFERENCES "public"."people"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
61
+ ALTER TABLE "attendances" ADD CONSTRAINT "attendances_event_person_key" UNIQUE ("event_id","person_id");--> statement-breakpoint
62
+ ALTER TABLE "attendances" ADD CONSTRAINT "attendances_status_check" CHECK ("attendances"."status" in ('registered', 'attended', 'no_show', 'cancelled'));--> statement-breakpoint
63
+ ALTER TABLE "attendances" ADD CONSTRAINT "attendances_source_check" CHECK ("attendances"."source" in ('manual', 'form'));--> statement-breakpoint
64
+ CREATE INDEX "attendances_workspace_idx" ON "attendances" USING btree ("workspace_id");--> statement-breakpoint
65
+ CREATE INDEX "attendances_event_idx" ON "attendances" USING btree ("event_id");--> statement-breakpoint
66
+ CREATE INDEX "attendances_person_idx" ON "attendances" USING btree ("person_id");--> statement-breakpoint
67
+ ALTER TABLE "event_associations" ADD CONSTRAINT "event_associations_workspace_id_workspaces_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspaces"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
68
+ ALTER TABLE "event_associations" ADD CONSTRAINT "event_associations_event_id_events_id_fk" FOREIGN KEY ("event_id") REFERENCES "public"."events"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
69
+ ALTER TABLE "event_associations" ADD CONSTRAINT "event_associations_event_target_key" UNIQUE ("event_id","target_type","target_id");--> statement-breakpoint
70
+ ALTER TABLE "event_associations" ADD CONSTRAINT "event_associations_target_type_check" CHECK ("event_associations"."target_type" in ('person', 'company', 'deal', 'opportunity', 'partnership', 'raise', 'enquiry', 'role', 'candidate'));--> statement-breakpoint
71
+ CREATE INDEX "event_associations_target_idx" ON "event_associations" USING btree ("workspace_id","target_type","target_id");--> statement-breakpoint
72
+ ALTER TABLE "notes" ADD CONSTRAINT "notes_target_type_check" CHECK ("notes"."target_type" in ('person', 'company', 'deal', 'opportunity', 'partnership', 'raise', 'enquiry', 'candidate', 'event', 'attendance'));--> statement-breakpoint
73
+ ALTER TABLE "activities" ADD CONSTRAINT "activities_target_type_check" CHECK ("activities"."target_type" in ('person', 'company', 'deal', 'opportunity', 'partnership', 'raise', 'enquiry', 'candidate', 'event', 'attendance'));--> statement-breakpoint
74
+ ALTER TABLE "decisions" ADD CONSTRAINT "decisions_target_type_check" CHECK ("decisions"."target_type" in ('person', 'company', 'deal', 'opportunity', 'partnership', 'raise', 'enquiry', 'candidate', 'event', 'attendance'));--> statement-breakpoint
75
+ ALTER TABLE "lists" ADD CONSTRAINT "lists_target_type_check" CHECK ("lists"."target_type" in ('person', 'company', 'deal', 'opportunity', 'partnership', 'raise', 'enquiry', 'candidate', 'event', 'attendance'));--> statement-breakpoint
76
+ ALTER TABLE "list_members" ADD CONSTRAINT "list_members_target_type_check" CHECK ("list_members"."target_type" in ('person', 'company', 'deal', 'opportunity', 'partnership', 'raise', 'enquiry', 'candidate', 'event', 'attendance'));--> statement-breakpoint
77
+ ALTER TABLE "plan_items" ADD CONSTRAINT "plan_items_target_type_check" CHECK ("plan_items"."target_type" in ('enquiry', 'deal', 'opportunity', 'raise', 'partnership', 'event'));--> statement-breakpoint
78
+ ALTER TABLE "form_attach_targets" ADD CONSTRAINT "form_attach_targets_target_type_check" CHECK ("form_attach_targets"."target_type" in ('enquiry', 'deal', 'opportunity', 'raise', 'partnership', 'event'));--> statement-breakpoint
79
+ ALTER TABLE "custom_field_definitions" ADD CONSTRAINT "custom_field_definitions_object_type_check" CHECK ("custom_field_definitions"."object_type" in ('person', 'company', 'deal', 'opportunity', 'partnership', 'raise', 'enquiry', 'event'));
@@ -0,0 +1,66 @@
1
+ -- Postal addresses on people and companies. jsonb, like phones and social
2
+ -- profiles: nothing queries into the parts, and the route layer is what
3
+ -- validates the shape. `kelpie_addresses_search_text` is hand-added because
4
+ -- Drizzle Kit does not manage functions, the same reason `kelpie_text_array_to_string`
5
+ -- lives in 0015. jsonb_array_elements is STABLE; the wrapper asserts IMMUTABLE
6
+ -- so a generated search_vector can read it.
7
+ --
8
+ -- Stored keys match the TypeScript shape (`postalCode`), not the wire
9
+ -- (`postal_code`). The route maps at the boundary the same way it does for
10
+ -- every other resource.
11
+ CREATE OR REPLACE FUNCTION kelpie_addresses_search_text(jsonb) RETURNS text
12
+ LANGUAGE sql IMMUTABLE PARALLEL SAFE STRICT
13
+ AS $$
14
+ SELECT coalesce((
15
+ SELECT string_agg(trim(both ' ' from concat_ws(' ',
16
+ nullif(elem->>'line1', ''),
17
+ nullif(elem->>'line2', ''),
18
+ nullif(elem->>'city', ''),
19
+ nullif(elem->>'region', ''),
20
+ nullif(elem->>'postalCode', ''),
21
+ nullif(elem->>'country', '')
22
+ )), ' ')
23
+ FROM jsonb_array_elements($1) AS t(elem)
24
+ ), '')
25
+ $$;
26
+ --> statement-breakpoint
27
+ ALTER TABLE "people" ADD COLUMN "addresses" jsonb DEFAULT '[]'::jsonb NOT NULL;--> statement-breakpoint
28
+ ALTER TABLE "companies" ADD COLUMN "addresses" jsonb DEFAULT '[]'::jsonb NOT NULL;--> statement-breakpoint
29
+ UPDATE "people"
30
+ SET "addresses" = jsonb_build_array(
31
+ jsonb_build_object(
32
+ 'kind', 'home',
33
+ 'line1', null,
34
+ 'line2', null,
35
+ 'city', "location",
36
+ 'region', null,
37
+ 'postalCode', null,
38
+ 'country', null,
39
+ 'primary', true
40
+ )
41
+ )
42
+ WHERE "location" IS NOT NULL AND btrim("location") <> '';--> statement-breakpoint
43
+ UPDATE "companies"
44
+ SET "addresses" = jsonb_build_array(
45
+ jsonb_build_object(
46
+ 'kind', 'hq',
47
+ 'line1', null,
48
+ 'line2', null,
49
+ 'city', "hq",
50
+ 'region', null,
51
+ 'postalCode', null,
52
+ 'country', null,
53
+ 'primary', true
54
+ )
55
+ )
56
+ WHERE "hq" IS NOT NULL AND btrim("hq") <> '';--> statement-breakpoint
57
+ UPDATE "form_fields" SET "map_to" = 'person.address.city' WHERE "map_to" = 'person.location';--> statement-breakpoint
58
+ UPDATE "form_fields" SET "map_to" = 'company.address.city' WHERE "map_to" = 'company.hq';--> statement-breakpoint
59
+ ALTER TABLE "people" DROP COLUMN "location";--> statement-breakpoint
60
+ ALTER TABLE "companies" DROP COLUMN "hq";--> statement-breakpoint
61
+ ALTER TABLE "people" DROP COLUMN "search_vector";--> statement-breakpoint
62
+ ALTER TABLE "people" ADD COLUMN "search_vector" "tsvector" GENERATED ALWAYS AS (setweight(to_tsvector('english', regexp_replace(coalesce("people"."name"::text, ''), '[^[:alnum:]]+', ' ', 'g')), 'A') || setweight(to_tsvector('english', regexp_replace(coalesce("people"."first_name"::text, ''), '[^[:alnum:]]+', ' ', 'g')), 'A') || setweight(to_tsvector('english', regexp_replace(coalesce("people"."last_name"::text, ''), '[^[:alnum:]]+', ' ', 'g')), 'A') || setweight(to_tsvector('english', regexp_replace(coalesce("people"."email"::text, ''), '[^[:alnum:]]+', ' ', 'g')), 'B') || setweight(to_tsvector('english', regexp_replace(coalesce("people"."summary"::text, ''), '[^[:alnum:]]+', ' ', 'g')), 'B') || setweight(to_tsvector('english', regexp_replace(coalesce(kelpie_text_array_to_string("people"."tags"), ''), '[^[:alnum:]]+', ' ', 'g')), 'C') || setweight(to_tsvector('english', regexp_replace(coalesce(kelpie_addresses_search_text("people"."addresses"), ''), '[^[:alnum:]]+', ' ', 'g')), 'C')) STORED;--> statement-breakpoint
63
+ CREATE INDEX "people_search_idx" ON "people" USING gin ("search_vector");--> statement-breakpoint
64
+ ALTER TABLE "companies" DROP COLUMN "search_vector";--> statement-breakpoint
65
+ ALTER TABLE "companies" ADD COLUMN "search_vector" "tsvector" GENERATED ALWAYS AS (setweight(to_tsvector('english', regexp_replace(coalesce("companies"."name"::text, ''), '[^[:alnum:]]+', ' ', 'g')), 'A') || setweight(to_tsvector('english', regexp_replace(coalesce("companies"."domain"::text, ''), '[^[:alnum:]]+', ' ', 'g')), 'B') || setweight(to_tsvector('english', regexp_replace(coalesce("companies"."industry"::text, ''), '[^[:alnum:]]+', ' ', 'g')), 'B') || setweight(to_tsvector('english', regexp_replace(coalesce("companies"."description"::text, ''), '[^[:alnum:]]+', ' ', 'g')), 'B') || setweight(to_tsvector('english', regexp_replace(coalesce("companies"."summary"::text, ''), '[^[:alnum:]]+', ' ', 'g')), 'B') || setweight(to_tsvector('english', regexp_replace(coalesce(kelpie_text_array_to_string("companies"."tags"), ''), '[^[:alnum:]]+', ' ', 'g')), 'C') || setweight(to_tsvector('english', regexp_replace(coalesce(kelpie_text_array_to_string("companies"."tech_stack"), ''), '[^[:alnum:]]+', ' ', 'g')), 'C') || setweight(to_tsvector('english', regexp_replace(coalesce(kelpie_addresses_search_text("companies"."addresses"), ''), '[^[:alnum:]]+', ' ', 'g')), 'C')) STORED;--> statement-breakpoint
66
+ CREATE INDEX "companies_search_idx" ON "companies" USING gin ("search_vector");
@@ -0,0 +1,66 @@
1
+ -- Domain is the company homepage. `website` was a second URL that almost
2
+ -- always repeated `https://` plus the same host. Fill a blank domain from
3
+ -- that URL, then drop the column. Visit on the heading opens https://{domain}.
4
+ UPDATE "companies" AS c
5
+ SET "domain" = v.host
6
+ FROM (
7
+ SELECT DISTINCT ON (workspace_id, host)
8
+ id,
9
+ workspace_id,
10
+ host
11
+ FROM (
12
+ SELECT
13
+ id,
14
+ workspace_id,
15
+ NULLIF(
16
+ regexp_replace(
17
+ regexp_replace(
18
+ regexp_replace(lower(trim(website)), '^[a-z][a-z0-9+.-]*://', ''),
19
+ '/.*$',
20
+ ''
21
+ ),
22
+ '\.$',
23
+ ''
24
+ ),
25
+ ''
26
+ ) AS host
27
+ FROM "companies"
28
+ WHERE "domain" IS NULL AND "website" IS NOT NULL
29
+ ) AS sourced
30
+ WHERE host IS NOT NULL
31
+ ORDER BY workspace_id, host, id
32
+ ) AS v
33
+ WHERE c.id = v.id
34
+ AND NOT EXISTS (
35
+ SELECT 1
36
+ FROM "companies" AS other
37
+ WHERE other.workspace_id = c.workspace_id
38
+ AND other.domain = v.host
39
+ AND other.id <> c.id
40
+ );
41
+ --> statement-breakpoint
42
+ ALTER TABLE "companies" DROP COLUMN "website";
43
+ --> statement-breakpoint
44
+ UPDATE "form_fields"
45
+ SET "map_to" = 'company.domain'
46
+ WHERE "map_to" = 'company.website';
47
+ --> statement-breakpoint
48
+ UPDATE "import_jobs"
49
+ SET "column_map" = ("column_map" - 'website') || jsonb_build_object('domain', "column_map"->>'website')
50
+ WHERE jsonb_typeof("column_map") = 'object'
51
+ AND "column_map" ? 'website'
52
+ AND "column_map"->>'website' IS NOT NULL
53
+ AND NOT (
54
+ "column_map" ? 'domain'
55
+ AND "column_map"->>'domain' IS NOT NULL
56
+ );
57
+ --> statement-breakpoint
58
+ UPDATE "import_jobs"
59
+ SET "column_map" = "column_map" - 'website'
60
+ WHERE jsonb_typeof("column_map") = 'object'
61
+ AND "column_map" ? 'website';
62
+ --> statement-breakpoint
63
+ UPDATE "import_jobs"
64
+ SET "match_key" = 'domain'
65
+ WHERE "object" = 'companies'
66
+ AND "match_key" = 'website';
@@ -309,6 +309,27 @@
309
309
  "when": 1788700000000,
310
310
  "tag": "0043_external_sign_in",
311
311
  "breakpoints": true
312
+ },
313
+ {
314
+ "idx": 44,
315
+ "version": "7",
316
+ "when": 1788800000000,
317
+ "tag": "0044_events",
318
+ "breakpoints": true
319
+ },
320
+ {
321
+ "idx": 45,
322
+ "version": "7",
323
+ "when": 1788900000000,
324
+ "tag": "0045_addresses",
325
+ "breakpoints": true
326
+ },
327
+ {
328
+ "idx": 46,
329
+ "version": "7",
330
+ "when": 1789000000000,
331
+ "tag": "0046_drop_company_website",
332
+ "breakpoints": true
312
333
  }
313
334
  ]
314
335
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kelpie/server",
3
- "version": "0.12.0",
3
+ "version": "0.14.0",
4
4
  "description": "The Kelpie service as a library: module runtime, core CRM modules, REST API, MCP surface, and the shared migration pipeline.",
5
5
  "keywords": [
6
6
  "kelpie",
@@ -52,13 +52,14 @@
52
52
  },
53
53
  "dependencies": {
54
54
  "@hono/node-server": "^2.0.12",
55
- "@kelpie/schemas": "^0.12.0",
55
+ "@kelpie/schemas": "^0.14.0",
56
56
  "@node-rs/argon2": "^2.0.2",
57
57
  "drizzle-orm": "^0.45.2",
58
58
  "emittery": "^2.0.0",
59
- "hono": "^4.12.32",
59
+ "hono": "^4.13.7",
60
60
  "mailgen": "^2.0.36",
61
61
  "nodemailer": "^9.0.5",
62
+ "pg-boss": "^12.31.0",
62
63
  "postgres": "^3.4.9",
63
64
  "ulid": "^3.0.2",
64
65
  "winston": "^3.19.0",
package/src/app.ts CHANGED
@@ -2,6 +2,8 @@ import { Hono } from 'hono'
2
2
  import type { Context } from 'hono'
3
3
  import { cors } from 'hono/cors'
4
4
 
5
+ import type { PublicRegion } from '@kelpie/schemas'
6
+
5
7
  import type { RuntimeMode } from './lib/config.ts'
6
8
  import type { DatabaseProbe } from './lib/database.ts'
7
9
  import { AppError, internalErrorBody, toErrorBody } from './lib/errors.ts'
@@ -65,6 +67,18 @@ export interface AppDependencies {
65
67
  * name itself".
66
68
  */
67
69
  readonly siteName?: string | undefined
70
+ /**
71
+ * Reported through `GET /v1/public/config` so the sign-up page can show a
72
+ * "signups are closed" message instead of the form. Defaults to `true`
73
+ * (open), matching the config layer's own default.
74
+ */
75
+ readonly signupsEnabled?: boolean
76
+ /**
77
+ * Reported through `GET /v1/public/config` so signed-out pages can draw a
78
+ * region switcher. Defaults to `[]` (no switcher), matching a self-hosted
79
+ * install that never declared regions.
80
+ */
81
+ readonly regions?: readonly PublicRegion[]
68
82
  }
69
83
 
70
84
  /** Per-request values the middleware chain sets and handlers read. */
@@ -185,9 +199,19 @@ export function createApp(dependencies: AppDependencies): Hono<AppBindings> {
185
199
  // exists to be visible; nothing here is sensitive.
186
200
  const runtimeMode: RuntimeMode = dependencies.runtimeMode ?? 'production'
187
201
  const siteName = dependencies.siteName ?? null
202
+ const signupsEnabled = dependencies.signupsEnabled ?? true
203
+ const regions = dependencies.regions ?? []
188
204
 
189
205
  app.get(`${PUBLIC_ROUTE_PREFIX}/config`, (context) =>
190
- context.json({ runtime_mode: runtimeMode, site_name: siteName }, 200),
206
+ context.json(
207
+ {
208
+ runtime_mode: runtimeMode,
209
+ site_name: siteName,
210
+ signups_enabled: signupsEnabled,
211
+ regions,
212
+ },
213
+ 200,
214
+ ),
191
215
  )
192
216
 
193
217
  for (const { router } of dependencies.contributions.routers) {
package/src/boot.ts CHANGED
@@ -11,6 +11,8 @@ import { readModuleConfigFile } from './lib/moduleConfig.ts'
11
11
  import { resolveActorFrom } from './modules/auth/credentials.ts'
12
12
  import type { CredentialDependencies } from './modules/auth/credentials.ts'
13
13
  import { createEventBus } from './runtime/events.ts'
14
+ import { createJobsRuntime } from './runtime/jobs.ts'
15
+ import type { JobsRuntime } from './runtime/jobs.ts'
14
16
  import { registerModules } from './runtime/registry.ts'
15
17
  import type { ModuleContributions } from './runtime/registry.ts'
16
18
  import { createTransactionScope } from './runtime/transaction.ts'
@@ -34,6 +36,14 @@ export interface AssemblyBoot {
34
36
  readonly createId: IdFactory
35
37
  readonly credentials: CredentialDependencies
36
38
  readonly contributions: ModuleContributions
39
+ /**
40
+ * The pg-boss-backed job runtime the modules registered against. The
41
+ * entry point owns its lifecycle: call `jobs.migrate()` alongside
42
+ * `runMigrations`, `jobs.start()` before serving, and — on the worker —
43
+ * `jobs.startWorking()` right after. `jobs.stop()` drains before
44
+ * `database.close()`.
45
+ */
46
+ readonly jobs: JobsRuntime
37
47
  }
38
48
 
39
49
  /**
@@ -60,6 +70,7 @@ export async function bootAssembly(
60
70
  })
61
71
  const database = connectDatabase(config.databaseUrl, logger)
62
72
  const events = createEventBus(logger)
73
+ const jobs = createJobsRuntime({ connectionString: config.databaseUrl, logger })
63
74
  const createId = createIdFactory()
64
75
  const credentials: CredentialDependencies = { db: database.db, now: () => new Date() }
65
76
  const moduleConfig = readModuleConfigFile(config.moduleConfigPath)
@@ -69,14 +80,22 @@ export async function bootAssembly(
69
80
  logger,
70
81
  events,
71
82
  moduleConfig,
83
+ jobs: jobs.registry,
72
84
  resolveActor: (context) => resolveActorFrom(credentials, context),
73
85
  services: {
74
86
  db: database.db,
75
- transaction: createTransactionScope({ db: database.db, bus: events, logger, createId }),
87
+ transaction: createTransactionScope({
88
+ db: database.db,
89
+ bus: events,
90
+ logger,
91
+ createId,
92
+ enqueueOnTx: jobs.enqueueOnTx,
93
+ }),
76
94
  createId,
77
95
  now: () => new Date(),
78
96
  appBaseUrl: config.appBaseUrl,
79
97
  secretEncryption: config.secretEncryption,
98
+ signupsEnabled: config.signups === 'open',
80
99
  },
81
100
  // `provider` picks a named sender from the runtime's registry. `'log'` is
82
101
  // built in; `'smtp'` is registered by the built-in `smtp-email` core
@@ -85,5 +104,5 @@ export async function bootAssembly(
85
104
  email: { provider: config.email.EMAIL_PROVIDER, from: config.email.EMAIL_FROM },
86
105
  })
87
106
 
88
- return { config, logger, database, createId, credentials, contributions }
107
+ return { config, logger, database, createId, credentials, contributions, jobs }
89
108
  }
package/src/index.ts CHANGED
@@ -9,6 +9,22 @@ export type { AppDependencies, AppBindings } from './app.ts'
9
9
  export { bootAssembly } from './boot.ts'
10
10
  export type { AssemblyBoot } from './boot.ts'
11
11
 
12
+ export { startWorker } from './worker.ts'
13
+
14
+ export { createJobsRuntime } from './runtime/jobs.ts'
15
+ export type { JobsRuntime, JobsRuntimeOptions } from './runtime/jobs.ts'
16
+ export { deadLetterQueueName } from './lib/jobs.ts'
17
+ export type {
18
+ EnqueueOptions,
19
+ JobContext,
20
+ JobDefaults,
21
+ JobDefinition,
22
+ JobHandle,
23
+ JobHandler,
24
+ JobRegistry,
25
+ TransactionJobs,
26
+ } from './lib/jobs.ts'
27
+
12
28
  export { WebBundleError, serveWebBundle } from './webBundle.ts'
13
29
  export type { WebBundleOptions } from './webBundle.ts'
14
30
 
@@ -143,6 +159,7 @@ export type { ModuleEventCatalog } from './runtime/module.ts'
143
159
  export { createTransactionScope } from './runtime/transaction.ts'
144
160
  export type {
145
161
  BufferedEvents,
162
+ EnqueueOnTransaction,
146
163
  Transaction,
147
164
  TransactionContext,
148
165
  TransactionOptions,
@@ -58,6 +58,10 @@ const ROUTE_SCOPE_RULES: readonly RouteScopeRule[] = [
58
58
  { methods: ['POST', 'PATCH', 'DELETE'], pattern: /^\/v1\/roles(?:\/[^/]+)?$/u, scope: 'roles:write' },
59
59
  { methods: ['GET'], pattern: /^\/v1\/candidates(?:\/[^/]+)?$/u, scope: 'candidates:read' },
60
60
  { methods: ['POST', 'PATCH', 'DELETE'], pattern: /^\/v1\/candidates(?:\/[^/]+)?$/u, scope: 'candidates:write' },
61
+ { methods: ['GET'], pattern: /^\/v1\/(?:events(?:\/[^/]+(?:\/attendances)?)?|event-associations)$/u, scope: 'events:read' },
62
+ { methods: ['POST', 'PATCH', 'DELETE'], pattern: /^\/v1\/(?:events(?:\/[^/]+(?:\/attendances)?)?|event-associations)$/u, scope: 'events:write' },
63
+ { methods: ['GET'], pattern: /^\/v1\/attendances(?:\/[^/]+)?$/u, scope: 'attendances:read' },
64
+ { methods: ['POST', 'PATCH', 'DELETE'], pattern: /^\/v1\/attendances(?:\/[^/]+)?$/u, scope: 'attendances:write' },
61
65
  { methods: ['GET'], pattern: /^\/v1\/decisions(?:\/[^/]+)?$/u, scope: 'decisions:read' },
62
66
  { methods: ['POST', 'PATCH', 'DELETE'], pattern: /^\/v1\/decisions(?:\/[^/]+)?$/u, scope: 'decisions:write' },
63
67
  { methods: ['GET'], pattern: /^\/v1\/plan_items(?:\/[^/]+)?$/u, scope: 'plan_items:read' },
@@ -100,6 +104,7 @@ const ROUTE_SCOPE_RULES: readonly RouteScopeRule[] = [
100
104
  { methods: ['GET'], pattern: /^\/v1\/workspaces\/[^/]+\/modules(?:\/[^/]+)?$/u, scope: 'modules:read' },
101
105
  { methods: ['PATCH'], pattern: /^\/v1\/workspaces\/[^/]+\/modules(?:\/[^/]+)?$/u, scope: 'modules:write' },
102
106
  { methods: ['POST'], pattern: /^\/v1\/workspaces\/[^/]+\/sample-data$/u, scope: 'sample_data:write' },
107
+ { methods: ['POST'], pattern: /^\/v1\/workspaces\/[^/]+\/handbook\/seed$/u, scope: 'handbook:write' },
103
108
  { methods: ['POST'], pattern: /^\/v1\/workspaces\/[^/]+\/relink-email-domains$/u, scope: 'workspace:write' },
104
109
  { methods: ['GET'], pattern: /^\/v1\/mcp\/tools$/u, scope: 'search:read' },
105
110
  ]
@@ -158,6 +163,7 @@ const MCP_RESOURCE_ALIASES: Readonly<Record<string, string>> = {
158
163
  consent_purposes: 'consent_purposes',
159
164
  form_submissions: 'forms',
160
165
  list_memberships: 'lists',
166
+ event_associations: 'events',
161
167
  agent_tasks: 'agent_tasks',
162
168
  agent_runs: 'agent_runs',
163
169
  import_jobs: 'import_export',
@@ -60,6 +60,12 @@ export interface SearchVectorPart {
60
60
  * a generated column built on it.
61
61
  */
62
62
  readonly array?: boolean
63
+ /**
64
+ * True for an `addresses` jsonb column. Flattened by
65
+ * `kelpie_addresses_search_text`, a wrapper that migration `0045` adds so the
66
+ * generated vector reads city and postal code without the kind keys.
67
+ */
68
+ readonly addresses?: boolean
63
69
  }
64
70
 
65
71
  /**
@@ -78,9 +84,12 @@ export interface SearchVectorPart {
78
84
  const SPLIT_ON_PUNCTUATION = sql.raw("'[^[:alnum:]]+', ' ', 'g'")
79
85
 
80
86
  function searchVectorPart(part: SearchVectorPart): SQL {
81
- const raw = part.array === true
82
- ? sql`coalesce(kelpie_text_array_to_string(${part.column}), '')`
83
- : sql`coalesce(${part.column}::text, '')`
87
+ const raw =
88
+ part.addresses === true
89
+ ? sql`coalesce(kelpie_addresses_search_text(${part.column}), '')`
90
+ : part.array === true
91
+ ? sql`coalesce(kelpie_text_array_to_string(${part.column}), '')`
92
+ : sql`coalesce(${part.column}::text, '')`
84
93
  const source = sql`regexp_replace(${raw}, ${SPLIT_ON_PUNCTUATION})`
85
94
 
86
95
  return sql`setweight(to_tsvector(${sql.raw(`'${SEARCH_CONFIGURATION}'`)}, ${source}), ${sql.raw(`'${part.weight}'`)})`
package/src/lib/config.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { PublicRegion } from '@kelpie/schemas'
1
2
  import { z } from 'zod'
2
3
 
3
4
  import { emailConfigSchema } from './email.ts'
@@ -24,6 +25,9 @@ export type RuntimeMode = 'development' | 'test' | 'production'
24
25
 
25
26
  export type LogLevel = 'debug' | 'info' | 'warn' | 'error'
26
27
 
28
+ /** Whether `POST /v1/auth/signup` and external-sign-in provisioning accept a brand-new account. */
29
+ export type SignupsMode = 'open' | 'closed'
30
+
27
31
  /**
28
32
  * Everything the logger needs to boot: the minimum severity to emit, and the
29
33
  * ordered list of destinations each line is written to. Stdout is one such
@@ -96,6 +100,20 @@ export interface KelpieConfig {
96
100
  * it, so both webhooks and agent-tasks receive it through `services`.
97
101
  */
98
102
  readonly secretEncryption: SecretEncryptionConfig | undefined
103
+ /**
104
+ * Whether the instance accepts a brand-new account, through password signup
105
+ * or external-sign-in provisioning. Defaults to `'open'`, so a self-hoster
106
+ * who never sets `SIGNUPS` sees no change in behaviour. Joining an existing
107
+ * workspace via invite, and workspace creation by an already-verified
108
+ * account, are unaffected either way.
109
+ */
110
+ readonly signups: SignupsMode
111
+ /**
112
+ * Origins a multi-region assembly advertises on signed-out pages. Empty
113
+ * (the default) hides the switcher. Two or more draws it. Exposed through
114
+ * `GET /v1/public/config`.
115
+ */
116
+ readonly regions: readonly PublicRegion[]
99
117
  }
100
118
 
101
119
  /** Thrown at boot when the environment cannot produce a valid configuration. */
@@ -129,6 +147,7 @@ const environmentSchema = z.object({
129
147
  KELPIE_SITE_NAME: z.string().min(1).optional(),
130
148
  WEB_BUNDLE_DIR: z.string().min(1).optional(),
131
149
  TRUSTED_PROXY_HOP_COUNT: z.coerce.number().int().nonnegative().default(0),
150
+ SIGNUPS: z.enum(['open', 'closed']).default('open'),
132
151
  ...rateLimitConfigSchema.shape,
133
152
  })
134
153
 
@@ -171,5 +190,7 @@ export function loadConfig(environment: Environment): KelpieConfig {
171
190
  env: environment,
172
191
  appBaseUrl: undefined,
173
192
  secretEncryption: undefined,
193
+ signups: environmentResult.data.SIGNUPS,
194
+ regions: [],
174
195
  }
175
196
  }
package/src/lib/egress.ts CHANGED
@@ -74,17 +74,21 @@ function isPrivateIpv4(ip: string): boolean {
74
74
 
75
75
  /** True for an IPv6 address in a loopback, unspecified, link-local, or unique-local range. */
76
76
  function isPrivateIpv6(ip: string): boolean {
77
- const lower = ip.toLowerCase()
77
+ // Normalize both DNS results and literals. URL parsing compresses IPv6 and
78
+ // rewrites a dotted IPv4 suffix as two hexadecimal groups.
79
+ const lower = new URL(`http://[${ip}]/`).hostname.slice(1, -1)
78
80
 
79
81
  if (lower === '::1' || lower === '::') {
80
82
  return true
81
83
  }
82
84
 
83
- const mapped = /^::ffff:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/u.exec(lower)
85
+ const mapped = /^::ffff:([\da-f]{1,4}):([\da-f]{1,4})$/u.exec(lower)
84
86
 
85
- if (mapped?.[1] !== undefined) {
87
+ if (mapped?.[1] !== undefined && mapped[2] !== undefined) {
86
88
  // An IPv4 address written in IPv6 form resolves by its IPv4 rules.
87
- return isPrivateIpv4(mapped[1])
89
+ const high = Number.parseInt(mapped[1], 16)
90
+ const low = Number.parseInt(mapped[2], 16)
91
+ return isPrivateIpv4([high >>> 8, high & 255, low >>> 8, low & 255].join('.'))
88
92
  }
89
93
 
90
94
  const firstHextet = Number.parseInt(lower.split(':')[0] || '0', 16)
package/src/lib/errors.ts CHANGED
@@ -89,25 +89,32 @@ export class AppError extends Error {
89
89
  * A reason rather than a message, so a provider module can branch on it (to
90
90
  * redirect somewhere useful, say) without matching on English text.
91
91
  */
92
- export type ExternalSignInRefusal = 'email_unverified' | 'unknown_identity'
92
+ export type ExternalSignInRefusal = 'email_unverified' | 'unknown_identity' | 'signups_closed'
93
+
94
+ const EXTERNAL_SIGN_IN_CODE: Record<ExternalSignInRefusal, ErrorCode> = {
95
+ email_unverified: 'unauthorized',
96
+ unknown_identity: 'forbidden',
97
+ signups_closed: 'forbidden',
98
+ }
99
+
100
+ const EXTERNAL_SIGN_IN_MESSAGE: Record<ExternalSignInRefusal, string> = {
101
+ email_unverified: 'The identity provider did not verify this email address',
102
+ unknown_identity: 'No account exists for this identity',
103
+ signups_closed: 'Signups are closed on this instance',
104
+ }
93
105
 
94
106
  /**
95
107
  * An external sign-in core would not complete.
96
108
  *
97
109
  * An `AppError`, so an uncaught one still renders as the `api.md` body. No new
98
- * `ErrorCode`: refusing an unverified identity is an ordinary `401`, and
99
- * refusing to provision is an ordinary `403`.
110
+ * `ErrorCode`: refusing an unverified identity is an ordinary `401`, and both
111
+ * refusing to provision and signups being closed are an ordinary `403`.
100
112
  */
101
113
  export class ExternalSignInError extends AppError {
102
114
  readonly reason: ExternalSignInRefusal
103
115
 
104
116
  constructor(reason: ExternalSignInRefusal) {
105
- super(
106
- reason === 'email_unverified' ? 'unauthorized' : 'forbidden',
107
- reason === 'email_unverified'
108
- ? 'The identity provider did not verify this email address'
109
- : 'No account exists for this identity',
110
- )
117
+ super(EXTERNAL_SIGN_IN_CODE[reason], EXTERNAL_SIGN_IN_MESSAGE[reason])
111
118
  this.name = 'ExternalSignInError'
112
119
  this.reason = reason
113
120
  }
package/src/lib/ids.ts CHANGED
@@ -71,6 +71,17 @@ export const idPrefixes = {
71
71
  * key. Not persisted by core.
72
72
  */
73
73
  event: 'ev',
74
+ /**
75
+ * A dated CRM gathering. The public prefix is `event_`; the factory key is
76
+ * `crmEvent` because `event` already stamps domain-event envelopes as `ev_`.
77
+ */
78
+ crmEvent: 'event',
79
+ attendance: 'atnd',
80
+ /**
81
+ * Never returns over the wire: associations nest on the Event and on
82
+ * `GET /v1/event-associations`. Same reason as `personLink`.
83
+ */
84
+ eventAssociation: 'elink',
74
85
  } as const
75
86
 
76
87
  export type ObjectKind = keyof typeof idPrefixes