@storyshelf/core 0.6.2 → 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 (628) 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/capture/a11y/axe-source.d.mts +18 -0
  57. package/dist/capture/a11y/axe-source.mjs +37 -0
  58. package/dist/capture/a11y/axe-source.mjs.map +1 -0
  59. package/dist/capture/a11y/in-page.d.mts +27 -0
  60. package/dist/capture/a11y/in-page.mjs +60 -0
  61. package/dist/capture/a11y/in-page.mjs.map +1 -0
  62. package/dist/capture/a11y/map.d.mts +24 -0
  63. package/dist/capture/a11y/map.mjs +48 -0
  64. package/dist/capture/a11y/map.mjs.map +1 -0
  65. package/dist/capture/a11y/options.d.mts +23 -0
  66. package/dist/capture/a11y/options.mjs +75 -0
  67. package/dist/capture/a11y/options.mjs.map +1 -0
  68. package/dist/capture/adapter.d.mts +41 -3
  69. package/dist/capture/adapter.mjs.map +1 -1
  70. package/dist/capture/affected.d.mts +2 -0
  71. package/dist/capture/affected.mjs +13 -4
  72. package/dist/capture/affected.mjs.map +1 -1
  73. package/dist/capture/attempt-logger.mjs +84 -0
  74. package/dist/capture/attempt-logger.mjs.map +1 -0
  75. package/dist/capture/browsers.d.mts +35 -0
  76. package/dist/capture/browsers.mjs +67 -0
  77. package/dist/capture/browsers.mjs.map +1 -0
  78. package/dist/capture/build-state/deliver.d.mts +32 -0
  79. package/dist/capture/build-state/deliver.mjs +118 -0
  80. package/dist/capture/build-state/deliver.mjs.map +1 -0
  81. package/dist/capture/build-state/index.d.mts +8 -0
  82. package/dist/capture/build-state/notify-job.d.mts +56 -0
  83. package/dist/capture/build-state/notify-job.mjs +133 -0
  84. package/dist/capture/build-state/notify-job.mjs.map +1 -0
  85. package/dist/capture/build-state/publisher.d.mts +23 -0
  86. package/dist/capture/build-state/publisher.mjs +107 -0
  87. package/dist/capture/build-state/publisher.mjs.map +1 -0
  88. package/dist/capture/build-state/resume.d.mts +9 -0
  89. package/dist/capture/build-state/resume.mjs +47 -0
  90. package/dist/capture/build-state/resume.mjs.map +1 -0
  91. package/dist/capture/build-state/types.d.mts +49 -0
  92. package/dist/capture/catalog/catalogue.d.mts +22 -0
  93. package/dist/capture/catalog/catalogue.mjs +53 -0
  94. package/dist/capture/catalog/catalogue.mjs.map +1 -0
  95. package/dist/capture/catalog/components.d.mts +23 -0
  96. package/dist/capture/catalog/components.mjs +55 -0
  97. package/dist/capture/catalog/components.mjs.map +1 -0
  98. package/dist/capture/catalog/history.d.mts +30 -0
  99. package/dist/capture/catalog/history.mjs +51 -0
  100. package/dist/capture/catalog/history.mjs.map +1 -0
  101. package/dist/capture/catalog/hook.d.mts +14 -0
  102. package/dist/capture/catalog/hook.mjs +19 -0
  103. package/dist/capture/catalog/hook.mjs.map +1 -0
  104. package/dist/capture/catalog/index.d.mts +11 -0
  105. package/dist/capture/catalog/load.d.mts +26 -0
  106. package/dist/capture/catalog/load.mjs +49 -0
  107. package/dist/capture/catalog/load.mjs.map +1 -0
  108. package/dist/capture/catalog/parse.d.mts +15 -0
  109. package/dist/capture/catalog/parse.mjs +56 -0
  110. package/dist/capture/catalog/parse.mjs.map +1 -0
  111. package/dist/capture/catalog/read.d.mts +12 -0
  112. package/dist/capture/catalog/read.mjs +33 -0
  113. package/dist/capture/catalog/read.mjs.map +1 -0
  114. package/dist/capture/catalog/types.d.mts +33 -0
  115. package/dist/capture/consumer/capture-handler.d.mts +58 -0
  116. package/dist/capture/consumer/capture-handler.mjs +118 -0
  117. package/dist/capture/consumer/capture-handler.mjs.map +1 -0
  118. package/dist/capture/consumer/idle.d.mts +17 -0
  119. package/dist/capture/consumer/idle.mjs +25 -0
  120. package/dist/capture/consumer/idle.mjs.map +1 -0
  121. package/dist/capture/consumer/index.d.mts +13 -0
  122. package/dist/capture/consumer/index.mjs +139 -0
  123. package/dist/capture/consumer/index.mjs.map +1 -0
  124. package/dist/capture/consumer/loop.mjs +110 -0
  125. package/dist/capture/consumer/loop.mjs.map +1 -0
  126. package/dist/capture/consumer/metrics.d.mts +41 -0
  127. package/dist/capture/consumer/metrics.mjs +38 -0
  128. package/dist/capture/consumer/metrics.mjs.map +1 -0
  129. package/dist/capture/consumer/retry.d.mts +33 -0
  130. package/dist/capture/consumer/retry.mjs +34 -0
  131. package/dist/capture/consumer/retry.mjs.map +1 -0
  132. package/dist/capture/consumer/run-job.mjs +195 -0
  133. package/dist/capture/consumer/run-job.mjs.map +1 -0
  134. package/dist/capture/consumer/startup.mjs +38 -0
  135. package/dist/capture/consumer/startup.mjs.map +1 -0
  136. package/dist/capture/consumer/types.d.mts +97 -0
  137. package/dist/capture/crash-failure.d.mts +25 -0
  138. package/dist/capture/crash-failure.mjs +70 -0
  139. package/dist/capture/crash-failure.mjs.map +1 -0
  140. package/dist/capture/db-queue/cadence.d.mts +32 -0
  141. package/dist/capture/db-queue/cadence.mjs +38 -0
  142. package/dist/capture/db-queue/cadence.mjs.map +1 -0
  143. package/dist/capture/db-queue/claim.d.mts +9 -0
  144. package/dist/capture/db-queue/claim.mjs +132 -0
  145. package/dist/capture/db-queue/claim.mjs.map +1 -0
  146. package/dist/capture/db-queue/clock.mjs +12 -0
  147. package/dist/capture/db-queue/clock.mjs.map +1 -0
  148. package/dist/capture/db-queue/codec.mjs +76 -0
  149. package/dist/capture/db-queue/codec.mjs.map +1 -0
  150. package/dist/capture/db-queue/index.d.mts +20 -0
  151. package/dist/capture/db-queue/index.mjs +89 -0
  152. package/dist/capture/db-queue/index.mjs.map +1 -0
  153. package/dist/capture/db-queue/keeper.d.mts +36 -0
  154. package/dist/capture/db-queue/keeper.mjs +92 -0
  155. package/dist/capture/db-queue/keeper.mjs.map +1 -0
  156. package/dist/capture/db-queue/lease.mjs +46 -0
  157. package/dist/capture/db-queue/lease.mjs.map +1 -0
  158. package/dist/capture/db-queue/lifecycle.mjs +36 -0
  159. package/dist/capture/db-queue/lifecycle.mjs.map +1 -0
  160. package/dist/capture/db-queue/operations.mjs +94 -0
  161. package/dist/capture/db-queue/operations.mjs.map +1 -0
  162. package/dist/capture/db-queue/poll-loop.d.mts +30 -0
  163. package/dist/capture/db-queue/poll-loop.mjs +89 -0
  164. package/dist/capture/db-queue/poll-loop.mjs.map +1 -0
  165. package/dist/capture/db-queue/poll.d.mts +10 -0
  166. package/dist/capture/db-queue/poll.mjs +69 -0
  167. package/dist/capture/db-queue/poll.mjs.map +1 -0
  168. package/dist/capture/db-queue/timers.d.mts +12 -0
  169. package/dist/capture/db-queue/timers.mjs +37 -0
  170. package/dist/capture/db-queue/timers.mjs.map +1 -0
  171. package/dist/capture/db-queue/types.d.mts +83 -0
  172. package/dist/capture/db-queue/types.mjs +12 -0
  173. package/dist/capture/db-queue/types.mjs.map +1 -0
  174. package/dist/capture/db-queue/wake.mjs +48 -0
  175. package/dist/capture/db-queue/wake.mjs.map +1 -0
  176. package/dist/capture/design.mjs +39 -0
  177. package/dist/capture/design.mjs.map +1 -0
  178. package/dist/capture/diff-record.mjs +23 -3
  179. package/dist/capture/diff-record.mjs.map +1 -1
  180. package/dist/capture/dispatch.d.mts +12 -7
  181. package/dist/capture/dispatch.mjs +86 -107
  182. package/dist/capture/dispatch.mjs.map +1 -1
  183. package/dist/capture/enrich.mjs +60 -0
  184. package/dist/capture/enrich.mjs.map +1 -0
  185. package/dist/capture/failure-sets.mjs +22 -0
  186. package/dist/capture/failure-sets.mjs.map +1 -0
  187. package/dist/capture/fingerprint.d.mts +42 -0
  188. package/dist/capture/fingerprint.mjs +83 -0
  189. package/dist/capture/fingerprint.mjs.map +1 -0
  190. package/dist/capture/forked/channel.d.mts +25 -0
  191. package/dist/capture/forked/child.d.mts +34 -0
  192. package/dist/capture/forked/child.mjs +202 -0
  193. package/dist/capture/forked/child.mjs.map +1 -0
  194. package/dist/capture/forked/crash-tracker.mjs +23 -0
  195. package/dist/capture/forked/crash-tracker.mjs.map +1 -0
  196. package/dist/capture/forked/decision.d.mts +26 -0
  197. package/dist/capture/forked/decision.mjs +27 -0
  198. package/dist/capture/forked/decision.mjs.map +1 -0
  199. package/dist/capture/forked/exit.mjs +12 -0
  200. package/dist/capture/forked/exit.mjs.map +1 -0
  201. package/dist/capture/forked/fork.d.mts +31 -0
  202. package/dist/capture/forked/fork.mjs +93 -0
  203. package/dist/capture/forked/fork.mjs.map +1 -0
  204. package/dist/capture/forked/forked-capture.d.mts +40 -0
  205. package/dist/capture/forked/forked-capture.mjs +240 -0
  206. package/dist/capture/forked/forked-capture.mjs.map +1 -0
  207. package/dist/capture/forked/lease-book.mjs +46 -0
  208. package/dist/capture/forked/lease-book.mjs.map +1 -0
  209. package/dist/capture/forked/protocol.d.mts +38 -0
  210. package/dist/capture/forked/protocol.mjs +25 -0
  211. package/dist/capture/forked/protocol.mjs.map +1 -0
  212. package/dist/capture/forked/runs.mjs +75 -0
  213. package/dist/capture/forked/runs.mjs.map +1 -0
  214. package/dist/capture/isolate.mjs +141 -0
  215. package/dist/capture/isolate.mjs.map +1 -0
  216. package/dist/capture/job-lease.d.mts +28 -0
  217. package/dist/capture/job-lease.mjs +57 -0
  218. package/dist/capture/job-lease.mjs.map +1 -0
  219. package/dist/capture/matrix.mjs +298 -0
  220. package/dist/capture/matrix.mjs.map +1 -0
  221. package/dist/capture/modes.d.mts +25 -0
  222. package/dist/capture/modes.mjs +107 -0
  223. package/dist/capture/modes.mjs.map +1 -0
  224. package/dist/capture/orchestrator.d.mts +37 -5
  225. package/dist/capture/orchestrator.mjs +118 -107
  226. package/dist/capture/orchestrator.mjs.map +1 -1
  227. package/dist/capture/pipeline.d.mts +9 -2
  228. package/dist/capture/pipeline.mjs +250 -45
  229. package/dist/capture/pipeline.mjs.map +1 -1
  230. package/dist/capture/progress.d.mts +11 -0
  231. package/dist/capture/progress.mjs +28 -0
  232. package/dist/capture/progress.mjs.map +1 -0
  233. package/dist/capture/publish/accept.d.mts +39 -0
  234. package/dist/capture/publish/accept.mjs +28 -0
  235. package/dist/capture/publish/accept.mjs.map +1 -0
  236. package/dist/capture/publish/complete.d.mts +46 -0
  237. package/dist/capture/publish/complete.mjs +64 -0
  238. package/dist/capture/publish/complete.mjs.map +1 -0
  239. package/dist/capture/publish/extract.d.mts +20 -0
  240. package/dist/capture/publish/extract.mjs +119 -0
  241. package/dist/capture/publish/extract.mjs.map +1 -0
  242. package/dist/capture/publish/handler.d.mts +28 -0
  243. package/dist/capture/publish/handler.mjs +62 -0
  244. package/dist/capture/publish/handler.mjs.map +1 -0
  245. package/dist/capture/publish/index.d.mts +10 -0
  246. package/dist/capture/publish/limits.d.mts +37 -0
  247. package/dist/capture/publish/limits.mjs +39 -0
  248. package/dist/capture/publish/limits.mjs.map +1 -0
  249. package/dist/capture/publish/published.d.mts +15 -0
  250. package/dist/capture/publish/published.mjs +18 -0
  251. package/dist/capture/publish/published.mjs.map +1 -0
  252. package/dist/capture/publish/run.d.mts +33 -0
  253. package/dist/capture/publish/run.mjs +30 -0
  254. package/dist/capture/publish/run.mjs.map +1 -0
  255. package/dist/capture/rediff.d.mts +1 -1
  256. package/dist/capture/rediff.mjs +4 -3
  257. package/dist/capture/rediff.mjs.map +1 -1
  258. package/dist/capture/render.mjs +53 -0
  259. package/dist/capture/render.mjs.map +1 -0
  260. package/dist/capture/scratch.d.mts +44 -0
  261. package/dist/capture/scratch.mjs +138 -0
  262. package/dist/capture/scratch.mjs.map +1 -0
  263. package/dist/capture/shard-plan.mjs +128 -0
  264. package/dist/capture/shard-plan.mjs.map +1 -0
  265. package/dist/capture/shards/context.mjs +51 -0
  266. package/dist/capture/shards/context.mjs.map +1 -0
  267. package/dist/capture/shards/execute.mjs +174 -0
  268. package/dist/capture/shards/execute.mjs.map +1 -0
  269. package/dist/capture/shards/fan-out.mjs +126 -0
  270. package/dist/capture/shards/fan-out.mjs.map +1 -0
  271. package/dist/capture/shards/history.mjs +35 -0
  272. package/dist/capture/shards/history.mjs.map +1 -0
  273. package/dist/capture/shards/reduce.mjs +97 -0
  274. package/dist/capture/shards/reduce.mjs.map +1 -0
  275. package/dist/capture/sizing.mjs +1 -11
  276. package/dist/capture/sizing.mjs.map +1 -1
  277. package/dist/capture/skip-checks.mjs +2 -65
  278. package/dist/capture/skip-checks.mjs.map +1 -1
  279. package/dist/capture/statics.d.mts +7 -8
  280. package/dist/capture/statics.mjs +25 -71
  281. package/dist/capture/statics.mjs.map +1 -1
  282. package/dist/capture/storybook-urls.d.mts +40 -0
  283. package/dist/capture/storybook-urls.mjs +41 -0
  284. package/dist/capture/storybook-urls.mjs.map +1 -0
  285. package/dist/capture/storybook.d.mts +12 -4
  286. package/dist/capture/storybook.mjs +56 -16
  287. package/dist/capture/storybook.mjs.map +1 -1
  288. package/dist/capture/target.mjs +21 -0
  289. package/dist/capture/target.mjs.map +1 -0
  290. package/dist/capture/targets.d.mts +25 -0
  291. package/dist/capture/targets.mjs +32 -0
  292. package/dist/capture/targets.mjs.map +1 -0
  293. package/dist/capture/tests.d.mts +37 -0
  294. package/dist/capture/tests.mjs +126 -0
  295. package/dist/capture/tests.mjs.map +1 -0
  296. package/dist/capture/token-chunks.mjs +74 -0
  297. package/dist/capture/token-chunks.mjs.map +1 -0
  298. package/dist/capture.d.mts +60 -7
  299. package/dist/capture.mjs +48 -3
  300. package/dist/config.d.mts +121 -12
  301. package/dist/config.mjs +9 -6
  302. package/dist/config.mjs.map +1 -1
  303. package/dist/diff.d.mts +9 -3
  304. package/dist/diff.mjs +16 -3
  305. package/dist/diff.mjs.map +1 -0
  306. package/dist/index.d.mts +1 -1
  307. package/dist/insights/budget.d.mts +20 -0
  308. package/dist/insights/budget.mjs +33 -0
  309. package/dist/insights/budget.mjs.map +1 -0
  310. package/dist/insights/envelope.d.mts +59 -0
  311. package/dist/insights/envelope.mjs +60 -0
  312. package/dist/insights/envelope.mjs.map +1 -0
  313. package/dist/insights/evidence-health.d.mts +32 -0
  314. package/dist/insights/evidence-health.mjs +28 -0
  315. package/dist/insights/evidence-health.mjs.map +1 -0
  316. package/dist/insights/evidence.d.mts +55 -0
  317. package/dist/insights/evidence.mjs +64 -0
  318. package/dist/insights/evidence.mjs.map +1 -0
  319. package/dist/insights/hash.d.mts +18 -0
  320. package/dist/insights/hash.mjs +21 -0
  321. package/dist/insights/hash.mjs.map +1 -0
  322. package/dist/insights/limits.d.mts +24 -0
  323. package/dist/insights/limits.mjs +24 -0
  324. package/dist/insights/limits.mjs.map +1 -0
  325. package/dist/insights/prompts/v1.d.mts +13 -0
  326. package/dist/insights/prompts/v1.mjs +34 -0
  327. package/dist/insights/prompts/v1.mjs.map +1 -0
  328. package/dist/insights/redact.d.mts +9 -0
  329. package/dist/insights/redact.mjs +24 -0
  330. package/dist/insights/redact.mjs.map +1 -0
  331. package/dist/insights/text.mjs +44 -0
  332. package/dist/insights/text.mjs.map +1 -0
  333. package/dist/insights.d.mts +11 -0
  334. package/dist/insights.mjs +12 -0
  335. package/dist/logger.mjs +17 -1
  336. package/dist/logger.mjs.map +1 -1
  337. package/dist/models/ai-budget-alert.d.mts +23 -0
  338. package/dist/models/ai-budget-alert.mjs +38 -0
  339. package/dist/models/ai-budget-alert.mjs.map +1 -0
  340. package/dist/models/ai-usage.d.mts +40 -0
  341. package/dist/models/ai-usage.mjs +61 -0
  342. package/dist/models/ai-usage.mjs.map +1 -0
  343. package/dist/models/baseline-archive.d.mts +84 -0
  344. package/dist/models/baseline-archive.mjs +106 -0
  345. package/dist/models/baseline-archive.mjs.map +1 -0
  346. package/dist/models/baseline-export.d.mts +19 -0
  347. package/dist/models/baseline-export.mjs +136 -0
  348. package/dist/models/baseline-export.mjs.map +1 -0
  349. package/dist/models/baseline-import.mjs +118 -0
  350. package/dist/models/baseline-import.mjs.map +1 -0
  351. package/dist/models/baseline-transfer.d.mts +49 -0
  352. package/dist/models/baseline-transfer.mjs +121 -0
  353. package/dist/models/baseline-transfer.mjs.map +1 -0
  354. package/dist/models/baseline.d.mts +32 -5
  355. package/dist/models/baseline.mjs +65 -20
  356. package/dist/models/baseline.mjs.map +1 -1
  357. package/dist/models/build-component.d.mts +28 -0
  358. package/dist/models/build-component.mjs +55 -0
  359. package/dist/models/build-component.mjs.map +1 -0
  360. package/dist/models/build-review-status.d.mts +29 -0
  361. package/dist/models/build-review-status.mjs +37 -0
  362. package/dist/models/build-review-status.mjs.map +1 -0
  363. package/dist/models/build-review.d.mts +28 -0
  364. package/dist/models/build-review.mjs +54 -0
  365. package/dist/models/build-review.mjs.map +1 -0
  366. package/dist/models/build.d.mts +25 -3
  367. package/dist/models/build.mjs +50 -11
  368. package/dist/models/build.mjs.map +1 -1
  369. package/dist/models/capture-attempt.d.mts +7 -1
  370. package/dist/models/capture-attempt.mjs +29 -4
  371. package/dist/models/capture-attempt.mjs.map +1 -1
  372. package/dist/models/capture-job.d.mts +106 -0
  373. package/dist/models/capture-job.mjs +288 -0
  374. package/dist/models/capture-job.mjs.map +1 -0
  375. package/dist/models/capture-log.d.mts +1 -1
  376. package/dist/models/capture-log.mjs +4 -3
  377. package/dist/models/capture-log.mjs.map +1 -1
  378. package/dist/models/capture-shard.d.mts +62 -0
  379. package/dist/models/capture-shard.mjs +190 -0
  380. package/dist/models/capture-shard.mjs.map +1 -0
  381. package/dist/models/changeset.d.mts +48 -0
  382. package/dist/models/changeset.mjs +81 -0
  383. package/dist/models/changeset.mjs.map +1 -0
  384. package/dist/models/columns.mjs +19 -0
  385. package/dist/models/columns.mjs.map +1 -0
  386. package/dist/models/comment.d.mts +4 -1
  387. package/dist/models/comment.mjs +7 -4
  388. package/dist/models/comment.mjs.map +1 -1
  389. package/dist/models/component.d.mts +43 -0
  390. package/dist/models/component.mjs +60 -0
  391. package/dist/models/component.mjs.map +1 -0
  392. package/dist/models/connections.d.mts +41 -0
  393. package/dist/models/connections.mjs +112 -0
  394. package/dist/models/connections.mjs.map +1 -0
  395. package/dist/models/content-ref.d.mts +1 -1
  396. package/dist/models/content-ref.mjs +1 -1
  397. package/dist/models/content-ref.mjs.map +1 -1
  398. package/dist/models/credentials.mjs +12 -2
  399. package/dist/models/credentials.mjs.map +1 -1
  400. package/dist/models/design-credentials.d.mts +41 -0
  401. package/dist/models/design-credentials.mjs +86 -0
  402. package/dist/models/design-credentials.mjs.map +1 -0
  403. package/dist/models/design-health.d.mts +16 -0
  404. package/dist/models/design-health.mjs +25 -0
  405. package/dist/models/design-health.mjs.map +1 -0
  406. package/dist/models/design-nodes.d.mts +83 -0
  407. package/dist/models/design-nodes.mjs +222 -0
  408. package/dist/models/design-nodes.mjs.map +1 -0
  409. package/dist/models/design-renders.mjs +74 -0
  410. package/dist/models/design-renders.mjs.map +1 -0
  411. package/dist/models/design-resolve.d.mts +29 -0
  412. package/dist/models/design-resolve.mjs +41 -0
  413. package/dist/models/design-resolve.mjs.map +1 -0
  414. package/dist/models/design.d.mts +39 -0
  415. package/dist/models/design.mjs +90 -0
  416. package/dist/models/design.mjs.map +1 -0
  417. package/dist/models/inherited-ratio.d.mts +21 -0
  418. package/dist/models/inherited-ratio.mjs +22 -0
  419. package/dist/models/inherited-ratio.mjs.map +1 -0
  420. package/dist/models/insight.d.mts +70 -0
  421. package/dist/models/insight.mjs +162 -0
  422. package/dist/models/insight.mjs.map +1 -0
  423. package/dist/models/label.d.mts +4 -2
  424. package/dist/models/label.mjs +25 -10
  425. package/dist/models/label.mjs.map +1 -1
  426. package/dist/models/member.d.mts +1 -1
  427. package/dist/models/member.mjs +5 -4
  428. package/dist/models/member.mjs.map +1 -1
  429. package/dist/models/notification-channel.d.mts +37 -3
  430. package/dist/models/notification-channel.mjs +88 -9
  431. package/dist/models/notification-channel.mjs.map +1 -1
  432. package/dist/models/notification-subscription.d.mts +1 -2
  433. package/dist/models/notification-subscription.mjs +7 -9
  434. package/dist/models/notification-subscription.mjs.map +1 -1
  435. package/dist/models/project-group-mapping.d.mts +6 -1
  436. package/dist/models/project-group-mapping.mjs +14 -4
  437. package/dist/models/project-group-mapping.mjs.map +1 -1
  438. package/dist/models/project.d.mts +9 -1
  439. package/dist/models/project.mjs +9 -4
  440. package/dist/models/project.mjs.map +1 -1
  441. package/dist/models/public-origin.d.mts +63 -0
  442. package/dist/models/public-origin.mjs +114 -0
  443. package/dist/models/public-origin.mjs.map +1 -0
  444. package/dist/models/review-switch-off.d.mts +14 -0
  445. package/dist/models/review-switch-off.mjs +26 -0
  446. package/dist/models/review-switch-off.mjs.map +1 -0
  447. package/dist/models/site-settings.d.mts +23 -0
  448. package/dist/models/site-settings.mjs +48 -0
  449. package/dist/models/site-settings.mjs.map +1 -0
  450. package/dist/models/snapshot.d.mts +36 -1
  451. package/dist/models/snapshot.mjs +50 -3
  452. package/dist/models/snapshot.mjs.map +1 -1
  453. package/dist/models/token.d.mts +13 -1
  454. package/dist/models/token.mjs +23 -5
  455. package/dist/models/token.mjs.map +1 -1
  456. package/dist/models/usage.d.mts +76 -0
  457. package/dist/models/usage.mjs +149 -0
  458. package/dist/models/usage.mjs.map +1 -0
  459. package/dist/models/user.d.mts +1 -1
  460. package/dist/models/user.mjs +4 -3
  461. package/dist/models/user.mjs.map +1 -1
  462. package/dist/models/webhook.d.mts +15 -1
  463. package/dist/models/webhook.mjs +33 -4
  464. package/dist/models/webhook.mjs.map +1 -1
  465. package/dist/models.d.mts +26 -3
  466. package/dist/models.mjs +27 -5
  467. package/dist/options.d.mts +18 -0
  468. package/dist/options.mjs +90 -0
  469. package/dist/options.mjs.map +1 -0
  470. package/dist/paths.d.mts +19 -4
  471. package/dist/paths.mjs +51 -10
  472. package/dist/paths.mjs.map +1 -1
  473. package/dist/retention/ai.d.mts +18 -0
  474. package/dist/retention/ai.mjs +23 -0
  475. package/dist/retention/ai.mjs.map +1 -0
  476. package/dist/retention/pending.mjs +47 -0
  477. package/dist/retention/pending.mjs.map +1 -0
  478. package/dist/retention/screenshot-sweep.d.mts +51 -0
  479. package/dist/retention/screenshot-sweep.mjs +128 -0
  480. package/dist/retention/screenshot-sweep.mjs.map +1 -0
  481. package/dist/retention.d.mts +16 -2
  482. package/dist/retention.mjs +71 -14
  483. package/dist/retention.mjs.map +1 -1
  484. package/dist/schema/baseline.d.mts +6 -0
  485. package/dist/schema/build-component.d.mts +25 -0
  486. package/dist/schema/build-review.d.mts +17 -0
  487. package/dist/schema/build.d.mts +7 -0
  488. package/dist/schema/capture-job.d.mts +60 -0
  489. package/dist/schema/capture-shard.d.mts +35 -0
  490. package/dist/schema/comment.d.mts +2 -0
  491. package/dist/schema/connection.d.mts +24 -0
  492. package/dist/schema/design.d.mts +58 -0
  493. package/dist/schema/insight.d.mts +54 -0
  494. package/dist/schema/notification.d.mts +4 -0
  495. package/dist/schema/project.d.mts +21 -0
  496. package/dist/schema/site-setting.d.mts +21 -0
  497. package/dist/schema/snapshot.d.mts +12 -0
  498. package/dist/schema/usage.d.mts +30 -0
  499. package/dist/schema.d.mts +11 -3
  500. package/dist/serverless.d.mts +21 -0
  501. package/dist/serverless.mjs +37 -0
  502. package/dist/serverless.mjs.map +1 -0
  503. package/dist/test-helpers/create-project.d.mts +1 -1
  504. package/dist/test-helpers/deferred.d.mts +39 -0
  505. package/dist/test-helpers/deferred.mjs +61 -0
  506. package/dist/test-helpers/deferred.mjs.map +1 -0
  507. package/dist/test-helpers/design-contract.d.mts +21 -0
  508. package/dist/test-helpers/design-contract.mjs +40 -0
  509. package/dist/test-helpers/design-contract.mjs.map +1 -0
  510. package/dist/test-helpers/fake-database.mjs +14 -2
  511. package/dist/test-helpers/fake-database.mjs.map +1 -1
  512. package/dist/test-helpers/fake-design.d.mts +43 -0
  513. package/dist/test-helpers/fake-design.mjs +114 -0
  514. package/dist/test-helpers/fake-design.mjs.map +1 -0
  515. package/dist/test-helpers/fake-notifier.d.mts +21 -0
  516. package/dist/test-helpers/fake-notifier.mjs +44 -0
  517. package/dist/test-helpers/fake-notifier.mjs.map +1 -0
  518. package/dist/test-helpers/fake-tables.mjs +255 -67
  519. package/dist/test-helpers/fake-tables.mjs.map +1 -1
  520. package/dist/test-helpers/manual-clock.d.mts +27 -0
  521. package/dist/test-helpers/manual-clock.mjs +56 -0
  522. package/dist/test-helpers/manual-clock.mjs.map +1 -0
  523. package/dist/test-helpers/notifier-contract.d.mts +7 -1
  524. package/dist/test-helpers/notifier-contract.mjs +32 -3
  525. package/dist/test-helpers/notifier-contract.mjs.map +1 -1
  526. package/dist/test-helpers/queue-contract.d.mts +16 -2
  527. package/dist/test-helpers/queue-contract.mjs +195 -1
  528. package/dist/test-helpers/queue-contract.mjs.map +1 -1
  529. package/dist/test-helpers/queue-multi-instance.d.mts +20 -0
  530. package/dist/test-helpers/queue-multi-instance.mjs +206 -0
  531. package/dist/test-helpers/queue-multi-instance.mjs.map +1 -0
  532. package/dist/test-helpers/runner-contract.mjs +11 -0
  533. package/dist/test-helpers/runner-contract.mjs.map +1 -1
  534. package/dist/test-helpers/sql-chunks.mjs +23 -0
  535. package/dist/test-helpers/sql-chunks.mjs.map +1 -1
  536. package/dist/test-helpers/storage-contract.mjs +1 -1
  537. package/dist/test-helpers.d.mts +9 -4
  538. package/dist/test-helpers.mjs +8 -3
  539. package/dist/types.d.mts +14 -3
  540. package/dist/types.mjs +21 -4
  541. package/dist/types.mjs.map +1 -1
  542. package/dist/urls.d.mts +4 -0
  543. package/dist/urls.mjs +11 -2
  544. package/dist/urls.mjs.map +1 -1
  545. package/dist/utils/adaptive-concurrency.d.mts +66 -0
  546. package/dist/utils/adaptive-concurrency.mjs +149 -0
  547. package/dist/utils/adaptive-concurrency.mjs.map +1 -0
  548. package/dist/utils/connect-retry.d.mts +26 -0
  549. package/dist/utils/connect-retry.mjs +43 -0
  550. package/dist/utils/connect-retry.mjs.map +1 -0
  551. package/dist/utils/environment.d.mts +19 -0
  552. package/dist/utils/environment.mjs +59 -0
  553. package/dist/utils/environment.mjs.map +1 -0
  554. package/dist/utils/http.d.mts +17 -1
  555. package/dist/utils/http.mjs +22 -3
  556. package/dist/utils/http.mjs.map +1 -1
  557. package/dist/utils/map-adaptive.d.mts +15 -0
  558. package/dist/utils/map-adaptive.mjs +72 -0
  559. package/dist/utils/map-adaptive.mjs.map +1 -0
  560. package/dist/utils/map-limit.d.mts +10 -0
  561. package/dist/utils/map-limit.mjs +32 -0
  562. package/dist/utils/map-limit.mjs.map +1 -0
  563. package/dist/utils/page-limiter.d.mts +37 -0
  564. package/dist/utils/page-limiter.mjs +56 -0
  565. package/dist/utils/page-limiter.mjs.map +1 -0
  566. package/dist/utils/public-origin.d.mts +20 -0
  567. package/dist/utils/public-origin.mjs +75 -0
  568. package/dist/utils/public-origin.mjs.map +1 -0
  569. package/dist/utils/redact.mjs +3 -0
  570. package/dist/utils/redact.mjs.map +1 -1
  571. package/dist/utils/remote-environment.d.mts +39 -0
  572. package/dist/utils/remote-environment.mjs +114 -0
  573. package/dist/utils/remote-environment.mjs.map +1 -0
  574. package/dist/utils/resources.d.mts +64 -0
  575. package/dist/utils/resources.mjs +122 -0
  576. package/dist/utils/resources.mjs.map +1 -0
  577. package/dist/utils/screenshot-store.d.mts +15 -0
  578. package/dist/utils/screenshot-store.mjs +41 -0
  579. package/dist/utils/screenshot-store.mjs.map +1 -0
  580. package/dist/utils/session-gate.d.mts +22 -0
  581. package/dist/utils/session-gate.mjs +53 -0
  582. package/dist/utils/session-gate.mjs.map +1 -0
  583. package/dist/utils/storybook-token.d.mts +81 -0
  584. package/dist/utils/storybook-token.mjs +134 -0
  585. package/dist/utils/storybook-token.mjs.map +1 -0
  586. package/dist/utils/storybook-url.d.mts +15 -0
  587. package/dist/utils/storybook-url.mjs +21 -0
  588. package/dist/utils/storybook-url.mjs.map +1 -0
  589. package/dist/utils/timing.mjs +1 -1
  590. package/dist/utils/timing.mjs.map +1 -1
  591. package/dist/utils/zip-read.mjs +129 -0
  592. package/dist/utils/zip-read.mjs.map +1 -0
  593. package/dist/utils/zip-write.d.mts +18 -0
  594. package/dist/utils/zip-write.mjs +188 -0
  595. package/dist/utils/zip-write.mjs.map +1 -0
  596. package/dist/utils.d.mts +17 -3
  597. package/dist/utils.mjs +18 -4
  598. package/package.json +32 -23
  599. package/dist/adapter/capture-runner.mjs.map +0 -1
  600. package/dist/adapter/git-host/comments.d.mts +0 -41
  601. package/dist/adapter/git-host/comments.mjs +0 -54
  602. package/dist/adapter/git-host/comments.mjs.map +0 -1
  603. package/dist/adapter/git-host/helpers.d.mts +0 -13
  604. package/dist/adapter/git-host/helpers.mjs +0 -30
  605. package/dist/adapter/git-host/helpers.mjs.map +0 -1
  606. package/dist/adapter/git-host.d.mts +0 -42
  607. package/dist/capture/queue.d.mts +0 -38
  608. package/dist/capture/queue.mjs +0 -98
  609. package/dist/capture/queue.mjs.map +0 -1
  610. package/dist/capture/recovery.d.mts +0 -21
  611. package/dist/capture/recovery.mjs +0 -83
  612. package/dist/capture/recovery.mjs.map +0 -1
  613. package/dist/capture/status-fanout.mjs +0 -72
  614. package/dist/capture/status-fanout.mjs.map +0 -1
  615. package/dist/diff/engine.d.mts +0 -16
  616. package/dist/diff/engine.mjs +0 -87
  617. package/dist/diff/engine.mjs.map +0 -1
  618. package/dist/diff/options.d.mts +0 -42
  619. package/dist/diff/options.mjs +0 -15
  620. package/dist/diff/options.mjs.map +0 -1
  621. package/dist/models/status-config.d.mts +0 -38
  622. package/dist/models/status-config.mjs +0 -63
  623. package/dist/models/status-config.mjs.map +0 -1
  624. package/dist/schema/status-config.d.mts +0 -16
  625. package/dist/test-helpers/git-contract.d.mts +0 -11
  626. package/dist/test-helpers/git-contract.mjs +0 -34
  627. package/dist/test-helpers/git-contract.mjs.map +0 -1
  628. /package/dist/adapter/{git-host.mjs → oauth.mjs} +0 -0
