@storyshelf/core 0.6.1 → 0.7.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 (631) hide show
  1. package/README.md +49 -32
  2. package/dist/adapter/capture-queue.d.mts +44 -2
  3. package/dist/adapter/capture-runner.d.mts +127 -10
  4. package/dist/adapter/capture-runner.mjs +5 -12
  5. package/dist/adapter/database.d.mts +27 -8
  6. package/dist/adapter/database.mjs.map +1 -1
  7. package/dist/adapter/design.d.mts +85 -0
  8. package/dist/adapter/design.mjs +28 -0
  9. package/dist/adapter/design.mjs.map +1 -0
  10. package/dist/adapter/metadata.d.mts +3 -10
  11. package/dist/adapter/notifier.d.mts +6 -3
  12. package/dist/adapter/notifier.mjs +5 -1
  13. package/dist/adapter/oauth.d.mts +52 -0
  14. package/dist/adapter/setup.d.mts +4 -5
  15. package/dist/adapter/setup.mjs +2 -1
  16. package/dist/adapter/setup.mjs.map +1 -1
  17. package/dist/adapter/webhook-events.mjs +1 -1
  18. package/dist/adapters/capture-target-offers.d.mts +28 -0
  19. package/dist/adapters/capture-target-offers.mjs +58 -0
  20. package/dist/adapters/capture-target-offers.mjs.map +1 -0
  21. package/dist/adapters/capture-target.d.mts +71 -0
  22. package/dist/adapters/capture-target.mjs +76 -0
  23. package/dist/adapters/capture-target.mjs.map +1 -0
  24. package/dist/adapters/notifier/build-comment.d.mts +24 -0
  25. package/dist/adapters/notifier/build-comment.mjs +24 -0
  26. package/dist/adapters/notifier/build-comment.mjs.map +1 -0
  27. package/dist/adapters/notifier/build-state.d.mts +99 -0
  28. package/dist/adapters/notifier/build-state.mjs +218 -0
  29. package/dist/adapters/notifier/build-state.mjs.map +1 -0
  30. package/dist/adapters/notifier/events.d.mts +4 -2
  31. package/dist/adapters/notifier/events.mjs +31 -18
  32. package/dist/adapters/notifier/events.mjs.map +1 -1
  33. package/dist/adapters/notifier/provider.d.mts +53 -7
  34. package/dist/adapters/notifier/provider.mjs +12 -0
  35. package/dist/adapters/notifier/provider.mjs.map +1 -0
  36. package/dist/adapters/notifier/state.d.mts +24 -0
  37. package/dist/adapters/notifier/state.mjs +66 -0
  38. package/dist/adapters/notifier/state.mjs.map +1 -0
  39. package/dist/adapters/notifier/types.d.mts +51 -2
  40. package/dist/adapters/remote-browser.d.mts +87 -0
  41. package/dist/adapters/remote-browser.mjs +103 -0
  42. package/dist/adapters/remote-browser.mjs.map +1 -0
  43. package/dist/adapters/render-runs.d.mts +25 -0
  44. package/dist/adapters/render-runs.mjs +41 -0
  45. package/dist/adapters/render-runs.mjs.map +1 -0
  46. package/dist/adapters/setup-runner.mjs +2 -4
  47. package/dist/adapters/setup-runner.mjs.map +1 -1
  48. package/dist/adapters/sources.d.mts +12 -0
  49. package/dist/adapters/sources.mjs +20 -0
  50. package/dist/adapters/sources.mjs.map +1 -0
  51. package/dist/adapters/validate.mjs +25 -18
  52. package/dist/adapters/validate.mjs.map +1 -1
  53. package/dist/ai.d.mts +105 -0
  54. package/dist/ai.mjs +19 -0
  55. package/dist/ai.mjs.map +1 -0
  56. package/dist/auth.d.mts +8 -1
  57. package/dist/auth.mjs +14 -1
  58. package/dist/auth.mjs.map +1 -0
  59. package/dist/capture/a11y/axe-source.d.mts +18 -0
  60. package/dist/capture/a11y/axe-source.mjs +37 -0
  61. package/dist/capture/a11y/axe-source.mjs.map +1 -0
  62. package/dist/capture/a11y/in-page.d.mts +27 -0
  63. package/dist/capture/a11y/in-page.mjs +60 -0
  64. package/dist/capture/a11y/in-page.mjs.map +1 -0
  65. package/dist/capture/a11y/map.d.mts +24 -0
  66. package/dist/capture/a11y/map.mjs +48 -0
  67. package/dist/capture/a11y/map.mjs.map +1 -0
  68. package/dist/capture/a11y/options.d.mts +23 -0
  69. package/dist/capture/a11y/options.mjs +75 -0
  70. package/dist/capture/a11y/options.mjs.map +1 -0
  71. package/dist/capture/adapter.d.mts +41 -3
  72. package/dist/capture/adapter.mjs.map +1 -1
  73. package/dist/capture/affected.d.mts +2 -0
  74. package/dist/capture/affected.mjs +13 -4
  75. package/dist/capture/affected.mjs.map +1 -1
  76. package/dist/capture/attempt-logger.mjs +84 -0
  77. package/dist/capture/attempt-logger.mjs.map +1 -0
  78. package/dist/capture/browsers.d.mts +35 -0
  79. package/dist/capture/browsers.mjs +67 -0
  80. package/dist/capture/browsers.mjs.map +1 -0
  81. package/dist/capture/build-state/deliver.d.mts +32 -0
  82. package/dist/capture/build-state/deliver.mjs +118 -0
  83. package/dist/capture/build-state/deliver.mjs.map +1 -0
  84. package/dist/capture/build-state/index.d.mts +8 -0
  85. package/dist/capture/build-state/notify-job.d.mts +56 -0
  86. package/dist/capture/build-state/notify-job.mjs +133 -0
  87. package/dist/capture/build-state/notify-job.mjs.map +1 -0
  88. package/dist/capture/build-state/publisher.d.mts +23 -0
  89. package/dist/capture/build-state/publisher.mjs +107 -0
  90. package/dist/capture/build-state/publisher.mjs.map +1 -0
  91. package/dist/capture/build-state/resume.d.mts +9 -0
  92. package/dist/capture/build-state/resume.mjs +47 -0
  93. package/dist/capture/build-state/resume.mjs.map +1 -0
  94. package/dist/capture/build-state/types.d.mts +49 -0
  95. package/dist/capture/catalog/catalogue.d.mts +22 -0
  96. package/dist/capture/catalog/catalogue.mjs +53 -0
  97. package/dist/capture/catalog/catalogue.mjs.map +1 -0
  98. package/dist/capture/catalog/components.d.mts +23 -0
  99. package/dist/capture/catalog/components.mjs +55 -0
  100. package/dist/capture/catalog/components.mjs.map +1 -0
  101. package/dist/capture/catalog/history.d.mts +30 -0
  102. package/dist/capture/catalog/history.mjs +51 -0
  103. package/dist/capture/catalog/history.mjs.map +1 -0
  104. package/dist/capture/catalog/hook.d.mts +14 -0
  105. package/dist/capture/catalog/hook.mjs +19 -0
  106. package/dist/capture/catalog/hook.mjs.map +1 -0
  107. package/dist/capture/catalog/index.d.mts +11 -0
  108. package/dist/capture/catalog/load.d.mts +26 -0
  109. package/dist/capture/catalog/load.mjs +49 -0
  110. package/dist/capture/catalog/load.mjs.map +1 -0
  111. package/dist/capture/catalog/parse.d.mts +15 -0
  112. package/dist/capture/catalog/parse.mjs +56 -0
  113. package/dist/capture/catalog/parse.mjs.map +1 -0
  114. package/dist/capture/catalog/read.d.mts +12 -0
  115. package/dist/capture/catalog/read.mjs +33 -0
  116. package/dist/capture/catalog/read.mjs.map +1 -0
  117. package/dist/capture/catalog/types.d.mts +33 -0
  118. package/dist/capture/consumer/capture-handler.d.mts +58 -0
  119. package/dist/capture/consumer/capture-handler.mjs +118 -0
  120. package/dist/capture/consumer/capture-handler.mjs.map +1 -0
  121. package/dist/capture/consumer/idle.d.mts +17 -0
  122. package/dist/capture/consumer/idle.mjs +25 -0
  123. package/dist/capture/consumer/idle.mjs.map +1 -0
  124. package/dist/capture/consumer/index.d.mts +13 -0
  125. package/dist/capture/consumer/index.mjs +139 -0
  126. package/dist/capture/consumer/index.mjs.map +1 -0
  127. package/dist/capture/consumer/loop.mjs +110 -0
  128. package/dist/capture/consumer/loop.mjs.map +1 -0
  129. package/dist/capture/consumer/metrics.d.mts +41 -0
  130. package/dist/capture/consumer/metrics.mjs +38 -0
  131. package/dist/capture/consumer/metrics.mjs.map +1 -0
  132. package/dist/capture/consumer/retry.d.mts +33 -0
  133. package/dist/capture/consumer/retry.mjs +34 -0
  134. package/dist/capture/consumer/retry.mjs.map +1 -0
  135. package/dist/capture/consumer/run-job.mjs +195 -0
  136. package/dist/capture/consumer/run-job.mjs.map +1 -0
  137. package/dist/capture/consumer/startup.mjs +38 -0
  138. package/dist/capture/consumer/startup.mjs.map +1 -0
  139. package/dist/capture/consumer/types.d.mts +97 -0
  140. package/dist/capture/crash-failure.d.mts +25 -0
  141. package/dist/capture/crash-failure.mjs +70 -0
  142. package/dist/capture/crash-failure.mjs.map +1 -0
  143. package/dist/capture/db-queue/cadence.d.mts +32 -0
  144. package/dist/capture/db-queue/cadence.mjs +38 -0
  145. package/dist/capture/db-queue/cadence.mjs.map +1 -0
  146. package/dist/capture/db-queue/claim.d.mts +9 -0
  147. package/dist/capture/db-queue/claim.mjs +132 -0
  148. package/dist/capture/db-queue/claim.mjs.map +1 -0
  149. package/dist/capture/db-queue/clock.mjs +12 -0
  150. package/dist/capture/db-queue/clock.mjs.map +1 -0
  151. package/dist/capture/db-queue/codec.mjs +76 -0
  152. package/dist/capture/db-queue/codec.mjs.map +1 -0
  153. package/dist/capture/db-queue/index.d.mts +20 -0
  154. package/dist/capture/db-queue/index.mjs +89 -0
  155. package/dist/capture/db-queue/index.mjs.map +1 -0
  156. package/dist/capture/db-queue/keeper.d.mts +36 -0
  157. package/dist/capture/db-queue/keeper.mjs +92 -0
  158. package/dist/capture/db-queue/keeper.mjs.map +1 -0
  159. package/dist/capture/db-queue/lease.mjs +46 -0
  160. package/dist/capture/db-queue/lease.mjs.map +1 -0
  161. package/dist/capture/db-queue/lifecycle.mjs +36 -0
  162. package/dist/capture/db-queue/lifecycle.mjs.map +1 -0
  163. package/dist/capture/db-queue/operations.mjs +94 -0
  164. package/dist/capture/db-queue/operations.mjs.map +1 -0
  165. package/dist/capture/db-queue/poll-loop.d.mts +30 -0
  166. package/dist/capture/db-queue/poll-loop.mjs +89 -0
  167. package/dist/capture/db-queue/poll-loop.mjs.map +1 -0
  168. package/dist/capture/db-queue/poll.d.mts +10 -0
  169. package/dist/capture/db-queue/poll.mjs +69 -0
  170. package/dist/capture/db-queue/poll.mjs.map +1 -0
  171. package/dist/capture/db-queue/timers.d.mts +12 -0
  172. package/dist/capture/db-queue/timers.mjs +37 -0
  173. package/dist/capture/db-queue/timers.mjs.map +1 -0
  174. package/dist/capture/db-queue/types.d.mts +83 -0
  175. package/dist/capture/db-queue/types.mjs +12 -0
  176. package/dist/capture/db-queue/types.mjs.map +1 -0
  177. package/dist/capture/db-queue/wake.mjs +48 -0
  178. package/dist/capture/db-queue/wake.mjs.map +1 -0
  179. package/dist/capture/design.mjs +39 -0
  180. package/dist/capture/design.mjs.map +1 -0
  181. package/dist/capture/diff-record.mjs +23 -3
  182. package/dist/capture/diff-record.mjs.map +1 -1
  183. package/dist/capture/dispatch.d.mts +12 -7
  184. package/dist/capture/dispatch.mjs +86 -107
  185. package/dist/capture/dispatch.mjs.map +1 -1
  186. package/dist/capture/enrich.mjs +60 -0
  187. package/dist/capture/enrich.mjs.map +1 -0
  188. package/dist/capture/failure-sets.mjs +22 -0
  189. package/dist/capture/failure-sets.mjs.map +1 -0
  190. package/dist/capture/fingerprint.d.mts +42 -0
  191. package/dist/capture/fingerprint.mjs +83 -0
  192. package/dist/capture/fingerprint.mjs.map +1 -0
  193. package/dist/capture/forked/channel.d.mts +25 -0
  194. package/dist/capture/forked/child.d.mts +34 -0
  195. package/dist/capture/forked/child.mjs +202 -0
  196. package/dist/capture/forked/child.mjs.map +1 -0
  197. package/dist/capture/forked/crash-tracker.mjs +23 -0
  198. package/dist/capture/forked/crash-tracker.mjs.map +1 -0
  199. package/dist/capture/forked/decision.d.mts +26 -0
  200. package/dist/capture/forked/decision.mjs +27 -0
  201. package/dist/capture/forked/decision.mjs.map +1 -0
  202. package/dist/capture/forked/exit.mjs +12 -0
  203. package/dist/capture/forked/exit.mjs.map +1 -0
  204. package/dist/capture/forked/fork.d.mts +31 -0
  205. package/dist/capture/forked/fork.mjs +93 -0
  206. package/dist/capture/forked/fork.mjs.map +1 -0
  207. package/dist/capture/forked/forked-capture.d.mts +40 -0
  208. package/dist/capture/forked/forked-capture.mjs +240 -0
  209. package/dist/capture/forked/forked-capture.mjs.map +1 -0
  210. package/dist/capture/forked/lease-book.mjs +46 -0
  211. package/dist/capture/forked/lease-book.mjs.map +1 -0
  212. package/dist/capture/forked/protocol.d.mts +38 -0
  213. package/dist/capture/forked/protocol.mjs +25 -0
  214. package/dist/capture/forked/protocol.mjs.map +1 -0
  215. package/dist/capture/forked/runs.mjs +75 -0
  216. package/dist/capture/forked/runs.mjs.map +1 -0
  217. package/dist/capture/isolate.mjs +141 -0
  218. package/dist/capture/isolate.mjs.map +1 -0
  219. package/dist/capture/job-lease.d.mts +28 -0
  220. package/dist/capture/job-lease.mjs +57 -0
  221. package/dist/capture/job-lease.mjs.map +1 -0
  222. package/dist/capture/matrix.mjs +298 -0
  223. package/dist/capture/matrix.mjs.map +1 -0
  224. package/dist/capture/modes.d.mts +25 -0
  225. package/dist/capture/modes.mjs +107 -0
  226. package/dist/capture/modes.mjs.map +1 -0
  227. package/dist/capture/orchestrator.d.mts +37 -5
  228. package/dist/capture/orchestrator.mjs +118 -107
  229. package/dist/capture/orchestrator.mjs.map +1 -1
  230. package/dist/capture/pipeline.d.mts +9 -2
  231. package/dist/capture/pipeline.mjs +250 -45
  232. package/dist/capture/pipeline.mjs.map +1 -1
  233. package/dist/capture/progress.d.mts +11 -0
  234. package/dist/capture/progress.mjs +28 -0
  235. package/dist/capture/progress.mjs.map +1 -0
  236. package/dist/capture/publish/accept.d.mts +39 -0
  237. package/dist/capture/publish/accept.mjs +28 -0
  238. package/dist/capture/publish/accept.mjs.map +1 -0
  239. package/dist/capture/publish/complete.d.mts +46 -0
  240. package/dist/capture/publish/complete.mjs +64 -0
  241. package/dist/capture/publish/complete.mjs.map +1 -0
  242. package/dist/capture/publish/extract.d.mts +20 -0
  243. package/dist/capture/publish/extract.mjs +119 -0
  244. package/dist/capture/publish/extract.mjs.map +1 -0
  245. package/dist/capture/publish/handler.d.mts +28 -0
  246. package/dist/capture/publish/handler.mjs +62 -0
  247. package/dist/capture/publish/handler.mjs.map +1 -0
  248. package/dist/capture/publish/index.d.mts +10 -0
  249. package/dist/capture/publish/limits.d.mts +37 -0
  250. package/dist/capture/publish/limits.mjs +39 -0
  251. package/dist/capture/publish/limits.mjs.map +1 -0
  252. package/dist/capture/publish/published.d.mts +15 -0
  253. package/dist/capture/publish/published.mjs +18 -0
  254. package/dist/capture/publish/published.mjs.map +1 -0
  255. package/dist/capture/publish/run.d.mts +33 -0
  256. package/dist/capture/publish/run.mjs +30 -0
  257. package/dist/capture/publish/run.mjs.map +1 -0
  258. package/dist/capture/rediff.d.mts +1 -1
  259. package/dist/capture/rediff.mjs +4 -3
  260. package/dist/capture/rediff.mjs.map +1 -1
  261. package/dist/capture/render.mjs +53 -0
  262. package/dist/capture/render.mjs.map +1 -0
  263. package/dist/capture/scratch.d.mts +44 -0
  264. package/dist/capture/scratch.mjs +138 -0
  265. package/dist/capture/scratch.mjs.map +1 -0
  266. package/dist/capture/shard-plan.mjs +128 -0
  267. package/dist/capture/shard-plan.mjs.map +1 -0
  268. package/dist/capture/shards/context.mjs +51 -0
  269. package/dist/capture/shards/context.mjs.map +1 -0
  270. package/dist/capture/shards/execute.mjs +174 -0
  271. package/dist/capture/shards/execute.mjs.map +1 -0
  272. package/dist/capture/shards/fan-out.mjs +126 -0
  273. package/dist/capture/shards/fan-out.mjs.map +1 -0
  274. package/dist/capture/shards/history.mjs +35 -0
  275. package/dist/capture/shards/history.mjs.map +1 -0
  276. package/dist/capture/shards/reduce.mjs +97 -0
  277. package/dist/capture/shards/reduce.mjs.map +1 -0
  278. package/dist/capture/sizing.mjs +1 -11
  279. package/dist/capture/sizing.mjs.map +1 -1
  280. package/dist/capture/skip-checks.mjs +2 -65
  281. package/dist/capture/skip-checks.mjs.map +1 -1
  282. package/dist/capture/statics.d.mts +7 -8
  283. package/dist/capture/statics.mjs +25 -71
  284. package/dist/capture/statics.mjs.map +1 -1
  285. package/dist/capture/storybook-urls.d.mts +40 -0
  286. package/dist/capture/storybook-urls.mjs +41 -0
  287. package/dist/capture/storybook-urls.mjs.map +1 -0
  288. package/dist/capture/storybook.d.mts +12 -4
  289. package/dist/capture/storybook.mjs +56 -16
  290. package/dist/capture/storybook.mjs.map +1 -1
  291. package/dist/capture/target.mjs +21 -0
  292. package/dist/capture/target.mjs.map +1 -0
  293. package/dist/capture/targets.d.mts +25 -0
  294. package/dist/capture/targets.mjs +32 -0
  295. package/dist/capture/targets.mjs.map +1 -0
  296. package/dist/capture/tests.d.mts +37 -0
  297. package/dist/capture/tests.mjs +126 -0
  298. package/dist/capture/tests.mjs.map +1 -0
  299. package/dist/capture/token-chunks.mjs +74 -0
  300. package/dist/capture/token-chunks.mjs.map +1 -0
  301. package/dist/capture.d.mts +60 -7
  302. package/dist/capture.mjs +48 -3
  303. package/dist/config.d.mts +121 -12
  304. package/dist/config.mjs +9 -6
  305. package/dist/config.mjs.map +1 -1
  306. package/dist/diff.d.mts +9 -3
  307. package/dist/diff.mjs +16 -3
  308. package/dist/diff.mjs.map +1 -0
  309. package/dist/index.d.mts +1 -1
  310. package/dist/insights/budget.d.mts +20 -0
  311. package/dist/insights/budget.mjs +33 -0
  312. package/dist/insights/budget.mjs.map +1 -0
  313. package/dist/insights/envelope.d.mts +59 -0
  314. package/dist/insights/envelope.mjs +60 -0
  315. package/dist/insights/envelope.mjs.map +1 -0
  316. package/dist/insights/evidence-health.d.mts +32 -0
  317. package/dist/insights/evidence-health.mjs +28 -0
  318. package/dist/insights/evidence-health.mjs.map +1 -0
  319. package/dist/insights/evidence.d.mts +55 -0
  320. package/dist/insights/evidence.mjs +64 -0
  321. package/dist/insights/evidence.mjs.map +1 -0
  322. package/dist/insights/hash.d.mts +18 -0
  323. package/dist/insights/hash.mjs +21 -0
  324. package/dist/insights/hash.mjs.map +1 -0
  325. package/dist/insights/limits.d.mts +24 -0
  326. package/dist/insights/limits.mjs +24 -0
  327. package/dist/insights/limits.mjs.map +1 -0
  328. package/dist/insights/prompts/v1.d.mts +13 -0
  329. package/dist/insights/prompts/v1.mjs +34 -0
  330. package/dist/insights/prompts/v1.mjs.map +1 -0
  331. package/dist/insights/redact.d.mts +9 -0
  332. package/dist/insights/redact.mjs +24 -0
  333. package/dist/insights/redact.mjs.map +1 -0
  334. package/dist/insights/text.mjs +44 -0
  335. package/dist/insights/text.mjs.map +1 -0
  336. package/dist/insights.d.mts +11 -0
  337. package/dist/insights.mjs +12 -0
  338. package/dist/logger.mjs +17 -1
  339. package/dist/logger.mjs.map +1 -1
  340. package/dist/models/ai-budget-alert.d.mts +23 -0
  341. package/dist/models/ai-budget-alert.mjs +38 -0
  342. package/dist/models/ai-budget-alert.mjs.map +1 -0
  343. package/dist/models/ai-usage.d.mts +40 -0
  344. package/dist/models/ai-usage.mjs +61 -0
  345. package/dist/models/ai-usage.mjs.map +1 -0
  346. package/dist/models/baseline-archive.d.mts +84 -0
  347. package/dist/models/baseline-archive.mjs +106 -0
  348. package/dist/models/baseline-archive.mjs.map +1 -0
  349. package/dist/models/baseline-export.d.mts +19 -0
  350. package/dist/models/baseline-export.mjs +136 -0
  351. package/dist/models/baseline-export.mjs.map +1 -0
  352. package/dist/models/baseline-import.mjs +118 -0
  353. package/dist/models/baseline-import.mjs.map +1 -0
  354. package/dist/models/baseline-transfer.d.mts +49 -0
  355. package/dist/models/baseline-transfer.mjs +121 -0
  356. package/dist/models/baseline-transfer.mjs.map +1 -0
  357. package/dist/models/baseline.d.mts +32 -5
  358. package/dist/models/baseline.mjs +65 -20
  359. package/dist/models/baseline.mjs.map +1 -1
  360. package/dist/models/build-component.d.mts +28 -0
  361. package/dist/models/build-component.mjs +55 -0
  362. package/dist/models/build-component.mjs.map +1 -0
  363. package/dist/models/build-review-status.d.mts +29 -0
  364. package/dist/models/build-review-status.mjs +37 -0
  365. package/dist/models/build-review-status.mjs.map +1 -0
  366. package/dist/models/build-review.d.mts +28 -0
  367. package/dist/models/build-review.mjs +54 -0
  368. package/dist/models/build-review.mjs.map +1 -0
  369. package/dist/models/build.d.mts +25 -3
  370. package/dist/models/build.mjs +50 -11
  371. package/dist/models/build.mjs.map +1 -1
  372. package/dist/models/capture-attempt.d.mts +7 -1
  373. package/dist/models/capture-attempt.mjs +29 -4
  374. package/dist/models/capture-attempt.mjs.map +1 -1
  375. package/dist/models/capture-job.d.mts +106 -0
  376. package/dist/models/capture-job.mjs +288 -0
  377. package/dist/models/capture-job.mjs.map +1 -0
  378. package/dist/models/capture-log.d.mts +1 -1
  379. package/dist/models/capture-log.mjs +4 -3
  380. package/dist/models/capture-log.mjs.map +1 -1
  381. package/dist/models/capture-shard.d.mts +62 -0
  382. package/dist/models/capture-shard.mjs +190 -0
  383. package/dist/models/capture-shard.mjs.map +1 -0
  384. package/dist/models/changeset.d.mts +48 -0
  385. package/dist/models/changeset.mjs +81 -0
  386. package/dist/models/changeset.mjs.map +1 -0
  387. package/dist/models/columns.mjs +19 -0
  388. package/dist/models/columns.mjs.map +1 -0
  389. package/dist/models/comment.d.mts +4 -1
  390. package/dist/models/comment.mjs +7 -4
  391. package/dist/models/comment.mjs.map +1 -1
  392. package/dist/models/component.d.mts +43 -0
  393. package/dist/models/component.mjs +60 -0
  394. package/dist/models/component.mjs.map +1 -0
  395. package/dist/models/connections.d.mts +41 -0
  396. package/dist/models/connections.mjs +112 -0
  397. package/dist/models/connections.mjs.map +1 -0
  398. package/dist/models/content-ref.d.mts +1 -1
  399. package/dist/models/content-ref.mjs +1 -1
  400. package/dist/models/content-ref.mjs.map +1 -1
  401. package/dist/models/credentials.mjs +12 -2
  402. package/dist/models/credentials.mjs.map +1 -1
  403. package/dist/models/design-credentials.d.mts +41 -0
  404. package/dist/models/design-credentials.mjs +86 -0
  405. package/dist/models/design-credentials.mjs.map +1 -0
  406. package/dist/models/design-health.d.mts +16 -0
  407. package/dist/models/design-health.mjs +25 -0
  408. package/dist/models/design-health.mjs.map +1 -0
  409. package/dist/models/design-nodes.d.mts +83 -0
  410. package/dist/models/design-nodes.mjs +222 -0
  411. package/dist/models/design-nodes.mjs.map +1 -0
  412. package/dist/models/design-renders.mjs +74 -0
  413. package/dist/models/design-renders.mjs.map +1 -0
  414. package/dist/models/design-resolve.d.mts +29 -0
  415. package/dist/models/design-resolve.mjs +41 -0
  416. package/dist/models/design-resolve.mjs.map +1 -0
  417. package/dist/models/design.d.mts +39 -0
  418. package/dist/models/design.mjs +90 -0
  419. package/dist/models/design.mjs.map +1 -0
  420. package/dist/models/inherited-ratio.d.mts +21 -0
  421. package/dist/models/inherited-ratio.mjs +22 -0
  422. package/dist/models/inherited-ratio.mjs.map +1 -0
  423. package/dist/models/insight.d.mts +70 -0
  424. package/dist/models/insight.mjs +162 -0
  425. package/dist/models/insight.mjs.map +1 -0
  426. package/dist/models/label.d.mts +4 -2
  427. package/dist/models/label.mjs +25 -10
  428. package/dist/models/label.mjs.map +1 -1
  429. package/dist/models/member.d.mts +1 -1
  430. package/dist/models/member.mjs +5 -4
  431. package/dist/models/member.mjs.map +1 -1
  432. package/dist/models/notification-channel.d.mts +37 -3
  433. package/dist/models/notification-channel.mjs +88 -9
  434. package/dist/models/notification-channel.mjs.map +1 -1
  435. package/dist/models/notification-subscription.d.mts +1 -2
  436. package/dist/models/notification-subscription.mjs +7 -9
  437. package/dist/models/notification-subscription.mjs.map +1 -1
  438. package/dist/models/project-group-mapping.d.mts +6 -1
  439. package/dist/models/project-group-mapping.mjs +14 -4
  440. package/dist/models/project-group-mapping.mjs.map +1 -1
  441. package/dist/models/project.d.mts +9 -1
  442. package/dist/models/project.mjs +9 -4
  443. package/dist/models/project.mjs.map +1 -1
  444. package/dist/models/public-origin.d.mts +63 -0
  445. package/dist/models/public-origin.mjs +114 -0
  446. package/dist/models/public-origin.mjs.map +1 -0
  447. package/dist/models/review-switch-off.d.mts +14 -0
  448. package/dist/models/review-switch-off.mjs +26 -0
  449. package/dist/models/review-switch-off.mjs.map +1 -0
  450. package/dist/models/site-settings.d.mts +23 -0
  451. package/dist/models/site-settings.mjs +48 -0
  452. package/dist/models/site-settings.mjs.map +1 -0
  453. package/dist/models/snapshot.d.mts +36 -1
  454. package/dist/models/snapshot.mjs +50 -3
  455. package/dist/models/snapshot.mjs.map +1 -1
  456. package/dist/models/token.d.mts +13 -1
  457. package/dist/models/token.mjs +23 -5
  458. package/dist/models/token.mjs.map +1 -1
  459. package/dist/models/usage.d.mts +76 -0
  460. package/dist/models/usage.mjs +149 -0
  461. package/dist/models/usage.mjs.map +1 -0
  462. package/dist/models/user.d.mts +1 -1
  463. package/dist/models/user.mjs +4 -3
  464. package/dist/models/user.mjs.map +1 -1
  465. package/dist/models/webhook.d.mts +15 -1
  466. package/dist/models/webhook.mjs +33 -4
  467. package/dist/models/webhook.mjs.map +1 -1
  468. package/dist/models.d.mts +26 -3
  469. package/dist/models.mjs +27 -5
  470. package/dist/options.d.mts +18 -0
  471. package/dist/options.mjs +90 -0
  472. package/dist/options.mjs.map +1 -0
  473. package/dist/paths.d.mts +19 -4
  474. package/dist/paths.mjs +51 -10
  475. package/dist/paths.mjs.map +1 -1
  476. package/dist/retention/ai.d.mts +18 -0
  477. package/dist/retention/ai.mjs +23 -0
  478. package/dist/retention/ai.mjs.map +1 -0
  479. package/dist/retention/pending.mjs +47 -0
  480. package/dist/retention/pending.mjs.map +1 -0
  481. package/dist/retention/screenshot-sweep.d.mts +51 -0
  482. package/dist/retention/screenshot-sweep.mjs +128 -0
  483. package/dist/retention/screenshot-sweep.mjs.map +1 -0
  484. package/dist/retention.d.mts +16 -2
  485. package/dist/retention.mjs +71 -14
  486. package/dist/retention.mjs.map +1 -1
  487. package/dist/schema/baseline.d.mts +6 -0
  488. package/dist/schema/build-component.d.mts +25 -0
  489. package/dist/schema/build-review.d.mts +17 -0
  490. package/dist/schema/build.d.mts +7 -0
  491. package/dist/schema/capture-job.d.mts +60 -0
  492. package/dist/schema/capture-shard.d.mts +35 -0
  493. package/dist/schema/comment.d.mts +2 -0
  494. package/dist/schema/connection.d.mts +24 -0
  495. package/dist/schema/design.d.mts +58 -0
  496. package/dist/schema/insight.d.mts +54 -0
  497. package/dist/schema/notification.d.mts +4 -0
  498. package/dist/schema/project.d.mts +21 -0
  499. package/dist/schema/site-setting.d.mts +21 -0
  500. package/dist/schema/snapshot.d.mts +12 -0
  501. package/dist/schema/usage.d.mts +30 -0
  502. package/dist/schema.d.mts +11 -3
  503. package/dist/serverless.d.mts +21 -0
  504. package/dist/serverless.mjs +37 -0
  505. package/dist/serverless.mjs.map +1 -0
  506. package/dist/test-helpers/create-project.d.mts +1 -1
  507. package/dist/test-helpers/deferred.d.mts +39 -0
  508. package/dist/test-helpers/deferred.mjs +61 -0
  509. package/dist/test-helpers/deferred.mjs.map +1 -0
  510. package/dist/test-helpers/design-contract.d.mts +21 -0
  511. package/dist/test-helpers/design-contract.mjs +40 -0
  512. package/dist/test-helpers/design-contract.mjs.map +1 -0
  513. package/dist/test-helpers/fake-database.mjs +14 -2
  514. package/dist/test-helpers/fake-database.mjs.map +1 -1
  515. package/dist/test-helpers/fake-design.d.mts +43 -0
  516. package/dist/test-helpers/fake-design.mjs +114 -0
  517. package/dist/test-helpers/fake-design.mjs.map +1 -0
  518. package/dist/test-helpers/fake-notifier.d.mts +21 -0
  519. package/dist/test-helpers/fake-notifier.mjs +44 -0
  520. package/dist/test-helpers/fake-notifier.mjs.map +1 -0
  521. package/dist/test-helpers/fake-tables.mjs +255 -67
  522. package/dist/test-helpers/fake-tables.mjs.map +1 -1
  523. package/dist/test-helpers/manual-clock.d.mts +27 -0
  524. package/dist/test-helpers/manual-clock.mjs +56 -0
  525. package/dist/test-helpers/manual-clock.mjs.map +1 -0
  526. package/dist/test-helpers/notifier-contract.d.mts +7 -1
  527. package/dist/test-helpers/notifier-contract.mjs +32 -3
  528. package/dist/test-helpers/notifier-contract.mjs.map +1 -1
  529. package/dist/test-helpers/queue-contract.d.mts +16 -2
  530. package/dist/test-helpers/queue-contract.mjs +195 -1
  531. package/dist/test-helpers/queue-contract.mjs.map +1 -1
  532. package/dist/test-helpers/queue-multi-instance.d.mts +20 -0
  533. package/dist/test-helpers/queue-multi-instance.mjs +206 -0
  534. package/dist/test-helpers/queue-multi-instance.mjs.map +1 -0
  535. package/dist/test-helpers/runner-contract.mjs +11 -0
  536. package/dist/test-helpers/runner-contract.mjs.map +1 -1
  537. package/dist/test-helpers/sql-chunks.mjs +23 -0
  538. package/dist/test-helpers/sql-chunks.mjs.map +1 -1
  539. package/dist/test-helpers/storage-contract.mjs +1 -1
  540. package/dist/test-helpers.d.mts +9 -4
  541. package/dist/test-helpers.mjs +8 -3
  542. package/dist/types.d.mts +14 -3
  543. package/dist/types.mjs +21 -4
  544. package/dist/types.mjs.map +1 -1
  545. package/dist/urls.d.mts +4 -0
  546. package/dist/urls.mjs +11 -2
  547. package/dist/urls.mjs.map +1 -1
  548. package/dist/utils/adaptive-concurrency.d.mts +66 -0
  549. package/dist/utils/adaptive-concurrency.mjs +149 -0
  550. package/dist/utils/adaptive-concurrency.mjs.map +1 -0
  551. package/dist/utils/connect-retry.d.mts +26 -0
  552. package/dist/utils/connect-retry.mjs +43 -0
  553. package/dist/utils/connect-retry.mjs.map +1 -0
  554. package/dist/utils/environment.d.mts +19 -0
  555. package/dist/utils/environment.mjs +59 -0
  556. package/dist/utils/environment.mjs.map +1 -0
  557. package/dist/utils/http.d.mts +17 -1
  558. package/dist/utils/http.mjs +22 -3
  559. package/dist/utils/http.mjs.map +1 -1
  560. package/dist/utils/map-adaptive.d.mts +15 -0
  561. package/dist/utils/map-adaptive.mjs +72 -0
  562. package/dist/utils/map-adaptive.mjs.map +1 -0
  563. package/dist/utils/map-limit.d.mts +10 -0
  564. package/dist/utils/map-limit.mjs +32 -0
  565. package/dist/utils/map-limit.mjs.map +1 -0
  566. package/dist/utils/page-limiter.d.mts +37 -0
  567. package/dist/utils/page-limiter.mjs +56 -0
  568. package/dist/utils/page-limiter.mjs.map +1 -0
  569. package/dist/utils/public-origin.d.mts +20 -0
  570. package/dist/utils/public-origin.mjs +75 -0
  571. package/dist/utils/public-origin.mjs.map +1 -0
  572. package/dist/utils/redact.mjs +3 -0
  573. package/dist/utils/redact.mjs.map +1 -1
  574. package/dist/utils/remote-environment.d.mts +39 -0
  575. package/dist/utils/remote-environment.mjs +114 -0
  576. package/dist/utils/remote-environment.mjs.map +1 -0
  577. package/dist/utils/resources.d.mts +64 -0
  578. package/dist/utils/resources.mjs +122 -0
  579. package/dist/utils/resources.mjs.map +1 -0
  580. package/dist/utils/screenshot-store.d.mts +15 -0
  581. package/dist/utils/screenshot-store.mjs +41 -0
  582. package/dist/utils/screenshot-store.mjs.map +1 -0
  583. package/dist/utils/session-gate.d.mts +22 -0
  584. package/dist/utils/session-gate.mjs +53 -0
  585. package/dist/utils/session-gate.mjs.map +1 -0
  586. package/dist/utils/storybook-token.d.mts +81 -0
  587. package/dist/utils/storybook-token.mjs +134 -0
  588. package/dist/utils/storybook-token.mjs.map +1 -0
  589. package/dist/utils/storybook-url.d.mts +15 -0
  590. package/dist/utils/storybook-url.mjs +21 -0
  591. package/dist/utils/storybook-url.mjs.map +1 -0
  592. package/dist/utils/timing.mjs +1 -1
  593. package/dist/utils/timing.mjs.map +1 -1
  594. package/dist/utils/zip-read.mjs +129 -0
  595. package/dist/utils/zip-read.mjs.map +1 -0
  596. package/dist/utils/zip-write.d.mts +18 -0
  597. package/dist/utils/zip-write.mjs +188 -0
  598. package/dist/utils/zip-write.mjs.map +1 -0
  599. package/dist/utils.d.mts +17 -3
  600. package/dist/utils.mjs +18 -4
  601. package/package.json +32 -20
  602. package/dist/adapter/capture-runner.mjs.map +0 -1
  603. package/dist/adapter/git-host/comments.d.mts +0 -41
  604. package/dist/adapter/git-host/comments.mjs +0 -54
  605. package/dist/adapter/git-host/comments.mjs.map +0 -1
  606. package/dist/adapter/git-host/helpers.d.mts +0 -13
  607. package/dist/adapter/git-host/helpers.mjs +0 -30
  608. package/dist/adapter/git-host/helpers.mjs.map +0 -1
  609. package/dist/adapter/git-host.d.mts +0 -42
  610. package/dist/capture/queue.d.mts +0 -38
  611. package/dist/capture/queue.mjs +0 -98
  612. package/dist/capture/queue.mjs.map +0 -1
  613. package/dist/capture/recovery.d.mts +0 -21
  614. package/dist/capture/recovery.mjs +0 -83
  615. package/dist/capture/recovery.mjs.map +0 -1
  616. package/dist/capture/status-fanout.mjs +0 -72
  617. package/dist/capture/status-fanout.mjs.map +0 -1
  618. package/dist/diff/engine.d.mts +0 -16
  619. package/dist/diff/engine.mjs +0 -87
  620. package/dist/diff/engine.mjs.map +0 -1
  621. package/dist/diff/options.d.mts +0 -42
  622. package/dist/diff/options.mjs +0 -15
  623. package/dist/diff/options.mjs.map +0 -1
  624. package/dist/models/status-config.d.mts +0 -38
  625. package/dist/models/status-config.mjs +0 -63
  626. package/dist/models/status-config.mjs.map +0 -1
  627. package/dist/schema/status-config.d.mts +0 -16
  628. package/dist/test-helpers/git-contract.d.mts +0 -11
  629. package/dist/test-helpers/git-contract.mjs +0 -34
  630. package/dist/test-helpers/git-contract.mjs.map +0 -1
  631. /package/dist/adapter/{git-host.mjs → oauth.mjs} +0 -0
