@storyshelf/core 0.6.2 → 0.8.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 (727) hide show
  1. package/README.md +49 -32
  2. package/dist/adapter/capture-queue.d.mts +44 -4
  3. package/dist/adapter/capture-runner.d.mts +134 -12
  4. package/dist/adapter/capture-runner.mjs +9 -12
  5. package/dist/adapter/database.d.mts +89 -10
  6. package/dist/adapter/database.mjs +4 -3
  7. package/dist/adapter/database.mjs.map +1 -1
  8. package/dist/adapter/design.d.mts +83 -0
  9. package/dist/adapter/design.mjs +27 -0
  10. package/dist/adapter/design.mjs.map +1 -0
  11. package/dist/adapter/email-sender.d.mts +0 -2
  12. package/dist/adapter/metadata.d.mts +3 -12
  13. package/dist/adapter/notifier.d.mts +6 -3
  14. package/dist/adapter/notifier.mjs +5 -1
  15. package/dist/adapter/oauth.d.mts +50 -0
  16. package/dist/adapter/setup.d.mts +4 -7
  17. package/dist/adapter/setup.mjs +3 -3
  18. package/dist/adapter/setup.mjs.map +1 -1
  19. package/dist/adapter/storage.d.mts +1 -3
  20. package/dist/adapter/webhook-events.d.mts +0 -2
  21. package/dist/adapter/webhook-events.mjs +13 -14
  22. package/dist/adapter/webhook-events.mjs.map +1 -1
  23. package/dist/adapters/capture-target-offers.d.mts +26 -0
  24. package/dist/adapters/capture-target-offers.mjs +57 -0
  25. package/dist/adapters/capture-target-offers.mjs.map +1 -0
  26. package/dist/adapters/capture-target.d.mts +69 -0
  27. package/dist/adapters/capture-target.mjs +75 -0
  28. package/dist/adapters/capture-target.mjs.map +1 -0
  29. package/dist/adapters/notifier/build-comment.d.mts +22 -0
  30. package/dist/adapters/notifier/build-comment.mjs +23 -0
  31. package/dist/adapters/notifier/build-comment.mjs.map +1 -0
  32. package/dist/adapters/notifier/build-state.d.mts +97 -0
  33. package/dist/adapters/notifier/build-state.mjs +217 -0
  34. package/dist/adapters/notifier/build-state.mjs.map +1 -0
  35. package/dist/adapters/notifier/events.d.mts +4 -4
  36. package/dist/adapters/notifier/events.mjs +32 -20
  37. package/dist/adapters/notifier/events.mjs.map +1 -1
  38. package/dist/adapters/notifier/format.d.mts +0 -2
  39. package/dist/adapters/notifier/format.mjs +1 -2
  40. package/dist/adapters/notifier/provider.d.mts +53 -9
  41. package/dist/adapters/notifier/provider.mjs +11 -0
  42. package/dist/adapters/notifier/provider.mjs.map +1 -0
  43. package/dist/adapters/notifier/state.d.mts +22 -0
  44. package/dist/adapters/notifier/state.mjs +65 -0
  45. package/dist/adapters/notifier/state.mjs.map +1 -0
  46. package/dist/adapters/notifier/types.d.mts +51 -4
  47. package/dist/adapters/remote-browser.d.mts +168 -0
  48. package/dist/adapters/remote-browser.mjs +201 -0
  49. package/dist/adapters/remote-browser.mjs.map +1 -0
  50. package/dist/adapters/remote-lease-attempt.mjs +39 -0
  51. package/dist/adapters/remote-lease-attempt.mjs.map +1 -0
  52. package/dist/adapters/remote-lease-error.d.mts +25 -0
  53. package/dist/adapters/remote-lease-error.mjs +54 -0
  54. package/dist/adapters/remote-lease-error.mjs.map +1 -0
  55. package/dist/adapters/remote-lease-held.d.mts +15 -0
  56. package/dist/adapters/remote-lease-held.mjs +86 -0
  57. package/dist/adapters/remote-lease-held.mjs.map +1 -0
  58. package/dist/adapters/remote-lease-shutdown.d.mts +12 -0
  59. package/dist/adapters/remote-lease-shutdown.mjs +32 -0
  60. package/dist/adapters/remote-lease-shutdown.mjs.map +1 -0
  61. package/dist/adapters/remote-lease.d.mts +39 -0
  62. package/dist/adapters/remote-lease.mjs +175 -0
  63. package/dist/adapters/remote-lease.mjs.map +1 -0
  64. package/dist/adapters/remote-limiter.mjs +121 -0
  65. package/dist/adapters/remote-limiter.mjs.map +1 -0
  66. package/dist/adapters/remote-pool.d.mts +56 -0
  67. package/dist/adapters/remote-pool.mjs +195 -0
  68. package/dist/adapters/remote-pool.mjs.map +1 -0
  69. package/dist/adapters/render-runs.d.mts +23 -0
  70. package/dist/adapters/render-runs.mjs +40 -0
  71. package/dist/adapters/render-runs.mjs.map +1 -0
  72. package/dist/adapters/setup-runner.d.mts +0 -2
  73. package/dist/adapters/setup-runner.mjs +3 -6
  74. package/dist/adapters/setup-runner.mjs.map +1 -1
  75. package/dist/adapters/sources.d.mts +10 -0
  76. package/dist/adapters/sources.mjs +19 -0
  77. package/dist/adapters/sources.mjs.map +1 -0
  78. package/dist/adapters/validate.d.mts +0 -2
  79. package/dist/adapters/validate.mjs +27 -20
  80. package/dist/adapters/validate.mjs.map +1 -1
  81. package/dist/ai.d.mts +103 -0
  82. package/dist/ai.mjs +18 -0
  83. package/dist/ai.mjs.map +1 -0
  84. package/dist/auth.d.mts +0 -2
  85. package/dist/auth.mjs +1 -2
  86. package/dist/brand-packs.d.mts +0 -2
  87. package/dist/brand-packs.mjs +0 -1
  88. package/dist/capture/a11y/axe-source.d.mts +16 -0
  89. package/dist/capture/a11y/axe-source.mjs +36 -0
  90. package/dist/capture/a11y/axe-source.mjs.map +1 -0
  91. package/dist/capture/a11y/in-page.d.mts +25 -0
  92. package/dist/capture/a11y/in-page.mjs +59 -0
  93. package/dist/capture/a11y/in-page.mjs.map +1 -0
  94. package/dist/capture/a11y/map.d.mts +22 -0
  95. package/dist/capture/a11y/map.mjs +47 -0
  96. package/dist/capture/a11y/map.mjs.map +1 -0
  97. package/dist/capture/a11y/options.d.mts +21 -0
  98. package/dist/capture/a11y/options.mjs +74 -0
  99. package/dist/capture/a11y/options.mjs.map +1 -0
  100. package/dist/capture/adapter.d.mts +46 -5
  101. package/dist/capture/adapter.mjs +1 -2
  102. package/dist/capture/adapter.mjs.map +1 -1
  103. package/dist/capture/affected.d.mts +2 -2
  104. package/dist/capture/affected.mjs +14 -6
  105. package/dist/capture/affected.mjs.map +1 -1
  106. package/dist/capture/attempt-logger.mjs +83 -0
  107. package/dist/capture/attempt-logger.mjs.map +1 -0
  108. package/dist/capture/browsers.d.mts +33 -0
  109. package/dist/capture/browsers.mjs +66 -0
  110. package/dist/capture/browsers.mjs.map +1 -0
  111. package/dist/capture/build-state/deliver.d.mts +30 -0
  112. package/dist/capture/build-state/deliver.mjs +117 -0
  113. package/dist/capture/build-state/deliver.mjs.map +1 -0
  114. package/dist/capture/build-state/index.d.mts +8 -0
  115. package/dist/capture/build-state/notify-job.d.mts +54 -0
  116. package/dist/capture/build-state/notify-job.mjs +132 -0
  117. package/dist/capture/build-state/notify-job.mjs.map +1 -0
  118. package/dist/capture/build-state/publisher.d.mts +21 -0
  119. package/dist/capture/build-state/publisher.mjs +106 -0
  120. package/dist/capture/build-state/publisher.mjs.map +1 -0
  121. package/dist/capture/build-state/resume.d.mts +7 -0
  122. package/dist/capture/build-state/resume.mjs +46 -0
  123. package/dist/capture/build-state/resume.mjs.map +1 -0
  124. package/dist/capture/build-state/types.d.mts +47 -0
  125. package/dist/capture/catalog/catalogue.d.mts +20 -0
  126. package/dist/capture/catalog/catalogue.mjs +52 -0
  127. package/dist/capture/catalog/catalogue.mjs.map +1 -0
  128. package/dist/capture/catalog/components.d.mts +21 -0
  129. package/dist/capture/catalog/components.mjs +54 -0
  130. package/dist/capture/catalog/components.mjs.map +1 -0
  131. package/dist/capture/catalog/history.d.mts +28 -0
  132. package/dist/capture/catalog/history.mjs +50 -0
  133. package/dist/capture/catalog/history.mjs.map +1 -0
  134. package/dist/capture/catalog/hook.d.mts +12 -0
  135. package/dist/capture/catalog/hook.mjs +18 -0
  136. package/dist/capture/catalog/hook.mjs.map +1 -0
  137. package/dist/capture/catalog/index.d.mts +11 -0
  138. package/dist/capture/catalog/load.d.mts +24 -0
  139. package/dist/capture/catalog/load.mjs +48 -0
  140. package/dist/capture/catalog/load.mjs.map +1 -0
  141. package/dist/capture/catalog/parse.d.mts +13 -0
  142. package/dist/capture/catalog/parse.mjs +55 -0
  143. package/dist/capture/catalog/parse.mjs.map +1 -0
  144. package/dist/capture/catalog/read.d.mts +15 -0
  145. package/dist/capture/catalog/read.mjs +39 -0
  146. package/dist/capture/catalog/read.mjs.map +1 -0
  147. package/dist/capture/catalog/types.d.mts +31 -0
  148. package/dist/capture/comparison-reference.d.mts +27 -0
  149. package/dist/capture/comparison-reference.mjs +75 -0
  150. package/dist/capture/comparison-reference.mjs.map +1 -0
  151. package/dist/capture/consumer/capture-handler.d.mts +56 -0
  152. package/dist/capture/consumer/capture-handler.mjs +117 -0
  153. package/dist/capture/consumer/capture-handler.mjs.map +1 -0
  154. package/dist/capture/consumer/idle.d.mts +15 -0
  155. package/dist/capture/consumer/idle.mjs +24 -0
  156. package/dist/capture/consumer/idle.mjs.map +1 -0
  157. package/dist/capture/consumer/index.d.mts +11 -0
  158. package/dist/capture/consumer/index.mjs +138 -0
  159. package/dist/capture/consumer/index.mjs.map +1 -0
  160. package/dist/capture/consumer/loop.mjs +109 -0
  161. package/dist/capture/consumer/loop.mjs.map +1 -0
  162. package/dist/capture/consumer/metrics.d.mts +39 -0
  163. package/dist/capture/consumer/metrics.mjs +37 -0
  164. package/dist/capture/consumer/metrics.mjs.map +1 -0
  165. package/dist/capture/consumer/retry.d.mts +31 -0
  166. package/dist/capture/consumer/retry.mjs +33 -0
  167. package/dist/capture/consumer/retry.mjs.map +1 -0
  168. package/dist/capture/consumer/run-job.mjs +194 -0
  169. package/dist/capture/consumer/run-job.mjs.map +1 -0
  170. package/dist/capture/consumer/startup.mjs +37 -0
  171. package/dist/capture/consumer/startup.mjs.map +1 -0
  172. package/dist/capture/consumer/types.d.mts +95 -0
  173. package/dist/capture/crash-failure.d.mts +23 -0
  174. package/dist/capture/crash-failure.mjs +69 -0
  175. package/dist/capture/crash-failure.mjs.map +1 -0
  176. package/dist/capture/db-queue/cadence.d.mts +30 -0
  177. package/dist/capture/db-queue/cadence.mjs +37 -0
  178. package/dist/capture/db-queue/cadence.mjs.map +1 -0
  179. package/dist/capture/db-queue/claim.d.mts +7 -0
  180. package/dist/capture/db-queue/claim.mjs +148 -0
  181. package/dist/capture/db-queue/claim.mjs.map +1 -0
  182. package/dist/capture/db-queue/clock.mjs +11 -0
  183. package/dist/capture/db-queue/clock.mjs.map +1 -0
  184. package/dist/capture/db-queue/codec.mjs +75 -0
  185. package/dist/capture/db-queue/codec.mjs.map +1 -0
  186. package/dist/capture/db-queue/index.d.mts +18 -0
  187. package/dist/capture/db-queue/index.mjs +89 -0
  188. package/dist/capture/db-queue/index.mjs.map +1 -0
  189. package/dist/capture/db-queue/keeper.d.mts +34 -0
  190. package/dist/capture/db-queue/keeper.mjs +91 -0
  191. package/dist/capture/db-queue/keeper.mjs.map +1 -0
  192. package/dist/capture/db-queue/lease.mjs +45 -0
  193. package/dist/capture/db-queue/lease.mjs.map +1 -0
  194. package/dist/capture/db-queue/lifecycle.mjs +35 -0
  195. package/dist/capture/db-queue/lifecycle.mjs.map +1 -0
  196. package/dist/capture/db-queue/operations.mjs +93 -0
  197. package/dist/capture/db-queue/operations.mjs.map +1 -0
  198. package/dist/capture/db-queue/poll-loop.d.mts +28 -0
  199. package/dist/capture/db-queue/poll-loop.mjs +88 -0
  200. package/dist/capture/db-queue/poll-loop.mjs.map +1 -0
  201. package/dist/capture/db-queue/poll.d.mts +8 -0
  202. package/dist/capture/db-queue/poll.mjs +68 -0
  203. package/dist/capture/db-queue/poll.mjs.map +1 -0
  204. package/dist/capture/db-queue/timers.d.mts +10 -0
  205. package/dist/capture/db-queue/timers.mjs +36 -0
  206. package/dist/capture/db-queue/timers.mjs.map +1 -0
  207. package/dist/capture/db-queue/types.d.mts +81 -0
  208. package/dist/capture/db-queue/types.mjs +11 -0
  209. package/dist/capture/db-queue/types.mjs.map +1 -0
  210. package/dist/capture/db-queue/wake.mjs +47 -0
  211. package/dist/capture/db-queue/wake.mjs.map +1 -0
  212. package/dist/capture/design.mjs +38 -0
  213. package/dist/capture/design.mjs.map +1 -0
  214. package/dist/capture/diff-record.mjs +24 -5
  215. package/dist/capture/diff-record.mjs.map +1 -1
  216. package/dist/capture/dispatch.d.mts +13 -10
  217. package/dist/capture/dispatch.mjs +102 -111
  218. package/dist/capture/dispatch.mjs.map +1 -1
  219. package/dist/capture/enrich.mjs +59 -0
  220. package/dist/capture/enrich.mjs.map +1 -0
  221. package/dist/capture/failure-sets.mjs +21 -0
  222. package/dist/capture/failure-sets.mjs.map +1 -0
  223. package/dist/capture/fingerprint.d.mts +40 -0
  224. package/dist/capture/fingerprint.mjs +82 -0
  225. package/dist/capture/fingerprint.mjs.map +1 -0
  226. package/dist/capture/forked/channel.d.mts +23 -0
  227. package/dist/capture/forked/child.d.mts +32 -0
  228. package/dist/capture/forked/child.mjs +201 -0
  229. package/dist/capture/forked/child.mjs.map +1 -0
  230. package/dist/capture/forked/crash-tracker.mjs +22 -0
  231. package/dist/capture/forked/crash-tracker.mjs.map +1 -0
  232. package/dist/capture/forked/decision.d.mts +24 -0
  233. package/dist/capture/forked/decision.mjs +26 -0
  234. package/dist/capture/forked/decision.mjs.map +1 -0
  235. package/dist/capture/forked/exit.mjs +11 -0
  236. package/dist/capture/forked/exit.mjs.map +1 -0
  237. package/dist/capture/forked/fork.d.mts +29 -0
  238. package/dist/capture/forked/fork.mjs +92 -0
  239. package/dist/capture/forked/fork.mjs.map +1 -0
  240. package/dist/capture/forked/forked-capture.d.mts +38 -0
  241. package/dist/capture/forked/forked-capture.mjs +239 -0
  242. package/dist/capture/forked/forked-capture.mjs.map +1 -0
  243. package/dist/capture/forked/lease-book.mjs +45 -0
  244. package/dist/capture/forked/lease-book.mjs.map +1 -0
  245. package/dist/capture/forked/protocol.d.mts +36 -0
  246. package/dist/capture/forked/protocol.mjs +24 -0
  247. package/dist/capture/forked/protocol.mjs.map +1 -0
  248. package/dist/capture/forked/runs.mjs +74 -0
  249. package/dist/capture/forked/runs.mjs.map +1 -0
  250. package/dist/capture/guards.mjs +1 -2
  251. package/dist/capture/ignore-regions.d.mts +36 -0
  252. package/dist/capture/ignore-regions.mjs +65 -0
  253. package/dist/capture/ignore-regions.mjs.map +1 -0
  254. package/dist/capture/isolate.mjs +140 -0
  255. package/dist/capture/isolate.mjs.map +1 -0
  256. package/dist/capture/job-lease.d.mts +26 -0
  257. package/dist/capture/job-lease.mjs +56 -0
  258. package/dist/capture/job-lease.mjs.map +1 -0
  259. package/dist/capture/matrix.mjs +298 -0
  260. package/dist/capture/matrix.mjs.map +1 -0
  261. package/dist/capture/modes.d.mts +23 -0
  262. package/dist/capture/modes.mjs +106 -0
  263. package/dist/capture/modes.mjs.map +1 -0
  264. package/dist/capture/orchestrator.d.mts +37 -7
  265. package/dist/capture/orchestrator.mjs +120 -110
  266. package/dist/capture/orchestrator.mjs.map +1 -1
  267. package/dist/capture/pipeline.d.mts +9 -4
  268. package/dist/capture/pipeline.mjs +284 -48
  269. package/dist/capture/pipeline.mjs.map +1 -1
  270. package/dist/capture/progress.d.mts +9 -0
  271. package/dist/capture/progress.mjs +27 -0
  272. package/dist/capture/progress.mjs.map +1 -0
  273. package/dist/capture/publish/accept.d.mts +37 -0
  274. package/dist/capture/publish/accept.mjs +27 -0
  275. package/dist/capture/publish/accept.mjs.map +1 -0
  276. package/dist/capture/publish/complete.d.mts +44 -0
  277. package/dist/capture/publish/complete.mjs +63 -0
  278. package/dist/capture/publish/complete.mjs.map +1 -0
  279. package/dist/capture/publish/extract.d.mts +18 -0
  280. package/dist/capture/publish/extract.mjs +120 -0
  281. package/dist/capture/publish/extract.mjs.map +1 -0
  282. package/dist/capture/publish/handler.d.mts +26 -0
  283. package/dist/capture/publish/handler.mjs +61 -0
  284. package/dist/capture/publish/handler.mjs.map +1 -0
  285. package/dist/capture/publish/index.d.mts +11 -0
  286. package/dist/capture/publish/invalid-zip.d.mts +8 -0
  287. package/dist/capture/publish/invalid-zip.mjs +11 -0
  288. package/dist/capture/publish/invalid-zip.mjs.map +1 -0
  289. package/dist/capture/publish/limits.d.mts +35 -0
  290. package/dist/capture/publish/limits.mjs +38 -0
  291. package/dist/capture/publish/limits.mjs.map +1 -0
  292. package/dist/capture/publish/published.d.mts +13 -0
  293. package/dist/capture/publish/published.mjs +17 -0
  294. package/dist/capture/publish/published.mjs.map +1 -0
  295. package/dist/capture/publish/run.d.mts +31 -0
  296. package/dist/capture/publish/run.mjs +29 -0
  297. package/dist/capture/publish/run.mjs.map +1 -0
  298. package/dist/capture/rediff.d.mts +1 -3
  299. package/dist/capture/rediff.mjs +23 -9
  300. package/dist/capture/rediff.mjs.map +1 -1
  301. package/dist/capture/render.mjs +52 -0
  302. package/dist/capture/render.mjs.map +1 -0
  303. package/dist/capture/scratch.d.mts +42 -0
  304. package/dist/capture/scratch.mjs +136 -0
  305. package/dist/capture/scratch.mjs.map +1 -0
  306. package/dist/capture/shard-plan.mjs +127 -0
  307. package/dist/capture/shard-plan.mjs.map +1 -0
  308. package/dist/capture/shards/context.mjs +51 -0
  309. package/dist/capture/shards/context.mjs.map +1 -0
  310. package/dist/capture/shards/execute.mjs +175 -0
  311. package/dist/capture/shards/execute.mjs.map +1 -0
  312. package/dist/capture/shards/fan-out.mjs +125 -0
  313. package/dist/capture/shards/fan-out.mjs.map +1 -0
  314. package/dist/capture/shards/history.mjs +34 -0
  315. package/dist/capture/shards/history.mjs.map +1 -0
  316. package/dist/capture/shards/reduce.mjs +96 -0
  317. package/dist/capture/shards/reduce.mjs.map +1 -0
  318. package/dist/capture/sizing.d.mts +0 -2
  319. package/dist/capture/sizing.mjs +2 -13
  320. package/dist/capture/sizing.mjs.map +1 -1
  321. package/dist/capture/skip-checks.mjs +3 -67
  322. package/dist/capture/skip-checks.mjs.map +1 -1
  323. package/dist/capture/statics.d.mts +7 -10
  324. package/dist/capture/statics.mjs +26 -74
  325. package/dist/capture/statics.mjs.map +1 -1
  326. package/dist/capture/storybook-urls.d.mts +38 -0
  327. package/dist/capture/storybook-urls.mjs +41 -0
  328. package/dist/capture/storybook-urls.mjs.map +1 -0
  329. package/dist/capture/storybook.d.mts +12 -6
  330. package/dist/capture/storybook.mjs +63 -19
  331. package/dist/capture/storybook.mjs.map +1 -1
  332. package/dist/capture/target.mjs +20 -0
  333. package/dist/capture/target.mjs.map +1 -0
  334. package/dist/capture/targets.d.mts +23 -0
  335. package/dist/capture/targets.mjs +31 -0
  336. package/dist/capture/targets.mjs.map +1 -0
  337. package/dist/capture/tests.d.mts +35 -0
  338. package/dist/capture/tests.mjs +136 -0
  339. package/dist/capture/tests.mjs.map +1 -0
  340. package/dist/capture/token-chunks.mjs +91 -0
  341. package/dist/capture/token-chunks.mjs.map +1 -0
  342. package/dist/capture.d.mts +63 -7
  343. package/dist/capture.mjs +51 -3
  344. package/dist/config.d.mts +136 -14
  345. package/dist/config.mjs +14 -8
  346. package/dist/config.mjs.map +1 -1
  347. package/dist/diff.d.mts +7 -3
  348. package/dist/diff.mjs +15 -3
  349. package/dist/diff.mjs.map +1 -0
  350. package/dist/index.d.mts +3 -3
  351. package/dist/index.mjs +2 -2
  352. package/dist/insights/budget.d.mts +18 -0
  353. package/dist/insights/budget.mjs +32 -0
  354. package/dist/insights/budget.mjs.map +1 -0
  355. package/dist/insights/envelope.d.mts +57 -0
  356. package/dist/insights/envelope.mjs +59 -0
  357. package/dist/insights/envelope.mjs.map +1 -0
  358. package/dist/insights/evidence-health.d.mts +30 -0
  359. package/dist/insights/evidence-health.mjs +27 -0
  360. package/dist/insights/evidence-health.mjs.map +1 -0
  361. package/dist/insights/evidence.d.mts +53 -0
  362. package/dist/insights/evidence.mjs +63 -0
  363. package/dist/insights/evidence.mjs.map +1 -0
  364. package/dist/insights/hash.d.mts +16 -0
  365. package/dist/insights/hash.mjs +20 -0
  366. package/dist/insights/hash.mjs.map +1 -0
  367. package/dist/insights/limits.d.mts +22 -0
  368. package/dist/insights/limits.mjs +23 -0
  369. package/dist/insights/limits.mjs.map +1 -0
  370. package/dist/insights/prompts/v1.d.mts +11 -0
  371. package/dist/insights/prompts/v1.mjs +33 -0
  372. package/dist/insights/prompts/v1.mjs.map +1 -0
  373. package/dist/insights/redact.d.mts +7 -0
  374. package/dist/insights/redact.mjs +23 -0
  375. package/dist/insights/redact.mjs.map +1 -0
  376. package/dist/insights/text.mjs +43 -0
  377. package/dist/insights/text.mjs.map +1 -0
  378. package/dist/insights.d.mts +11 -0
  379. package/dist/insights.mjs +12 -0
  380. package/dist/logger.d.mts +0 -2
  381. package/dist/logger.mjs +18 -3
  382. package/dist/logger.mjs.map +1 -1
  383. package/dist/models/ai-budget-alert.d.mts +21 -0
  384. package/dist/models/ai-budget-alert.mjs +37 -0
  385. package/dist/models/ai-budget-alert.mjs.map +1 -0
  386. package/dist/models/ai-usage.d.mts +38 -0
  387. package/dist/models/ai-usage.mjs +60 -0
  388. package/dist/models/ai-usage.mjs.map +1 -0
  389. package/dist/models/baseline-approval.d.mts +18 -0
  390. package/dist/models/baseline-approval.mjs +41 -0
  391. package/dist/models/baseline-approval.mjs.map +1 -0
  392. package/dist/models/baseline-archive.d.mts +82 -0
  393. package/dist/models/baseline-archive.mjs +105 -0
  394. package/dist/models/baseline-archive.mjs.map +1 -0
  395. package/dist/models/baseline-atomic.mjs +66 -0
  396. package/dist/models/baseline-atomic.mjs.map +1 -0
  397. package/dist/models/baseline-conflict.d.mts +6 -0
  398. package/dist/models/baseline-conflict.mjs +9 -0
  399. package/dist/models/baseline-conflict.mjs.map +1 -0
  400. package/dist/models/baseline-export.d.mts +17 -0
  401. package/dist/models/baseline-export.mjs +135 -0
  402. package/dist/models/baseline-export.mjs.map +1 -0
  403. package/dist/models/baseline-import.mjs +117 -0
  404. package/dist/models/baseline-import.mjs.map +1 -0
  405. package/dist/models/baseline-origin.d.mts +16 -0
  406. package/dist/models/baseline-origin.mjs +35 -0
  407. package/dist/models/baseline-origin.mjs.map +1 -0
  408. package/dist/models/baseline-status.d.mts +0 -2
  409. package/dist/models/baseline-status.mjs +1 -2
  410. package/dist/models/baseline-transfer.d.mts +48 -0
  411. package/dist/models/baseline-transfer.mjs +149 -0
  412. package/dist/models/baseline-transfer.mjs.map +1 -0
  413. package/dist/models/baseline.d.mts +37 -7
  414. package/dist/models/baseline.mjs +152 -28
  415. package/dist/models/baseline.mjs.map +1 -1
  416. package/dist/models/build-component.d.mts +26 -0
  417. package/dist/models/build-component.mjs +54 -0
  418. package/dist/models/build-component.mjs.map +1 -0
  419. package/dist/models/build-review-status.d.mts +27 -0
  420. package/dist/models/build-review-status.mjs +36 -0
  421. package/dist/models/build-review-status.mjs.map +1 -0
  422. package/dist/models/build-review.d.mts +26 -0
  423. package/dist/models/build-review.mjs +53 -0
  424. package/dist/models/build-review.mjs.map +1 -0
  425. package/dist/models/build.d.mts +25 -5
  426. package/dist/models/build.mjs +53 -15
  427. package/dist/models/build.mjs.map +1 -1
  428. package/dist/models/capture-attempt.d.mts +7 -3
  429. package/dist/models/capture-attempt.mjs +30 -6
  430. package/dist/models/capture-attempt.mjs.map +1 -1
  431. package/dist/models/capture-job.d.mts +104 -0
  432. package/dist/models/capture-job.mjs +287 -0
  433. package/dist/models/capture-job.mjs.map +1 -0
  434. package/dist/models/capture-log.d.mts +1 -3
  435. package/dist/models/capture-log.mjs +5 -5
  436. package/dist/models/capture-log.mjs.map +1 -1
  437. package/dist/models/capture-shard.d.mts +60 -0
  438. package/dist/models/capture-shard.mjs +189 -0
  439. package/dist/models/capture-shard.mjs.map +1 -0
  440. package/dist/models/changeset.d.mts +46 -0
  441. package/dist/models/changeset.mjs +80 -0
  442. package/dist/models/changeset.mjs.map +1 -0
  443. package/dist/models/columns.mjs +18 -0
  444. package/dist/models/columns.mjs.map +1 -0
  445. package/dist/models/comment.d.mts +4 -3
  446. package/dist/models/comment.mjs +8 -6
  447. package/dist/models/comment.mjs.map +1 -1
  448. package/dist/models/component.d.mts +41 -0
  449. package/dist/models/component.mjs +59 -0
  450. package/dist/models/component.mjs.map +1 -0
  451. package/dist/models/connections.d.mts +39 -0
  452. package/dist/models/connections.mjs +111 -0
  453. package/dist/models/connections.mjs.map +1 -0
  454. package/dist/models/content-ref.d.mts +1 -3
  455. package/dist/models/content-ref.mjs +2 -3
  456. package/dist/models/content-ref.mjs.map +1 -1
  457. package/dist/models/credentials.d.mts +0 -2
  458. package/dist/models/credentials.mjs +13 -4
  459. package/dist/models/credentials.mjs.map +1 -1
  460. package/dist/models/design-credentials.d.mts +39 -0
  461. package/dist/models/design-credentials.mjs +85 -0
  462. package/dist/models/design-credentials.mjs.map +1 -0
  463. package/dist/models/design-health.d.mts +14 -0
  464. package/dist/models/design-health.mjs +24 -0
  465. package/dist/models/design-health.mjs.map +1 -0
  466. package/dist/models/design-nodes.d.mts +81 -0
  467. package/dist/models/design-nodes.mjs +221 -0
  468. package/dist/models/design-nodes.mjs.map +1 -0
  469. package/dist/models/design-renders.mjs +73 -0
  470. package/dist/models/design-renders.mjs.map +1 -0
  471. package/dist/models/design-resolve.d.mts +27 -0
  472. package/dist/models/design-resolve.mjs +40 -0
  473. package/dist/models/design-resolve.mjs.map +1 -0
  474. package/dist/models/design.d.mts +37 -0
  475. package/dist/models/design.mjs +89 -0
  476. package/dist/models/design.mjs.map +1 -0
  477. package/dist/models/inherited-ratio.d.mts +19 -0
  478. package/dist/models/inherited-ratio.mjs +21 -0
  479. package/dist/models/inherited-ratio.mjs.map +1 -0
  480. package/dist/models/insight.d.mts +68 -0
  481. package/dist/models/insight.mjs +161 -0
  482. package/dist/models/insight.mjs.map +1 -0
  483. package/dist/models/label.d.mts +4 -4
  484. package/dist/models/label.mjs +26 -12
  485. package/dist/models/label.mjs.map +1 -1
  486. package/dist/models/member.d.mts +1 -3
  487. package/dist/models/member.mjs +6 -6
  488. package/dist/models/member.mjs.map +1 -1
  489. package/dist/models/notification-channel.d.mts +37 -5
  490. package/dist/models/notification-channel.mjs +89 -11
  491. package/dist/models/notification-channel.mjs.map +1 -1
  492. package/dist/models/notification-subscription.d.mts +1 -4
  493. package/dist/models/notification-subscription.mjs +8 -11
  494. package/dist/models/notification-subscription.mjs.map +1 -1
  495. package/dist/models/project-group-mapping.d.mts +6 -3
  496. package/dist/models/project-group-mapping.mjs +15 -6
  497. package/dist/models/project-group-mapping.mjs.map +1 -1
  498. package/dist/models/project.d.mts +9 -3
  499. package/dist/models/project.mjs +10 -6
  500. package/dist/models/project.mjs.map +1 -1
  501. package/dist/models/public-origin.d.mts +61 -0
  502. package/dist/models/public-origin.mjs +113 -0
  503. package/dist/models/public-origin.mjs.map +1 -0
  504. package/dist/models/review-switch-off.d.mts +12 -0
  505. package/dist/models/review-switch-off.mjs +25 -0
  506. package/dist/models/review-switch-off.mjs.map +1 -0
  507. package/dist/models/site-settings.d.mts +21 -0
  508. package/dist/models/site-settings.mjs +47 -0
  509. package/dist/models/site-settings.mjs.map +1 -0
  510. package/dist/models/snapshot.d.mts +39 -3
  511. package/dist/models/snapshot.mjs +54 -5
  512. package/dist/models/snapshot.mjs.map +1 -1
  513. package/dist/models/token.d.mts +13 -3
  514. package/dist/models/token.mjs +24 -7
  515. package/dist/models/token.mjs.map +1 -1
  516. package/dist/models/usage.d.mts +74 -0
  517. package/dist/models/usage.mjs +148 -0
  518. package/dist/models/usage.mjs.map +1 -0
  519. package/dist/models/user.d.mts +1 -3
  520. package/dist/models/user.mjs +5 -5
  521. package/dist/models/user.mjs.map +1 -1
  522. package/dist/models/webhook.d.mts +15 -3
  523. package/dist/models/webhook.mjs +34 -6
  524. package/dist/models/webhook.mjs.map +1 -1
  525. package/dist/models.d.mts +30 -4
  526. package/dist/models.mjs +31 -6
  527. package/dist/options.d.mts +16 -0
  528. package/dist/options.mjs +88 -0
  529. package/dist/options.mjs.map +1 -0
  530. package/dist/paths.d.mts +21 -6
  531. package/dist/paths.mjs +57 -12
  532. package/dist/paths.mjs.map +1 -1
  533. package/dist/retention/ai.d.mts +16 -0
  534. package/dist/retention/ai.mjs +22 -0
  535. package/dist/retention/ai.mjs.map +1 -0
  536. package/dist/retention/pending.mjs +46 -0
  537. package/dist/retention/pending.mjs.map +1 -0
  538. package/dist/retention/published-gzip-sweep.mjs +28 -0
  539. package/dist/retention/published-gzip-sweep.mjs.map +1 -0
  540. package/dist/retention/screenshot-sweep.d.mts +49 -0
  541. package/dist/retention/screenshot-sweep.mjs +127 -0
  542. package/dist/retention/screenshot-sweep.mjs.map +1 -0
  543. package/dist/retention.d.mts +16 -4
  544. package/dist/retention.mjs +76 -16
  545. package/dist/retention.mjs.map +1 -1
  546. package/dist/schema/baseline.d.mts +8 -2
  547. package/dist/schema/build-component.d.mts +23 -0
  548. package/dist/schema/build-review.d.mts +15 -0
  549. package/dist/schema/build.d.mts +7 -2
  550. package/dist/schema/capture-attempt.d.mts +0 -2
  551. package/dist/schema/capture-job.d.mts +58 -0
  552. package/dist/schema/capture-shard.d.mts +33 -0
  553. package/dist/schema/comment.d.mts +2 -2
  554. package/dist/schema/connection.d.mts +22 -0
  555. package/dist/schema/content-ref.d.mts +0 -2
  556. package/dist/schema/design.d.mts +56 -0
  557. package/dist/schema/insight.d.mts +52 -0
  558. package/dist/schema/label.d.mts +0 -2
  559. package/dist/schema/member.d.mts +0 -2
  560. package/dist/schema/notification.d.mts +4 -2
  561. package/dist/schema/project-group-mapping.d.mts +0 -2
  562. package/dist/schema/project.d.mts +21 -2
  563. package/dist/schema/site-setting.d.mts +19 -0
  564. package/dist/schema/snapshot.d.mts +17 -2
  565. package/dist/schema/token.d.mts +0 -2
  566. package/dist/schema/usage.d.mts +28 -0
  567. package/dist/schema/user.d.mts +0 -2
  568. package/dist/schema/webhook.d.mts +0 -2
  569. package/dist/schema.d.mts +12 -6
  570. package/dist/serverless.d.mts +19 -0
  571. package/dist/serverless.mjs +36 -0
  572. package/dist/serverless.mjs.map +1 -0
  573. package/dist/test-helpers/build-reference-contract.d.mts +14 -0
  574. package/dist/test-helpers/build-reference-contract.mjs +66 -0
  575. package/dist/test-helpers/build-reference-contract.mjs.map +1 -0
  576. package/dist/test-helpers/create-project.d.mts +1 -3
  577. package/dist/test-helpers/create-project.mjs +1 -2
  578. package/dist/test-helpers/database-contract.d.mts +0 -2
  579. package/dist/test-helpers/database-contract.mjs +37 -2
  580. package/dist/test-helpers/database-contract.mjs.map +1 -1
  581. package/dist/test-helpers/deferred.d.mts +37 -0
  582. package/dist/test-helpers/deferred.mjs +60 -0
  583. package/dist/test-helpers/deferred.mjs.map +1 -0
  584. package/dist/test-helpers/design-contract.d.mts +19 -0
  585. package/dist/test-helpers/design-contract.mjs +39 -0
  586. package/dist/test-helpers/design-contract.mjs.map +1 -0
  587. package/dist/test-helpers/fake-adapters.d.mts +0 -2
  588. package/dist/test-helpers/fake-adapters.mjs +1 -2
  589. package/dist/test-helpers/fake-database.d.mts +0 -2
  590. package/dist/test-helpers/fake-database.mjs +21 -4
  591. package/dist/test-helpers/fake-database.mjs.map +1 -1
  592. package/dist/test-helpers/fake-design.d.mts +41 -0
  593. package/dist/test-helpers/fake-design.mjs +113 -0
  594. package/dist/test-helpers/fake-design.mjs.map +1 -0
  595. package/dist/test-helpers/fake-notifier.d.mts +19 -0
  596. package/dist/test-helpers/fake-notifier.mjs +43 -0
  597. package/dist/test-helpers/fake-notifier.mjs.map +1 -0
  598. package/dist/test-helpers/fake-storage.d.mts +0 -2
  599. package/dist/test-helpers/fake-storage.mjs +1 -2
  600. package/dist/test-helpers/fake-tables.mjs +260 -69
  601. package/dist/test-helpers/fake-tables.mjs.map +1 -1
  602. package/dist/test-helpers/manual-clock.d.mts +25 -0
  603. package/dist/test-helpers/manual-clock.mjs +55 -0
  604. package/dist/test-helpers/manual-clock.mjs.map +1 -0
  605. package/dist/test-helpers/notifier-contract.d.mts +7 -3
  606. package/dist/test-helpers/notifier-contract.mjs +33 -5
  607. package/dist/test-helpers/notifier-contract.mjs.map +1 -1
  608. package/dist/test-helpers/queue-contract.d.mts +16 -4
  609. package/dist/test-helpers/queue-contract.mjs +196 -3
  610. package/dist/test-helpers/queue-contract.mjs.map +1 -1
  611. package/dist/test-helpers/queue-multi-instance.d.mts +18 -0
  612. package/dist/test-helpers/queue-multi-instance.mjs +205 -0
  613. package/dist/test-helpers/queue-multi-instance.mjs.map +1 -0
  614. package/dist/test-helpers/runner-contract.d.mts +0 -2
  615. package/dist/test-helpers/runner-contract.mjs +12 -2
  616. package/dist/test-helpers/runner-contract.mjs.map +1 -1
  617. package/dist/test-helpers/sql-chunks.mjs +25 -2
  618. package/dist/test-helpers/sql-chunks.mjs.map +1 -1
  619. package/dist/test-helpers/storage-contract.d.mts +0 -2
  620. package/dist/test-helpers/storage-contract.mjs +2 -3
  621. package/dist/test-helpers.d.mts +10 -4
  622. package/dist/test-helpers.mjs +9 -3
  623. package/dist/tracing.mjs +36 -6
  624. package/dist/tracing.mjs.map +1 -1
  625. package/dist/types.d.mts +14 -5
  626. package/dist/types.mjs +22 -6
  627. package/dist/types.mjs.map +1 -1
  628. package/dist/urls.d.mts +4 -2
  629. package/dist/urls.mjs +12 -4
  630. package/dist/urls.mjs.map +1 -1
  631. package/dist/utils/adaptive-concurrency.d.mts +64 -0
  632. package/dist/utils/adaptive-concurrency.mjs +148 -0
  633. package/dist/utils/adaptive-concurrency.mjs.map +1 -0
  634. package/dist/utils/connect-retry.d.mts +26 -0
  635. package/dist/utils/connect-retry.mjs +42 -0
  636. package/dist/utils/connect-retry.mjs.map +1 -0
  637. package/dist/utils/encrypt.d.mts +0 -2
  638. package/dist/utils/encrypt.mjs +1 -2
  639. package/dist/utils/environment.d.mts +17 -0
  640. package/dist/utils/environment.mjs +58 -0
  641. package/dist/utils/environment.mjs.map +1 -0
  642. package/dist/utils/hash.d.mts +0 -2
  643. package/dist/utils/hash.mjs +1 -2
  644. package/dist/utils/http.d.mts +29 -3
  645. package/dist/utils/http.mjs +100 -19
  646. package/dist/utils/http.mjs.map +1 -1
  647. package/dist/utils/map-adaptive.d.mts +13 -0
  648. package/dist/utils/map-adaptive.mjs +71 -0
  649. package/dist/utils/map-adaptive.mjs.map +1 -0
  650. package/dist/utils/map-limit.d.mts +8 -0
  651. package/dist/utils/map-limit.mjs +31 -0
  652. package/dist/utils/map-limit.mjs.map +1 -0
  653. package/dist/utils/mime.d.mts +0 -2
  654. package/dist/utils/mime.mjs +1 -2
  655. package/dist/utils/page-limiter.d.mts +35 -0
  656. package/dist/utils/page-limiter.mjs +55 -0
  657. package/dist/utils/page-limiter.mjs.map +1 -0
  658. package/dist/utils/path-security.d.mts +0 -2
  659. package/dist/utils/path-security.mjs +2 -4
  660. package/dist/utils/path-security.mjs.map +1 -1
  661. package/dist/utils/public-origin.d.mts +18 -0
  662. package/dist/utils/public-origin.mjs +74 -0
  663. package/dist/utils/public-origin.mjs.map +1 -0
  664. package/dist/utils/redact.d.mts +0 -2
  665. package/dist/utils/redact.mjs +4 -2
  666. package/dist/utils/redact.mjs.map +1 -1
  667. package/dist/utils/remote-environment.d.mts +76 -0
  668. package/dist/utils/remote-environment.mjs +163 -0
  669. package/dist/utils/remote-environment.mjs.map +1 -0
  670. package/dist/utils/resources.d.mts +62 -0
  671. package/dist/utils/resources.mjs +121 -0
  672. package/dist/utils/resources.mjs.map +1 -0
  673. package/dist/utils/screenshot-store.d.mts +13 -0
  674. package/dist/utils/screenshot-store.mjs +40 -0
  675. package/dist/utils/screenshot-store.mjs.map +1 -0
  676. package/dist/utils/session-gate.d.mts +23 -0
  677. package/dist/utils/session-gate.mjs +69 -0
  678. package/dist/utils/session-gate.mjs.map +1 -0
  679. package/dist/utils/storybook-token.d.mts +89 -0
  680. package/dist/utils/storybook-token.mjs +135 -0
  681. package/dist/utils/storybook-token.mjs.map +1 -0
  682. package/dist/utils/storybook-url.d.mts +13 -0
  683. package/dist/utils/storybook-url.mjs +20 -0
  684. package/dist/utils/storybook-url.mjs.map +1 -0
  685. package/dist/utils/timing.d.mts +0 -2
  686. package/dist/utils/timing.mjs +2 -3
  687. package/dist/utils/timing.mjs.map +1 -1
  688. package/dist/utils/ulid.d.mts +0 -2
  689. package/dist/utils/ulid.mjs +1 -2
  690. package/dist/utils/zip-read.mjs +128 -0
  691. package/dist/utils/zip-read.mjs.map +1 -0
  692. package/dist/utils/zip-write.d.mts +16 -0
  693. package/dist/utils/zip-write.mjs +187 -0
  694. package/dist/utils/zip-write.mjs.map +1 -0
  695. package/dist/utils.d.mts +17 -3
  696. package/dist/utils.mjs +18 -4
  697. package/package.json +35 -26
  698. package/dist/adapter/capture-runner.mjs.map +0 -1
  699. package/dist/adapter/git-host/comments.d.mts +0 -41
  700. package/dist/adapter/git-host/comments.mjs +0 -54
  701. package/dist/adapter/git-host/comments.mjs.map +0 -1
  702. package/dist/adapter/git-host/helpers.d.mts +0 -13
  703. package/dist/adapter/git-host/helpers.mjs +0 -30
  704. package/dist/adapter/git-host/helpers.mjs.map +0 -1
  705. package/dist/adapter/git-host.d.mts +0 -42
  706. package/dist/capture/queue.d.mts +0 -38
  707. package/dist/capture/queue.mjs +0 -98
  708. package/dist/capture/queue.mjs.map +0 -1
  709. package/dist/capture/recovery.d.mts +0 -21
  710. package/dist/capture/recovery.mjs +0 -83
  711. package/dist/capture/recovery.mjs.map +0 -1
  712. package/dist/capture/status-fanout.mjs +0 -72
  713. package/dist/capture/status-fanout.mjs.map +0 -1
  714. package/dist/diff/engine.d.mts +0 -16
  715. package/dist/diff/engine.mjs +0 -87
  716. package/dist/diff/engine.mjs.map +0 -1
  717. package/dist/diff/options.d.mts +0 -42
  718. package/dist/diff/options.mjs +0 -15
  719. package/dist/diff/options.mjs.map +0 -1
  720. package/dist/models/status-config.d.mts +0 -38
  721. package/dist/models/status-config.mjs +0 -63
  722. package/dist/models/status-config.mjs.map +0 -1
  723. package/dist/schema/status-config.d.mts +0 -16
  724. package/dist/test-helpers/git-contract.d.mts +0 -11
  725. package/dist/test-helpers/git-contract.mjs +0 -34
  726. package/dist/test-helpers/git-contract.mjs.map +0 -1
  727. /package/dist/adapter/{git-host.mjs → oauth.mjs} +0 -0