@@ -0,0 +1,48 @@
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 { Build } from "../schema/build.mjs";
5
+ import { BuildComponent } from "../schema/build-component.mjs";
6
+ //#region src/models/changeset.d.ts
7
+ /** What the changeset shows of a component. */
8
+ type ChangesetComponent = Pick<BuildComponent, "importPath" | "title" | "storyCount">;
9
+ /** A component present in both builds whose hash differs. */
10
+ interface ChangedComponent extends ChangesetComponent {
11
+ /** Stories in the baseline build's version. */
12
+ previousStoryCount: number;
13
+ }
14
+ /** Components added, removed and changed from a baseline build to a build. */
15
+ interface Changeset {
16
+ added: ChangesetComponent[];
17
+ removed: ChangesetComponent[];
18
+ changed: ChangedComponent[];
19
+ /** Components with the same hash in both builds. */
20
+ unchanged: number;
21
+ }
22
+ /** A build's changeset and what it was measured against. */
23
+ interface BuildChangeset {
24
+ /** The baseline build; null when there is none (every component then counts as added). */
25
+ baseline: Build | null;
26
+ changeset: Changeset;
27
+ }
28
+ /**
29
+ * Compare a build's components with the baseline's. Components match by story file, or by title
30
+ * when the index carries no file. With no baseline (`null`) every component is `added`.
31
+ */
32
+ declare function computeChangeset(current: readonly BuildComponent[], baseline: readonly BuildComponent[] | null): Changeset;
33
+ /**
34
+ * The build a build is compared with: the latest accepted build of its branch before it, else the
35
+ * latest accepted default-branch build (the rule baselines use). Null for the first build.
36
+ */
37
+ declare function findChangesetBaseline(db: DatabaseAdapter, project: {
38
+ id: string;
39
+ gitDefaultBranch: string;
40
+ }, build: Build): Promise<Build | null>;
41
+ /** The changeset of `build` against its baseline; empty rows on both sides when it has no catalog. */
42
+ declare function changesetOf(db: DatabaseAdapter, project: {
43
+ id: string;
44
+ gitDefaultBranch: string;
45
+ }, build: Build): Promise<BuildChangeset>;
46
+ //#endregion
47
+ export { BuildChangeset, ChangedComponent, Changeset, ChangesetComponent, changesetOf, computeChangeset, findChangesetBaseline };
48
+ //# sourceMappingURL=changeset.d.mts.map
@@ -0,0 +1,81 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { BuildModel } from "./build.mjs";
5
+ import { BuildComponentModel } from "./build-component.mjs";
6
+ //#region src/models/changeset.ts
7
+ /**
8
+ * Compare a build's components with the baseline's. Components match by story file, or by title
9
+ * when the index carries no file. With no baseline (`null`) every component is `added`.
10
+ */
11
+ function computeChangeset(current, baseline) {
12
+ const was = new Map((baseline ?? []).map((row) => [keyOf(row), row]));
13
+ const now = new Map(current.map((row) => [keyOf(row), row]));
14
+ const changeset = {
15
+ added: [],
16
+ removed: [],
17
+ changed: [],
18
+ unchanged: 0
19
+ };
20
+ for (const [key, row] of now) place(changeset, row, was.get(key));
21
+ for (const [key, row] of was) if (!now.has(key)) changeset.removed.push(summary(row));
22
+ return {
23
+ ...changeset,
24
+ added: changeset.added.toSorted(byTitle),
25
+ removed: changeset.removed.toSorted(byTitle),
26
+ changed: changeset.changed.toSorted(byTitle)
27
+ };
28
+ }
29
+ /** File `row` under added, changed or unchanged, given the baseline's version of it. */
30
+ function place(changeset, row, before) {
31
+ if (before === void 0) changeset.added.push(summary(row));
32
+ else if (before.hash === row.hash) changeset.unchanged += 1;
33
+ else changeset.changed.push({
34
+ ...summary(row),
35
+ previousStoryCount: before.storyCount
36
+ });
37
+ }
38
+ /**
39
+ * The build a build is compared with: the latest accepted build of its branch before it, else the
40
+ * latest accepted default-branch build (the rule baselines use). Null for the first build.
41
+ */
42
+ async function findChangesetBaseline(db, project, build) {
43
+ const builds = new BuildModel(db);
44
+ const branches = [.../* @__PURE__ */ new Set([build.gitBranch, project.gitDefaultBranch])];
45
+ const lists = await Promise.all(branches.map(async (branch) => await builds.list(project.id, {
46
+ branch,
47
+ status: "approved"
48
+ })));
49
+ for (const accepted of lists) {
50
+ const baseline = accepted.find((candidate) => candidate.id !== build.id && candidate.createdAt < build.createdAt);
51
+ if (baseline) return baseline;
52
+ }
53
+ return null;
54
+ }
55
+ /** The changeset of `build` against its baseline; empty rows on both sides when it has no catalog. */
56
+ async function changesetOf(db, project, build) {
57
+ const components = new BuildComponentModel(db);
58
+ const baseline = await findChangesetBaseline(db, project, build);
59
+ const [current, before] = await Promise.all([components.listByBuild(build.id), baseline ? components.listByBuild(baseline.id) : Promise.resolve(null)]);
60
+ return {
61
+ baseline,
62
+ changeset: computeChangeset(current, before)
63
+ };
64
+ }
65
+ function keyOf(row) {
66
+ return row.importPath === "" ? `title:${row.title}` : row.importPath;
67
+ }
68
+ function summary(row) {
69
+ return {
70
+ importPath: row.importPath,
71
+ title: row.title,
72
+ storyCount: row.storyCount
73
+ };
74
+ }
75
+ function byTitle(a, b) {
76
+ return a.title.localeCompare(b.title) || a.importPath.localeCompare(b.importPath);
77
+ }
78
+ //#endregion
79
+ export { changesetOf, computeChangeset, findChangesetBaseline };
80
+
81
+ //# sourceMappingURL=changeset.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"changeset.mjs","names":[],"sources":["../../src/models/changeset.ts"],"sourcesContent":["/**\n * The changeset of a build without screenshots (ADR 0013, decision 8): which components the build\n * added, removed or changed against its baseline build, from the `build_components` hashes. A\n * changed hash means the component's stories changed, not that anything looks different.\n */\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { BuildComponent } from \"../schema/build-component.ts\";\nimport type { Build } from \"../schema/build.ts\";\nimport { BuildComponentModel } from \"./build-component.ts\";\nimport { BuildModel } from \"./build.ts\";\n\n/** What the changeset shows of a component. */\nexport type ChangesetComponent = Pick<BuildComponent, \"importPath\" | \"title\" | \"storyCount\">;\n\n/** A component present in both builds whose hash differs. */\nexport interface ChangedComponent extends ChangesetComponent {\n /** Stories in the baseline build's version. */\n previousStoryCount: number;\n}\n\n/** Components added, removed and changed from a baseline build to a build. */\nexport interface Changeset {\n added: ChangesetComponent[];\n removed: ChangesetComponent[];\n changed: ChangedComponent[];\n /** Components with the same hash in both builds. */\n unchanged: number;\n}\n\n/** A build's changeset and what it was measured against. */\nexport interface BuildChangeset {\n /** The baseline build; null when there is none (every component then counts as added). */\n baseline: Build | null;\n changeset: Changeset;\n}\n\n/**\n * Compare a build's components with the baseline's. Components match by story file, or by title\n * when the index carries no file. With no baseline (`null`) every component is `added`.\n */\nexport function computeChangeset(\n current: readonly BuildComponent[],\n baseline: readonly BuildComponent[] | null,\n): Changeset {\n const was = new Map((baseline ?? []).map((row) => [keyOf(row), row] as const));\n const now = new Map(current.map((row) => [keyOf(row), row] as const));\n const changeset: Changeset = { added: [], removed: [], changed: [], unchanged: 0 };\n for (const [key, row] of now) {\n place(changeset, row, was.get(key));\n }\n for (const [key, row] of was) {\n if (!now.has(key)) {\n changeset.removed.push(summary(row));\n }\n }\n return {\n ...changeset,\n added: changeset.added.toSorted(byTitle),\n removed: changeset.removed.toSorted(byTitle),\n changed: changeset.changed.toSorted(byTitle),\n };\n}\n\n/** File `row` under added, changed or unchanged, given the baseline's version of it. */\nfunction place(\n changeset: Changeset,\n row: BuildComponent,\n before: BuildComponent | undefined,\n): void {\n if (before === undefined) {\n changeset.added.push(summary(row));\n } else if (before.hash === row.hash) {\n changeset.unchanged += 1;\n } else {\n changeset.changed.push({ ...summary(row), previousStoryCount: before.storyCount });\n }\n}\n\n/**\n * The build a build is compared with: the latest accepted build of its branch before it, else the\n * latest accepted default-branch build (the rule baselines use). Null for the first build.\n */\nexport async function findChangesetBaseline(\n db: DatabaseAdapter,\n project: { id: string; gitDefaultBranch: string },\n build: Build,\n): Promise<Build | null> {\n const builds = new BuildModel(db);\n const branches = [...new Set([build.gitBranch, project.gitDefaultBranch])];\n const lists = await Promise.all(\n branches.map(async (branch) => await builds.list(project.id, { branch, status: \"approved\" })),\n );\n for (const accepted of lists) {\n const baseline = accepted.find(\n (candidate) => candidate.id !== build.id && candidate.createdAt < build.createdAt,\n );\n if (baseline) {\n return baseline;\n }\n }\n return null;\n}\n\n/** The changeset of `build` against its baseline; empty rows on both sides when it has no catalog. */\nexport async function changesetOf(\n db: DatabaseAdapter,\n project: { id: string; gitDefaultBranch: string },\n build: Build,\n): Promise<BuildChangeset> {\n const components = new BuildComponentModel(db);\n const baseline = await findChangesetBaseline(db, project, build);\n const [current, before] = await Promise.all([\n components.listByBuild(build.id),\n baseline ? components.listByBuild(baseline.id) : Promise.resolve(null),\n ]);\n return { baseline, changeset: computeChangeset(current, before) };\n}\n\nfunction keyOf(row: Pick<BuildComponent, \"importPath\" | \"title\">): string {\n return row.importPath === \"\" ? `title:${row.title}` : row.importPath;\n}\n\nfunction summary(row: BuildComponent): ChangesetComponent {\n return { importPath: row.importPath, title: row.title, storyCount: row.storyCount };\n}\n\nfunction byTitle(a: ChangesetComponent, b: ChangesetComponent): number {\n return a.title.localeCompare(b.title) || a.importPath.localeCompare(b.importPath);\n}\n"],"mappings":";;;;;;;;;;AAwCA,SAAgB,iBACd,SACA,UACW;CACX,MAAM,MAAM,IAAI,KAAK,YAAY,CAAC,EAAA,CAAG,KAAK,QAAQ,CAAC,MAAM,GAAG,GAAG,GAAG,CAAU,CAAC;CAC7E,MAAM,MAAM,IAAI,IAAI,QAAQ,KAAK,QAAQ,CAAC,MAAM,GAAG,GAAG,GAAG,CAAU,CAAC;CACpE,MAAM,YAAuB;EAAE,OAAO,CAAC;EAAG,SAAS,CAAC;EAAG,SAAS,CAAC;EAAG,WAAW;CAAE;CACjF,KAAK,MAAM,CAAC,KAAK,QAAQ,KACvB,MAAM,WAAW,KAAK,IAAI,IAAI,GAAG,CAAC;CAEpC,KAAK,MAAM,CAAC,KAAK,QAAQ,KACvB,IAAI,CAAC,IAAI,IAAI,GAAG,GACd,UAAU,QAAQ,KAAK,QAAQ,GAAG,CAAC;CAGvC,OAAO;EACL,GAAG;EACH,OAAO,UAAU,MAAM,SAAS,OAAO;EACvC,SAAS,UAAU,QAAQ,SAAS,OAAO;EAC3C,SAAS,UAAU,QAAQ,SAAS,OAAO;CAC7C;AACF;;AAGA,SAAS,MACP,WACA,KACA,QACM;CACN,IAAI,WAAW,KAAA,GACb,UAAU,MAAM,KAAK,QAAQ,GAAG,CAAC;MAC5B,IAAI,OAAO,SAAS,IAAI,MAC7B,UAAU,aAAa;MAEvB,UAAU,QAAQ,KAAK;EAAE,GAAG,QAAQ,GAAG;EAAG,oBAAoB,OAAO;CAAW,CAAC;AAErF;;;;;AAMA,eAAsB,sBACpB,IACA,SACA,OACuB;CACvB,MAAM,SAAS,IAAI,WAAW,EAAE;CAChC,MAAM,WAAW,CAAC,mBAAG,IAAI,IAAI,CAAC,MAAM,WAAW,QAAQ,gBAAgB,CAAC,CAAC;CACzE,MAAM,QAAQ,MAAM,QAAQ,IAC1B,SAAS,IAAI,OAAO,WAAW,MAAM,OAAO,KAAK,QAAQ,IAAI;EAAE;EAAQ,QAAQ;CAAW,CAAC,CAAC,CAC9F;CACA,KAAK,MAAM,YAAY,OAAO;EAC5B,MAAM,WAAW,SAAS,MACvB,cAAc,UAAU,OAAO,MAAM,MAAM,UAAU,YAAY,MAAM,SAC1E;EACA,IAAI,UACF,OAAO;CAEX;CACA,OAAO;AACT;;AAGA,eAAsB,YACpB,IACA,SACA,OACyB;CACzB,MAAM,aAAa,IAAI,oBAAoB,EAAE;CAC7C,MAAM,WAAW,MAAM,sBAAsB,IAAI,SAAS,KAAK;CAC/D,MAAM,CAAC,SAAS,UAAU,MAAM,QAAQ,IAAI,CAC1C,WAAW,YAAY,MAAM,EAAE,GAC/B,WAAW,WAAW,YAAY,SAAS,EAAE,IAAI,QAAQ,QAAQ,IAAI,CACvE,CAAC;CACD,OAAO;EAAE;EAAU,WAAW,iBAAiB,SAAS,MAAM;CAAE;AAClE;AAEA,SAAS,MAAM,KAA2D;CACxE,OAAO,IAAI,eAAe,KAAK,SAAS,IAAI,UAAU,IAAI;AAC5D;AAEA,SAAS,QAAQ,KAAyC;CACxD,OAAO;EAAE,YAAY,IAAI;EAAY,OAAO,IAAI;EAAO,YAAY,IAAI;CAAW;AACpF;AAEA,SAAS,QAAQ,GAAuB,GAA+B;CACrE,OAAO,EAAE,MAAM,cAAc,EAAE,KAAK,KAAK,EAAE,WAAW,cAAc,EAAE,UAAU;AAClF"}
@@ -0,0 +1,19 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { getTableColumns } from "drizzle-orm";
5
+ //#region src/models/columns.ts
6
+ /**
7
+ * Column handle for query builders (`eq`, `inArray`, `desc`, …) from a table
8
+ * handle and a property name. Throws on an unknown name instead of letting
9
+ * `undefined` reach the query builder.
10
+ */
11
+ function col(table, name) {
12
+ const column = getTableColumns(table)[name];
13
+ if (!column) throw new Error(`Unknown column "${name}"`);
14
+ return column;
15
+ }
16
+ //#endregion
17
+ export { col };
18
+
19
+ //# sourceMappingURL=columns.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"columns.mjs","names":[],"sources":["../../src/models/columns.ts"],"sourcesContent":["import { getTableColumns } from \"drizzle-orm\";\nimport type { Column, Table } from \"drizzle-orm\";\n\n/**\n * Column handle for query builders (`eq`, `inArray`, `desc`, …) from a table\n * handle and a property name. Throws on an unknown name instead of letting\n * `undefined` reach the query builder.\n */\nexport function col(table: Table, name: string): Column {\n const column = getTableColumns(table)[name];\n if (!column) {\n throw new Error(`Unknown column \"${name}\"`);\n }\n return column as Column;\n}\n"],"mappings":";;;;;;;;;;AAQA,SAAgB,IAAI,OAAc,MAAsB;CACtD,MAAM,SAAS,gBAAgB,KAAK,CAAC,CAAC;CACtC,IAAI,CAAC,QACH,MAAM,IAAI,MAAM,mBAAmB,KAAK,EAAE;CAE5C,OAAO;AACT"}
@@ -11,8 +11,8 @@ interface CommentTables {
11
11
  }