@@ -1,14 +1,16 @@
1
1
  import "node:path";
2
2
  import "node:url";
3
3
  import.meta.url;
4
+ import "../adapters/capture-target.mjs";
4
5
  import { ulid } from "../utils/ulid.mjs";
5
- import { eq, getTableColumns } from "drizzle-orm";
6
+ import { col } from "./columns.mjs";
7
+ import { and, eq, inArray } from "drizzle-orm";
6
8
  //#region src/models/snapshot.ts
7
9
  /** Snapshot records for captured stories within a build. */
8
10
  /** Data operations for snapshot records. */
9
11
  var SnapshotModel = class {
10
- db;
11
12
  tables;
13
+ db;
12
14
  /**
13
15
  * @param db - Database adapter.
14
16
  * @param tables - Table handles.
@@ -35,22 +37,67 @@ var SnapshotModel = class {
35
37
  storyName: input.storyName,
36
38
  storyTitle: input.storyTitle,
37
39
  storyImportPath: input.storyImportPath,
40
+ designUrl: input.designUrl ?? null,
38
41
  viewportName: input.viewportName,
39
42
  viewportWidth: input.viewportWidth,
40
43
  viewportHeight: input.viewportHeight,
41
44
  screenshotPath: input.screenshotPath,
42
45
  infraHash: input.infraHash ?? null,
46
+ envChange: input.envChange ?? null,
43
47
  inherited: input.inherited ?? false,
44
48
  baselineId: input.baselineId ?? null,
45
49
  baselineVersion: input.baselineVersion ?? null,
50
+ mode: input.mode ?? null,
51
+ browser: input.browser ?? "chromium",
52
+ durationMs: input.durationMs ?? null,
53
+ a11yViolations: input.a11yViolations ?? null,
46
54
  status: "pending",
47
55
  createdAt: now,
48
56
  updatedAt: now
49
57
  });
50
58
  }
59
+ /**
60
+ * Delete the rendered snapshots a shard is about to write again: rows of `(story, engine)` pairs
61
+ * in `work`, so a rerun starts from a clean slate instead of colliding with rows an earlier,
62
+ * interrupted run left behind. Inherited rows are kept: the plan wrote them, for engines or
63
+ * viewports this shard does not render.
64
+ */
65
+ async removeRendered(buildId, work) {
66
+ const doomed = (await this.listByBuild(buildId)).filter((row) => !row.inherited && work.some((engine) => engine.browser === (row.browser ?? "chromium") && engine.storyIds.has(row.storyId)));
67
+ await this.#removeAll(doomed);
68
+ return doomed.length;
69
+ }
70
+ /** Delete every snapshot of a build: a new plan of it starts clean. */
71
+ async removeForBuild(buildId) {
72
+ const doomed = await this.listByBuild(buildId);
73
+ await this.#removeAll(doomed);
74
+ return doomed.length;
75
+ }
76
+ async #removeAll(rows) {
77
+ await Promise.all(rows.map(async (row) => {
78
+ await this.db.remove(this.tables.snapshots, row.id);
79
+ }));
80
+ }
81
+ /**
82
+ * Milliseconds each story cost on one build, summed over its viewports and engines. Stories the
83
+ * renderer did not time are left out, so an empty map means this build carries no timings.
84
+ */
85
+ async durationsByStory(buildId) {
86
+ const totals = /* @__PURE__ */ new Map();
87
+ for (const row of await this.listByBuild(buildId)) if (typeof row.durationMs === "number") totals.set(row.storyId, (totals.get(row.storyId) ?? 0) + row.durationMs);
88
+ return totals;
89
+ }
51
90
  /** List all snapshots belonging to a build. */