@@ -3,13 +3,14 @@ import "node:url";
3
3
  import.meta.url;
4
4
  import { PERSISTENT_LABEL_KEY, RESERVED_LABEL_KEYS, SEEDED_LABEL_KEYS } from "../types.mjs";
5
5
  import { ulid } from "../utils/ulid.mjs";
6
- import { and, desc, eq, getTableColumns } from "drizzle-orm";
7
- //#region src/models/label.ts
6
+ import { col } from "./columns.mjs";
7
+ import { and, desc, eq, inArray } from "drizzle-orm";
8
+ // src/models/label.ts
8
9
  /** Build labels and project label types. */
9
10
  /** Data operations for label types and build labels. */
10
11
  var LabelModel = class {
11
- db;
12
12
  tables;
13
+ db;
13
14
  /**
14
15
  * @param db - Database adapter.
15
16
  * @param tables - Table handles.
@@ -38,18 +39,19 @@ var LabelModel = class {
38
39
  projectId,
39
40
  key,
40
41
  name: SEEDED_NAMES[key] ?? key,
42
+ linkTemplate: SEEDED_LINK_TEMPLATES[key],
41
43
  createdAt: now
42
44
  });
43
45
  }));
44
46
  }
45
47
  /** List all label types for a project. */
46
48
  async listTypes(projectId) {
47
- return await this.db.list(this.tables.labelTypes, { where: eq(getTableColumns(this.tables.labelTypes)["projectId"], projectId) });
49
+ return await this.db.list(this.tables.labelTypes, { where: eq(col(this.tables.labelTypes, "projectId"), projectId) });
48
50
  }
49
51
  /** Fetch a label type by key, or null if not found. */
50
52
  async getType(projectId, key) {
51
53
  return (await this.db.list(this.tables.labelTypes, {
52
- where: and(eq(getTableColumns(this.tables.labelTypes)["projectId"], projectId), eq(getTableColumns(this.tables.labelTypes)["key"], key)),
54
+ where: and(eq(col(this.tables.labelTypes, "projectId"), projectId), eq(col(this.tables.labelTypes, "key"), key)),
53
55
  limit: 1
54
56
  }))[0] ?? null;
55
57
  }
@@ -95,18 +97,26 @@ var LabelModel = class {
95
97
  }
96
98
  /** List all labels attached to a build. */
97
99
  async listForBuild(buildId) {
98
- return await this.db.list(this.tables.buildLabels, { where: eq(getTableColumns(this.tables.buildLabels)["buildId"], buildId) });
100
+ return await this.db.list(this.tables.buildLabels, { where: eq(col(this.tables.buildLabels, "buildId"), buildId) });
99
101
  }
100
102
  /** Return the id of the latest build carrying a given label value, if any. */
101
103
  async latestBuildId(projectId, typeKey, value) {
102
- const labels = await this.db.list(this.tables.buildLabels, { where: and(eq(getTableColumns(this.tables.buildLabels)["projectId"], projectId), eq(getTableColumns(this.tables.buildLabels)["typeKey"], typeKey), eq(getTableColumns(this.tables.buildLabels)["value"], value)) });
104
+ const labels = await this.db.list(this.tables.buildLabels, { where: and(eq(col(this.tables.buildLabels, "projectId"), projectId), eq(col(this.tables.buildLabels, "typeKey"), typeKey), eq(col(this.tables.buildLabels, "value"), value)) });
103
105
  if (labels.length === 0) return null;
104
- const idSet = new Set(labels.map((l) => l.buildId));
105
- return (await this.db.list(this.tables.builds, { orderBy: desc(getTableColumns(this.tables.builds)["createdAt"]) })).find((b) => idSet.has(b.id))?.id ?? null;
106
+ return (await this.db.list(this.tables.builds, {
107
+ where: inArray(col(this.tables.builds, "id"), labels.map((label) => label.buildId)),
108
+ orderBy: desc(col(this.tables.builds, "createdAt")),
109
+ limit: 1
110
+ }))[0]?.id ?? null;
106
111
  }
107
- /** Return whether a build is marked as persistent. */
112
+ /** Whether a build is marked as persistent. */
108
113
  async hasPersistent(projectId, buildId) {
109
- return (await this.db.list(this.tables.buildLabels, { where: and(eq(getTableColumns(this.tables.buildLabels)["projectId"], projectId), eq(getTableColumns(this.tables.buildLabels)["buildId"], buildId), eq(getTableColumns(this.tables.buildLabels)["typeKey"], PERSISTENT_LABEL_KEY)) })).length > 0;
114
+ return (await this.persistentBuildIds(projectId)).has(buildId);
115
+ }
116
+ /** Ids of the project's builds marked persistent (exempt from retention). */
117
+ async persistentBuildIds(projectId) {
118
+ const labels = await this.db.list(this.tables.buildLabels, { where: and(eq(col(this.tables.buildLabels, "projectId"), projectId), eq(col(this.tables.buildLabels, "typeKey"), PERSISTENT_LABEL_KEY)) });
119
+ return new Set(labels.map((label) => label.buildId));
110
120
  }
111
121
  };