12
12
  /** Data operations for review comments. */
13
13
  declare class CommentModel {
14
- private readonly db;
15
14
  private readonly tables;
15
+ private readonly db;
16
16
  constructor(db: DatabaseAdapter, tables?: CommentTables);
17
17
  listByBuild(buildId: string): Promise<Comment[]>;
18
18
  create(projectId: string, buildId: string, userId: string | null, input: CommentCreateInput): Promise<Comment>;
@@ -21,7 +21,10 @@ declare class CommentModel {
21
21
  /** Input for creating a review comment. */
22
22
  interface CommentCreateInput {
23
23
  body: string;
24
+ /** Anchor to a snapshot (builds with snapshots). */
24
25
  snapshotId?: string;
26
+ /** Anchor to a story of the build's catalog; not together with `snapshotId` (ADR 0013, decision 2). */
27
+ storyId?: string;
25
28
  parentId?: string;
26
29
  }
27
30
  //#endregion
@@ -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 { eq, getTableColumns } from "drizzle-orm";
5
+ import { col } from "./columns.mjs";
6
+ import { eq } from "drizzle-orm";
6
7
  //#region src/models/comment.ts
7
8
  /** Build and snapshot review comments. */
8
9
  /** Data operations for review comments. */
9
10
  var CommentModel = class {
10
- db;
11
11
  tables;
12
+ db;
12
13
  constructor(db, tables) {
13
14
  this.db = db;
14
15
  this.tables = tables ?? {
@@ -17,16 +18,18 @@ var CommentModel = class {
17
18
  };
18
19
  }
19
20
  async listByBuild(buildId) {
20
- return await this.db.list(this.tables.comments, { where: eq(getTableColumns(this.tables.comments)["buildId"], buildId) });
21
+ return await this.db.list(this.tables.comments, { where: eq(col(this.tables.comments, "buildId"), buildId) });
21
22
  }
22
23
  async create(projectId, buildId, userId, input) {
23
24
  const now = (/* @__PURE__ */ new Date()).toISOString();
24
25
  if (!await this.db.get(this.tables.projects, projectId)) throw new Error(`Project not found: ${projectId}`);
26
+ if (input.snapshotId !== void 0 && input.storyId !== void 0) throw new Error("A comment anchors to a build, a story or a snapshot, not several");
25
27
  return await this.db.insert(this.tables.comments, {
26
28
  id: ulid(),
27
29
  projectId,
28
30
  buildId,
29
- snapshotId: input.snapshotId,
31
+ snapshotId: input.snapshotId ?? null,
32
+ storyId: input.storyId ?? null,
30
33
  userId,
31
34
  body: input.body,
32
35
  parentId: input.parentId,
@@ -1 +1 @@
1
- {"version":3,"file":"comment.mjs","names":[],"sources":["../../src/models/comment.ts"],"sourcesContent":["/** Build and snapshot review comments. */\nimport { eq, getTableColumns } from \"drizzle-orm\";\nimport type { SQLWrapper, Table } from \"drizzle-orm\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { Comment } from \"../schema/comment.ts\";\nimport { ulid } from \"../utils/ulid.ts\";\n\n/** Tables required by {@link CommentModel}. */\nexport interface CommentTables {\n comments: Table;\n projects: Table;\n}\n\n/** Data operations for review comments. */\nexport class CommentModel {\n private readonly tables: CommentTables;\n constructor(\n private readonly db: DatabaseAdapter,\n tables?: CommentTables,\n ) {\n this.tables = tables ?? {\n comments: db.tables.comments,\n projects: db.tables.projects,\n };\n }\n\n async listByBuild(buildId: string): Promise<Comment[]> {\n return (await this.db.list(this.tables.comments, {\n where: eq(getTableColumns(this.tables.comments)[\"buildId\"] as unknown as SQLWrapper, buildId),\n })) as unknown as Comment[];\n }\n\n async create(\n projectId: string,\n buildId: string,\n userId: string | null,\n input: CommentCreateInput,\n ): Promise<Comment> {\n const now = new Date().toISOString();\n const project = (await this.db.get(this.tables.projects, projectId)) as unknown as {\n id: string;\n } | null;\n if (!project) {\n throw new Error(`Project not found: ${projectId}`);\n }\n return (await this.db.insert(this.tables.comments, {\n id: ulid(),\n projectId,\n buildId,\n snapshotId: input.snapshotId,\n userId,\n body: input.body,\n parentId: input.parentId,\n resolved: false,\n createdAt: now,\n updatedAt: now,\n })) as unknown as Comment;\n }\n\n async resolve(id: string): Promise<Comment> {\n return (await this.db.update(this.tables.comments, id, {\n resolved: true,\n updatedAt: new Date().toISOString(),\n })) as unknown as Comment;\n }\n}\n\n/** Input for creating a review comment. */\nexport interface CommentCreateInput {\n body: string;\n snapshotId?: string;\n parentId?: string;\n}\n"],"mappings":";;;;;;;;AAcA,IAAa,eAAb,MAA0B;CAGL;CAFnB;CACA,YACE,IACA,QACA;EAFiB,KAAA,KAAA;EAGjB,KAAK,SAAS,UAAU;GACtB,UAAU,GAAG,OAAO;GACpB,UAAU,GAAG,OAAO;EACtB;CACF;CAEA,MAAM,YAAY,SAAqC;EACrD,OAAQ,MAAM,KAAK,GAAG,KAAK,KAAK,OAAO,UAAU,EAC/C,OAAO,GAAG,gBAAgB,KAAK,OAAO,QAAQ,CAAC,CAAC,YAAqC,OAAO,EAC9F,CAAC;CACH;CAEA,MAAM,OACJ,WACA,SACA,QACA,OACkB;EAClB,MAAM,uBAAM,IAAI,KAAK,EAAA,CAAE,YAAY;EAInC,IAAI,CAAC,MAHkB,KAAK,GAAG,IAAI,KAAK,OAAO,UAAU,SAAS,GAIhE,MAAM,IAAI,MAAM,sBAAsB,WAAW;EAEnD,OAAQ,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,UAAU;GACjD,IAAI,KAAK;GACT;GACA;GACA,YAAY,MAAM;GAClB;GACA,MAAM,MAAM;GACZ,UAAU,MAAM;GAChB,UAAU;GACV,WAAW;GACX,WAAW;EACb,CAAC;CACH;CAEA,MAAM,QAAQ,IAA8B;EAC1C,OAAQ,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,UAAU,IAAI;GACrD,UAAU;GACV,4BAAW,IAAI,KAAK,EAAA,CAAE,YAAY;EACpC,CAAC;CACH;AACF"}
1
+ {"version":3,"file":"comment.mjs","names":[],"sources":["../../src/models/comment.ts"],"sourcesContent":["/** Build and snapshot review comments. */\nimport { eq } from \"drizzle-orm\";\nimport type { Table } from \"drizzle-orm\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { Comment } from \"../schema/comment.ts\";\nimport { ulid } from \"../utils/ulid.ts\";\nimport { col } from \"./columns.ts\";\n\n/** Tables required by {@link CommentModel}. */\nexport interface CommentTables {\n comments: Table;\n projects: Table;\n}\n\n/** Data operations for review comments. */\nexport class CommentModel {\n private readonly tables: CommentTables;\n private readonly db: DatabaseAdapter;\n\n constructor(db: DatabaseAdapter, tables?: CommentTables) {\n this.db = db;\n this.tables = tables ?? {\n comments: db.tables.comments,\n projects: db.tables.projects,\n };\n }\n\n async listByBuild(buildId: string): Promise<Comment[]> {\n return (await this.db.list(this.tables.comments, {\n where: eq(col(this.tables.comments, \"buildId\"), buildId),\n })) as unknown as Comment[];\n }\n\n async create(\n projectId: string,\n buildId: string,\n userId: string | null,\n input: CommentCreateInput,\n ): Promise<Comment> {\n const now = new Date().toISOString();\n const project = (await this.db.get(this.tables.projects, projectId)) as unknown as {\n id: string;\n } | null;\n if (!project) {\n throw new Error(`Project not found: ${projectId}`);\n }\n if (input.snapshotId !== undefined && input.storyId !== undefined) {\n throw new Error(\"A comment anchors to a build, a story or a snapshot, not several\");\n }\n return (await this.db.insert(this.tables.comments, {\n id: ulid(),\n projectId,\n buildId,\n snapshotId: input.snapshotId ?? null,\n storyId: input.storyId ?? null,\n userId,\n body: input.body,\n parentId: input.parentId,\n resolved: false,\n createdAt: now,\n updatedAt: now,\n })) as unknown as Comment;\n }\n\n async resolve(id: string): Promise<Comment> {\n return (await this.db.update(this.tables.comments, id, {\n resolved: true,\n updatedAt: new Date().toISOString(),\n })) as unknown as Comment;\n }\n}\n\n/** Input for creating a review comment. */\nexport interface CommentCreateInput {\n body: string;\n /** Anchor to a snapshot (builds with snapshots). */\n snapshotId?: string;\n /** Anchor to a story of the build's catalog; not together with `snapshotId` (ADR 0013, decision 2). */\n storyId?: string;\n parentId?: string;\n}\n"],"mappings":";;;;;;;;;AAeA,IAAa,eAAb,MAA0B;CACxB;CACA;CAEA,YAAY,IAAqB,QAAwB;EACvD,KAAK,KAAK;EACV,KAAK,SAAS,UAAU;GACtB,UAAU,GAAG,OAAO;GACpB,UAAU,GAAG,OAAO;EACtB;CACF;CAEA,MAAM,YAAY,SAAqC;EACrD,OAAQ,MAAM,KAAK,GAAG,KAAK,KAAK,OAAO,UAAU,EAC/C,OAAO,GAAG,IAAI,KAAK,OAAO,UAAU,SAAS,GAAG,OAAO,EACzD,CAAC;CACH;CAEA,MAAM,OACJ,WACA,SACA,QACA,OACkB;EAClB,MAAM,uBAAM,IAAI,KAAK,EAAA,CAAE,YAAY;EAInC,IAAI,CAAC,MAHkB,KAAK,GAAG,IAAI,KAAK,OAAO,UAAU,SAAS,GAIhE,MAAM,IAAI,MAAM,sBAAsB,WAAW;EAEnD,IAAI,MAAM,eAAe,KAAA,KAAa,MAAM,YAAY,KAAA,GACtD,MAAM,IAAI,MAAM,kEAAkE;EAEpF,OAAQ,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,UAAU;GACjD,IAAI,KAAK;GACT;GACA;GACA,YAAY,MAAM,cAAc;GAChC,SAAS,MAAM,WAAW;GAC1B;GACA,MAAM,MAAM;GACZ,UAAU,MAAM;GAChB,UAAU;GACV,WAAW;GACX,WAAW;EACb,CAAC;CACH;CAEA,MAAM,QAAQ,IAA8B;EAC1C,OAAQ,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,UAAU,IAAI;GACrD,UAAU;GACV,4BAAW,IAAI,KAAK,EAAA,CAAE,YAAY;EACpC,CAAC;CACH;AACF"}
@@ -0,0 +1,43 @@
1
+ import __tsdown_shims_path from 'node:path';
2
+ import __tsdown_shims_url from 'node:url';
3
+ import { SnapshotStatus } from "../types.mjs";
4
+ import { Build } from "../schema/build.mjs";
5
+ import { Snapshot } from "../schema/snapshot.mjs";
6
+ //#region src/models/component.d.ts
7
+ /** The most attention-worthy status of a set (unchanged when empty). */
8
+ declare function worstStatus(statuses: readonly SnapshotStatus[]): SnapshotStatus;
9
+ /** One build's view of a component. */
10
+ interface ComponentHistoryRow {
11
+ build: Build;
12
+ /** Distinct stories (states) of the file captured in the build. */
13
+ states: number;
14
+ /** Distinct stories that are new or changed in the build. */
15
+ changedStates: number;
16
+ status: SnapshotStatus;
17
+ /** Snapshot the row links to: the first needing attention, else the first. */
18
+ snapshotId: string;
19
+ }
20
+ /** Per-build history of a component, newest build first; builds without a snapshot of it are skipped. */
21
+ declare function componentHistory(snapshots: readonly Snapshot[], builds: readonly Build[]): ComponentHistoryRow[];
22
+ /** Where a component page stands relative to the project's latest build. */
23
+ type ComponentPresence = {
24
+ kind: "present";
25
+ } | {
26
+ kind: "moved";
27
+ to: string;
28
+ title: string;
29
+ } | {
30
+ kind: "deleted";
31
+ lastSeen: Build;
32
+ } | {
33
+ kind: "unknown";
34
+ };
35
+ /**
36
+ * Classify a component against the latest build. `history` is the component's
37
+ * past builds (newest first); `latest` holds the latest build's snapshots of
38
+ * every file, used to find a moved file by its last-seen title.
39
+ */
40
+ declare function componentPresence(history: readonly ComponentHistoryRow[], latestBuildId: string | null, latest: readonly Pick<Snapshot, "storyTitle" | "storyImportPath">[], lastTitle: string | null): ComponentPresence;
41
+ //#endregion
42
+ export { ComponentHistoryRow, ComponentPresence, componentHistory, componentPresence, worstStatus };
43
+ //# sourceMappingURL=component.d.mts.map
@@ -0,0 +1,60 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ //#region src/models/component.ts
5
+ /** Worst-first order used for status roll-ups. */
6
+ const STATUS_PRIORITY = [
7
+ "rejected",
8
+ "changed",
9
+ "new",
10
+ "pending",
11
+ "approved",
12
+ "unchanged"
13
+ ];
14
+ /** Statuses that count as "state changed" in a build. */
15
+ const CHANGED = /* @__PURE__ */ new Set(["changed", "new"]);
16
+ /** The most attention-worthy status of a set (unchanged when empty). */
17
+ function worstStatus(statuses) {
18
+ return STATUS_PRIORITY.find((status) => statuses.includes(status)) ?? "unchanged";
19
+ }
20
+ /** Per-build history of a component, newest build first; builds without a snapshot of it are skipped. */
21
+ function componentHistory(snapshots, builds) {
22
+ const byBuild = Map.groupBy(snapshots, (snapshot) => snapshot.buildId);
23
+ return builds.toSorted((a, b) => b.createdAt.localeCompare(a.createdAt)).flatMap((build) => {
24
+ const own = byBuild.get(build.id) ?? [];
25
+ const representative = own.find((snap) => snap.status === "changed" || snap.status === "new") ?? own[0];
26
+ if (!representative) return [];
27
+ const changed = own.filter((snap) => CHANGED.has(snap.status));
28
+ return [{
29
+ build,
30
+ states: new Set(own.map((snap) => snap.storyId)).size,
31
+ changedStates: new Set(changed.map((snap) => snap.storyId)).size,
32
+ status: worstStatus(own.map((snap) => snap.status)),
33
+ snapshotId: representative.id
34
+ }];
35
+ });
36
+ }
37
+ /**
38
+ * Classify a component against the latest build. `history` is the component's
39
+ * past builds (newest first); `latest` holds the latest build's snapshots of
40
+ * every file, used to find a moved file by its last-seen title.
41
+ */
42
+ function componentPresence(history, latestBuildId, latest, lastTitle) {
43
+ const [newest] = history;
44
+ if (!newest) return { kind: "unknown" };
45
+ if (newest.build.id === latestBuildId) return { kind: "present" };
46
+ const moved = latest.find((snap) => snap.storyTitle === lastTitle && snap.storyImportPath);
47
+ if (moved?.storyImportPath) return {
48
+ kind: "moved",
49
+ to: moved.storyImportPath,
50
+ title: moved.storyTitle
51
+ };
52
+ return {
53
+ kind: "deleted",
54
+ lastSeen: newest.build
55
+ };
56
+ }
57
+ //#endregion
58
+ export { componentHistory, componentPresence, worstStatus };
59
+
60
+ //# sourceMappingURL=component.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"component.mjs","names":[],"sources":["../../src/models/component.ts"],"sourcesContent":["/** Component (story file) roll-ups: status priority, per-build history, and tombstone lookup. */\nimport type { Build } from \"../schema/build.ts\";\nimport type { Snapshot } from \"../schema/snapshot.ts\";\nimport type { SnapshotStatus } from \"../types.ts\";\n\n/** Worst-first order used for status roll-ups. */\nconst STATUS_PRIORITY: readonly SnapshotStatus[] = [\n \"rejected\",\n \"changed\",\n \"new\",\n \"pending\",\n \"approved\",\n \"unchanged\",\n];\n\n/** Statuses that count as \"state changed\" in a build. */\nconst CHANGED = new Set<SnapshotStatus>([\"changed\", \"new\"]);\n\n/** The most attention-worthy status of a set (unchanged when empty). */\nexport function worstStatus(statuses: readonly SnapshotStatus[]): SnapshotStatus {\n return STATUS_PRIORITY.find((status) => statuses.includes(status)) ?? \"unchanged\";\n}\n\n/** One build's view of a component. */\nexport interface ComponentHistoryRow {\n build: Build;\n /** Distinct stories (states) of the file captured in the build. */\n states: number;\n /** Distinct stories that are new or changed in the build. */\n changedStates: number;\n status: SnapshotStatus;\n /** Snapshot the row links to: the first needing attention, else the first. */\n snapshotId: string;\n}\n\n/** Per-build history of a component, newest build first; builds without a snapshot of it are skipped. */\nexport function componentHistory(\n snapshots: readonly Snapshot[],\n builds: readonly Build[],\n): ComponentHistoryRow[] {\n const byBuild = Map.groupBy(snapshots, (snapshot) => snapshot.buildId);\n return builds\n .toSorted((a, b) => b.createdAt.localeCompare(a.createdAt))\n .flatMap((build): ComponentHistoryRow[] => {\n const own = byBuild.get(build.id) ?? [];\n const representative =\n own.find((snap) => snap.status === \"changed\" || snap.status === \"new\") ?? own[0];\n if (!representative) return [];\n const changed = own.filter((snap) => CHANGED.has(snap.status));\n return [\n {\n build,\n states: new Set(own.map((snap) => snap.storyId)).size,\n changedStates: new Set(changed.map((snap) => snap.storyId)).size,\n status: worstStatus(own.map((snap) => snap.status)),\n snapshotId: representative.id,\n },\n ];\n });\n}\n\n/** Where a component page stands relative to the project's latest build. */\nexport type ComponentPresence =\n | { kind: \"present\" }\n | { kind: \"moved\"; to: string; title: string }\n | { kind: \"deleted\"; lastSeen: Build }\n | { kind: \"unknown\" };\n\n/**\n * Classify a component against the latest build. `history` is the component's\n * past builds (newest first); `latest` holds the latest build's snapshots of\n * every file, used to find a moved file by its last-seen title.\n */\nexport function componentPresence(\n history: readonly ComponentHistoryRow[],\n latestBuildId: string | null,\n latest: readonly Pick<Snapshot, \"storyTitle\" | \"storyImportPath\">[],\n lastTitle: string | null,\n): ComponentPresence {\n const [newest] = history;\n if (!newest) return { kind: \"unknown\" };\n if (newest.build.id === latestBuildId) return { kind: \"present\" };\n const moved = latest.find((snap) => snap.storyTitle === lastTitle && snap.storyImportPath);\n if (moved?.storyImportPath) {\n return { kind: \"moved\", to: moved.storyImportPath, title: moved.storyTitle };\n }\n return { kind: \"deleted\", lastSeen: newest.build };\n}\n"],"mappings":";;;;;AAMA,MAAM,kBAA6C;CACjD;CACA;CACA;CACA;CACA;CACA;AACF;;AAGA,MAAM,0BAAU,IAAI,IAAoB,CAAC,WAAW,KAAK,CAAC;;AAG1D,SAAgB,YAAY,UAAqD;CAC/E,OAAO,gBAAgB,MAAM,WAAW,SAAS,SAAS,MAAM,CAAC,KAAK;AACxE;;AAeA,SAAgB,iBACd,WACA,QACuB;CACvB,MAAM,UAAU,IAAI,QAAQ,YAAY,aAAa,SAAS,OAAO;CACrE,OAAO,OACJ,UAAU,GAAG,MAAM,EAAE,UAAU,cAAc,EAAE,SAAS,CAAC,CAAC,CAC1D,SAAS,UAAiC;EACzC,MAAM,MAAM,QAAQ,IAAI,MAAM,EAAE,KAAK,CAAC;EACtC,MAAM,iBACJ,IAAI,MAAM,SAAS,KAAK,WAAW,aAAa,KAAK,WAAW,KAAK,KAAK,IAAI;EAChF,IAAI,CAAC,gBAAgB,OAAO,CAAC;EAC7B,MAAM,UAAU,IAAI,QAAQ,SAAS,QAAQ,IAAI,KAAK,MAAM,CAAC;EAC7D,OAAO,CACL;GACE;GACA,QAAQ,IAAI,IAAI,IAAI,KAAK,SAAS,KAAK,OAAO,CAAC,CAAC,CAAC;GACjD,eAAe,IAAI,IAAI,QAAQ,KAAK,SAAS,KAAK,OAAO,CAAC,CAAC,CAAC;GAC5D,QAAQ,YAAY,IAAI,KAAK,SAAS,KAAK,MAAM,CAAC;GAClD,YAAY,eAAe;EAC7B,CACF;CACF,CAAC;AACL;;;;;;AAcA,SAAgB,kBACd,SACA,eACA,QACA,WACmB;CACnB,MAAM,CAAC,UAAU;CACjB,IAAI,CAAC,QAAQ,OAAO,EAAE,MAAM,UAAU;CACtC,IAAI,OAAO,MAAM,OAAO,eAAe,OAAO,EAAE,MAAM,UAAU;CAChE,MAAM,QAAQ,OAAO,MAAM,SAAS,KAAK,eAAe,aAAa,KAAK,eAAe;CACzF,IAAI,OAAO,iBACT,OAAO;EAAE,MAAM;EAAS,IAAI,MAAM;EAAiB,OAAO,MAAM;CAAW;CAE7E,OAAO;EAAE,MAAM;EAAW,UAAU,OAAO;CAAM;AACnD"}
@@ -0,0 +1,41 @@
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 { OAuthClient, OAuthTokens } from "../adapter/oauth.mjs";
5
+ import { SecretInput } from "../utils/encrypt.mjs";
6
+ import { UserConnection } from "../schema/connection.mjs";
7
+ import { Table } from "drizzle-orm";
8
+ //#region src/models/connections.d.ts
9
+ /** Tables required by {@link ConnectionModel}. */
10
+ interface ConnectionTables {
11
+ userConnections: Table;
12
+ }
13
+ /**
14
+ * Data operations for user OAuth connections, addressed by `(user, service)`.
15
+ * Plaintext only leaves through {@link accessToken}; a token is refreshed lazily,
16
+ * when its owner (or a request on their behalf) needs it — never in the background.
17
+ */
18
+ declare class ConnectionModel {
19
+ private readonly tables;
20
+ private readonly db;
21
+ private readonly secret?;
22
+ constructor(db: DatabaseAdapter, secret?: SecretInput, tables?: ConnectionTables);
23
+ /** The connection row (never the plaintext), or null. */
24
+ info(userId: string, service: string): Promise<UserConnection | null>;
25
+ /** Every connection of a user (no plaintext), for a "connected apps" list. */
26
+ listForUser(userId: string): Promise<UserConnection[]>;
27
+ /** Store (or replace) a connection from fresh tokens; resets it to healthy. */
28
+ connect(userId: string, service: string, tokens: OAuthTokens): Promise<void>;
29
+ /** Remove a connection. */
30
+ remove(userId: string, service: string): Promise<void>;
31
+ /**
32
+ * A usable access token, refreshing it (once, under concurrent calls) when it is
33
+ * about to expire. A failed refresh marks the connection `expired` and returns
34
+ * null until the user reconnects.
35
+ */
36
+ accessToken(userId: string, service: string, oauth?: Pick<OAuthClient, "refresh">, now?: Date): Promise<string | null>;
37
+ private refreshOnce;
38
+ }
39
+ //#endregion
40
+ export { ConnectionModel, ConnectionTables };
41
+ //# sourceMappingURL=connections.d.mts.map
@@ -0,0 +1,112 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { decryptCredential, encrypt } from "../utils/encrypt.mjs";
5
+ import { ulid } from "../utils/ulid.mjs";
6
+ import { col } from "./columns.mjs";
7
+ import { and, eq } from "drizzle-orm";
8
+ //#region src/models/connections.ts
9
+ /** A user's own OAuth connections to external services: encrypted tokens, lazy refresh on use. */
10
+ /** Refresh this long before expiry so a call never starts with a token about to lapse. */
11
+ const REFRESH_MARGIN_MS = 6e4;
12
+ /** In-flight refreshes by connection, so concurrent page views refresh once (per process). */
13
+ const refreshing = /* @__PURE__ */ new Map();
14
+ /**
15
+ * Data operations for user OAuth connections, addressed by `(user, service)`.
16
+ * Plaintext only leaves through {@link accessToken}; a token is refreshed lazily,
17
+ * when its owner (or a request on their behalf) needs it — never in the background.
18
+ */
19
+ var ConnectionModel = class {
20
+ tables;
21
+ db;
22
+ secret;
23
+ constructor(db, secret, tables) {
24
+ this.db = db;
25
+ this.secret = secret;
26
+ this.tables = tables ?? { userConnections: db.tables.userConnections };
27
+ }
28
+ /** The connection row (never the plaintext), or null. */
29
+ async info(userId, service) {
30
+ return (await this.db.list(this.tables.userConnections, {
31
+ where: and(eq(col(this.tables.userConnections, "userId"), userId), eq(col(this.tables.userConnections, "service"), service)),
32
+ limit: 1
33
+ }))[0] ?? null;
34
+ }
35
+ /** Every connection of a user (no plaintext), for a "connected apps" list. */
36
+ async listForUser(userId) {
37
+ return await this.db.list(this.tables.userConnections, { where: eq(col(this.tables.userConnections, "userId"), userId) });
38
+ }
39
+ /** Store (or replace) a connection from fresh tokens; resets it to healthy. */
40
+ async connect(userId, service, tokens) {
41
+ const now = (/* @__PURE__ */ new Date()).toISOString();
42
+ const values = {
43
+ tokenEncrypted: encrypt(this.secret, tokens.accessToken),
44
+ refreshTokenEncrypted: tokens.refreshToken ? encrypt(this.secret, tokens.refreshToken) : null,
45
+ expiresAt: tokens.expiresAt,
46
+ status: "ok",
47
+ updatedAt: now
48
+ };
49
+ const existing = await this.info(userId, service);
50
+ if (existing) {
51
+ await this.db.update(this.tables.userConnections, existing.id, values);
52
+ return;
53
+ }
54
+ await this.db.insert(this.tables.userConnections, {
55
+ id: ulid(),
56
+ userId,
57
+ service,
58
+ createdAt: now,
59
+ ...values
60
+ });
61
+ }
62
+ /** Remove a connection. */
63
+ async remove(userId, service) {
64
+ const existing = await this.info(userId, service);
65
+ if (existing) await this.db.remove(this.tables.userConnections, existing.id);
66
+ }
67
+ /**
68
+ * A usable access token, refreshing it (once, under concurrent calls) when it is
69
+ * about to expire. A failed refresh marks the connection `expired` and returns
70
+ * null until the user reconnects.
71
+ */
72
+ async accessToken(userId, service, oauth, now = /* @__PURE__ */ new Date()) {
73
+ const row = await this.info(userId, service);
74
+ if (!row || row.status === "expired") return null;
75
+ if (!isExpiring(row.expiresAt, now)) return decryptCredential(this.secret, row.tokenEncrypted);
76
+ const inFlight = refreshing.get(row.id);
77
+ if (inFlight) return await inFlight;
78
+ const pending = this.refreshOnce(row, oauth).finally(() => {
79
+ refreshing.delete(row.id);
80
+ });
81
+ refreshing.set(row.id, pending);
82
+ return await pending;
83
+ }
84
+ async refreshOnce(row, oauth) {
85
+ try {
86
+ if (!oauth || !row.refreshTokenEncrypted) throw new Error("connection cannot be refreshed");
87
+ const tokens = await oauth.refresh({ refreshToken: decryptCredential(this.secret, row.refreshTokenEncrypted) });
88
+ await this.db.update(this.tables.userConnections, row.id, {
89
+ tokenEncrypted: encrypt(this.secret, tokens.accessToken),
90
+ refreshTokenEncrypted: tokens.refreshToken ? encrypt(this.secret, tokens.refreshToken) : row.refreshTokenEncrypted,
91
+ expiresAt: tokens.expiresAt,
92
+ status: "ok",
93
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
94
+ });
95
+ return tokens.accessToken;
96
+ } catch {
97
+ await this.db.update(this.tables.userConnections, row.id, {
98
+ status: "expired",
99
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
100
+ });
101
+ return null;
102
+ }
103
+ }
104
+ };
105
+ /** Expiring within the margin (null never expires). */
106
+ function isExpiring(expiresAt, now) {
107
+ return expiresAt !== null && Date.parse(expiresAt) - now.getTime() < REFRESH_MARGIN_MS;
108
+ }
109
+ //#endregion
110
+ export { ConnectionModel };
111
+
112
+ //# sourceMappingURL=connections.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connections.mjs","names":[],"sources":["../../src/models/connections.ts"],"sourcesContent":["/** A user's own OAuth connections to external services: encrypted tokens, lazy refresh on use. */\nimport { and, eq } from \"drizzle-orm\";\nimport type { Table } from \"drizzle-orm\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { OAuthClient, OAuthTokens } from \"../adapters/oauth.ts\";\nimport type { UserConnection } from \"../schema/connection.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 ConnectionModel}. */\nexport interface ConnectionTables {\n userConnections: Table;\n}\n\n/** Refresh this long before expiry so a call never starts with a token about to lapse. */\nconst REFRESH_MARGIN_MS = 60_000;\n\n/** In-flight refreshes by connection, so concurrent page views refresh once (per process). */\nconst refreshing = new Map<string, Promise<string | null>>();\n\n/**\n * Data operations for user OAuth connections, addressed by `(user, service)`.\n * Plaintext only leaves through {@link accessToken}; a token is refreshed lazily,\n * when its owner (or a request on their behalf) needs it — never in the background.\n */\nexport class ConnectionModel {\n private readonly tables: ConnectionTables;\n private readonly db: DatabaseAdapter;\n private readonly secret?: SecretInput;\n\n constructor(db: DatabaseAdapter, secret?: SecretInput, tables?: ConnectionTables) {\n this.db = db;\n this.secret = secret;\n this.tables = tables ?? { userConnections: db.tables.userConnections };\n }\n\n /** The connection row (never the plaintext), or null. */\n async info(userId: string, service: string): Promise<UserConnection | null> {\n const rows = (await this.db.list(this.tables.userConnections, {\n where: and(\n eq(col(this.tables.userConnections, \"userId\"), userId),\n eq(col(this.tables.userConnections, \"service\"), service),\n ),\n limit: 1,\n })) as unknown as UserConnection[];\n return rows[0] ?? null;\n }\n\n /** Every connection of a user (no plaintext), for a \"connected apps\" list. */\n async listForUser(userId: string): Promise<UserConnection[]> {\n return (await this.db.list(this.tables.userConnections, {\n where: eq(col(this.tables.userConnections, \"userId\"), userId),\n })) as unknown as UserConnection[];\n }\n\n /** Store (or replace) a connection from fresh tokens; resets it to healthy. */\n async connect(userId: string, service: string, tokens: OAuthTokens): Promise<void> {\n const now = new Date().toISOString();\n const values = {\n tokenEncrypted: encrypt(this.secret, tokens.accessToken),\n refreshTokenEncrypted: tokens.refreshToken ? encrypt(this.secret, tokens.refreshToken) : null,\n expiresAt: tokens.expiresAt,\n status: \"ok\",\n updatedAt: now,\n };\n const existing = await this.info(userId, service);\n if (existing) {\n await this.db.update(this.tables.userConnections, existing.id, values);\n return;\n }\n await this.db.insert(this.tables.userConnections, {\n id: ulid(),\n userId,\n service,\n createdAt: now,\n ...values,\n });\n }\n\n /** Remove a connection. */\n async remove(userId: string, service: string): Promise<void> {\n const existing = await this.info(userId, service);\n if (existing) await this.db.remove(this.tables.userConnections, existing.id);\n }\n\n /**\n * A usable access token, refreshing it (once, under concurrent calls) when it is\n * about to expire. A failed refresh marks the connection `expired` and returns\n * null until the user reconnects.\n */\n async accessToken(\n userId: string,\n service: string,\n oauth?: Pick<OAuthClient, \"refresh\">,\n now = new Date(),\n ): Promise<string | null> {\n const row = await this.info(userId, service);\n if (!row || row.status === \"expired\") return null;\n if (!isExpiring(row.expiresAt, now)) return decryptCredential(this.secret, row.tokenEncrypted);\n const inFlight = refreshing.get(row.id);\n if (inFlight) return await inFlight;\n const pending = this.refreshOnce(row, oauth).finally(() => {\n refreshing.delete(row.id);\n });\n refreshing.set(row.id, pending);\n return await pending;\n }\n\n private async refreshOnce(\n row: UserConnection,\n oauth: Pick<OAuthClient, \"refresh\"> | undefined,\n ): Promise<string | null> {\n try {\n if (!oauth || !row.refreshTokenEncrypted) throw new Error(\"connection cannot be refreshed\");\n const tokens = await oauth.refresh({\n refreshToken: decryptCredential(this.secret, row.refreshTokenEncrypted),\n });\n await this.db.update(this.tables.userConnections, row.id, {\n tokenEncrypted: encrypt(this.secret, tokens.accessToken),\n refreshTokenEncrypted: tokens.refreshToken\n ? encrypt(this.secret, tokens.refreshToken)\n : row.refreshTokenEncrypted,\n expiresAt: tokens.expiresAt,\n status: \"ok\",\n updatedAt: new Date().toISOString(),\n });\n return tokens.accessToken;\n } catch {\n await this.db.update(this.tables.userConnections, row.id, {\n status: \"expired\",\n updatedAt: new Date().toISOString(),\n });\n return null;\n }\n }\n}\n\n/** Expiring within the margin (null never expires). */\nfunction isExpiring(expiresAt: string | null, now: Date): boolean {\n return expiresAt !== null && Date.parse(expiresAt) - now.getTime() < REFRESH_MARGIN_MS;\n}\n"],"mappings":";;;;;;;;;;AAgBA,MAAM,oBAAoB;;AAG1B,MAAM,6BAAa,IAAI,IAAoC;;;;;;AAO3D,IAAa,kBAAb,MAA6B;CAC3B;CACA;CACA;CAEA,YAAY,IAAqB,QAAsB,QAA2B;EAChF,KAAK,KAAK;EACV,KAAK,SAAS;EACd,KAAK,SAAS,UAAU,EAAE,iBAAiB,GAAG,OAAO,gBAAgB;CACvE;;CAGA,MAAM,KAAK,QAAgB,SAAiD;EAQ1E,QAAO,MAPa,KAAK,GAAG,KAAK,KAAK,OAAO,iBAAiB;GAC5D,OAAO,IACL,GAAG,IAAI,KAAK,OAAO,iBAAiB,QAAQ,GAAG,MAAM,GACrD,GAAG,IAAI,KAAK,OAAO,iBAAiB,SAAS,GAAG,OAAO,CACzD;GACA,OAAO;EACT,CAAC,EAAA,CACW,MAAM;CACpB;;CAGA,MAAM,YAAY,QAA2C;EAC3D,OAAQ,MAAM,KAAK,GAAG,KAAK,KAAK,OAAO,iBAAiB,EACtD,OAAO,GAAG,IAAI,KAAK,OAAO,iBAAiB,QAAQ,GAAG,MAAM,EAC9D,CAAC;CACH;;CAGA,MAAM,QAAQ,QAAgB,SAAiB,QAAoC;EACjF,MAAM,uBAAM,IAAI,KAAK,EAAA,CAAE,YAAY;EACnC,MAAM,SAAS;GACb,gBAAgB,QAAQ,KAAK,QAAQ,OAAO,WAAW;GACvD,uBAAuB,OAAO,eAAe,QAAQ,KAAK,QAAQ,OAAO,YAAY,IAAI;GACzF,WAAW,OAAO;GAClB,QAAQ;GACR,WAAW;EACb;EACA,MAAM,WAAW,MAAM,KAAK,KAAK,QAAQ,OAAO;EAChD,IAAI,UAAU;GACZ,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,iBAAiB,SAAS,IAAI,MAAM;GACrE;EACF;EACA,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,iBAAiB;GAChD,IAAI,KAAK;GACT;GACA;GACA,WAAW;GACX,GAAG;EACL,CAAC;CACH;;CAGA,MAAM,OAAO,QAAgB,SAAgC;EAC3D,MAAM,WAAW,MAAM,KAAK,KAAK,QAAQ,OAAO;EAChD,IAAI,UAAU,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,iBAAiB,SAAS,EAAE;CAC7E;;;;;;CAOA,MAAM,YACJ,QACA,SACA,OACA,sBAAM,IAAI,KAAK,GACS;EACxB,MAAM,MAAM,MAAM,KAAK,KAAK,QAAQ,OAAO;EAC3C,IAAI,CAAC,OAAO,IAAI,WAAW,WAAW,OAAO;EAC7C,IAAI,CAAC,WAAW,IAAI,WAAW,GAAG,GAAG,OAAO,kBAAkB,KAAK,QAAQ,IAAI,cAAc;EAC7F,MAAM,WAAW,WAAW,IAAI,IAAI,EAAE;EACtC,IAAI,UAAU,OAAO,MAAM;EAC3B,MAAM,UAAU,KAAK,YAAY,KAAK,KAAK,CAAC,CAAC,cAAc;GACzD,WAAW,OAAO,IAAI,EAAE;EAC1B,CAAC;EACD,WAAW,IAAI,IAAI,IAAI,OAAO;EAC9B,OAAO,MAAM;CACf;CAEA,MAAc,YACZ,KACA,OACwB;EACxB,IAAI;GACF,IAAI,CAAC,SAAS,CAAC,IAAI,uBAAuB,MAAM,IAAI,MAAM,gCAAgC;GAC1F,MAAM,SAAS,MAAM,MAAM,QAAQ,EACjC,cAAc,kBAAkB,KAAK,QAAQ,IAAI,qBAAqB,EACxE,CAAC;GACD,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,iBAAiB,IAAI,IAAI;IACxD,gBAAgB,QAAQ,KAAK,QAAQ,OAAO,WAAW;IACvD,uBAAuB,OAAO,eAC1B,QAAQ,KAAK,QAAQ,OAAO,YAAY,IACxC,IAAI;IACR,WAAW,OAAO;IAClB,QAAQ;IACR,4BAAW,IAAI,KAAK,EAAA,CAAE,YAAY;GACpC,CAAC;GACD,OAAO,OAAO;EAChB,QAAQ;GACN,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,iBAAiB,IAAI,IAAI;IACxD,QAAQ;IACR,4BAAW,IAAI,KAAK,EAAA,CAAE,YAAY;GACpC,CAAC;GACD,OAAO;EACT;CACF;AACF;;AAGA,SAAS,WAAW,WAA0B,KAAoB;CAChE,OAAO,cAAc,QAAQ,KAAK,MAAM,SAAS,IAAI,IAAI,QAAQ,IAAI;AACvE"}
@@ -6,8 +6,8 @@ import { Table } from "drizzle-orm";
6
6
  //#region src/models/content-ref.d.ts
7
7
  /** Content refs for deduplicated Storybook assets. */
8
8
  declare class ContentRefModel {
9
- private readonly db;
10
9
  private readonly tables;
10
+ private readonly db;
11
11
  constructor(db: DatabaseAdapter, tables?: {
12
12
  contentRefs: Table;
13
13
  });
@@ -4,8 +4,8 @@ import.meta.url;
4
4
  //#region src/models/content-ref.ts
5
5
  /** Content refs for deduplicated Storybook assets. */
6
6
  var ContentRefModel = class {
7
- db;
8
7
  tables;
8
+ db;
9
9
  constructor(db, tables) {
10
10
  this.db = db;
11
11
  this.tables = tables ?? { contentRefs: db.tables.contentRefs };
@@ -1 +1 @@
1
- {"version":3,"file":"content-ref.mjs","names":[],"sources":["../../src/models/content-ref.ts"],"sourcesContent":["import type { Table } from \"drizzle-orm\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { ContentRef } from \"../schema/content-ref.ts\";\n\n/** Content refs for deduplicated Storybook assets. */\nexport class ContentRefModel {\n private readonly tables: { contentRefs: Table };\n constructor(\n private readonly db: DatabaseAdapter,\n tables?: { contentRefs: Table },\n ) {\n this.tables = tables ?? { contentRefs: db.tables.contentRefs };\n }\n\n async get(hash: string): Promise<ContentRef | null> {\n return (await this.db.get(this.tables.contentRefs, hash)) as unknown as ContentRef | null;\n }\n\n async upsert(hash: string): Promise<ContentRef> {\n const now = new Date().toISOString();\n const existing = await this.get(hash);\n if (existing) {\n return (await this.db.update(this.tables.contentRefs, hash, {\n refCount: existing.refCount + 1,\n lastSeenAt: now,\n })) as unknown as ContentRef;\n }\n return (await this.db.insert(this.tables.contentRefs, {\n hash,\n refCount: 1,\n lastSeenAt: now,\n createdAt: now,\n })) as unknown as ContentRef;\n }\n\n async decrement(hash: string): Promise<ContentRef | null> {\n const existing = await this.get(hash);\n if (!existing) return null;\n if (existing.refCount <= 1) {\n await this.db.remove(this.tables.contentRefs, hash);\n return null;\n }\n return (await this.db.update(this.tables.contentRefs, hash, {\n refCount: existing.refCount - 1,\n lastSeenAt: new Date().toISOString(),\n })) as unknown as ContentRef;\n }\n\n async listStale(cutoff: string): Promise<ContentRef[]> {\n const all = (await this.db.list(this.tables.contentRefs)) as unknown as ContentRef[];\n return all.filter((r) => r.refCount === 0 && r.lastSeenAt < cutoff);\n }\n}\n"],"mappings":";;;;;AAKA,IAAa,kBAAb,MAA6B;CAGR;CAFnB;CACA,YACE,IACA,QACA;EAFiB,KAAA,KAAA;EAGjB,KAAK,SAAS,UAAU,EAAE,aAAa,GAAG,OAAO,YAAY;CAC/D;CAEA,MAAM,IAAI,MAA0C;EAClD,OAAQ,MAAM,KAAK,GAAG,IAAI,KAAK,OAAO,aAAa,IAAI;CACzD;CAEA,MAAM,OAAO,MAAmC;EAC9C,MAAM,uBAAM,IAAI,KAAK,EAAA,CAAE,YAAY;EACnC,MAAM,WAAW,MAAM,KAAK,IAAI,IAAI;EACpC,IAAI,UACF,OAAQ,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,aAAa,MAAM;GAC1D,UAAU,SAAS,WAAW;GAC9B,YAAY;EACd,CAAC;EAEH,OAAQ,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,aAAa;GACpD;GACA,UAAU;GACV,YAAY;GACZ,WAAW;EACb,CAAC;CACH;CAEA,MAAM,UAAU,MAA0C;EACxD,MAAM,WAAW,MAAM,KAAK,IAAI,IAAI;EACpC,IAAI,CAAC,UAAU,OAAO;EACtB,IAAI,SAAS,YAAY,GAAG;GAC1B,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,aAAa,IAAI;GAClD,OAAO;EACT;EACA,OAAQ,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,aAAa,MAAM;GAC1D,UAAU,SAAS,WAAW;GAC9B,6BAAY,IAAI,KAAK,EAAA,CAAE,YAAY;EACrC,CAAC;CACH;CAEA,MAAM,UAAU,QAAuC;EAErD,QAAO,MADY,KAAK,GAAG,KAAK,KAAK,OAAO,WAAW,EAAA,CAC5C,QAAQ,MAAM,EAAE,aAAa,KAAK,EAAE,aAAa,MAAM;CACpE;AACF"}
1
+ {"version":3,"file":"content-ref.mjs","names":[],"sources":["../../src/models/content-ref.ts"],"sourcesContent":["import type { Table } from \"drizzle-orm\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { ContentRef } from \"../schema/content-ref.ts\";\n\n/** Content refs for deduplicated Storybook assets. */\nexport class ContentRefModel {\n private readonly tables: { contentRefs: Table };\n private readonly db: DatabaseAdapter;\n\n constructor(db: DatabaseAdapter, tables?: { contentRefs: Table }) {\n this.db = db;\n this.tables = tables ?? { contentRefs: db.tables.contentRefs };\n }\n\n async get(hash: string): Promise<ContentRef | null> {\n return (await this.db.get(this.tables.contentRefs, hash)) as unknown as ContentRef | null;\n }\n\n async upsert(hash: string): Promise<ContentRef> {\n const now = new Date().toISOString();\n const existing = await this.get(hash);\n if (existing) {\n return (await this.db.update(this.tables.contentRefs, hash, {\n refCount: existing.refCount + 1,\n lastSeenAt: now,\n })) as unknown as ContentRef;\n }\n return (await this.db.insert(this.tables.contentRefs, {\n hash,\n refCount: 1,\n lastSeenAt: now,\n createdAt: now,\n })) as unknown as ContentRef;\n }\n\n async decrement(hash: string): Promise<ContentRef | null> {\n const existing = await this.get(hash);\n if (!existing) return null;\n if (existing.refCount <= 1) {\n await this.db.remove(this.tables.contentRefs, hash);\n return null;\n }\n return (await this.db.update(this.tables.contentRefs, hash, {\n refCount: existing.refCount - 1,\n lastSeenAt: new Date().toISOString(),\n })) as unknown as ContentRef;\n }\n\n async listStale(cutoff: string): Promise<ContentRef[]> {\n const all = (await this.db.list(this.tables.contentRefs)) as unknown as ContentRef[];\n return all.filter((r) => r.refCount === 0 && r.lastSeenAt < cutoff);\n }\n}\n"],"mappings":";;;;;AAKA,IAAa,kBAAb,MAA6B;CAC3B;CACA;CAEA,YAAY,IAAqB,QAAiC;EAChE,KAAK,KAAK;EACV,KAAK,SAAS,UAAU,EAAE,aAAa,GAAG,OAAO,YAAY;CAC/D;CAEA,MAAM,IAAI,MAA0C;EAClD,OAAQ,MAAM,KAAK,GAAG,IAAI,KAAK,OAAO,aAAa,IAAI;CACzD;CAEA,MAAM,OAAO,MAAmC;EAC9C,MAAM,uBAAM,IAAI,KAAK,EAAA,CAAE,YAAY;EACnC,MAAM,WAAW,MAAM,KAAK,IAAI,IAAI;EACpC,IAAI,UACF,OAAQ,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,aAAa,MAAM;GAC1D,UAAU,SAAS,WAAW;GAC9B,YAAY;EACd,CAAC;EAEH,OAAQ,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,aAAa;GACpD;GACA,UAAU;GACV,YAAY;GACZ,WAAW;EACb,CAAC;CACH;CAEA,MAAM,UAAU,MAA0C;EACxD,MAAM,WAAW,MAAM,KAAK,IAAI,IAAI;EACpC,IAAI,CAAC,UAAU,OAAO;EACtB,IAAI,SAAS,YAAY,GAAG;GAC1B,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,aAAa,IAAI;GAClD,OAAO;EACT;EACA,OAAQ,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,aAAa,MAAM;GAC1D,UAAU,SAAS,WAAW;GAC9B,6BAAY,IAAI,KAAK,EAAA,CAAE,YAAY;EACrC,CAAC;CACH;CAEA,MAAM,UAAU,QAAuC;EAErD,QAAO,MADY,KAAK,GAAG,KAAK,KAAK,OAAO,WAAW,EAAA,CAC5C,QAAQ,MAAM,EAAE,aAAa,KAAK,EAAE,aAAa,MAAM;CACpE;AACF"}
@@ -11,10 +11,20 @@ const SOURCES = [
11
11
  column: "secretEncrypted"
12
12
  },
13
13
  {
14
- name: "git status tokens",
15
- table: "projectStatusConfigs",
14
+ name: "design credentials",
15
+ table: "designCredentials",
16
16
  column: "tokenEncrypted"
17
17
  },
18
+ {
19
+ name: "user connections",
20
+ table: "userConnections",
21
+ column: "tokenEncrypted"
22
+ },
23
+ {
24
+ name: "user connection refresh tokens",
25
+ table: "userConnections",
26
+ column: "refreshTokenEncrypted"
27
+ },
18
28
  {
19
29
  name: "notification channels",
20
30
  table: "notificationChannels",