52
91
  async listByBuild(buildId) {
53
- return await this.db.list(this.tables.snapshots, { where: eq(getTableColumns(this.tables.snapshots)["buildId"], buildId) });
92
+ return await this.db.list(this.tables.snapshots, { where: eq(col(this.tables.snapshots, "buildId"), buildId) });
93
+ }
94
+ /**
95
+ * Every snapshot of a story file across the project's builds. Index paths are
96
+ * stored raw (`./src/A.stories.tsx`), so both spellings of `importPath` match.
97
+ */
98
+ async listByImportPath(projectId, importPath) {
99
+ const bare = importPath.replace(/^(?:\.\/)+/u, "");
100
+ return await this.db.list(this.tables.snapshots, { where: and(eq(col(this.tables.snapshots, "projectId"), projectId), inArray(col(this.tables.snapshots, "storyImportPath"), [bare, `./${bare}`])) });
54
101
  }
55
102
  /** Fetch a snapshot by id, or null if not found. */
56
103
  async get(id) {
@@ -1 +1 @@
1
- {"version":3,"file":"snapshot.mjs","names":[],"sources":["../../src/models/snapshot.ts"],"sourcesContent":["/** Snapshot records for captured stories within a build. */\nimport { eq, getTableColumns } from \"drizzle-orm\";\nimport type { SQLWrapper, Table } from \"drizzle-orm\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { Snapshot } from \"../schema/snapshot.ts\";\nimport type { SnapshotStatus } from \"../types.ts\";\nimport { ulid } from \"../utils/ulid.ts\";\n\n/** Tables required by {@link SnapshotModel}. */\nexport interface SnapshotTables {\n snapshots: Table;\n}\n\n/** Data operations for snapshot records. */\nexport class SnapshotModel {\n private readonly tables: SnapshotTables;\n /**\n * @param db - Database adapter.\n * @param tables - Table handles.\n */\n constructor(\n private readonly db: DatabaseAdapter,\n tables?: SnapshotTables,\n ) {\n this.tables = tables ?? { snapshots: db.tables.snapshots };\n }\n\n /**\n * Create a snapshot for a story within a build.\n *\n * @param projectId - Project ID.\n * @param buildId - Build ID.\n * @param input - Snapshot creation input.\n * @returns The created snapshot.\n */\n async create(projectId: string, buildId: string, input: SnapshotCreateInput): Promise<Snapshot> {\n const now = new Date().toISOString();\n return (await this.db.insert(this.tables.snapshots, {\n id: ulid(),\n projectId,\n buildId,\n storyId: input.storyId,\n storyName: input.storyName,\n storyTitle: input.storyTitle,\n storyImportPath: input.storyImportPath,\n viewportName: input.viewportName,\n viewportWidth: input.viewportWidth,\n viewportHeight: input.viewportHeight,\n screenshotPath: input.screenshotPath,\n infraHash: input.infraHash ?? null,\n inherited: input.inherited ?? false,\n baselineId: input.baselineId ?? null,\n baselineVersion: input.baselineVersion ?? null,\n status: \"pending\",\n createdAt: now,\n updatedAt: now,\n })) as unknown as Snapshot;\n }\n\n /** List all snapshots belonging to a build. */\n async listByBuild(buildId: string): Promise<Snapshot[]> {\n return (await this.db.list(this.tables.snapshots, {\n where: eq(\n getTableColumns(this.tables.snapshots)[\"buildId\"] as unknown as SQLWrapper,\n buildId,\n ),\n })) as unknown as Snapshot[];\n }\n\n /** Fetch a snapshot by id, or null if not found. */\n async get(id: string): Promise<Snapshot | null> {\n return (await this.db.get(this.tables.snapshots, id)) as unknown as Snapshot | null;\n }\n\n /** Update mutable fields of a snapshot. */\n async update(id: string, patch: Partial<Snapshot>): Promise<Snapshot> {\n return (await this.db.update(this.tables.snapshots, id, {\n ...patch,\n updatedAt: new Date().toISOString(),\n })) as unknown as Snapshot;\n }\n\n /** Set the status of a snapshot. */\n async setStatus(id: string, status: SnapshotStatus): Promise<Snapshot> {\n return await this.update(id, { status });\n }\n\n /** Record a reviewer's decision on a snapshot. */\n async review(id: string, status: SnapshotStatus, userId: string | null): Promise<Snapshot> {\n return await this.update(id, {\n status,\n reviewedBy: userId,\n reviewedAt: new Date().toISOString(),\n });\n }\n}\n\n/** Input for creating a snapshot. */\nexport interface SnapshotCreateInput {\n storyId: string;\n storyName: string;\n storyTitle: string;\n storyImportPath?: string;\n viewportName: string;\n viewportWidth: number;\n viewportHeight: number;\n screenshotPath: string;\n infraHash?: string | null;\n inherited?: boolean;\n baselineId?: string | null;\n baselineVersion?: string | null;\n}\n"],"mappings":";;;;;;;;AAcA,IAAa,gBAAb,MAA2B;CAON;CANnB;;;;;CAKA,YACE,IACA,QACA;EAFiB,KAAA,KAAA;EAGjB,KAAK,SAAS,UAAU,EAAE,WAAW,GAAG,OAAO,UAAU;CAC3D;;;;;;;;;CAUA,MAAM,OAAO,WAAmB,SAAiB,OAA+C;EAC9F,MAAM,uBAAM,IAAI,KAAK,EAAA,CAAE,YAAY;EACnC,OAAQ,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,WAAW;GAClD,IAAI,KAAK;GACT;GACA;GACA,SAAS,MAAM;GACf,WAAW,MAAM;GACjB,YAAY,MAAM;GAClB,iBAAiB,MAAM;GACvB,cAAc,MAAM;GACpB,eAAe,MAAM;GACrB,gBAAgB,MAAM;GACtB,gBAAgB,MAAM;GACtB,WAAW,MAAM,aAAa;GAC9B,WAAW,MAAM,aAAa;GAC9B,YAAY,MAAM,cAAc;GAChC,iBAAiB,MAAM,mBAAmB;GAC1C,QAAQ;GACR,WAAW;GACX,WAAW;EACb,CAAC;CACH;;CAGA,MAAM,YAAY,SAAsC;EACtD,OAAQ,MAAM,KAAK,GAAG,KAAK,KAAK,OAAO,WAAW,EAChD,OAAO,GACL,gBAAgB,KAAK,OAAO,SAAS,CAAC,CAAC,YACvC,OACF,EACF,CAAC;CACH;;CAGA,MAAM,IAAI,IAAsC;EAC9C,OAAQ,MAAM,KAAK,GAAG,IAAI,KAAK,OAAO,WAAW,EAAE;CACrD;;CAGA,MAAM,OAAO,IAAY,OAA6C;EACpE,OAAQ,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,WAAW,IAAI;GACtD,GAAG;GACH,4BAAW,IAAI,KAAK,EAAA,CAAE,YAAY;EACpC,CAAC;CACH;;CAGA,MAAM,UAAU,IAAY,QAA2C;EACrE,OAAO,MAAM,KAAK,OAAO,IAAI,EAAE,OAAO,CAAC;CACzC;;CAGA,MAAM,OAAO,IAAY,QAAwB,QAA0C;EACzF,OAAO,MAAM,KAAK,OAAO,IAAI;GAC3B;GACA,YAAY;GACZ,6BAAY,IAAI,KAAK,EAAA,CAAE,YAAY;EACrC,CAAC;CACH;AACF"}
1
+ {"version":3,"file":"snapshot.mjs","names":[],"sources":["../../src/models/snapshot.ts"],"sourcesContent":["/** Snapshot records for captured stories within a build. */\nimport { and, eq, inArray } from \"drizzle-orm\";\nimport type { Table } from \"drizzle-orm\";\nimport { DEFAULT_BROWSER } from \"../adapters/capture-runner.ts\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { Snapshot } from \"../schema/snapshot.ts\";\nimport type { SnapshotStatus } from \"../types.ts\";\nimport { ulid } from \"../utils/ulid.ts\";\nimport { col } from \"./columns.ts\";\n\n/** Tables required by {@link SnapshotModel}. */\nexport interface SnapshotTables {\n snapshots: Table;\n}\n\n/** Data operations for snapshot records. */\nexport class SnapshotModel {\n private readonly tables: SnapshotTables;\n private readonly db: DatabaseAdapter;\n\n /**\n * @param db - Database adapter.\n * @param tables - Table handles.\n */\n constructor(db: DatabaseAdapter, tables?: SnapshotTables) {\n this.db = db;\n this.tables = tables ?? { snapshots: db.tables.snapshots };\n }\n\n /**\n * Create a snapshot for a story within a build.\n *\n * @param projectId - Project ID.\n * @param buildId - Build ID.\n * @param input - Snapshot creation input.\n * @returns The created snapshot.\n */\n async create(projectId: string, buildId: string, input: SnapshotCreateInput): Promise<Snapshot> {\n const now = new Date().toISOString();\n return (await this.db.insert(this.tables.snapshots, {\n id: ulid(),\n projectId,\n buildId,\n storyId: input.storyId,\n storyName: input.storyName,\n storyTitle: input.storyTitle,\n storyImportPath: input.storyImportPath,\n designUrl: input.designUrl ?? null,\n viewportName: input.viewportName,\n viewportWidth: input.viewportWidth,\n viewportHeight: input.viewportHeight,\n screenshotPath: input.screenshotPath,\n infraHash: input.infraHash ?? null,\n envChange: input.envChange ?? null,\n inherited: input.inherited ?? false,\n baselineId: input.baselineId ?? null,\n baselineVersion: input.baselineVersion ?? null,\n mode: input.mode ?? null,\n browser: input.browser ?? DEFAULT_BROWSER,\n durationMs: input.durationMs ?? null,\n a11yViolations: input.a11yViolations ?? null,\n status: \"pending\",\n createdAt: now,\n updatedAt: now,\n })) as unknown as Snapshot;\n }\n\n /**\n * Delete the rendered snapshots a shard is about to write again: rows of `(story, engine)` pairs\n * in `work`, so a rerun starts from a clean slate instead of colliding with rows an earlier,\n * interrupted run left behind. Inherited rows are kept: the plan wrote them, for engines or\n * viewports this shard does not render.\n */\n async removeRendered(\n buildId: string,\n work: readonly { browser: string; storyIds: ReadonlySet<string> }[],\n ): Promise<number> {\n const doomed = (await this.listByBuild(buildId)).filter(\n (row) =>\n !row.inherited &&\n work.some(\n (engine) =>\n engine.browser === (row.browser ?? DEFAULT_BROWSER) && engine.storyIds.has(row.storyId),\n ),\n );\n await this.#removeAll(doomed);\n return doomed.length;\n }\n\n /** Delete every snapshot of a build: a new plan of it starts clean. */\n async removeForBuild(buildId: string): Promise<number> {\n const doomed = await this.listByBuild(buildId);\n await this.#removeAll(doomed);\n return doomed.length;\n }\n\n async #removeAll(rows: readonly Snapshot[]): Promise<void> {\n await Promise.all(\n rows.map(async (row) => {\n await this.db.remove(this.tables.snapshots, row.id);\n }),\n );\n }\n\n /**\n * Milliseconds each story cost on one build, summed over its viewports and engines. Stories the\n * renderer did not time are left out, so an empty map means this build carries no timings.\n */\n async durationsByStory(buildId: string): Promise<Map<string, number>> {\n const totals = new Map<string, number>();\n for (const row of await this.listByBuild(buildId)) {\n if (typeof row.durationMs === \"number\") {\n totals.set(row.storyId, (totals.get(row.storyId) ?? 0) + row.durationMs);\n }\n }\n return totals;\n }\n\n /** List all snapshots belonging to a build. */\n async listByBuild(buildId: string): Promise<Snapshot[]> {\n return (await this.db.list(this.tables.snapshots, {\n where: eq(col(this.tables.snapshots, \"buildId\"), buildId),\n })) as unknown as Snapshot[];\n }\n\n /**\n * Every snapshot of a story file across the project's builds. Index paths are\n * stored raw (`./src/A.stories.tsx`), so both spellings of `importPath` match.\n */\n async listByImportPath(projectId: string, importPath: string): Promise<Snapshot[]> {\n const bare = importPath.replace(/^(?:\\.\\/)+/u, \"\");\n return (await this.db.list(this.tables.snapshots, {\n where: and(\n eq(col(this.tables.snapshots, \"projectId\"), projectId),\n inArray(col(this.tables.snapshots, \"storyImportPath\"), [bare, `./${bare}`]),\n ),\n })) as unknown as Snapshot[];\n }\n\n /** Fetch a snapshot by id, or null if not found. */\n async get(id: string): Promise<Snapshot | null> {\n return (await this.db.get(this.tables.snapshots, id)) as unknown as Snapshot | null;\n }\n\n /** Update mutable fields of a snapshot. */\n async update(id: string, patch: Partial<Snapshot>): Promise<Snapshot> {\n return (await this.db.update(this.tables.snapshots, id, {\n ...patch,\n updatedAt: new Date().toISOString(),\n })) as unknown as Snapshot;\n }\n\n /** Set the status of a snapshot. */\n async setStatus(id: string, status: SnapshotStatus): Promise<Snapshot> {\n return await this.update(id, { status });\n }\n\n /** Record a reviewer's decision on a snapshot. */\n async review(id: string, status: SnapshotStatus, userId: string | null): Promise<Snapshot> {\n return await this.update(id, {\n status,\n reviewedBy: userId,\n reviewedAt: new Date().toISOString(),\n });\n }\n}\n\n/** Input for creating a snapshot. */\nexport interface SnapshotCreateInput {\n storyId: string;\n storyName: string;\n storyTitle: string;\n storyImportPath?: string;\n /** Figma URL declared by the story (`parameters.design` / `storyshelf.figma`). */\n designUrl?: string | null;\n viewportName: string;\n viewportWidth: number;\n viewportHeight: number;\n screenshotPath: string;\n infraHash?: string | null;\n /** JSON `EnvironmentChange` when accepted as an environment migration. */\n envChange?: string | null;\n inherited?: boolean;\n baselineId?: string | null;\n baselineVersion?: string | null;\n /** Resolved mode definition as JSON (display/capture only). */\n mode?: string | null;\n /** Engine the screenshot was captured with (default chromium). */\n browser?: string;\n /** Milliseconds the renderer spent on this capture, when it reported any. */\n durationMs?: number | null;\n /** JSON array of axe violation texts found on this capture; null when none. */\n a11yViolations?: string | null;\n}\n"],"mappings":";;;;;;;;;;AAgBA,IAAa,gBAAb,MAA2B;CACzB;CACA;;;;;CAMA,YAAY,IAAqB,QAAyB;EACxD,KAAK,KAAK;EACV,KAAK,SAAS,UAAU,EAAE,WAAW,GAAG,OAAO,UAAU;CAC3D;;;;;;;;;CAUA,MAAM,OAAO,WAAmB,SAAiB,OAA+C;EAC9F,MAAM,uBAAM,IAAI,KAAK,EAAA,CAAE,YAAY;EACnC,OAAQ,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,WAAW;GAClD,IAAI,KAAK;GACT;GACA;GACA,SAAS,MAAM;GACf,WAAW,MAAM;GACjB,YAAY,MAAM;GAClB,iBAAiB,MAAM;GACvB,WAAW,MAAM,aAAa;GAC9B,cAAc,MAAM;GACpB,eAAe,MAAM;GACrB,gBAAgB,MAAM;GACtB,gBAAgB,MAAM;GACtB,WAAW,MAAM,aAAa;GAC9B,WAAW,MAAM,aAAa;GAC9B,WAAW,MAAM,aAAa;GAC9B,YAAY,MAAM,cAAc;GAChC,iBAAiB,MAAM,mBAAmB;GAC1C,MAAM,MAAM,QAAQ;GACpB,SAAS,MAAM,WAAA;GACf,YAAY,MAAM,cAAc;GAChC,gBAAgB,MAAM,kBAAkB;GACxC,QAAQ;GACR,WAAW;GACX,WAAW;EACb,CAAC;CACH;;;;;;;CAQA,MAAM,eACJ,SACA,MACiB;EACjB,MAAM,UAAU,MAAM,KAAK,YAAY,OAAO,EAAA,CAAG,QAC9C,QACC,CAAC,IAAI,aACL,KAAK,MACF,WACC,OAAO,aAAa,IAAI,WAAA,eAA+B,OAAO,SAAS,IAAI,IAAI,OAAO,CAC1F,CACJ;EACA,MAAM,KAAK,WAAW,MAAM;EAC5B,OAAO,OAAO;CAChB;;CAGA,MAAM,eAAe,SAAkC;EACrD,MAAM,SAAS,MAAM,KAAK,YAAY,OAAO;EAC7C,MAAM,KAAK,WAAW,MAAM;EAC5B,OAAO,OAAO;CAChB;CAEA,MAAM,WAAW,MAA0C;EACzD,MAAM,QAAQ,IACZ,KAAK,IAAI,OAAO,QAAQ;GACtB,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,WAAW,IAAI,EAAE;EACpD,CAAC,CACH;CACF;;;;;CAMA,MAAM,iBAAiB,SAA+C;EACpE,MAAM,yBAAS,IAAI,IAAoB;EACvC,KAAK,MAAM,OAAO,MAAM,KAAK,YAAY,OAAO,GAC9C,IAAI,OAAO,IAAI,eAAe,UAC5B,OAAO,IAAI,IAAI,UAAU,OAAO,IAAI,IAAI,OAAO,KAAK,KAAK,IAAI,UAAU;EAG3E,OAAO;CACT;;CAGA,MAAM,YAAY,SAAsC;EACtD,OAAQ,MAAM,KAAK,GAAG,KAAK,KAAK,OAAO,WAAW,EAChD,OAAO,GAAG,IAAI,KAAK,OAAO,WAAW,SAAS,GAAG,OAAO,EAC1D,CAAC;CACH;;;;;CAMA,MAAM,iBAAiB,WAAmB,YAAyC;EACjF,MAAM,OAAO,WAAW,QAAQ,eAAe,EAAE;EACjD,OAAQ,MAAM,KAAK,GAAG,KAAK,KAAK,OAAO,WAAW,EAChD,OAAO,IACL,GAAG,IAAI,KAAK,OAAO,WAAW,WAAW,GAAG,SAAS,GACrD,QAAQ,IAAI,KAAK,OAAO,WAAW,iBAAiB,GAAG,CAAC,MAAM,KAAK,MAAM,CAAC,CAC5E,EACF,CAAC;CACH;;CAGA,MAAM,IAAI,IAAsC;EAC9C,OAAQ,MAAM,KAAK,GAAG,IAAI,KAAK,OAAO,WAAW,EAAE;CACrD;;CAGA,MAAM,OAAO,IAAY,OAA6C;EACpE,OAAQ,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,WAAW,IAAI;GACtD,GAAG;GACH,4BAAW,IAAI,KAAK,EAAA,CAAE,YAAY;EACpC,CAAC;CACH;;CAGA,MAAM,UAAU,IAAY,QAA2C;EACrE,OAAO,MAAM,KAAK,OAAO,IAAI,EAAE,OAAO,CAAC;CACzC;;CAGA,MAAM,OAAO,IAAY,QAAwB,QAA0C;EACzF,OAAO,MAAM,KAAK,OAAO,IAAI;GAC3B;GACA,YAAY;GACZ,6BAAY,IAAI,KAAK,EAAA,CAAE,YAAY;EACrC,CAAC;CACH;AACF"}
@@ -10,8 +10,8 @@ interface TokenTables {
10
10
  }
11
11
  /** Data operations for CI tokens. */
12
12
  declare class TokenModel {
13
- private readonly db;
14
13
  private readonly tables;
14
+ private readonly db;
15
15
  /**
16
16
  * @param db - Database adapter.
17
17
  * @param tables - Table handles.
@@ -30,6 +30,18 @@ declare class TokenModel {
30
30
  hash: string;
31
31
  userId?: string | null;
32
32
  }): Promise<Token>;
33
+ /**
34
+ * Mint a new `shelf_` token for a project and store only its hash.
35
+ *
36
+ * @returns The stored record and the plaintext value (shown to the user once).
37
+ */
38
+ issue(projectId: string, input: {
39
+ name: string;
40
+ userId?: string | null;
41
+ }): Promise<{
42
+ token: Token;
43
+ value: string;
44
+ }>;
33
45
  /** List all tokens for a project. */
34
46
  list(projectId: string): Promise<Token[]>;
35
47
  /** Fetch a token by id within a project, or null if not found. */
@@ -1,14 +1,16 @@
1
1
  import "node:path";
2
2
  import "node:url";
3
3
  import.meta.url;
4
+ import { randomToken } from "../utils/hash.mjs";
4
5
  import { ulid } from "../utils/ulid.mjs";
5
- import { and, eq, getTableColumns } from "drizzle-orm";
6
+ import { col } from "./columns.mjs";
7
+ import { and, eq } from "drizzle-orm";
6
8
  //#region src/models/token.ts
7
9
  /** CI tokens for project-scoped API access. */
8
10
  /** Data operations for CI tokens. */
9
11
  var TokenModel = class {
10
- db;
11
12
  tables;
13
+ db;
12
14
  /**
13
15
  * @param db - Database adapter.
14
16
  * @param tables - Table handles.
@@ -35,21 +37,37 @@ var TokenModel = class {
35
37
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
36
38
  });
37
39
  }
40
+ /**
41
+ * Mint a new `shelf_` token for a project and store only its hash.
42
+ *
43
+ * @returns The stored record and the plaintext value (shown to the user once).
44
+ */
45
+ async issue(projectId, input) {
46
+ const { value, hash } = randomToken("shelf_");
47
+ return {
48
+ token: await this.create(projectId, {
49
+ name: input.name,
50
+ hash,
51
+ userId: input.userId
52
+ }),
53
+ value
54
+ };
55
+ }
38
56
  /** List all tokens for a project. */
39
57
  async list(projectId) {
40
- return await this.db.list(this.tables.tokens, { where: eq(getTableColumns(this.tables.tokens)["projectId"], projectId) });
58
+ return await this.db.list(this.tables.tokens, { where: eq(col(this.tables.tokens, "projectId"), projectId) });
41
59
  }
42
60
  /** Fetch a token by id within a project, or null if not found. */
43
61
  async get(projectId, id) {
44
62
  return (await this.db.list(this.tables.tokens, {
45
- where: and(eq(getTableColumns(this.tables.tokens)["projectId"], projectId), eq(getTableColumns(this.tables.tokens)["id"], id)),
63
+ where: and(eq(col(this.tables.tokens, "projectId"), projectId), eq(col(this.tables.tokens, "id"), id)),
46
64
  limit: 1
47
65
  }))[0] ?? null;
48
66
  }
49
67
  /** Fetch a token by its hashed value, or null if not found. */
50
68
  async findByHash(hash) {
51
69
  return (await this.db.list(this.tables.tokens, {
52
- where: eq(getTableColumns(this.tables.tokens)["hash"], hash),
70
+ where: eq(col(this.tables.tokens, "hash"), hash),
53
71
  limit: 1
54
72
  }))[0] ?? null;
55
73
  }
@@ -1 +1 @@
1
- {"version":3,"file":"token.mjs","names":[],"sources":["../../src/models/token.ts"],"sourcesContent":["/** CI tokens for project-scoped API access. */\nimport { and, eq, getTableColumns } from \"drizzle-orm\";\nimport type { SQLWrapper, Table } from \"drizzle-orm\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { Token } from \"../schema/token.ts\";\nimport { ulid } from \"../utils/ulid.ts\";\n\n/** Tables required by {@link TokenModel}. */\nexport interface TokenTables {\n tokens: Table;\n}\n\n/** Data operations for CI tokens. */\nexport class TokenModel {\n private readonly tables: TokenTables;\n /**\n * @param db - Database adapter.\n * @param tables - Table handles.\n */\n constructor(\n private readonly db: DatabaseAdapter,\n tables?: TokenTables,\n ) {\n this.tables = tables ?? { tokens: db.tables.tokens };\n }\n\n /**\n * Create a token record storing its hash.\n *\n * @param projectId - Project ID.\n * @param input - Token name, hash, and optional owning user ID (null for\n * legacy/admin-token minting; such tokens resolve as viewer).\n * @returns The created token.\n */\n async create(\n projectId: string,\n input: { name: string; hash: string; userId?: string | null },\n ): Promise<Token> {\n return (await this.db.insert(this.tables.tokens, {\n id: ulid(),\n projectId,\n name: input.name,\n hash: input.hash,\n userId: input.userId ?? null,\n createdAt: new Date().toISOString(),\n })) as unknown as Token;\n }\n\n /** List all tokens for a project. */\n async list(projectId: string): Promise<Token[]> {\n return (await this.db.list(this.tables.tokens, {\n where: eq(\n getTableColumns(this.tables.tokens)[\"projectId\"] as unknown as SQLWrapper,\n projectId,\n ),\n })) as unknown as Token[];\n }\n\n /** Fetch a token by id within a project, or null if not found. */\n async get(projectId: string, id: string): Promise<Token | null> {\n const rows = (await this.db.list(this.tables.tokens, {\n where: and(\n eq(getTableColumns(this.tables.tokens)[\"projectId\"] as unknown as SQLWrapper, projectId),\n eq(getTableColumns(this.tables.tokens)[\"id\"] as unknown as SQLWrapper, id),\n ),\n limit: 1,\n })) as unknown as Token[];\n return rows[0] ?? null;\n }\n\n /** Fetch a token by its hashed value, or null if not found. */\n async findByHash(hash: string): Promise<Token | null> {\n const rows = (await this.db.list(this.tables.tokens, {\n where: eq(getTableColumns(this.tables.tokens)[\"hash\"] as unknown as SQLWrapper, hash),\n limit: 1,\n })) as unknown as Token[];\n return rows[0] ?? null;\n }\n\n /** Delete a token by id. */\n async remove(id: string): Promise<void> {\n await this.db.remove(this.tables.tokens, id);\n }\n}\n"],"mappings":";;;;;;;;AAaA,IAAa,aAAb,MAAwB;CAOH;CANnB;;;;;CAKA,YACE,IACA,QACA;EAFiB,KAAA,KAAA;EAGjB,KAAK,SAAS,UAAU,EAAE,QAAQ,GAAG,OAAO,OAAO;CACrD;;;;;;;;;CAUA,MAAM,OACJ,WACA,OACgB;EAChB,OAAQ,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,QAAQ;GAC/C,IAAI,KAAK;GACT;GACA,MAAM,MAAM;GACZ,MAAM,MAAM;GACZ,QAAQ,MAAM,UAAU;GACxB,4BAAW,IAAI,KAAK,EAAA,CAAE,YAAY;EACpC,CAAC;CACH;;CAGA,MAAM,KAAK,WAAqC;EAC9C,OAAQ,MAAM,KAAK,GAAG,KAAK,KAAK,OAAO,QAAQ,EAC7C,OAAO,GACL,gBAAgB,KAAK,OAAO,MAAM,CAAC,CAAC,cACpC,SACF,EACF,CAAC;CACH;;CAGA,MAAM,IAAI,WAAmB,IAAmC;EAQ9D,QAAO,MAPa,KAAK,GAAG,KAAK,KAAK,OAAO,QAAQ;GACnD,OAAO,IACL,GAAG,gBAAgB,KAAK,OAAO,MAAM,CAAC,CAAC,cAAuC,SAAS,GACvF,GAAG,gBAAgB,KAAK,OAAO,MAAM,CAAC,CAAC,OAAgC,EAAE,CAC3E;GACA,OAAO;EACT,CAAC,EAAA,CACW,MAAM;CACpB;;CAGA,MAAM,WAAW,MAAqC;EAKpD,QAAO,MAJa,KAAK,GAAG,KAAK,KAAK,OAAO,QAAQ;GACnD,OAAO,GAAG,gBAAgB,KAAK,OAAO,MAAM,CAAC,CAAC,SAAkC,IAAI;GACpF,OAAO;EACT,CAAC,EAAA,CACW,MAAM;CACpB;;CAGA,MAAM,OAAO,IAA2B;EACtC,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,QAAQ,EAAE;CAC7C;AACF"}
1
+ {"version":3,"file":"token.mjs","names":[],"sources":["../../src/models/token.ts"],"sourcesContent":["/** CI tokens for project-scoped API access. */\nimport { and, eq } from \"drizzle-orm\";\nimport type { Table } from \"drizzle-orm\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { Token } from \"../schema/token.ts\";\nimport { randomToken } from \"../utils/hash.ts\";\nimport { ulid } from \"../utils/ulid.ts\";\nimport { col } from \"./columns.ts\";\n\n/** Tables required by {@link TokenModel}. */\nexport interface TokenTables {\n tokens: Table;\n}\n\n/** Data operations for CI tokens. */\nexport class TokenModel {\n private readonly tables: TokenTables;\n private readonly db: DatabaseAdapter;\n\n /**\n * @param db - Database adapter.\n * @param tables - Table handles.\n */\n constructor(db: DatabaseAdapter, tables?: TokenTables) {\n this.db = db;\n this.tables = tables ?? { tokens: db.tables.tokens };\n }\n\n /**\n * Create a token record storing its hash.\n *\n * @param projectId - Project ID.\n * @param input - Token name, hash, and optional owning user ID (null for\n * legacy/admin-token minting; such tokens resolve as viewer).\n * @returns The created token.\n */\n async create(\n projectId: string,\n input: { name: string; hash: string; userId?: string | null },\n ): Promise<Token> {\n return (await this.db.insert(this.tables.tokens, {\n id: ulid(),\n projectId,\n name: input.name,\n hash: input.hash,\n userId: input.userId ?? null,\n createdAt: new Date().toISOString(),\n })) as unknown as Token;\n }\n\n /**\n * Mint a new `shelf_` token for a project and store only its hash.\n *\n * @returns The stored record and the plaintext value (shown to the user once).\n */\n async issue(\n projectId: string,\n input: { name: string; userId?: string | null },\n ): Promise<{ token: Token; value: string }> {\n const { value, hash } = randomToken(\"shelf_\");\n const token = await this.create(projectId, { name: input.name, hash, userId: input.userId });\n return { token, value };\n }\n\n /** List all tokens for a project. */\n async list(projectId: string): Promise<Token[]> {\n return (await this.db.list(this.tables.tokens, {\n where: eq(col(this.tables.tokens, \"projectId\"), projectId),\n })) as unknown as Token[];\n }\n\n /** Fetch a token by id within a project, or null if not found. */\n async get(projectId: string, id: string): Promise<Token | null> {\n const rows = (await this.db.list(this.tables.tokens, {\n where: and(\n eq(col(this.tables.tokens, \"projectId\"), projectId),\n eq(col(this.tables.tokens, \"id\"), id),\n ),\n limit: 1,\n })) as unknown as Token[];\n return rows[0] ?? null;\n }\n\n /** Fetch a token by its hashed value, or null if not found. */\n async findByHash(hash: string): Promise<Token | null> {\n const rows = (await this.db.list(this.tables.tokens, {\n where: eq(col(this.tables.tokens, \"hash\"), hash),\n limit: 1,\n })) as unknown as Token[];\n return rows[0] ?? null;\n }\n\n /** Delete a token by id. */\n async remove(id: string): Promise<void> {\n await this.db.remove(this.tables.tokens, id);\n }\n}\n"],"mappings":";;;;;;;;;;AAeA,IAAa,aAAb,MAAwB;CACtB;CACA;;;;;CAMA,YAAY,IAAqB,QAAsB;EACrD,KAAK,KAAK;EACV,KAAK,SAAS,UAAU,EAAE,QAAQ,GAAG,OAAO,OAAO;CACrD;;;;;;;;;CAUA,MAAM,OACJ,WACA,OACgB;EAChB,OAAQ,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,QAAQ;GAC/C,IAAI,KAAK;GACT;GACA,MAAM,MAAM;GACZ,MAAM,MAAM;GACZ,QAAQ,MAAM,UAAU;GACxB,4BAAW,IAAI,KAAK,EAAA,CAAE,YAAY;EACpC,CAAC;CACH;;;;;;CAOA,MAAM,MACJ,WACA,OAC0C;EAC1C,MAAM,EAAE,OAAO,SAAS,YAAY,QAAQ;EAE5C,OAAO;GAAE,OAAA,MADW,KAAK,OAAO,WAAW;IAAE,MAAM,MAAM;IAAM;IAAM,QAAQ,MAAM;GAAO,CAAC;GAC3E;EAAM;CACxB;;CAGA,MAAM,KAAK,WAAqC;EAC9C,OAAQ,MAAM,KAAK,GAAG,KAAK,KAAK,OAAO,QAAQ,EAC7C,OAAO,GAAG,IAAI,KAAK,OAAO,QAAQ,WAAW,GAAG,SAAS,EAC3D,CAAC;CACH;;CAGA,MAAM,IAAI,WAAmB,IAAmC;EAQ9D,QAAO,MAPa,KAAK,GAAG,KAAK,KAAK,OAAO,QAAQ;GACnD,OAAO,IACL,GAAG,IAAI,KAAK,OAAO,QAAQ,WAAW,GAAG,SAAS,GAClD,GAAG,IAAI,KAAK,OAAO,QAAQ,IAAI,GAAG,EAAE,CACtC;GACA,OAAO;EACT,CAAC,EAAA,CACW,MAAM;CACpB;;CAGA,MAAM,WAAW,MAAqC;EAKpD,QAAO,MAJa,KAAK,GAAG,KAAK,KAAK,OAAO,QAAQ;GACnD,OAAO,GAAG,IAAI,KAAK,OAAO,QAAQ,MAAM,GAAG,IAAI;GAC/C,OAAO;EACT,CAAC,EAAA,CACW,MAAM;CACpB;;CAGA,MAAM,OAAO,IAA2B;EACtC,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,QAAQ,EAAE;CAC7C;AACF"}
@@ -0,0 +1,76 @@
1
+ import __tsdown_shims_path from 'node:path';
2
+ import __tsdown_shims_url from 'node:url';
3
+ import { DatabaseAdapter } from "../adapter/database.mjs";
4
+ import { BuildPackageUsage, ProjectLink } from "../schema/usage.mjs";
5
+ import { Table } from "drizzle-orm";
6
+ //#region src/models/usage.d.ts
7
+ /** Tables required by {@link PackageUsageModel}. */
8
+ interface PackageUsageTables {
9
+ buildPackageUsage: Table;
10
+ projectLinks: Table;
11
+ }
12
+ /** A usage row as reported by the CLI at upload time. */
13
+ interface PackageUsageInput {
14
+ storyImportPath: string;
15
+ packageName: string;
16
+ modulePath: string;
17
+ version?: string | null;
18
+ }
19
+ /** A candidate upstream story: where it is defined and its Storybook id. */
20
+ interface UpstreamStory {
21
+ importPath: string;
22
+ storyId: string;
23
+ }
24
+ /**
25
+ * Pick the upstream story that documents the package module a story reaches.
26
+ * Exact stem match only (`dist/Button.js` ↔ `Button.stories.tsx`): resolves to the
27
+ * first story id of the single matching story file, or null when nothing matches
28
+ * or several different files share the stem (callers then fall back to the library).
29
+ */
30
+ declare function matchUpstreamStory(modulePath: string, stories: readonly UpstreamStory[]): string | null;
31
+ /** Data operations for build package usage and project links. */
32
+ declare class PackageUsageModel {
33
+ private readonly tables;
34
+ private readonly db;
35
+ /**
36
+ * @param db - Database adapter.
37
+ * @param tables - Table handles.
38
+ */
39
+ constructor(db: DatabaseAdapter, tables?: PackageUsageTables);
40
+ /**
41
+ * Replace the usage rows of a build, so re-reporting is idempotent.
42
+ * Runs in one transaction when the driver supports it and is serialized per
43
+ * build, so concurrent re-reports leave exactly one report's rows.
44
+ *
45
+ * @returns The number of rows stored.
46
+ */
47
+ replaceForBuild(projectId: string, buildId: string, rows: readonly PackageUsageInput[]): Promise<number>;
48
+ /**
49
+ * Explicitly remove all usage rows of a build (no reliance on FK cascade).
50
+ *
51
+ * @returns The number of rows removed.
52
+ */
53
+ removeForBuild(buildId: string): Promise<number>;
54
+ /** List all usage rows of a build. */
55
+ listForBuild(buildId: string): Promise<BuildPackageUsage[]>;
56
+ /** List the packages a downstream project links to upstream projects. */
57
+ listLinks(downstreamId: string): Promise<ProjectLink[]>;
58
+ /** List the usage rows of one story file within a build. */
59
+ listForStory(buildId: string, storyImportPath: string): Promise<BuildPackageUsage[]>;
60
+ /**
61
+ * Replace a downstream project's links with the given list, atomically when
62
+ * the driver supports transactions and serialized per downstream project.
63
+ *
64
+ * @returns The stored links.
65
+ */
66
+ replaceLinks(downstreamId: string, links: readonly {
67
+ upstreamId: string;
68
+ packageName: string;
69
+ }[]): Promise<ProjectLink[]>;
70
+ /** Run `work` in a transaction when the driver has one, else directly on the adapter. */
71
+ private atomic;
72
+ private buildWhere;
73
+ }
74
+ //#endregion
75
+ export { PackageUsageInput, PackageUsageModel, PackageUsageTables, UpstreamStory, matchUpstreamStory };
76
+ //# sourceMappingURL=usage.d.mts.map
@@ -0,0 +1,149 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { ulid } from "../utils/ulid.mjs";
5
+ import { col } from "./columns.mjs";
6
+ import { and, eq } from "drizzle-orm";
7
+ //#region src/models/usage.ts
8
+ /** Per-build package usage and cross-project package links. */
9
+ /** `Button.stories.tsx` / `Button.js` / `Button/index.js` → `Button`. */
10
+ function componentStem(path) {
11
+ const parts = path.split("/").filter((part) => part.length > 0);
12
+ const file = (parts.at(-1) ?? "").replace(/(?:\.stories)?\.[cm]?[jt]sx?$|\.vue$|\.svelte$/u, "");
13
+ return file === "index" ? parts.at(-2) ?? file : file;
14
+ }
15
+ /**
16
+ * Pick the upstream story that documents the package module a story reaches.
17
+ * Exact stem match only (`dist/Button.js` ↔ `Button.stories.tsx`): resolves to the
18
+ * first story id of the single matching story file, or null when nothing matches
19
+ * or several different files share the stem (callers then fall back to the library).
20
+ */
21
+ function matchUpstreamStory(modulePath, stories) {
22
+ const stem = componentStem(modulePath);
23
+ if (!stem) return null;
24
+ const matching = stories.filter((story) => componentStem(story.importPath) === stem);
25
+ if (new Set(matching.map((story) => story.importPath)).size !== 1) return null;
26
+ return matching.map((story) => story.storyId).toSorted()[0] ?? null;
27
+ }
28
+ /** Data operations for build package usage and project links. */
29
+ var PackageUsageModel = class {
30
+ tables;
31
+ db;
32
+ /**
33
+ * @param db - Database adapter.
34
+ * @param tables - Table handles.
35
+ */
36
+ constructor(db, tables) {
37
+ this.db = db;
38
+ this.tables = tables ?? {
39
+ buildPackageUsage: db.tables.buildPackageUsage,
40
+ projectLinks: db.tables.projectLinks
41
+ };
42
+ }
43
+ /**
44
+ * Replace the usage rows of a build, so re-reporting is idempotent.
45
+ * Runs in one transaction when the driver supports it and is serialized per
46
+ * build, so concurrent re-reports leave exactly one report's rows.
47
+ *
48
+ * @returns The number of rows stored.
49
+ */
50
+ async replaceForBuild(projectId, buildId, rows) {
51
+ const table = this.tables.buildPackageUsage;
52
+ const createdAt = (/* @__PURE__ */ new Date()).toISOString();
53
+ return await serialized(`usage:${buildId}`, async () => await this.atomic(async (store) => {
54
+ await removeAll(store, table, this.buildWhere(buildId));
55
+ await Promise.all(rows.map(async (row) => {
56
+ await store.insert(table, {
57
+ id: ulid(),
58
+ projectId,
59
+ buildId,
60
+ storyImportPath: row.storyImportPath,
61
+ packageName: row.packageName,
62
+ modulePath: row.modulePath,
63
+ version: row.version ?? null,
64
+ createdAt
65
+ });
66
+ }));
67
+ return rows.length;
68
+ }));
69
+ }
70
+ /**
71
+ * Explicitly remove all usage rows of a build (no reliance on FK cascade).
72
+ *
73
+ * @returns The number of rows removed.
74
+ */
75
+ async removeForBuild(buildId) {
76
+ return await removeAll(this.db, this.tables.buildPackageUsage, this.buildWhere(buildId));
77
+ }
78
+ /** List all usage rows of a build. */
79
+ async listForBuild(buildId) {
80
+ return await this.db.list(this.tables.buildPackageUsage, { where: this.buildWhere(buildId) });
81
+ }
82
+ /** List the packages a downstream project links to upstream projects. */
83
+ async listLinks(downstreamId) {
84
+ const table = this.tables.projectLinks;
85
+ return await this.db.list(table, { where: eq(col(table, "downstreamId"), downstreamId) });
86
+ }
87
+ /** List the usage rows of one story file within a build. */
88
+ async listForStory(buildId, storyImportPath) {
89
+ const table = this.tables.buildPackageUsage;
90
+ return await this.db.list(table, { where: and(eq(col(table, "buildId"), buildId), eq(col(table, "storyImportPath"), storyImportPath)) });
91
+ }
92
+ /**
93
+ * Replace a downstream project's links with the given list, atomically when
94
+ * the driver supports transactions and serialized per downstream project.
95
+ *
96
+ * @returns The stored links.
97
+ */
98
+ async replaceLinks(downstreamId, links) {
99
+ const table = this.tables.projectLinks;
100
+ const where = eq(col(table, "downstreamId"), downstreamId);
101
+ const createdAt = (/* @__PURE__ */ new Date()).toISOString();
102
+ return await serialized(`links:${downstreamId}`, async () => await this.atomic(async (store) => {
103
+ await removeAll(store, table, where);
104
+ return await Promise.all(links.map(async (link) => await store.insert(table, {
105
+ id: ulid(),
106
+ downstreamId,
107
+ upstreamId: link.upstreamId,
108
+ packageName: link.packageName,
109
+ createdAt
110
+ })));
111
+ }));
112
+ }
113
+ /** Run `work` in a transaction when the driver has one, else directly on the adapter. */
114
+ async atomic(work) {
115
+ return this.db.transact ? await this.db.transact(work) : await work(this.db);
116
+ }
117
+ buildWhere(buildId) {
118
+ return eq(col(this.tables.buildPackageUsage, "buildId"), buildId);
119
+ }
120
+ };
121
+ /** Delete every row of `table` matching `where`; returns the number removed. */
122
+ async function removeAll(store, table, where) {
123
+ const rows = await store.list(table, { where });
124
+ await Promise.all(rows.map(async (row) => {
125
+ await store.remove(table, row.id);
126
+ }));
127
+ return rows.length;
128
+ }
129
+ /** In-process tail of pending replaces, keyed by the rows they own. */
130
+ const pending = /* @__PURE__ */ new Map();
131
+ /**
132
+ * Chain `task` after any in-flight call with the same key so overlapping replaces
133
+ * of the same rows never interleave (covers drivers without `transact` and
134
+ * drivers whose transactions do not serialize callers sharing one connection).
135
+ */
136
+ async function serialized(key, task) {
137
+ const run = (pending.get(key) ?? Promise.resolve()).then(task, task);
138
+ const tail = run.catch(() => null);
139
+ pending.set(key, tail);
140
+ try {
141
+ return await run;
142
+ } finally {
143
+ if (pending.get(key) === tail) pending.delete(key);
144
+ }
145
+ }
146
+ //#endregion
147
+ export { PackageUsageModel, matchUpstreamStory };
148
+
149
+ //# sourceMappingURL=usage.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usage.mjs","names":[],"sources":["../../src/models/usage.ts"],"sourcesContent":["/** Per-build package usage and cross-project package links. */\nimport { and, eq } from \"drizzle-orm\";\nimport type { SQL, Table } from \"drizzle-orm\";\nimport type { DatabaseAdapter, TxStore } from \"../adapters/database.ts\";\nimport type { BuildPackageUsage, ProjectLink } from \"../schema/usage.ts\";\nimport { ulid } from \"../utils/ulid.ts\";\nimport { col } from \"./columns.ts\";\n\n/** Tables required by {@link PackageUsageModel}. */\nexport interface PackageUsageTables {\n buildPackageUsage: Table;\n projectLinks: Table;\n}\n\n/** A usage row as reported by the CLI at upload time. */\nexport interface PackageUsageInput {\n storyImportPath: string;\n packageName: string;\n modulePath: string;\n version?: string | null;\n}\n\n/** A candidate upstream story: where it is defined and its Storybook id. */\nexport interface UpstreamStory {\n importPath: string;\n storyId: string;\n}\n\n/** `Button.stories.tsx` / `Button.js` / `Button/index.js` → `Button`. */\nfunction componentStem(path: string): string {\n const parts = path.split(\"/\").filter((part) => part.length > 0);\n const last = parts.at(-1) ?? \"\";\n const file = last.replace(/(?:\\.stories)?\\.[cm]?[jt]sx?$|\\.vue$|\\.svelte$/u, \"\");\n return file === \"index\" ? (parts.at(-2) ?? file) : file;\n}\n\n/**\n * Pick the upstream story that documents the package module a story reaches.\n * Exact stem match only (`dist/Button.js` ↔ `Button.stories.tsx`): resolves to the\n * first story id of the single matching story file, or null when nothing matches\n * or several different files share the stem (callers then fall back to the library).\n */\nexport function matchUpstreamStory(\n modulePath: string,\n stories: readonly UpstreamStory[],\n): string | null {\n const stem = componentStem(modulePath);\n if (!stem) {\n return null;\n }\n const matching = stories.filter((story) => componentStem(story.importPath) === stem);\n if (new Set(matching.map((story) => story.importPath)).size !== 1) {\n return null;\n }\n return matching.map((story) => story.storyId).toSorted()[0] ?? null;\n}\n\n/** Data operations for build package usage and project links. */\nexport class PackageUsageModel {\n private readonly tables: PackageUsageTables;\n\n private readonly db: DatabaseAdapter;\n\n /**\n * @param db - Database adapter.\n * @param tables - Table handles.\n */\n constructor(db: DatabaseAdapter, tables?: PackageUsageTables) {\n this.db = db;\n this.tables = tables ?? {\n buildPackageUsage: db.tables.buildPackageUsage,\n projectLinks: db.tables.projectLinks,\n };\n }\n\n /**\n * Replace the usage rows of a build, so re-reporting is idempotent.\n * Runs in one transaction when the driver supports it and is serialized per\n * build, so concurrent re-reports leave exactly one report's rows.\n *\n * @returns The number of rows stored.\n */\n async replaceForBuild(\n projectId: string,\n buildId: string,\n rows: readonly PackageUsageInput[],\n ): Promise<number> {\n const table = this.tables.buildPackageUsage;\n const createdAt = new Date().toISOString();\n return await serialized(\n `usage:${buildId}`,\n async () =>\n await this.atomic(async (store) => {\n await removeAll(store, table, this.buildWhere(buildId));\n await Promise.all(\n rows.map(async (row) => {\n await store.insert(table, {\n id: ulid(),\n projectId,\n buildId,\n storyImportPath: row.storyImportPath,\n packageName: row.packageName,\n modulePath: row.modulePath,\n version: row.version ?? null,\n createdAt,\n });\n }),\n );\n return rows.length;\n }),\n );\n }\n\n /**\n * Explicitly remove all usage rows of a build (no reliance on FK cascade).\n *\n * @returns The number of rows removed.\n */\n async removeForBuild(buildId: string): Promise<number> {\n return await removeAll(this.db, this.tables.buildPackageUsage, this.buildWhere(buildId));\n }\n\n /** List all usage rows of a build. */\n async listForBuild(buildId: string): Promise<BuildPackageUsage[]> {\n return (await this.db.list(this.tables.buildPackageUsage, {\n where: this.buildWhere(buildId),\n })) as unknown as BuildPackageUsage[];\n }\n\n /** List the packages a downstream project links to upstream projects. */\n async listLinks(downstreamId: string): Promise<ProjectLink[]> {\n const table = this.tables.projectLinks;\n return (await this.db.list(table, {\n where: eq(col(table, \"downstreamId\"), downstreamId),\n })) as unknown as ProjectLink[];\n }\n\n /** List the usage rows of one story file within a build. */\n async listForStory(buildId: string, storyImportPath: string): Promise<BuildPackageUsage[]> {\n const table = this.tables.buildPackageUsage;\n return (await this.db.list(table, {\n where: and(\n eq(col(table, \"buildId\"), buildId),\n eq(col(table, \"storyImportPath\"), storyImportPath),\n ),\n })) as unknown as BuildPackageUsage[];\n }\n\n /**\n * Replace a downstream project's links with the given list, atomically when\n * the driver supports transactions and serialized per downstream project.\n *\n * @returns The stored links.\n */\n async replaceLinks(\n downstreamId: string,\n links: readonly { upstreamId: string; packageName: string }[],\n ): Promise<ProjectLink[]> {\n const table = this.tables.projectLinks;\n const where = eq(col(table, \"downstreamId\"), downstreamId);\n const createdAt = new Date().toISOString();\n return await serialized(\n `links:${downstreamId}`,\n async () =>\n await this.atomic(async (store) => {\n await removeAll(store, table, where);\n return await Promise.all(\n links.map(\n async (link) =>\n (await store.insert(table, {\n id: ulid(),\n downstreamId,\n upstreamId: link.upstreamId,\n packageName: link.packageName,\n createdAt,\n })) as unknown as ProjectLink,\n ),\n );\n }),\n );\n }\n\n /** Run `work` in a transaction when the driver has one, else directly on the adapter. */\n private async atomic<R>(work: (store: TxStore) => Promise<R>): Promise<R> {\n return this.db.transact ? await this.db.transact(work) : await work(this.db);\n }\n\n private buildWhere(buildId: string): SQL {\n return eq(col(this.tables.buildPackageUsage, \"buildId\"), buildId);\n }\n}\n\n/** Delete every row of `table` matching `where`; returns the number removed. */\nasync function removeAll(store: TxStore, table: Table, where: SQL): Promise<number> {\n const rows = (await store.list(table, { where })) as unknown as { id: string }[];\n await Promise.all(\n rows.map(async (row) => {\n await store.remove(table, row.id);\n }),\n );\n return rows.length;\n}\n\n/** In-process tail of pending replaces, keyed by the rows they own. */\nconst pending = new Map<string, Promise<unknown>>();\n\n/**\n * Chain `task` after any in-flight call with the same key so overlapping replaces\n * of the same rows never interleave (covers drivers without `transact` and\n * drivers whose transactions do not serialize callers sharing one connection).\n */\nasync function serialized<R>(key: string, task: () => Promise<R>): Promise<R> {\n const previous = pending.get(key) ?? Promise.resolve();\n const run = previous.then(task, task);\n const tail = run.catch(() => null);\n pending.set(key, tail);\n try {\n return await run;\n } finally {\n if (pending.get(key) === tail) {\n pending.delete(key);\n }\n }\n}\n"],"mappings":"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"}
@@ -10,8 +10,8 @@ interface UserTables {
10
10
  }
11
11
  /** Data operations for users. */
12
12
  declare class UserModel {
13
- private readonly db;
14
13
  private readonly tables;
14
+ private readonly db;
15
15
  /**
16
16
  * @param db - Database adapter.
17
17
  * @param tables - Table handles.
@@ -1,13 +1,14 @@
1
1
  import "node:path";
2
2
  import "node:url";
3
3
  import.meta.url;
4
- import { eq, getTableColumns } from "drizzle-orm";
4
+ import { col } from "./columns.mjs";
5
+ import { eq } from "drizzle-orm";
5
6
  //#region src/models/user.ts
6
7
  /** User lookups for token and membership resolution. */
7
8
  /** Data operations for users. */
8
9
  var UserModel = class {
9
- db;
10
10
  tables;
11
+ db;
11
12
  /**
12
13
  * @param db - Database adapter.
13
14
  * @param tables - Table handles.
@@ -19,7 +20,7 @@ var UserModel = class {
19
20
  /** Fetch a user by id, or null if not found (e.g. deleted after tokens were minted). */
20
21
  async get(id) {
21
22
  return (await this.db.list(this.tables.users, {
22
- where: eq(getTableColumns(this.tables.users)["id"], id),
23
+ where: eq(col(this.tables.users, "id"), id),
23
24
  limit: 1
24
25
  }))[0] ?? null;
25
26
  }
@@ -1 +1 @@
1
- {"version":3,"file":"user.mjs","names":[],"sources":["../../src/models/user.ts"],"sourcesContent":["/** User lookups for token and membership resolution. */\nimport { eq, getTableColumns } from \"drizzle-orm\";\nimport type { SQLWrapper, Table } from \"drizzle-orm\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { User } from \"../schema/user.ts\";\n\n/** Tables required by {@link UserModel}. */\nexport interface UserTables {\n users: Table;\n}\n\n/** Data operations for users. */\nexport class UserModel {\n private readonly tables: UserTables;\n /**\n * @param db - Database adapter.\n * @param tables - Table handles.\n */\n constructor(\n private readonly db: DatabaseAdapter,\n tables?: UserTables,\n ) {\n this.tables = tables ?? { users: db.tables.users };\n }\n\n /** Fetch a user by id, or null if not found (e.g. deleted after tokens were minted). */\n async get(id: string): Promise<User | null> {\n const rows = (await this.db.list(this.tables.users, {\n where: eq(getTableColumns(this.tables.users)[\"id\"] as unknown as SQLWrapper, id),\n limit: 1,\n })) as unknown as User[];\n return rows[0] ?? null;\n }\n\n /**\n * Insert or update a user row from an authenticated identity (login sync).\n * Email, name, avatar, and site role refresh on every login; the row id\n * (the provider `sub`) is stable. Preserves `displayNameOverride` so a\n * user-edited name survives IdP refresh; `passwordHash` and `disabled`\n * are never clobbered by SSO sync.\n */\n async upsert(input: {\n id: string;\n email: string;\n name: string;\n avatarUrl?: string | null;\n role: User[\"role\"];\n authProvider?: User[\"authProvider\"];\n }): Promise<User> {\n const now = new Date().toISOString();\n const existing = await this.get(input.id);\n if (!existing) {\n return (await this.db.insert(this.tables.users, {\n id: input.id,\n email: input.email,\n name: input.name,\n avatarUrl: input.avatarUrl ?? null,\n role: input.role,\n lastLoginAt: now,\n createdAt: now,\n passwordHash: null,\n displayNameOverride: null,\n authProvider: input.authProvider ?? \"oidc\",\n disabled: false,\n })) as unknown as User;\n }\n return (await this.db.update(this.tables.users, existing.id, {\n email: input.email,\n // Keep a user-edited display name: `name` stays the IdP value,\n // `displayNameOverride` is the source of truth for rendering.\n name: input.name,\n avatarUrl: input.avatarUrl ?? null,\n role: input.role,\n lastLoginAt: now,\n })) as unknown as User;\n }\n\n /** Update the display name override for a user (profile edit). */\n async setDisplayNameOverride(id: string, displayName: string | null): Promise<User> {\n return (await this.db.update(this.tables.users, id, {\n displayNameOverride: displayName?.trim() ? displayName.trim() : null,\n })) as unknown as User;\n }\n}\n"],"mappings":";;;;;;;AAYA,IAAa,YAAb,MAAuB;CAOF;CANnB;;;;;CAKA,YACE,IACA,QACA;EAFiB,KAAA,KAAA;EAGjB,KAAK,SAAS,UAAU,EAAE,OAAO,GAAG,OAAO,MAAM;CACnD;;CAGA,MAAM,IAAI,IAAkC;EAK1C,QAAO,MAJa,KAAK,GAAG,KAAK,KAAK,OAAO,OAAO;GAClD,OAAO,GAAG,gBAAgB,KAAK,OAAO,KAAK,CAAC,CAAC,OAAgC,EAAE;GAC/E,OAAO;EACT,CAAC,EAAA,CACW,MAAM;CACpB;;;;;;;;CASA,MAAM,OAAO,OAOK;EAChB,MAAM,uBAAM,IAAI,KAAK,EAAA,CAAE,YAAY;EACnC,MAAM,WAAW,MAAM,KAAK,IAAI,MAAM,EAAE;EACxC,IAAI,CAAC,UACH,OAAQ,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,OAAO;GAC9C,IAAI,MAAM;GACV,OAAO,MAAM;GACb,MAAM,MAAM;GACZ,WAAW,MAAM,aAAa;GAC9B,MAAM,MAAM;GACZ,aAAa;GACb,WAAW;GACX,cAAc;GACd,qBAAqB;GACrB,cAAc,MAAM,gBAAgB;GACpC,UAAU;EACZ,CAAC;EAEH,OAAQ,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,OAAO,SAAS,IAAI;GAC3D,OAAO,MAAM;GAGb,MAAM,MAAM;GACZ,WAAW,MAAM,aAAa;GAC9B,MAAM,MAAM;GACZ,aAAa;EACf,CAAC;CACH;;CAGA,MAAM,uBAAuB,IAAY,aAA2C;EAClF,OAAQ,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,OAAO,IAAI,EAClD,qBAAqB,aAAa,KAAK,IAAI,YAAY,KAAK,IAAI,KAClE,CAAC;CACH;AACF"}
1
+ {"version":3,"file":"user.mjs","names":[],"sources":["../../src/models/user.ts"],"sourcesContent":["/** User lookups for token and membership resolution. */\nimport { eq } from \"drizzle-orm\";\nimport type { Table } from \"drizzle-orm\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { User } from \"../schema/user.ts\";\nimport { col } from \"./columns.ts\";\n\n/** Tables required by {@link UserModel}. */\nexport interface UserTables {\n users: Table;\n}\n\n/** Data operations for users. */\nexport class UserModel {\n private readonly tables: UserTables;\n private readonly db: DatabaseAdapter;\n\n /**\n * @param db - Database adapter.\n * @param tables - Table handles.\n */\n constructor(db: DatabaseAdapter, tables?: UserTables) {\n this.db = db;\n this.tables = tables ?? { users: db.tables.users };\n }\n\n /** Fetch a user by id, or null if not found (e.g. deleted after tokens were minted). */\n async get(id: string): Promise<User | null> {\n const rows = (await this.db.list(this.tables.users, {\n where: eq(col(this.tables.users, \"id\"), id),\n limit: 1,\n })) as unknown as User[];\n return rows[0] ?? null;\n }\n\n /**\n * Insert or update a user row from an authenticated identity (login sync).\n * Email, name, avatar, and site role refresh on every login; the row id\n * (the provider `sub`) is stable. Preserves `displayNameOverride` so a\n * user-edited name survives IdP refresh; `passwordHash` and `disabled`\n * are never clobbered by SSO sync.\n */\n async upsert(input: {\n id: string;\n email: string;\n name: string;\n avatarUrl?: string | null;\n role: User[\"role\"];\n authProvider?: User[\"authProvider\"];\n }): Promise<User> {\n const now = new Date().toISOString();\n const existing = await this.get(input.id);\n if (!existing) {\n return (await this.db.insert(this.tables.users, {\n id: input.id,\n email: input.email,\n name: input.name,\n avatarUrl: input.avatarUrl ?? null,\n role: input.role,\n lastLoginAt: now,\n createdAt: now,\n passwordHash: null,\n displayNameOverride: null,\n authProvider: input.authProvider ?? \"oidc\",\n disabled: false,\n })) as unknown as User;\n }\n return (await this.db.update(this.tables.users, existing.id, {\n email: input.email,\n // Keep a user-edited display name: `name` stays the IdP value,\n // `displayNameOverride` is the source of truth for rendering.\n name: input.name,\n avatarUrl: input.avatarUrl ?? null,\n role: input.role,\n lastLoginAt: now,\n })) as unknown as User;\n }\n\n /** Update the display name override for a user (profile edit). */\n async setDisplayNameOverride(id: string, displayName: string | null): Promise<User> {\n return (await this.db.update(this.tables.users, id, {\n displayNameOverride: displayName?.trim() ? displayName.trim() : null,\n })) as unknown as User;\n }\n}\n"],"mappings":";;;;;;;;AAaA,IAAa,YAAb,MAAuB;CACrB;CACA;;;;;CAMA,YAAY,IAAqB,QAAqB;EACpD,KAAK,KAAK;EACV,KAAK,SAAS,UAAU,EAAE,OAAO,GAAG,OAAO,MAAM;CACnD;;CAGA,MAAM,IAAI,IAAkC;EAK1C,QAAO,MAJa,KAAK,GAAG,KAAK,KAAK,OAAO,OAAO;GAClD,OAAO,GAAG,IAAI,KAAK,OAAO,OAAO,IAAI,GAAG,EAAE;GAC1C,OAAO;EACT,CAAC,EAAA,CACW,MAAM;CACpB;;;;;;;;CASA,MAAM,OAAO,OAOK;EAChB,MAAM,uBAAM,IAAI,KAAK,EAAA,CAAE,YAAY;EACnC,MAAM,WAAW,MAAM,KAAK,IAAI,MAAM,EAAE;EACxC,IAAI,CAAC,UACH,OAAQ,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,OAAO;GAC9C,IAAI,MAAM;GACV,OAAO,MAAM;GACb,MAAM,MAAM;GACZ,WAAW,MAAM,aAAa;GAC9B,MAAM,MAAM;GACZ,aAAa;GACb,WAAW;GACX,cAAc;GACd,qBAAqB;GACrB,cAAc,MAAM,gBAAgB;GACpC,UAAU;EACZ,CAAC;EAEH,OAAQ,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,OAAO,SAAS,IAAI;GAC3D,OAAO,MAAM;GAGb,MAAM,MAAM;GACZ,WAAW,MAAM,aAAa;GAC9B,MAAM,MAAM;GACZ,aAAa;EACf,CAAC;CACH;;CAGA,MAAM,uBAAuB,IAAY,aAA2C;EAClF,OAAQ,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,OAAO,IAAI,EAClD,qBAAqB,aAAa,KAAK,IAAI,YAAY,KAAK,IAAI,KAClE,CAAC;CACH;AACF"}
@@ -11,9 +11,9 @@ interface WebhookTables {
11
11
  }
12
12
  /** Data operations for webhook subscriptions. */
13
13
  declare class WebhookModel {
14
- private readonly db;
15
14
  private readonly tables;
16
15
  private readonly secret?;
16
+ private readonly db;
17
17
  /**
18
18
  * @param db - Database adapter.
19
19
  * @param tables - Table handles.
@@ -22,6 +22,20 @@ declare class WebhookModel {
22
22
  constructor(db: DatabaseAdapter, tables?: WebhookTables, secret?: SecretInput);
23
23
  /** List all webhooks for a project. */
24
24
  list(projectId: string): Promise<Webhook[]>;
25
+ /** Validate a webhook target URL; returns an error message, or null when acceptable. */
26
+ static urlError(url: string | undefined): string | null;
27
+ /**
28
+ * Create a webhook with a freshly generated `whsec_` signing secret.
29
+ *
30
+ * @returns The stored webhook and the plaintext secret (shown to the user once).
31
+ */
32
+ issue(projectId: string, input: {
33
+ url: string;
34
+ events?: string[];
35
+ }): Promise<{
36
+ webhook: Webhook;
37
+ secret: string;
38
+ }>;
25
39
  /**
26
40
  * Create a webhook subscription for a project.
27
41
  *
@@ -1,16 +1,18 @@
1
1
  import "node:path";
2
2
  import "node:url";
3
3
  import.meta.url;
4
+ import { randomToken } from "../utils/hash.mjs";
4
5
  import { decryptCredential, encrypt } from "../utils/encrypt.mjs";
5
6
  import { ulid } from "../utils/ulid.mjs";
6
- import { eq, getTableColumns } from "drizzle-orm";
7
+ import { col } from "./columns.mjs";
8
+ import { eq } from "drizzle-orm";
7
9
  //#region src/models/webhook.ts
8
10
  /** Webhook subscriptions for project events. */
9
11
  /** Data operations for webhook subscriptions. */
10
12
  var WebhookModel = class {
11
- db;
12
13
  tables;
13
14
  secret;
15
+ db;
14
16
  /**
15
17
  * @param db - Database adapter.
16
18
  * @param tables - Table handles.
@@ -23,7 +25,34 @@ var WebhookModel = class {
23
25
  }
24
26
  /** List all webhooks for a project. */
25
27
  async list(projectId) {
26
- return await this.db.list(this.tables.webhooks, { where: eq(getTableColumns(this.tables.webhooks)["projectId"], projectId) });
28
+ return await this.db.list(this.tables.webhooks, { where: eq(col(this.tables.webhooks, "projectId"), projectId) });
29
+ }
30
+ /** Validate a webhook target URL; returns an error message, or null when acceptable. */
31
+ static urlError(url) {
32
+ if (!url) return "A valid URL is required";
33
+ let protocol;
34
+ try {
35
+ ({protocol} = new URL(url));
36
+ } catch {
37
+ return "Enter a valid URL";
38
+ }
39
+ return protocol === "https:" || protocol === "http:" ? null : "URL must use http or https";
40
+ }
41
+ /**
42
+ * Create a webhook with a freshly generated `whsec_` signing secret.
43
+ *
44
+ * @returns The stored webhook and the plaintext secret (shown to the user once).
45
+ */
46
+ async issue(projectId, input) {
47
+ const secret = randomToken("whsec_").value;
48
+ return {
49
+ webhook: await this.create(projectId, {
50
+ url: input.url,
51
+ events: input.events,
52
+ secret
53
+ }),
54
+ secret
55
+ };
27
56
  }
28
57
  /**
29
58
  * Create a webhook subscription for a project.
@@ -51,7 +80,7 @@ var WebhookModel = class {
51
80
  /** Fetch a webhook by id scoped to a project, or null if not found. */
52
81
  async get(projectId, id) {
53
82
  const found = (await this.db.list(this.tables.webhooks, {
54
- where: eq(getTableColumns(this.tables.webhooks)["id"], id),
83
+ where: eq(col(this.tables.webhooks, "id"), id),
55
84
  limit: 1
56
85
  }))[0] ?? null;
57
86
  return found?.projectId === projectId ? found : null;