112
122
  const SEEDED_NAMES = {
@@ -119,7 +129,11 @@ const SEEDED_NAMES = {
119
129
  figma: "Figma file",
120
130
  custom: "Custom"
121
131
  };
122
- //#endregion
132
+ /** Default link templates; jira, linear and figma are org-specific, so they stay unset. */
133
+ const SEEDED_LINK_TEMPLATES = {
134
+ pr: "https://github.com/{repo}/pull/{value}",
135
+ mr: "https://gitlab.com/{repo}/-/merge_requests/{value}"
136
+ };
123
137
  export { LabelModel };
124
138
 
125
139
  //# sourceMappingURL=label.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"label.mjs","names":[],"sources":["../../src/models/label.ts"],"sourcesContent":["/** Build labels and project label types. */\nimport { and, desc, eq, getTableColumns } from \"drizzle-orm\";\nimport type { SQLWrapper, Table } from \"drizzle-orm\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { BuildLabel, LabelType } from \"../schema/label.ts\";\nimport { PERSISTENT_LABEL_KEY, RESERVED_LABEL_KEYS, SEEDED_LABEL_KEYS } from \"../types.ts\";\nimport { ulid } from \"../utils/ulid.ts\";\n\n/** Tables required by {@link LabelModel}. */\nexport interface LabelTables {\n builds: Table;\n buildLabels: Table;\n labelTypes: Table;\n}\n\n/** Data operations for label types and build labels. */\nexport class LabelModel {\n private readonly tables: LabelTables;\n /**\n * @param db - Database adapter.\n * @param tables - Table handles.\n */\n constructor(\n private readonly db: DatabaseAdapter,\n tables?: LabelTables,\n ) {\n this.tables = tables ?? {\n builds: db.tables.builds,\n buildLabels: db.tables.buildLabels,\n labelTypes: db.tables.labelTypes,\n };\n }\n\n /**\n * Seed the default label types for a project if missing.\n *\n * @param projectId - Project ID.\n */\n async seedFor(projectId: string): Promise<void> {\n const now = new Date().toISOString();\n const existing = await this.listTypes(projectId);\n const existingKeys = new Set(existing.map((t) => t.key));\n const missing = SEEDED_LABEL_KEYS.filter((key) => !existingKeys.has(key));\n await Promise.all(\n missing.map(async (key) => {\n await this.db.insert(this.tables.labelTypes, {\n id: ulid(),\n projectId,\n key,\n name: SEEDED_NAMES[key] ?? key,\n createdAt: now,\n });\n }),\n );\n }\n\n /** List all label types for a project. */\n async listTypes(projectId: string): Promise<LabelType[]> {\n return (await this.db.list(this.tables.labelTypes, {\n where: eq(\n getTableColumns(this.tables.labelTypes)[\"projectId\"] as unknown as SQLWrapper,\n projectId,\n ),\n })) as unknown as LabelType[];\n }\n\n /** Fetch a label type by key, or null if not found. */\n async getType(projectId: string, key: string): Promise<LabelType | null> {\n const rows = (await this.db.list(this.tables.labelTypes, {\n where: and(\n eq(\n getTableColumns(this.tables.labelTypes)[\"projectId\"] as unknown as SQLWrapper,\n projectId,\n ),\n eq(getTableColumns(this.tables.labelTypes)[\"key\"] as unknown as SQLWrapper, key),\n ),\n limit: 1,\n })) as unknown as LabelType[];\n return rows[0] ?? null;\n }\n\n /** Create a custom label type for a project. */\n async createType(\n projectId: string,\n input: { key: string; name: string; linkTemplate?: string; color?: string },\n ): Promise<LabelType> {\n return (await this.db.insert(this.tables.labelTypes, {\n id: ulid(),\n projectId,\n key: input.key,\n name: input.name,\n linkTemplate: input.linkTemplate,\n color: input.color,\n createdAt: new Date().toISOString(),\n })) as unknown as LabelType;\n }\n\n /** Update a custom label type's name, template or color. */\n async updateType(\n projectId: string,\n key: string,\n input: { name?: string; linkTemplate?: string | null; color?: string | null },\n ): Promise<LabelType | null> {\n if (key === PERSISTENT_LABEL_KEY || RESERVED_LABEL_KEYS.includes(key as never)) {\n throw new Error(`Label type '${key}' cannot be updated.`);\n }\n const existing = await this.getType(projectId, key);\n if (!existing) {\n return null;\n }\n return (await this.db.update(this.tables.labelTypes, existing.id, {\n name: input.name ?? existing.name,\n linkTemplate: input.linkTemplate === undefined ? existing.linkTemplate : input.linkTemplate,\n color: input.color === undefined ? existing.color : input.color,\n })) as unknown as LabelType;\n }\n\n /** Remove a custom label type, rejecting reserved or persistent types. */\n async removeType(projectId: string, key: string): Promise<void> {\n if (key === PERSISTENT_LABEL_KEY || RESERVED_LABEL_KEYS.includes(key as never)) {\n throw new Error(`Label type '${key}' cannot be removed.`);\n }\n const existing = await this.getType(projectId, key);\n if (existing) {\n await this.db.remove(this.tables.labelTypes, existing.id);\n }\n }\n\n /** Attach a label value to a build. */\n async attach(\n projectId: string,\n buildId: string,\n typeKey: string,\n value: string,\n ): Promise<BuildLabel> {\n return (await this.db.insert(this.tables.buildLabels, {\n id: ulid(),\n projectId,\n buildId,\n typeKey,\n value,\n createdAt: new Date().toISOString(),\n })) as unknown as BuildLabel;\n }\n\n /** List all labels attached to a build. */\n async listForBuild(buildId: string): Promise<BuildLabel[]> {\n return (await this.db.list(this.tables.buildLabels, {\n where: eq(\n getTableColumns(this.tables.buildLabels)[\"buildId\"] as unknown as SQLWrapper,\n buildId,\n ),\n })) as unknown as BuildLabel[];\n }\n\n /** Return the id of the latest build carrying a given label value, if any. */\n async latestBuildId(projectId: string, typeKey: string, value: string): Promise<string | null> {\n const labels = (await this.db.list(this.tables.buildLabels, {\n where: and(\n eq(\n getTableColumns(this.tables.buildLabels)[\"projectId\"] as unknown as SQLWrapper,\n projectId,\n ),\n eq(getTableColumns(this.tables.buildLabels)[\"typeKey\"] as unknown as SQLWrapper, typeKey),\n eq(getTableColumns(this.tables.buildLabels)[\"value\"] as unknown as SQLWrapper, value),\n ),\n })) as unknown as BuildLabel[];\n if (labels.length === 0) {\n return null;\n }\n const idSet = new Set(labels.map((l) => l.buildId));\n const rows = (await this.db.list(this.tables.builds, {\n orderBy: desc(getTableColumns(this.tables.builds)[\"createdAt\"] as unknown as SQLWrapper),\n })) as unknown as { id: string }[];\n return rows.find((b) => idSet.has(b.id))?.id ?? null;\n }\n\n /** Return whether a build is marked as persistent. */\n async hasPersistent(projectId: string, buildId: string): Promise<boolean> {\n const labels = (await this.db.list(this.tables.buildLabels, {\n where: and(\n eq(\n getTableColumns(this.tables.buildLabels)[\"projectId\"] as unknown as SQLWrapper,\n projectId,\n ),\n eq(getTableColumns(this.tables.buildLabels)[\"buildId\"] as unknown as SQLWrapper, buildId),\n eq(\n getTableColumns(this.tables.buildLabels)[\"typeKey\"] as unknown as SQLWrapper,\n PERSISTENT_LABEL_KEY,\n ),\n ),\n })) as unknown as BuildLabel[];\n return labels.length > 0;\n }\n}\n\nconst SEEDED_NAMES: Record<string, string> = {\n branch: \"Branch\",\n persistent: \"Persistent\",\n pr: \"Pull request\",\n mr: \"Merge request\",\n jira: \"Jira issue\",\n linear: \"Linear issue\",\n figma: \"Figma file\",\n custom: \"Custom\",\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"label.mjs","names":[],"sources":["../../src/models/label.ts"],"sourcesContent":["/** Build labels and project label types. */\nimport { and, desc, eq, inArray } from \"drizzle-orm\";\nimport type { Table } from \"drizzle-orm\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { BuildLabel, LabelType } from \"../schema/label.ts\";\nimport { PERSISTENT_LABEL_KEY, RESERVED_LABEL_KEYS, SEEDED_LABEL_KEYS } from \"../types.ts\";\nimport { ulid } from \"../utils/ulid.ts\";\nimport { col } from \"./columns.ts\";\n\n/** Tables required by {@link LabelModel}. */\nexport interface LabelTables {\n builds: Table;\n buildLabels: Table;\n labelTypes: Table;\n}\n\n/** Data operations for label types and build labels. */\nexport class LabelModel {\n private readonly tables: LabelTables;\n private readonly db: DatabaseAdapter;\n\n /**\n * @param db - Database adapter.\n * @param tables - Table handles.\n */\n constructor(db: DatabaseAdapter, tables?: LabelTables) {\n this.db = db;\n this.tables = tables ?? {\n builds: db.tables.builds,\n buildLabels: db.tables.buildLabels,\n labelTypes: db.tables.labelTypes,\n };\n }\n\n /**\n * Seed the default label types for a project if missing.\n *\n * @param projectId - Project ID.\n */\n async seedFor(projectId: string): Promise<void> {\n const now = new Date().toISOString();\n const existing = await this.listTypes(projectId);\n const existingKeys = new Set(existing.map((t) => t.key));\n const missing = SEEDED_LABEL_KEYS.filter((key) => !existingKeys.has(key));\n await Promise.all(\n missing.map(async (key) => {\n await this.db.insert(this.tables.labelTypes, {\n id: ulid(),\n projectId,\n key,\n name: SEEDED_NAMES[key] ?? key,\n linkTemplate: SEEDED_LINK_TEMPLATES[key],\n createdAt: now,\n });\n }),\n );\n }\n\n /** List all label types for a project. */\n async listTypes(projectId: string): Promise<LabelType[]> {\n return (await this.db.list(this.tables.labelTypes, {\n where: eq(col(this.tables.labelTypes, \"projectId\"), projectId),\n })) as unknown as LabelType[];\n }\n\n /** Fetch a label type by key, or null if not found. */\n async getType(projectId: string, key: string): Promise<LabelType | null> {\n const rows = (await this.db.list(this.tables.labelTypes, {\n where: and(\n eq(col(this.tables.labelTypes, \"projectId\"), projectId),\n eq(col(this.tables.labelTypes, \"key\"), key),\n ),\n limit: 1,\n })) as unknown as LabelType[];\n return rows[0] ?? null;\n }\n\n /** Create a custom label type for a project. */\n async createType(\n projectId: string,\n input: { key: string; name: string; linkTemplate?: string; color?: string },\n ): Promise<LabelType> {\n return (await this.db.insert(this.tables.labelTypes, {\n id: ulid(),\n projectId,\n key: input.key,\n name: input.name,\n linkTemplate: input.linkTemplate,\n color: input.color,\n createdAt: new Date().toISOString(),\n })) as unknown as LabelType;\n }\n\n /** Update a custom label type's name, template or color. */\n async updateType(\n projectId: string,\n key: string,\n input: { name?: string; linkTemplate?: string | null; color?: string | null },\n ): Promise<LabelType | null> {\n if (key === PERSISTENT_LABEL_KEY || RESERVED_LABEL_KEYS.includes(key as never)) {\n throw new Error(`Label type '${key}' cannot be updated.`);\n }\n const existing = await this.getType(projectId, key);\n if (!existing) {\n return null;\n }\n return (await this.db.update(this.tables.labelTypes, existing.id, {\n name: input.name ?? existing.name,\n linkTemplate: input.linkTemplate === undefined ? existing.linkTemplate : input.linkTemplate,\n color: input.color === undefined ? existing.color : input.color,\n })) as unknown as LabelType;\n }\n\n /** Remove a custom label type, rejecting reserved or persistent types. */\n async removeType(projectId: string, key: string): Promise<void> {\n if (key === PERSISTENT_LABEL_KEY || RESERVED_LABEL_KEYS.includes(key as never)) {\n throw new Error(`Label type '${key}' cannot be removed.`);\n }\n const existing = await this.getType(projectId, key);\n if (existing) {\n await this.db.remove(this.tables.labelTypes, existing.id);\n }\n }\n\n /** Attach a label value to a build. */\n async attach(\n projectId: string,\n buildId: string,\n typeKey: string,\n value: string,\n ): Promise<BuildLabel> {\n return (await this.db.insert(this.tables.buildLabels, {\n id: ulid(),\n projectId,\n buildId,\n typeKey,\n value,\n createdAt: new Date().toISOString(),\n })) as unknown as BuildLabel;\n }\n\n /** List all labels attached to a build. */\n async listForBuild(buildId: string): Promise<BuildLabel[]> {\n return (await this.db.list(this.tables.buildLabels, {\n where: eq(col(this.tables.buildLabels, \"buildId\"), buildId),\n })) as unknown as BuildLabel[];\n }\n\n /** Return the id of the latest build carrying a given label value, if any. */\n async latestBuildId(projectId: string, typeKey: string, value: string): Promise<string | null> {\n const labels = (await this.db.list(this.tables.buildLabels, {\n where: and(\n eq(col(this.tables.buildLabels, \"projectId\"), projectId),\n eq(col(this.tables.buildLabels, \"typeKey\"), typeKey),\n eq(col(this.tables.buildLabels, \"value\"), value),\n ),\n })) as unknown as BuildLabel[];\n if (labels.length === 0) {\n return null;\n }\n const newest = (await this.db.list(this.tables.builds, {\n where: inArray(\n col(this.tables.builds, \"id\"),\n labels.map((label) => label.buildId),\n ),\n orderBy: desc(col(this.tables.builds, \"createdAt\")),\n limit: 1,\n })) as unknown as { id: string }[];\n return newest[0]?.id ?? null;\n }\n\n /** Whether a build is marked as persistent. */\n async hasPersistent(projectId: string, buildId: string): Promise<boolean> {\n return (await this.persistentBuildIds(projectId)).has(buildId);\n }\n\n /** Ids of the project's builds marked persistent (exempt from retention). */\n async persistentBuildIds(projectId: string): Promise<Set<string>> {\n const labels = (await this.db.list(this.tables.buildLabels, {\n where: and(\n eq(col(this.tables.buildLabels, \"projectId\"), projectId),\n eq(col(this.tables.buildLabels, \"typeKey\"), PERSISTENT_LABEL_KEY),\n ),\n })) as unknown as BuildLabel[];\n return new Set(labels.map((label) => label.buildId));\n }\n}\n\nconst SEEDED_NAMES: Record<string, string> = {\n branch: \"Branch\",\n persistent: \"Persistent\",\n pr: \"Pull request\",\n mr: \"Merge request\",\n jira: \"Jira issue\",\n linear: \"Linear issue\",\n figma: \"Figma file\",\n custom: \"Custom\",\n};\n\n/** Default link templates; jira, linear and figma are org-specific, so they stay unset. */\nconst SEEDED_LINK_TEMPLATES: Record<string, string | undefined> = {\n pr: \"https://github.com/{repo}/pull/{value}\",\n mr: \"https://gitlab.com/{repo}/-/merge_requests/{value}\",\n};\n"],"mappings":"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"}
@@ -4,15 +4,14 @@ import { DatabaseAdapter } from "../adapter/database.mjs";
4
4
  import { ProjectRole, SiteRole } from "../types.mjs";
5
5
  import { ProjectMember } from "../schema/member.mjs";
6
6
  import { Table } from "drizzle-orm";
7
- //#region src/models/member.d.ts
8
7
  /** Tables required by {@link MemberModel}. */
9
8
  interface MemberTables {
10
9
  projectMembers: Table;
11
10
  }
12
11
  /** Data operations for project membership and roles. */
13
12
  declare class MemberModel {
14
- private readonly db;
15
13
  private readonly tables;
14
+ private readonly db;
16
15
  /**
17
16
  * @param db - Database adapter.
18
17
  * @param tables - Table handles.
@@ -29,6 +28,5 @@ declare class MemberModel {
29
28
  /** Resolve a user's effective project role, honoring site-wide admins. */
30
29
  effectiveRole(siteRole: SiteRole, projectId: string, userId: string): Promise<ProjectRole | null>;
31
30
  }
32
- //#endregion
33
31
  export { MemberModel, MemberTables };
34
32
  //# sourceMappingURL=member.d.mts.map
@@ -2,13 +2,14 @@ import "node:path";
2
2
  import "node:url";
3
3
  import.meta.url;
4
4
  import { ulid } from "../utils/ulid.mjs";
5
- import { and, eq, getTableColumns } from "drizzle-orm";
6
- //#region src/models/member.ts
5
+ import { col } from "./columns.mjs";
6
+ import { and, eq } from "drizzle-orm";
7
+ // src/models/member.ts
7
8
  /** Project membership and role resolution. */
8
9
  /** Data operations for project membership and roles. */
9
10
  var MemberModel = class {
10
- db;
11
11
  tables;
12
+ db;
12
13
  /**
13
14
  * @param db - Database adapter.
14
15
  * @param tables - Table handles.
@@ -19,12 +20,12 @@ var MemberModel = class {
19
20
  }
20
21
  /** List all members of a project. */
21
22
  async list(projectId) {
22
- return await this.db.list(this.tables.projectMembers, { where: eq(getTableColumns(this.tables.projectMembers)["projectId"], projectId) });
23
+ return await this.db.list(this.tables.projectMembers, { where: eq(col(this.tables.projectMembers, "projectId"), projectId) });
23
24
  }
24
25
  /** Fetch a project member by project and user id, or null if not found. */
25
26
  async get(projectId, userId) {
26
27
  return (await this.db.list(this.tables.projectMembers, {
27
- where: and(eq(getTableColumns(this.tables.projectMembers)["projectId"], projectId), eq(getTableColumns(this.tables.projectMembers)["userId"], userId)),
28
+ where: and(eq(col(this.tables.projectMembers, "projectId"), projectId), eq(col(this.tables.projectMembers, "userId"), userId)),
28
29
  limit: 1
29
30
  }))[0] ?? null;
30
31
  }
@@ -58,7 +59,6 @@ var MemberModel = class {
58
59
  return null;
59
60
  }
60
61
  };
61
- //#endregion
62
62
  export { MemberModel };
63
63
 
64
64
  //# sourceMappingURL=member.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"member.mjs","names":[],"sources":["../../src/models/member.ts"],"sourcesContent":["/** Project membership and role resolution. */\nimport { and, eq, getTableColumns } from \"drizzle-orm\";\nimport type { SQLWrapper, Table } from \"drizzle-orm\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { ProjectMember } from \"../schema/member.ts\";\nimport type { ProjectRole, SiteRole } from \"../types.ts\";\nimport { ulid } from \"../utils/ulid.ts\";\n\n/** Tables required by {@link MemberModel}. */\nexport interface MemberTables {\n projectMembers: Table;\n}\n\n/** Data operations for project membership and roles. */\nexport class MemberModel {\n private readonly tables: MemberTables;\n /**\n * @param db - Database adapter.\n * @param tables - Table handles.\n */\n constructor(\n private readonly db: DatabaseAdapter,\n tables?: MemberTables,\n ) {\n this.tables = tables ?? { projectMembers: db.tables.projectMembers };\n }\n\n /** List all members of a project. */\n async list(projectId: string): Promise<ProjectMember[]> {\n return (await this.db.list(this.tables.projectMembers, {\n where: eq(\n getTableColumns(this.tables.projectMembers)[\"projectId\"] as unknown as SQLWrapper,\n projectId,\n ),\n })) as unknown as ProjectMember[];\n }\n\n /** Fetch a project member by project and user id, or null if not found. */\n async get(projectId: string, userId: string): Promise<ProjectMember | null> {\n const rows = (await this.db.list(this.tables.projectMembers, {\n where: and(\n eq(\n getTableColumns(this.tables.projectMembers)[\"projectId\"] as unknown as SQLWrapper,\n projectId,\n ),\n eq(getTableColumns(this.tables.projectMembers)[\"userId\"] as unknown as SQLWrapper, userId),\n ),\n limit: 1,\n })) as unknown as ProjectMember[];\n return rows[0] ?? null;\n }\n\n /** Set a user's role in a project, creating the membership if needed. */\n async set(\n projectId: string,\n userId: string,\n role: ProjectRole,\n source = \"manual\",\n ): Promise<ProjectMember> {\n const existing = await this.get(projectId, userId);\n if (existing) {\n return (await this.db.update(this.tables.projectMembers, existing.id, {\n role,\n source,\n })) as unknown as ProjectMember;\n }\n return (await this.db.insert(this.tables.projectMembers, {\n id: ulid(),\n projectId,\n userId,\n role,\n source,\n createdAt: new Date().toISOString(),\n })) as unknown as ProjectMember;\n }\n\n /** Remove a user from a project if they are a member. */\n async remove(projectId: string, userId: string): Promise<void> {\n const existing = await this.get(projectId, userId);\n if (existing) {\n await this.db.remove(this.tables.projectMembers, existing.id);\n }\n }\n\n /** Resolve a user's effective project role, honoring site-wide admins. */\n async effectiveRole(\n siteRole: SiteRole,\n projectId: string,\n userId: string,\n ): Promise<ProjectRole | null> {\n if (siteRole === \"admin\") {\n return \"admin\";\n }\n const member = await this.get(projectId, userId);\n if (member) {\n return member.role;\n }\n // Site viewers read every project; explicit membership always wins.\n if (siteRole === \"viewer\") {\n return \"viewer\";\n }\n return null;\n }\n}\n"],"mappings":";;;;;;;;AAcA,IAAa,cAAb,MAAyB;CAOJ;CANnB;;;;;CAKA,YACE,IACA,QACA;EAFiB,KAAA,KAAA;EAGjB,KAAK,SAAS,UAAU,EAAE,gBAAgB,GAAG,OAAO,eAAe;CACrE;;CAGA,MAAM,KAAK,WAA6C;EACtD,OAAQ,MAAM,KAAK,GAAG,KAAK,KAAK,OAAO,gBAAgB,EACrD,OAAO,GACL,gBAAgB,KAAK,OAAO,cAAc,CAAC,CAAC,cAC5C,SACF,EACF,CAAC;CACH;;CAGA,MAAM,IAAI,WAAmB,QAA+C;EAW1E,QAAO,MAVa,KAAK,GAAG,KAAK,KAAK,OAAO,gBAAgB;GAC3D,OAAO,IACL,GACE,gBAAgB,KAAK,OAAO,cAAc,CAAC,CAAC,cAC5C,SACF,GACA,GAAG,gBAAgB,KAAK,OAAO,cAAc,CAAC,CAAC,WAAoC,MAAM,CAC3F;GACA,OAAO;EACT,CAAC,EAAA,CACW,MAAM;CACpB;;CAGA,MAAM,IACJ,WACA,QACA,MACA,SAAS,UACe;EACxB,MAAM,WAAW,MAAM,KAAK,IAAI,WAAW,MAAM;EACjD,IAAI,UACF,OAAQ,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,gBAAgB,SAAS,IAAI;GACpE;GACA;EACF,CAAC;EAEH,OAAQ,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,gBAAgB;GACvD,IAAI,KAAK;GACT;GACA;GACA;GACA;GACA,4BAAW,IAAI,KAAK,EAAA,CAAE,YAAY;EACpC,CAAC;CACH;;CAGA,MAAM,OAAO,WAAmB,QAA+B;EAC7D,MAAM,WAAW,MAAM,KAAK,IAAI,WAAW,MAAM;EACjD,IAAI,UACF,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,gBAAgB,SAAS,EAAE;CAEhE;;CAGA,MAAM,cACJ,UACA,WACA,QAC6B;EAC7B,IAAI,aAAa,SACf,OAAO;EAET,MAAM,SAAS,MAAM,KAAK,IAAI,WAAW,MAAM;EAC/C,IAAI,QACF,OAAO,OAAO;EAGhB,IAAI,aAAa,UACf,OAAO;EAET,OAAO;CACT;AACF"}
1
+ {"version":3,"file":"member.mjs","names":[],"sources":["../../src/models/member.ts"],"sourcesContent":["/** Project membership and role resolution. */\nimport { and, eq } from \"drizzle-orm\";\nimport type { Table } from \"drizzle-orm\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { ProjectMember } from \"../schema/member.ts\";\nimport type { ProjectRole, SiteRole } from \"../types.ts\";\nimport { ulid } from \"../utils/ulid.ts\";\nimport { col } from \"./columns.ts\";\n\n/** Tables required by {@link MemberModel}. */\nexport interface MemberTables {\n projectMembers: Table;\n}\n\n/** Data operations for project membership and roles. */\nexport class MemberModel {\n private readonly tables: MemberTables;\n private readonly db: DatabaseAdapter;\n\n /**\n * @param db - Database adapter.\n * @param tables - Table handles.\n */\n constructor(db: DatabaseAdapter, tables?: MemberTables) {\n this.db = db;\n this.tables = tables ?? { projectMembers: db.tables.projectMembers };\n }\n\n /** List all members of a project. */\n async list(projectId: string): Promise<ProjectMember[]> {\n return (await this.db.list(this.tables.projectMembers, {\n where: eq(col(this.tables.projectMembers, \"projectId\"), projectId),\n })) as unknown as ProjectMember[];\n }\n\n /** Fetch a project member by project and user id, or null if not found. */\n async get(projectId: string, userId: string): Promise<ProjectMember | null> {\n const rows = (await this.db.list(this.tables.projectMembers, {\n where: and(\n eq(col(this.tables.projectMembers, \"projectId\"), projectId),\n eq(col(this.tables.projectMembers, \"userId\"), userId),\n ),\n limit: 1,\n })) as unknown as ProjectMember[];\n return rows[0] ?? null;\n }\n\n /** Set a user's role in a project, creating the membership if needed. */\n async set(\n projectId: string,\n userId: string,\n role: ProjectRole,\n source = \"manual\",\n ): Promise<ProjectMember> {\n const existing = await this.get(projectId, userId);\n if (existing) {\n return (await this.db.update(this.tables.projectMembers, existing.id, {\n role,\n source,\n })) as unknown as ProjectMember;\n }\n return (await this.db.insert(this.tables.projectMembers, {\n id: ulid(),\n projectId,\n userId,\n role,\n source,\n createdAt: new Date().toISOString(),\n })) as unknown as ProjectMember;\n }\n\n /** Remove a user from a project if they are a member. */\n async remove(projectId: string, userId: string): Promise<void> {\n const existing = await this.get(projectId, userId);\n if (existing) {\n await this.db.remove(this.tables.projectMembers, existing.id);\n }\n }\n\n /** Resolve a user's effective project role, honoring site-wide admins. */\n async effectiveRole(\n siteRole: SiteRole,\n projectId: string,\n userId: string,\n ): Promise<ProjectRole | null> {\n if (siteRole === \"admin\") {\n return \"admin\";\n }\n const member = await this.get(projectId, userId);\n if (member) {\n return member.role;\n }\n // Site viewers read every project; explicit membership always wins.\n if (siteRole === \"viewer\") {\n return \"viewer\";\n }\n return null;\n }\n}\n"],"mappings":";;;;;;;;;AAeA,IAAa,cAAb,MAAyB;CACvB;CACA;;;;;CAMA,YAAY,IAAqB,QAAuB;EACtD,KAAK,KAAK;EACV,KAAK,SAAS,UAAU,EAAE,gBAAgB,GAAG,OAAO,eAAe;CACrE;;CAGA,MAAM,KAAK,WAA6C;EACtD,OAAQ,MAAM,KAAK,GAAG,KAAK,KAAK,OAAO,gBAAgB,EACrD,OAAO,GAAG,IAAI,KAAK,OAAO,gBAAgB,WAAW,GAAG,SAAS,EACnE,CAAC;CACH;;CAGA,MAAM,IAAI,WAAmB,QAA+C;EAQ1E,QAAO,MAPa,KAAK,GAAG,KAAK,KAAK,OAAO,gBAAgB;GAC3D,OAAO,IACL,GAAG,IAAI,KAAK,OAAO,gBAAgB,WAAW,GAAG,SAAS,GAC1D,GAAG,IAAI,KAAK,OAAO,gBAAgB,QAAQ,GAAG,MAAM,CACtD;GACA,OAAO;EACT,CAAC,EAAA,CACW,MAAM;CACpB;;CAGA,MAAM,IACJ,WACA,QACA,MACA,SAAS,UACe;EACxB,MAAM,WAAW,MAAM,KAAK,IAAI,WAAW,MAAM;EACjD,IAAI,UACF,OAAQ,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,gBAAgB,SAAS,IAAI;GACpE;GACA;EACF,CAAC;EAEH,OAAQ,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,gBAAgB;GACvD,IAAI,KAAK;GACT;GACA;GACA;GACA;GACA,4BAAW,IAAI,KAAK,EAAA,CAAE,YAAY;EACpC,CAAC;CACH;;CAGA,MAAM,OAAO,WAAmB,QAA+B;EAC7D,MAAM,WAAW,MAAM,KAAK,IAAI,WAAW,MAAM;EACjD,IAAI,UACF,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,gBAAgB,SAAS,EAAE;CAEhE;;CAGA,MAAM,cACJ,UACA,WACA,QAC6B;EAC7B,IAAI,aAAa,SACf,OAAO;EAET,MAAM,SAAS,MAAM,KAAK,IAAI,WAAW,MAAM;EAC/C,IAAI,QACF,OAAO,OAAO;EAGhB,IAAI,aAAa,UACf,OAAO;EAET,OAAO;CACT;AACF"}
@@ -1,14 +1,28 @@
1
1
  import __tsdown_shims_path from 'node:path';
2
2
  import __tsdown_shims_url from 'node:url';
3
3
  import { DatabaseAdapter } from "../adapter/database.mjs";
4
+ import { NotificationChannel } from "../adapters/notifier/types.mjs";
5
+ import { NotifierProvider } from "../adapters/notifier/provider.mjs";
4
6
  import { SecretInput } from "../utils/encrypt.mjs";
5
7
  import { NotificationChannelRow } from "../schema/notification.mjs";
6
8
  import { Table } from "drizzle-orm";
7
- //#region src/models/notification-channel.d.ts
8
9
  /** Tables required by {@link NotificationChannelModel}. */
9
10
  interface NotificationChannelTables {
10
11
  notificationChannels: Table;
11
12
  }
13
+ /** Result of {@link NotificationChannelModel.validateInput}. */
14
+ type ChannelValidation = {
15
+ ok: true;
16
+ } | {
17
+ ok: false;
18
+ field: "provider" | "config" | "secret";
19
+ message: string;
20
+ /** Per-field problems of an invalid config, keyed by the first path segment. */
21
+ issues?: {
22
+ path: string;
23
+ message: string;
24
+ }[];
25
+ };
12
26
  /** Input for creating a notification channel. */
13
27
  interface NotificationChannelCreateInput {
14
28
  projectId: string | null;
@@ -20,9 +34,9 @@ interface NotificationChannelCreateInput {
20
34
  }
21
35
  /** Data operations for notification channels. */
22
36
  declare class NotificationChannelModel {
23
- private readonly db;
24
37
  private readonly tables;
25
38
  private readonly secret?;
39
+ private readonly db;
26
40
  /**
27
41
  * @param db - Database adapter.
28
42
  * @param tables - Table handles (defaults to the adapter's tables).
@@ -39,14 +53,32 @@ declare class NotificationChannelModel {
39
53
  get(id: string): Promise<NotificationChannelRow | null>;
40
54
  /** Remove a channel by id. */
41
55
  remove(id: string): Promise<void>;
56
+ /**
57
+ * Turn a stored row into the send-ready view providers take, with the secret decrypted
58
+ * in memory. Returns null when the secret cannot be decrypted with the configured keys.
59
+ */
60
+ toView(row: NotificationChannelRow): NotificationChannel | null;
42
61
  /** Decrypt the secret for a stored row (in memory only, at send time). */
43
62
  decryptSecret(row: NotificationChannelRow): string;
63
+ /**
64
+ * Record the outcome of a delivery attempt: the sanitized error with its time, or
65
+ * nothing to clear a previous error. Skips the write when the row is already in that state.
66
+ */
67
+ recordOutcome(id: string, error?: string): Promise<void>;
68
+ /**
69
+ * Validate channel input against the matching notifier's schema. With `scope`, also
70
+ * enforce what `state` providers (git hosts) need: a project and a token.
71
+ */
72
+ static validateInput(notifiers: readonly NotifierProvider[], provider: string, config: unknown, scope?: {
73
+ projectId: string | null;
74
+ hasSecret: boolean;
75
+ }): ChannelValidation;
76
+ /** Whether the provider kind delivers state (git hosts), so it needs a project and a token. */
77
+ static isStateKind(notifiers: readonly NotifierProvider[], provider: string): boolean;
44
78
  /** Decode the JSON-serialized event list of a channel. */
45
79
  static eventsOf(row: NotificationChannelRow): string[];
46
80
  /** Decode the JSON-serialized config of a channel. */
47
81
  static configOf(row: NotificationChannelRow): Record<string, unknown>;
48
- private column;
49
82
  }
50
- //#endregion
51
- export { NotificationChannelCreateInput, NotificationChannelModel, NotificationChannelTables };
83
+ export { ChannelValidation, NotificationChannelCreateInput, NotificationChannelModel, NotificationChannelTables };
52
84
  //# sourceMappingURL=notification-channel.d.mts.map
@@ -3,14 +3,16 @@ import "node:url";
3
3
  import.meta.url;
4
4
  import { decryptCredential, encrypt } from "../utils/encrypt.mjs";
5
5
  import { ulid } from "../utils/ulid.mjs";
6
- import { eq, getTableColumns, isNull } from "drizzle-orm";
7
- //#region src/models/notification-channel.ts
6
+ import { col } from "./columns.mjs";
7
+ import { isStateProvider } from "../adapters/notifier/provider.mjs";
8
+ import { eq, isNull } from "drizzle-orm";
9
+ // src/models/notification-channel.ts
8
10
  /** Project and site-wide notification channel subscriptions. */
9
11
  /** Data operations for notification channels. */
10
- var NotificationChannelModel = class {
11
- db;
12
+ var NotificationChannelModel = class NotificationChannelModel {
12
13
  tables;
13
14
  secret;
15
+ db;
14
16
  /**
15
17
  * @param db - Database adapter.
16
18
  * @param tables - Table handles (defaults to the adapter's tables).
@@ -23,11 +25,11 @@ var NotificationChannelModel = class {
23
25
  }
24
26
  /** List channels for a project. */
25
27
  async list(projectId) {
26
- return await this.db.list(this.tables.notificationChannels, { where: eq(this.column("projectId"), projectId) });
28
+ return await this.db.list(this.tables.notificationChannels, { where: eq(col(this.tables.notificationChannels, "projectId"), projectId) });
27
29
  }
28
30
  /** List site-wide admin channels (`project_id IS NULL`). */
29
31
  async listSystem() {
30
- return await this.db.list(this.tables.notificationChannels, { where: isNull(this.column("projectId")) });
32
+ return await this.db.list(this.tables.notificationChannels, { where: isNull(col(this.tables.notificationChannels, "projectId")) });
31
33
  }
32
34
  /** Create a channel (secret encrypted at rest when provided). */
33
35
  async create(input) {
@@ -40,6 +42,8 @@ var NotificationChannelModel = class {
40
42
  secretEncrypted: input.secret ? encrypt(this.secret, input.secret) : null,
41
43
  events: input.events && input.events.length > 0 ? JSON.stringify(input.events) : null,
42
44
  enabled: input.enabled ?? true,
45
+ lastError: null,
46
+ lastErrorAt: null,
43
47
  createdAt: now,
44
48
  updatedAt: now
45
49
  });
@@ -47,7 +51,7 @@ var NotificationChannelModel = class {
47
51
  /** Fetch a channel by id, or null. */
48
52
  async get(id) {
49
53
  return (await this.db.list(this.tables.notificationChannels, {
50
- where: eq(this.column("id"), id),
54
+ where: eq(col(this.tables.notificationChannels, "id"), id),
51
55
  limit: 1
52
56
  }))[0] ?? null;
53
57
  }
@@ -55,11 +59,76 @@ var NotificationChannelModel = class {
55
59
  async remove(id) {
56
60
  await this.db.remove(this.tables.notificationChannels, id);
57
61
  }
62
+ /**
63
+ * Turn a stored row into the send-ready view providers take, with the secret decrypted
64
+ * in memory. Returns null when the secret cannot be decrypted with the configured keys.
65
+ */
66
+ toView(row) {
67
+ let secret;
68
+ try {
69
+ secret = row.secretEncrypted ? this.decryptSecret(row) : void 0;
70
+ } catch {
71
+ return null;
72
+ }
73
+ return {
74
+ id: row.id,
75
+ projectId: row.projectId,
76
+ provider: row.provider,
77
+ config: NotificationChannelModel.configOf(row),
78
+ secret,
79
+ events: NotificationChannelModel.eventsOf(row),
80
+ enabled: row.enabled,
81
+ ...row.lastError === null || row.lastError === void 0 ? {} : { lastError: row.lastError }
82
+ };
83
+ }
58
84
  /** Decrypt the secret for a stored row (in memory only, at send time). */
59
85
  decryptSecret(row) {
60
86
  if (!row.secretEncrypted) throw new Error("Channel has no secret");
61
87
  return decryptCredential(this.secret, row.secretEncrypted);
62
88
  }
89
+ /**
90
+ * Record the outcome of a delivery attempt: the sanitized error with its time, or
91
+ * nothing to clear a previous error. Skips the write when the row is already in that state.
92
+ */
93
+ async recordOutcome(id, error) {
94
+ const row = await this.get(id);
95
+ if (!row || error === void 0 && row.lastError === null) return;
96
+ await this.db.update(this.tables.notificationChannels, id, {
97
+ lastError: error ?? null,
98
+ lastErrorAt: error === void 0 ? null : (/* @__PURE__ */ new Date()).toISOString()
99
+ });
100
+ }
101
+ /**
102
+ * Validate channel input against the matching notifier's schema. With `scope`, also
103
+ * enforce what `state` providers (git hosts) need: a project and a token.
104
+ */
105
+ static validateInput(notifiers, provider, config, scope) {
106
+ const descriptor = notifiers.find((candidate) => candidate.metadata.kind === provider);
107
+ if (!descriptor) return {
108
+ ok: false,
109
+ field: "provider",
110
+ message: `Unknown provider: ${provider}`
111
+ };
112
+ const parsed = descriptor.metadata.schema.safeParse(config);
113
+ if (!parsed.success) {
114
+ const issues = parsed.error.issues.map((issue) => ({
115
+ path: String(issue.path[0] ?? ""),
116
+ message: issue.message
117
+ }));
118
+ return {
119
+ ok: false,
120
+ field: "config",
121
+ message: parsed.error.message,
122
+ issues
123
+ };
124
+ }
125
+ return scope && isStateProvider(descriptor) ? stateScopeProblem(scope) : { ok: true };
126
+ }
127
+ /** Whether the provider kind delivers state (git hosts), so it needs a project and a token. */
128
+ static isStateKind(notifiers, provider) {
129
+ const descriptor = notifiers.find((candidate) => candidate.metadata.kind === provider);
130
+ return descriptor !== void 0 && isStateProvider(descriptor);
131
+ }
63
132
  /** Decode the JSON-serialized event list of a channel. */
64
133
  static eventsOf(row) {
65
134
  return decodeEvents(row.events);
@@ -73,10 +142,20 @@ var NotificationChannelModel = class {
73
142
  return {};
74
143
  }
75
144
  }
76
- column(name) {
77
- return getTableColumns(this.tables.notificationChannels)[name];
78
- }
79
145
  };
146
+ /** Git channels belong to one project and carry their own token. */
147
+ function stateScopeProblem(scope) {
148
+ if (scope.projectId === null) return {
149
+ ok: false,
150
+ field: "provider",
151
+ message: "Git channels belong to a project"
152
+ };
153
+ return scope.hasSecret ? { ok: true } : {
154
+ ok: false,
155
+ field: "secret",
156
+ message: "A git channel needs a token or private key"
157
+ };
158
+ }
80
159
  /** Decode a JSON event list column (empty = all events). */
81
160
  function decodeEvents(raw) {
82
161
  if (!raw) return [];
@@ -87,7 +166,6 @@ function decodeEvents(raw) {
87
166
  return [];
88
167
  }
89
168
  }
90
- //#endregion
91
169
  export { NotificationChannelModel };
92
170
 
93
171
  //# sourceMappingURL=notification-channel.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"notification-channel.mjs","names":[],"sources":["../../src/models/notification-channel.ts"],"sourcesContent":["/** Project and site-wide notification channel subscriptions. */\nimport { eq, getTableColumns, isNull } from \"drizzle-orm\";\nimport type { SQLWrapper, Table } from \"drizzle-orm\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { NotificationChannelRow } from \"../schema/notification.ts\";\nimport { decryptCredential, encrypt, type SecretInput } from \"../utils/encrypt.ts\";\nimport { ulid } from \"../utils/ulid.ts\";\n\n/** Tables required by {@link NotificationChannelModel}. */\nexport interface NotificationChannelTables {\n notificationChannels: Table;\n}\n\n/** Input for creating a notification channel. */\nexport interface NotificationChannelCreateInput {\n projectId: string | null;\n provider: string;\n config: Record<string, unknown>;\n secret?: string;\n events?: string[];\n enabled?: boolean;\n}\n\n/** Data operations for notification channels. */\nexport class NotificationChannelModel {\n private readonly tables: NotificationChannelTables;\n private readonly secret?: SecretInput;\n /**\n * @param db - Database adapter.\n * @param tables - Table handles (defaults to the adapter's tables).\n * @param secret - Server secret for channel-secret encryption.\n */\n constructor(\n private readonly db: DatabaseAdapter,\n tables?: NotificationChannelTables,\n secret?: SecretInput,\n ) {\n this.tables = tables ?? { notificationChannels: db.tables.notificationChannels };\n this.secret = secret;\n }\n\n /** List channels for a project. */\n async list(projectId: string): Promise<NotificationChannelRow[]> {\n return (await this.db.list(this.tables.notificationChannels, {\n where: eq(this.column(\"projectId\"), projectId),\n })) as unknown as NotificationChannelRow[];\n }\n\n /** List site-wide admin channels (`project_id IS NULL`). */\n async listSystem(): Promise<NotificationChannelRow[]> {\n return (await this.db.list(this.tables.notificationChannels, {\n where: isNull(this.column(\"projectId\")),\n })) as unknown as NotificationChannelRow[];\n }\n\n /** Create a channel (secret encrypted at rest when provided). */\n async create(input: NotificationChannelCreateInput): Promise<NotificationChannelRow> {\n const now = new Date().toISOString();\n return (await this.db.insert(this.tables.notificationChannels, {\n id: ulid(),\n projectId: input.projectId,\n provider: input.provider,\n config: JSON.stringify(input.config),\n secretEncrypted: input.secret ? encrypt(this.secret, input.secret) : null,\n events: input.events && input.events.length > 0 ? JSON.stringify(input.events) : null,\n enabled: input.enabled ?? true,\n createdAt: now,\n updatedAt: now,\n })) as unknown as NotificationChannelRow;\n }\n\n /** Fetch a channel by id, or null. */\n async get(id: string): Promise<NotificationChannelRow | null> {\n const rows = (await this.db.list(this.tables.notificationChannels, {\n where: eq(this.column(\"id\"), id),\n limit: 1,\n })) as unknown as NotificationChannelRow[];\n return rows[0] ?? null;\n }\n\n /** Remove a channel by id. */\n async remove(id: string): Promise<void> {\n await this.db.remove(this.tables.notificationChannels, id);\n }\n\n /** Decrypt the secret for a stored row (in memory only, at send time). */\n decryptSecret(row: NotificationChannelRow): string {\n if (!row.secretEncrypted) {\n throw new Error(\"Channel has no secret\");\n }\n return decryptCredential(this.secret, row.secretEncrypted);\n }\n\n /** Decode the JSON-serialized event list of a channel. */\n static eventsOf(row: NotificationChannelRow): string[] {\n return decodeEvents(row.events);\n }\n\n /** Decode the JSON-serialized config of a channel. */\n static configOf(row: NotificationChannelRow): Record<string, unknown> {\n try {\n const parsed: unknown = JSON.parse(row.config);\n return typeof parsed === \"object\" && parsed !== null\n ? (parsed as Record<string, unknown>)\n : {};\n } catch {\n return {};\n }\n }\n\n private column(name: string): SQLWrapper {\n return getTableColumns(this.tables.notificationChannels)[name] as unknown as SQLWrapper;\n }\n}\n\n/** Decode a JSON event list column (empty = all events). */\nfunction decodeEvents(raw: string | null): string[] {\n if (!raw) {\n return [];\n }\n try {\n const parsed: unknown = JSON.parse(raw);\n return Array.isArray(parsed)\n ? parsed.filter((event): event is string => typeof event === \"string\")\n : [];\n } catch {\n return [];\n }\n}\n"],"mappings":";;;;;;;;;AAwBA,IAAa,2BAAb,MAAsC;CASjB;CARnB;CACA;;;;;;CAMA,YACE,IACA,QACA,QACA;EAHiB,KAAA,KAAA;EAIjB,KAAK,SAAS,UAAU,EAAE,sBAAsB,GAAG,OAAO,qBAAqB;EAC/E,KAAK,SAAS;CAChB;;CAGA,MAAM,KAAK,WAAsD;EAC/D,OAAQ,MAAM,KAAK,GAAG,KAAK,KAAK,OAAO,sBAAsB,EAC3D,OAAO,GAAG,KAAK,OAAO,WAAW,GAAG,SAAS,EAC/C,CAAC;CACH;;CAGA,MAAM,aAAgD;EACpD,OAAQ,MAAM,KAAK,GAAG,KAAK,KAAK,OAAO,sBAAsB,EAC3D,OAAO,OAAO,KAAK,OAAO,WAAW,CAAC,EACxC,CAAC;CACH;;CAGA,MAAM,OAAO,OAAwE;EACnF,MAAM,uBAAM,IAAI,KAAK,EAAA,CAAE,YAAY;EACnC,OAAQ,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,sBAAsB;GAC7D,IAAI,KAAK;GACT,WAAW,MAAM;GACjB,UAAU,MAAM;GAChB,QAAQ,KAAK,UAAU,MAAM,MAAM;GACnC,iBAAiB,MAAM,SAAS,QAAQ,KAAK,QAAQ,MAAM,MAAM,IAAI;GACrE,QAAQ,MAAM,UAAU,MAAM,OAAO,SAAS,IAAI,KAAK,UAAU,MAAM,MAAM,IAAI;GACjF,SAAS,MAAM,WAAW;GAC1B,WAAW;GACX,WAAW;EACb,CAAC;CACH;;CAGA,MAAM,IAAI,IAAoD;EAK5D,QAAO,MAJa,KAAK,GAAG,KAAK,KAAK,OAAO,sBAAsB;GACjE,OAAO,GAAG,KAAK,OAAO,IAAI,GAAG,EAAE;GAC/B,OAAO;EACT,CAAC,EAAA,CACW,MAAM;CACpB;;CAGA,MAAM,OAAO,IAA2B;EACtC,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,sBAAsB,EAAE;CAC3D;;CAGA,cAAc,KAAqC;EACjD,IAAI,CAAC,IAAI,iBACP,MAAM,IAAI,MAAM,uBAAuB;EAEzC,OAAO,kBAAkB,KAAK,QAAQ,IAAI,eAAe;CAC3D;;CAGA,OAAO,SAAS,KAAuC;EACrD,OAAO,aAAa,IAAI,MAAM;CAChC;;CAGA,OAAO,SAAS,KAAsD;EACpE,IAAI;GACF,MAAM,SAAkB,KAAK,MAAM,IAAI,MAAM;GAC7C,OAAO,OAAO,WAAW,YAAY,WAAW,OAC3C,SACD,CAAC;EACP,QAAQ;GACN,OAAO,CAAC;EACV;CACF;CAEA,OAAe,MAA0B;EACvC,OAAO,gBAAgB,KAAK,OAAO,oBAAoB,CAAC,CAAC;CAC3D;AACF;;AAGA,SAAS,aAAa,KAA8B;CAClD,IAAI,CAAC,KACH,OAAO,CAAC;CAEV,IAAI;EACF,MAAM,SAAkB,KAAK,MAAM,GAAG;EACtC,OAAO,MAAM,QAAQ,MAAM,IACvB,OAAO,QAAQ,UAA2B,OAAO,UAAU,QAAQ,IACnE,CAAC;CACP,QAAQ;EACN,OAAO,CAAC;CACV;AACF"}
1
+ {"version":3,"file":"notification-channel.mjs","names":[],"sources":["../../src/models/notification-channel.ts"],"sourcesContent":["/** Project and site-wide notification channel subscriptions. */\nimport { eq, isNull } from \"drizzle-orm\";\nimport type { Table } from \"drizzle-orm\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport { isStateProvider, type NotifierProvider } from \"../adapters/notifier/provider.ts\";\nimport type { NotificationChannel } from \"../adapters/notifier/types.ts\";\nimport type { NotificationChannelRow } from \"../schema/notification.ts\";\nimport { decryptCredential, encrypt, type SecretInput } from \"../utils/encrypt.ts\";\nimport { ulid } from \"../utils/ulid.ts\";\nimport { col } from \"./columns.ts\";\n\n/** Tables required by {@link NotificationChannelModel}. */\nexport interface NotificationChannelTables {\n notificationChannels: Table;\n}\n\n/** Result of {@link NotificationChannelModel.validateInput}. */\nexport type ChannelValidation =\n | { ok: true }\n | {\n ok: false;\n field: \"provider\" | \"config\" | \"secret\";\n message: string;\n /** Per-field problems of an invalid config, keyed by the first path segment. */\n issues?: { path: string; message: string }[];\n };\n\n/** Input for creating a notification channel. */\nexport interface NotificationChannelCreateInput {\n projectId: string | null;\n provider: string;\n config: Record<string, unknown>;\n secret?: string;\n events?: string[];\n enabled?: boolean;\n}\n\n/** Data operations for notification channels. */\nexport class NotificationChannelModel {\n private readonly tables: NotificationChannelTables;\n private readonly secret?: SecretInput;\n private readonly db: DatabaseAdapter;\n\n /**\n * @param db - Database adapter.\n * @param tables - Table handles (defaults to the adapter's tables).\n * @param secret - Server secret for channel-secret encryption.\n */\n constructor(db: DatabaseAdapter, tables?: NotificationChannelTables, secret?: SecretInput) {\n this.db = db;\n this.tables = tables ?? { notificationChannels: db.tables.notificationChannels };\n this.secret = secret;\n }\n\n /** List channels for a project. */\n async list(projectId: string): Promise<NotificationChannelRow[]> {\n return (await this.db.list(this.tables.notificationChannels, {\n where: eq(col(this.tables.notificationChannels, \"projectId\"), projectId),\n })) as unknown as NotificationChannelRow[];\n }\n\n /** List site-wide admin channels (`project_id IS NULL`). */\n async listSystem(): Promise<NotificationChannelRow[]> {\n return (await this.db.list(this.tables.notificationChannels, {\n where: isNull(col(this.tables.notificationChannels, \"projectId\")),\n })) as unknown as NotificationChannelRow[];\n }\n\n /** Create a channel (secret encrypted at rest when provided). */\n async create(input: NotificationChannelCreateInput): Promise<NotificationChannelRow> {\n const now = new Date().toISOString();\n return (await this.db.insert(this.tables.notificationChannels, {\n id: ulid(),\n projectId: input.projectId,\n provider: input.provider,\n config: JSON.stringify(input.config),\n secretEncrypted: input.secret ? encrypt(this.secret, input.secret) : null,\n events: input.events && input.events.length > 0 ? JSON.stringify(input.events) : null,\n enabled: input.enabled ?? true,\n lastError: null,\n lastErrorAt: null,\n createdAt: now,\n updatedAt: now,\n })) as unknown as NotificationChannelRow;\n }\n\n /** Fetch a channel by id, or null. */\n async get(id: string): Promise<NotificationChannelRow | null> {\n const rows = (await this.db.list(this.tables.notificationChannels, {\n where: eq(col(this.tables.notificationChannels, \"id\"), id),\n limit: 1,\n })) as unknown as NotificationChannelRow[];\n return rows[0] ?? null;\n }\n\n /** Remove a channel by id. */\n async remove(id: string): Promise<void> {\n await this.db.remove(this.tables.notificationChannels, id);\n }\n\n /**\n * Turn a stored row into the send-ready view providers take, with the secret decrypted\n * in memory. Returns null when the secret cannot be decrypted with the configured keys.\n */\n toView(row: NotificationChannelRow): NotificationChannel | null {\n let secret: string | undefined;\n try {\n secret = row.secretEncrypted ? this.decryptSecret(row) : undefined;\n } catch {\n return null;\n }\n return {\n id: row.id,\n projectId: row.projectId,\n provider: row.provider,\n config: NotificationChannelModel.configOf(row),\n secret,\n events: NotificationChannelModel.eventsOf(row),\n enabled: row.enabled,\n ...(row.lastError === null || row.lastError === undefined\n ? {}\n : { lastError: row.lastError }),\n };\n }\n\n /** Decrypt the secret for a stored row (in memory only, at send time). */\n decryptSecret(row: NotificationChannelRow): string {\n if (!row.secretEncrypted) {\n throw new Error(\"Channel has no secret\");\n }\n return decryptCredential(this.secret, row.secretEncrypted);\n }\n\n /**\n * Record the outcome of a delivery attempt: the sanitized error with its time, or\n * nothing to clear a previous error. Skips the write when the row is already in that state.\n */\n async recordOutcome(id: string, error?: string): Promise<void> {\n const row = await this.get(id);\n if (!row || (error === undefined && row.lastError === null)) {\n return;\n }\n await this.db.update(this.tables.notificationChannels, id, {\n lastError: error ?? null,\n lastErrorAt: error === undefined ? null : new Date().toISOString(),\n });\n }\n\n /**\n * Validate channel input against the matching notifier's schema. With `scope`, also\n * enforce what `state` providers (git hosts) need: a project and a token.\n */\n static validateInput(\n notifiers: readonly NotifierProvider[],\n provider: string,\n config: unknown,\n scope?: { projectId: string | null; hasSecret: boolean },\n ): ChannelValidation {\n const descriptor = notifiers.find((candidate) => candidate.metadata.kind === provider);\n if (!descriptor) {\n return { ok: false, field: \"provider\", message: `Unknown provider: ${provider}` };\n }\n const parsed = descriptor.metadata.schema.safeParse(config);\n if (!parsed.success) {\n const issues = parsed.error.issues.map((issue) => ({\n path: String(issue.path[0] ?? \"\"),\n message: issue.message,\n }));\n return { ok: false, field: \"config\", message: parsed.error.message, issues };\n }\n return scope && isStateProvider(descriptor) ? stateScopeProblem(scope) : { ok: true };\n }\n\n /** Whether the provider kind delivers state (git hosts), so it needs a project and a token. */\n static isStateKind(notifiers: readonly NotifierProvider[], provider: string): boolean {\n const descriptor = notifiers.find((candidate) => candidate.metadata.kind === provider);\n return descriptor !== undefined && isStateProvider(descriptor);\n }\n\n /** Decode the JSON-serialized event list of a channel. */\n static eventsOf(row: NotificationChannelRow): string[] {\n return decodeEvents(row.events);\n }\n\n /** Decode the JSON-serialized config of a channel. */\n static configOf(row: NotificationChannelRow): Record<string, unknown> {\n try {\n const parsed: unknown = JSON.parse(row.config);\n return typeof parsed === \"object\" && parsed !== null\n ? (parsed as Record<string, unknown>)\n : {};\n } catch {\n return {};\n }\n }\n}\n\n/** Git channels belong to one project and carry their own token. */\nfunction stateScopeProblem(scope: {\n projectId: string | null;\n hasSecret: boolean;\n}): ChannelValidation {\n if (scope.projectId === null) {\n return { ok: false, field: \"provider\", message: \"Git channels belong to a project\" };\n }\n return scope.hasSecret\n ? { ok: true }\n : { ok: false, field: \"secret\", message: \"A git channel needs a token or private key\" };\n}\n\n/** Decode a JSON event list column (empty = all events). */\nfunction decodeEvents(raw: string | null): string[] {\n if (!raw) {\n return [];\n }\n try {\n const parsed: unknown = JSON.parse(raw);\n return Array.isArray(parsed)\n ? parsed.filter((event): event is string => typeof event === \"string\")\n : [];\n } catch {\n return [];\n }\n}\n"],"mappings":"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"}
@@ -3,7 +3,6 @@ import __tsdown_shims_url from 'node:url';
3
3
  import { DatabaseAdapter } from "../adapter/database.mjs";
4
4
  import { NotificationSubscriptionRow } from "../schema/notification.mjs";
5
5
  import { Table } from "drizzle-orm";
6
- //#region src/models/notification-subscription.d.ts
7
6
  /** Tables required by {@link NotificationSubscriptionModel}. */
8
7
  interface NotificationSubscriptionTables {
9
8
  notificationSubscriptions: Table;
@@ -16,8 +15,8 @@ interface NotificationSubscriptionInput {
16
15
  }
17
16
  /** Data operations for per-user notification subscriptions. */
18
17
  declare class NotificationSubscriptionModel {
19
- private readonly db;
20
18
  private readonly tables;
19
+ private readonly db;
21
20
  /**
22
21
  * @param db - Database adapter.
23
22
  * @param tables - Table handles (defaults to the adapter's tables).
@@ -37,8 +36,6 @@ declare class NotificationSubscriptionModel {
37
36
  static eventsOf(row: NotificationSubscriptionRow): string[];
38
37
  /** Decode the JSON-serialized channel list of a subscription. */
39
38
  static viaOf(row: NotificationSubscriptionRow): string[];
40
- private column;
41
39
  }
42
- //#endregion
43
40
  export { NotificationSubscriptionInput, NotificationSubscriptionModel, NotificationSubscriptionTables };
44
41
  //# sourceMappingURL=notification-subscription.d.mts.map
@@ -2,13 +2,14 @@ import "node:path";
2
2
  import "node:url";
3
3
  import.meta.url;
4
4
  import { ulid } from "../utils/ulid.mjs";
5
- import { and, eq, getTableColumns } from "drizzle-orm";
6
- //#region src/models/notification-subscription.ts
5
+ import { col } from "./columns.mjs";
6
+ import { and, eq } from "drizzle-orm";
7
+ // src/models/notification-subscription.ts
7
8
  /** Per-user opt-in rows for project notifications. */
8
9
  /** Data operations for per-user notification subscriptions. */
9
10
  var NotificationSubscriptionModel = class {
10
- db;
11
11
  tables;
12
+ db;
12
13
  /**
13
14
  * @param db - Database adapter.
14
15
  * @param tables - Table handles (defaults to the adapter's tables).
@@ -19,8 +20,8 @@ var NotificationSubscriptionModel = class {
19
20
  }
20
21
  /** Fetch a user's subscription for a project, or null (null = silent). */
21
22
  async getFor(projectId, userId) {
22
- const projectScope = eq(this.column("projectId"), projectId);
23
- const userScope = eq(this.column("userId"), userId);
23
+ const projectScope = eq(col(this.tables.notificationSubscriptions, "projectId"), projectId);
24
+ const userScope = eq(col(this.tables.notificationSubscriptions, "userId"), userId);
24
25
  return (await this.db.list(this.tables.notificationSubscriptions, {
25
26
  where: and(projectScope, userScope),
26
27
  limit: 1
@@ -28,11 +29,11 @@ var NotificationSubscriptionModel = class {
28
29
  }
29
30
  /** List all subscriptions for a project (admin audit). */
30
31
  async list(projectId) {
31
- return await this.db.list(this.tables.notificationSubscriptions, { where: eq(this.column("projectId"), projectId) });
32
+ return await this.db.list(this.tables.notificationSubscriptions, { where: eq(col(this.tables.notificationSubscriptions, "projectId"), projectId) });
32
33
  }
33
34
  /** List all subscriptions for a user (profile overview). */
34
35
  async listForUser(userId) {
35
- return await this.db.list(this.tables.notificationSubscriptions, { where: eq(this.column("userId"), userId) });
36
+ return await this.db.list(this.tables.notificationSubscriptions, { where: eq(col(this.tables.notificationSubscriptions, "userId"), userId) });
36
37
  }
37
38
  /** Create or replace a user's subscription for a project. */
38
39
  async upsert(projectId, userId, input) {
@@ -65,9 +66,6 @@ var NotificationSubscriptionModel = class {
65
66
  static viaOf(row) {
66
67
  return decodeList(row.via);
67
68
  }
68
- column(name) {
69
- return getTableColumns(this.tables.notificationSubscriptions)[name];
70
- }
71
69
  };
72
70
  /** Encode an optional string list (undefined/empty = all, stored as null). */
73
71
  function encodeList(values) {
@@ -83,7 +81,6 @@ function decodeList(raw) {
83
81
  return [];
84
82
  }
85
83
  }
86
- //#endregion
87
84
  export { NotificationSubscriptionModel };
88
85
 
89
86
  //# sourceMappingURL=notification-subscription.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"notification-subscription.mjs","names":[],"sources":["../../src/models/notification-subscription.ts"],"sourcesContent":["/** Per-user opt-in rows for project notifications. */\nimport { and, eq, getTableColumns } from \"drizzle-orm\";\nimport type { SQLWrapper, Table } from \"drizzle-orm\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { NotificationSubscriptionRow } from \"../schema/notification.ts\";\nimport { ulid } from \"../utils/ulid.ts\";\n\n/** Tables required by {@link NotificationSubscriptionModel}. */\nexport interface NotificationSubscriptionTables {\n notificationSubscriptions: Table;\n}\n\n/** Input for upserting a subscription (all fields optional except identity). */\nexport interface NotificationSubscriptionInput {\n events?: string[];\n via?: string[];\n enabled?: boolean;\n}\n\n/** Data operations for per-user notification subscriptions. */\nexport class NotificationSubscriptionModel {\n private readonly tables: NotificationSubscriptionTables;\n /**\n * @param db - Database adapter.\n * @param tables - Table handles (defaults to the adapter's tables).\n */\n constructor(\n private readonly db: DatabaseAdapter,\n tables?: NotificationSubscriptionTables,\n ) {\n this.tables = tables ?? { notificationSubscriptions: db.tables.notificationSubscriptions };\n }\n\n /** Fetch a user's subscription for a project, or null (null = silent). */\n async getFor(projectId: string, userId: string): Promise<NotificationSubscriptionRow | null> {\n const projectScope = eq(this.column(\"projectId\"), projectId);\n const userScope = eq(this.column(\"userId\"), userId);\n const rows = (await this.db.list(this.tables.notificationSubscriptions, {\n where: and(projectScope, userScope),\n limit: 1,\n })) as unknown as NotificationSubscriptionRow[];\n return rows[0] ?? null;\n }\n\n /** List all subscriptions for a project (admin audit). */\n async list(projectId: string): Promise<NotificationSubscriptionRow[]> {\n return (await this.db.list(this.tables.notificationSubscriptions, {\n where: eq(this.column(\"projectId\"), projectId),\n })) as unknown as NotificationSubscriptionRow[];\n }\n\n /** List all subscriptions for a user (profile overview). */\n async listForUser(userId: string): Promise<NotificationSubscriptionRow[]> {\n return (await this.db.list(this.tables.notificationSubscriptions, {\n where: eq(this.column(\"userId\"), userId),\n })) as unknown as NotificationSubscriptionRow[];\n }\n\n /** Create or replace a user's subscription for a project. */\n async upsert(\n projectId: string,\n userId: string,\n input: NotificationSubscriptionInput,\n ): Promise<NotificationSubscriptionRow> {\n const existing = await this.getFor(projectId, userId);\n if (existing) {\n return (await this.db.update(this.tables.notificationSubscriptions, existing.id, {\n events: encodeList(input.events),\n via: encodeList(input.via),\n enabled: input.enabled ?? true,\n })) as unknown as NotificationSubscriptionRow;\n }\n return (await this.db.insert(this.tables.notificationSubscriptions, {\n id: ulid(),\n projectId,\n userId,\n events: encodeList(input.events),\n via: encodeList(input.via),\n enabled: input.enabled ?? true,\n createdAt: new Date().toISOString(),\n })) as unknown as NotificationSubscriptionRow;\n }\n\n /** Remove a user's subscription (opt out). */\n async remove(projectId: string, userId: string): Promise<void> {\n const existing = await this.getFor(projectId, userId);\n if (existing) {\n await this.db.remove(this.tables.notificationSubscriptions, existing.id);\n }\n }\n\n /** Decode the JSON-serialized event list of a subscription. */\n static eventsOf(row: NotificationSubscriptionRow): string[] {\n return decodeList(row.events);\n }\n\n /** Decode the JSON-serialized channel list of a subscription. */\n static viaOf(row: NotificationSubscriptionRow): string[] {\n return decodeList(row.via);\n }\n\n private column(name: string): SQLWrapper {\n return getTableColumns(this.tables.notificationSubscriptions)[name] as unknown as SQLWrapper;\n }\n}\n\n/** Encode an optional string list (undefined/empty = all, stored as null). */\nfunction encodeList(values: string[] | undefined): string | null {\n return values && values.length > 0 ? JSON.stringify(values) : null;\n}\n\n/** Decode a JSON list column (null = all). */\nfunction decodeList(raw: string | null): string[] {\n if (!raw) {\n return [];\n }\n try {\n const parsed: unknown = JSON.parse(raw);\n return Array.isArray(parsed)\n ? parsed.filter((entry): entry is string => typeof entry === \"string\")\n : [];\n } catch {\n return [];\n }\n}\n"],"mappings":";;;;;;;;AAoBA,IAAa,gCAAb,MAA2C;CAOtB;CANnB;;;;;CAKA,YACE,IACA,QACA;EAFiB,KAAA,KAAA;EAGjB,KAAK,SAAS,UAAU,EAAE,2BAA2B,GAAG,OAAO,0BAA0B;CAC3F;;CAGA,MAAM,OAAO,WAAmB,QAA6D;EAC3F,MAAM,eAAe,GAAG,KAAK,OAAO,WAAW,GAAG,SAAS;EAC3D,MAAM,YAAY,GAAG,KAAK,OAAO,QAAQ,GAAG,MAAM;EAKlD,QAAO,MAJa,KAAK,GAAG,KAAK,KAAK,OAAO,2BAA2B;GACtE,OAAO,IAAI,cAAc,SAAS;GAClC,OAAO;EACT,CAAC,EAAA,CACW,MAAM;CACpB;;CAGA,MAAM,KAAK,WAA2D;EACpE,OAAQ,MAAM,KAAK,GAAG,KAAK,KAAK,OAAO,2BAA2B,EAChE,OAAO,GAAG,KAAK,OAAO,WAAW,GAAG,SAAS,EAC/C,CAAC;CACH;;CAGA,MAAM,YAAY,QAAwD;EACxE,OAAQ,MAAM,KAAK,GAAG,KAAK,KAAK,OAAO,2BAA2B,EAChE,OAAO,GAAG,KAAK,OAAO,QAAQ,GAAG,MAAM,EACzC,CAAC;CACH;;CAGA,MAAM,OACJ,WACA,QACA,OACsC;EACtC,MAAM,WAAW,MAAM,KAAK,OAAO,WAAW,MAAM;EACpD,IAAI,UACF,OAAQ,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,2BAA2B,SAAS,IAAI;GAC/E,QAAQ,WAAW,MAAM,MAAM;GAC/B,KAAK,WAAW,MAAM,GAAG;GACzB,SAAS,MAAM,WAAW;EAC5B,CAAC;EAEH,OAAQ,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,2BAA2B;GAClE,IAAI,KAAK;GACT;GACA;GACA,QAAQ,WAAW,MAAM,MAAM;GAC/B,KAAK,WAAW,MAAM,GAAG;GACzB,SAAS,MAAM,WAAW;GAC1B,4BAAW,IAAI,KAAK,EAAA,CAAE,YAAY;EACpC,CAAC;CACH;;CAGA,MAAM,OAAO,WAAmB,QAA+B;EAC7D,MAAM,WAAW,MAAM,KAAK,OAAO,WAAW,MAAM;EACpD,IAAI,UACF,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,2BAA2B,SAAS,EAAE;CAE3E;;CAGA,OAAO,SAAS,KAA4C;EAC1D,OAAO,WAAW,IAAI,MAAM;CAC9B;;CAGA,OAAO,MAAM,KAA4C;EACvD,OAAO,WAAW,IAAI,GAAG;CAC3B;CAEA,OAAe,MAA0B;EACvC,OAAO,gBAAgB,KAAK,OAAO,yBAAyB,CAAC,CAAC;CAChE;AACF;;AAGA,SAAS,WAAW,QAA6C;CAC/D,OAAO,UAAU,OAAO,SAAS,IAAI,KAAK,UAAU,MAAM,IAAI;AAChE;;AAGA,SAAS,WAAW,KAA8B;CAChD,IAAI,CAAC,KACH,OAAO,CAAC;CAEV,IAAI;EACF,MAAM,SAAkB,KAAK,MAAM,GAAG;EACtC,OAAO,MAAM,QAAQ,MAAM,IACvB,OAAO,QAAQ,UAA2B,OAAO,UAAU,QAAQ,IACnE,CAAC;CACP,QAAQ;EACN,OAAO,CAAC;CACV;AACF"}
1
+ {"version":3,"file":"notification-subscription.mjs","names":[],"sources":["../../src/models/notification-subscription.ts"],"sourcesContent":["/** Per-user opt-in rows for project notifications. */\nimport { and, eq } from \"drizzle-orm\";\nimport type { Table } from \"drizzle-orm\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { NotificationSubscriptionRow } from \"../schema/notification.ts\";\nimport { ulid } from \"../utils/ulid.ts\";\nimport { col } from \"./columns.ts\";\n\n/** Tables required by {@link NotificationSubscriptionModel}. */\nexport interface NotificationSubscriptionTables {\n notificationSubscriptions: Table;\n}\n\n/** Input for upserting a subscription (all fields optional except identity). */\nexport interface NotificationSubscriptionInput {\n events?: string[];\n via?: string[];\n enabled?: boolean;\n}\n\n/** Data operations for per-user notification subscriptions. */\nexport class NotificationSubscriptionModel {\n private readonly tables: NotificationSubscriptionTables;\n private readonly db: DatabaseAdapter;\n\n /**\n * @param db - Database adapter.\n * @param tables - Table handles (defaults to the adapter's tables).\n */\n constructor(db: DatabaseAdapter, tables?: NotificationSubscriptionTables) {\n this.db = db;\n this.tables = tables ?? { notificationSubscriptions: db.tables.notificationSubscriptions };\n }\n\n /** Fetch a user's subscription for a project, or null (null = silent). */\n async getFor(projectId: string, userId: string): Promise<NotificationSubscriptionRow | null> {\n const projectScope = eq(col(this.tables.notificationSubscriptions, \"projectId\"), projectId);\n const userScope = eq(col(this.tables.notificationSubscriptions, \"userId\"), userId);\n const rows = (await this.db.list(this.tables.notificationSubscriptions, {\n where: and(projectScope, userScope),\n limit: 1,\n })) as unknown as NotificationSubscriptionRow[];\n return rows[0] ?? null;\n }\n\n /** List all subscriptions for a project (admin audit). */\n async list(projectId: string): Promise<NotificationSubscriptionRow[]> {\n return (await this.db.list(this.tables.notificationSubscriptions, {\n where: eq(col(this.tables.notificationSubscriptions, \"projectId\"), projectId),\n })) as unknown as NotificationSubscriptionRow[];\n }\n\n /** List all subscriptions for a user (profile overview). */\n async listForUser(userId: string): Promise<NotificationSubscriptionRow[]> {\n return (await this.db.list(this.tables.notificationSubscriptions, {\n where: eq(col(this.tables.notificationSubscriptions, \"userId\"), userId),\n })) as unknown as NotificationSubscriptionRow[];\n }\n\n /** Create or replace a user's subscription for a project. */\n async upsert(\n projectId: string,\n userId: string,\n input: NotificationSubscriptionInput,\n ): Promise<NotificationSubscriptionRow> {\n const existing = await this.getFor(projectId, userId);\n if (existing) {\n return (await this.db.update(this.tables.notificationSubscriptions, existing.id, {\n events: encodeList(input.events),\n via: encodeList(input.via),\n enabled: input.enabled ?? true,\n })) as unknown as NotificationSubscriptionRow;\n }\n return (await this.db.insert(this.tables.notificationSubscriptions, {\n id: ulid(),\n projectId,\n userId,\n events: encodeList(input.events),\n via: encodeList(input.via),\n enabled: input.enabled ?? true,\n createdAt: new Date().toISOString(),\n })) as unknown as NotificationSubscriptionRow;\n }\n\n /** Remove a user's subscription (opt out). */\n async remove(projectId: string, userId: string): Promise<void> {\n const existing = await this.getFor(projectId, userId);\n if (existing) {\n await this.db.remove(this.tables.notificationSubscriptions, existing.id);\n }\n }\n\n /** Decode the JSON-serialized event list of a subscription. */\n static eventsOf(row: NotificationSubscriptionRow): string[] {\n return decodeList(row.events);\n }\n\n /** Decode the JSON-serialized channel list of a subscription. */\n static viaOf(row: NotificationSubscriptionRow): string[] {\n return decodeList(row.via);\n }\n}\n\n/** Encode an optional string list (undefined/empty = all, stored as null). */\nfunction encodeList(values: string[] | undefined): string | null {\n return values && values.length > 0 ? JSON.stringify(values) : null;\n}\n\n/** Decode a JSON list column (null = all). */\nfunction decodeList(raw: string | null): string[] {\n if (!raw) {\n return [];\n }\n try {\n const parsed: unknown = JSON.parse(raw);\n return Array.isArray(parsed)\n ? parsed.filter((entry): entry is string => typeof entry === \"string\")\n : [];\n } catch {\n return [];\n }\n}\n"],"mappings":";;;;;;;;;AAqBA,IAAa,gCAAb,MAA2C;CACzC;CACA;;;;;CAMA,YAAY,IAAqB,QAAyC;EACxE,KAAK,KAAK;EACV,KAAK,SAAS,UAAU,EAAE,2BAA2B,GAAG,OAAO,0BAA0B;CAC3F;;CAGA,MAAM,OAAO,WAAmB,QAA6D;EAC3F,MAAM,eAAe,GAAG,IAAI,KAAK,OAAO,2BAA2B,WAAW,GAAG,SAAS;EAC1F,MAAM,YAAY,GAAG,IAAI,KAAK,OAAO,2BAA2B,QAAQ,GAAG,MAAM;EAKjF,QAAO,MAJa,KAAK,GAAG,KAAK,KAAK,OAAO,2BAA2B;GACtE,OAAO,IAAI,cAAc,SAAS;GAClC,OAAO;EACT,CAAC,EAAA,CACW,MAAM;CACpB;;CAGA,MAAM,KAAK,WAA2D;EACpE,OAAQ,MAAM,KAAK,GAAG,KAAK,KAAK,OAAO,2BAA2B,EAChE,OAAO,GAAG,IAAI,KAAK,OAAO,2BAA2B,WAAW,GAAG,SAAS,EAC9E,CAAC;CACH;;CAGA,MAAM,YAAY,QAAwD;EACxE,OAAQ,MAAM,KAAK,GAAG,KAAK,KAAK,OAAO,2BAA2B,EAChE,OAAO,GAAG,IAAI,KAAK,OAAO,2BAA2B,QAAQ,GAAG,MAAM,EACxE,CAAC;CACH;;CAGA,MAAM,OACJ,WACA,QACA,OACsC;EACtC,MAAM,WAAW,MAAM,KAAK,OAAO,WAAW,MAAM;EACpD,IAAI,UACF,OAAQ,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,2BAA2B,SAAS,IAAI;GAC/E,QAAQ,WAAW,MAAM,MAAM;GAC/B,KAAK,WAAW,MAAM,GAAG;GACzB,SAAS,MAAM,WAAW;EAC5B,CAAC;EAEH,OAAQ,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,2BAA2B;GAClE,IAAI,KAAK;GACT;GACA;GACA,QAAQ,WAAW,MAAM,MAAM;GAC/B,KAAK,WAAW,MAAM,GAAG;GACzB,SAAS,MAAM,WAAW;GAC1B,4BAAW,IAAI,KAAK,EAAA,CAAE,YAAY;EACpC,CAAC;CACH;;CAGA,MAAM,OAAO,WAAmB,QAA+B;EAC7D,MAAM,WAAW,MAAM,KAAK,OAAO,WAAW,MAAM;EACpD,IAAI,UACF,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,2BAA2B,SAAS,EAAE;CAE3E;;CAGA,OAAO,SAAS,KAA4C;EAC1D,OAAO,WAAW,IAAI,MAAM;CAC9B;;CAGA,OAAO,MAAM,KAA4C;EACvD,OAAO,WAAW,IAAI,GAAG;CAC3B;AACF;;AAGA,SAAS,WAAW,QAA6C;CAC/D,OAAO,UAAU,OAAO,SAAS,IAAI,KAAK,UAAU,MAAM,IAAI;AAChE;;AAGA,SAAS,WAAW,KAA8B;CAChD,IAAI,CAAC,KACH,OAAO,CAAC;CAEV,IAAI;EACF,MAAM,SAAkB,KAAK,MAAM,GAAG;EACtC,OAAO,MAAM,QAAQ,MAAM,IACvB,OAAO,QAAQ,UAA2B,OAAO,UAAU,QAAQ,IACnE,CAAC;CACP,QAAQ;EACN,OAAO,CAAC;CACV;AACF"}