@storyshelf/core 0.3.2 → 0.5.3

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 (393) hide show
  1. package/README.md +38 -31
  2. package/dist/adapter/capture-queue.d.mts +29 -1
  3. package/dist/adapter/capture-runner.d.mts +18 -2
  4. package/dist/adapter/capture-runner.mjs +12 -1
  5. package/dist/adapter/capture-runner.mjs.map +1 -0
  6. package/dist/adapter/database.d.mts +67 -17
  7. package/dist/adapter/database.mjs +22 -55
  8. package/dist/adapter/database.mjs.map +1 -1
  9. package/dist/adapter/email-sender.d.mts +27 -0
  10. package/dist/adapter/email-sender.mjs +4 -0
  11. package/dist/adapter/metadata.d.mts +21 -15
  12. package/dist/adapter/notifier.d.mts +7 -0
  13. package/dist/adapter/notifier.mjs +6 -0
  14. package/dist/adapter/setup.d.mts +61 -0
  15. package/dist/adapter/setup.mjs +29 -0
  16. package/dist/adapter/setup.mjs.map +1 -0
  17. package/dist/adapter/webhook-events.d.mts +29 -0
  18. package/dist/adapter/webhook-events.mjs +83 -0
  19. package/dist/adapter/webhook-events.mjs.map +1 -0
  20. package/dist/adapters/notifier/events.d.mts +26 -0
  21. package/dist/adapters/notifier/events.mjs +66 -0
  22. package/dist/adapters/notifier/events.mjs.map +1 -0
  23. package/dist/adapters/notifier/format.d.mts +14 -0
  24. package/dist/adapters/notifier/format.mjs +96 -0
  25. package/dist/adapters/notifier/format.mjs.map +1 -0
  26. package/dist/adapters/notifier/provider.d.mts +32 -0
  27. package/dist/adapters/notifier/types.d.mts +56 -0
  28. package/dist/adapters/setup-runner.d.mts +23 -0
  29. package/dist/adapters/setup-runner.mjs +149 -0
  30. package/dist/adapters/setup-runner.mjs.map +1 -0
  31. package/dist/adapters/validate.d.mts +30 -0
  32. package/dist/adapters/validate.mjs +313 -0
  33. package/dist/adapters/validate.mjs.map +1 -0
  34. package/dist/auth.d.mts +94 -0
  35. package/dist/auth.mjs +4 -0
  36. package/dist/brand-packs.d.mts +16 -0
  37. package/dist/brand-packs.mjs +130 -0
  38. package/dist/brand-packs.mjs.map +1 -0
  39. package/dist/capture/adapter.d.mts +51 -1
  40. package/dist/capture/adapter.mjs.map +1 -1
  41. package/dist/capture/affected.d.mts +16 -0
  42. package/dist/capture/affected.mjs +64 -0
  43. package/dist/capture/affected.mjs.map +1 -0
  44. package/dist/capture/diff-record.mjs +30 -0
  45. package/dist/capture/diff-record.mjs.map +1 -0
  46. package/dist/capture/dispatch.d.mts +36 -0
  47. package/dist/capture/dispatch.mjs +88 -31
  48. package/dist/capture/dispatch.mjs.map +1 -1
  49. package/dist/capture/guards.mjs +31 -0
  50. package/dist/capture/guards.mjs.map +1 -0
  51. package/dist/capture/orchestrator.d.mts +41 -6
  52. package/dist/capture/orchestrator.mjs +151 -28
  53. package/dist/capture/orchestrator.mjs.map +1 -1
  54. package/dist/capture/pipeline.d.mts +30 -16
  55. package/dist/capture/pipeline.mjs +143 -39
  56. package/dist/capture/pipeline.mjs.map +1 -1
  57. package/dist/capture/recovery.d.mts +21 -0
  58. package/dist/capture/recovery.mjs +83 -0
  59. package/dist/capture/recovery.mjs.map +1 -0
  60. package/dist/capture/rediff.d.mts +35 -0
  61. package/dist/capture/rediff.mjs +77 -0
  62. package/dist/capture/rediff.mjs.map +1 -0
  63. package/dist/capture/sizing.d.mts +58 -0
  64. package/dist/capture/sizing.mjs +119 -0
  65. package/dist/capture/sizing.mjs.map +1 -0
  66. package/dist/capture/skip-checks.mjs +3 -3
  67. package/dist/capture/skip-checks.mjs.map +1 -1
  68. package/dist/capture/statics.d.mts +38 -0
  69. package/dist/capture/statics.mjs +38 -4
  70. package/dist/capture/statics.mjs.map +1 -1
  71. package/dist/capture/status-fanout.mjs +7 -2
  72. package/dist/capture/status-fanout.mjs.map +1 -1
  73. package/dist/capture/storybook.d.mts +31 -2
  74. package/dist/capture/storybook.mjs +77 -2
  75. package/dist/capture/storybook.mjs.map +1 -1
  76. package/dist/capture.d.mts +10 -4
  77. package/dist/capture.mjs +7 -2
  78. package/dist/config.d.mts +121 -4
  79. package/dist/config.mjs +55 -8
  80. package/dist/config.mjs.map +1 -1
  81. package/dist/index.d.mts +5 -56
  82. package/dist/index.mjs +4 -284
  83. package/dist/logger.d.mts +26 -17
  84. package/dist/logger.mjs +10 -5
  85. package/dist/logger.mjs.map +1 -1
  86. package/dist/models/baseline-status.d.mts +29 -0
  87. package/dist/models/baseline-status.mjs +26 -0
  88. package/dist/models/baseline-status.mjs.map +1 -0
  89. package/dist/models/baseline.d.mts +33 -0
  90. package/dist/models/baseline.mjs +35 -13
  91. package/dist/models/baseline.mjs.map +1 -1
  92. package/dist/models/build.d.mts +87 -0
  93. package/dist/models/build.mjs +83 -35
  94. package/dist/models/build.mjs.map +1 -1
  95. package/dist/models/capture-attempt.d.mts +40 -0
  96. package/dist/models/capture-attempt.mjs +85 -0
  97. package/dist/models/capture-attempt.mjs.map +1 -0
  98. package/dist/models/capture-log.d.mts +21 -0
  99. package/dist/models/capture-log.mjs +44 -0
  100. package/dist/models/capture-log.mjs.map +1 -0
  101. package/dist/models/comment.d.mts +29 -0
  102. package/dist/models/comment.mjs +11 -8
  103. package/dist/models/comment.mjs.map +1 -1
  104. package/dist/models/content-ref.d.mts +21 -0
  105. package/dist/models/content-ref.mjs +49 -0
  106. package/dist/models/content-ref.mjs.map +1 -0
  107. package/dist/models/credentials.d.mts +36 -0
  108. package/dist/models/credentials.mjs +103 -0
  109. package/dist/models/credentials.mjs.map +1 -0
  110. package/dist/models/label.d.mts +58 -0
  111. package/dist/models/label.mjs +32 -27
  112. package/dist/models/label.mjs.map +1 -1
  113. package/dist/models/member.d.mts +34 -0
  114. package/dist/models/member.mjs +20 -11
  115. package/dist/models/member.mjs.map +1 -1
  116. package/dist/models/notification-channel.d.mts +52 -0
  117. package/dist/models/notification-channel.mjs +93 -0
  118. package/dist/models/notification-channel.mjs.map +1 -0
  119. package/dist/models/notification-subscription.d.mts +44 -0
  120. package/dist/models/notification-subscription.mjs +89 -0
  121. package/dist/models/notification-subscription.mjs.map +1 -0
  122. package/dist/models/project-group-mapping.d.mts +30 -0
  123. package/dist/models/project-group-mapping.mjs +46 -0
  124. package/dist/models/project-group-mapping.mjs.map +1 -0
  125. package/dist/models/project.d.mts +54 -0
  126. package/dist/models/project.mjs +15 -11
  127. package/dist/models/project.mjs.map +1 -1
  128. package/dist/models/snapshot.d.mts +58 -0
  129. package/dist/models/snapshot.mjs +13 -7
  130. package/dist/models/snapshot.mjs.map +1 -1
  131. package/dist/models/status-config.d.mts +38 -0
  132. package/dist/models/status-config.mjs +12 -11
  133. package/dist/models/status-config.mjs.map +1 -1
  134. package/dist/models/token.d.mts +44 -0
  135. package/dist/models/token.mjs +18 -15
  136. package/dist/models/token.mjs.map +1 -1
  137. package/dist/models/user.d.mts +42 -0
  138. package/dist/models/user.mjs +65 -0
  139. package/dist/models/user.mjs.map +1 -0
  140. package/dist/models/webhook.d.mts +50 -0
  141. package/dist/models/webhook.mjs +12 -10
  142. package/dist/models/webhook.mjs.map +1 -1
  143. package/dist/models.d.mts +22 -0
  144. package/dist/models.mjs +23 -0
  145. package/dist/paths.mjs +29 -0
  146. package/dist/paths.mjs.map +1 -1
  147. package/dist/retention.d.mts +45 -0
  148. package/dist/retention.mjs +145 -0
  149. package/dist/retention.mjs.map +1 -0
  150. package/dist/schema/baseline.d.mts +1 -0
  151. package/dist/schema/build.d.mts +8 -0
  152. package/dist/schema/capture-attempt.d.mts +38 -0
  153. package/dist/schema/comment.d.mts +1 -1
  154. package/dist/schema/content-ref.d.mts +13 -0
  155. package/dist/schema/member.d.mts +2 -0
  156. package/dist/schema/notification.d.mts +35 -0
  157. package/dist/schema/project-group-mapping.d.mts +15 -0
  158. package/dist/schema/project.d.mts +4 -0
  159. package/dist/schema/snapshot.d.mts +7 -0
  160. package/dist/schema/token.d.mts +2 -0
  161. package/dist/schema/user.d.mts +11 -1
  162. package/dist/schema.d.mts +9 -46
  163. package/dist/schema.mjs +1 -55
  164. package/dist/test-helpers/create-project.d.mts +15 -0
  165. package/dist/test-helpers/create-project.mjs +28 -0
  166. package/dist/test-helpers/create-project.mjs.map +1 -0
  167. package/dist/test-helpers/database-contract.d.mts +13 -0
  168. package/dist/test-helpers/database-contract.mjs +58 -0
  169. package/dist/test-helpers/database-contract.mjs.map +1 -0
  170. package/dist/test-helpers/fake-adapters.d.mts +14 -0
  171. package/dist/test-helpers/fake-adapters.mjs +20 -0
  172. package/dist/test-helpers/fake-adapters.mjs.map +1 -0
  173. package/dist/test-helpers/fake-database.d.mts +11 -0
  174. package/dist/test-helpers/fake-database.mjs +104 -0
  175. package/dist/test-helpers/fake-database.mjs.map +1 -0
  176. package/dist/test-helpers/fake-storage.d.mts +14 -0
  177. package/dist/test-helpers/fake-storage.mjs +49 -0
  178. package/dist/test-helpers/fake-storage.mjs.map +1 -0
  179. package/dist/test-helpers/fake-tables.mjs +276 -0
  180. package/dist/test-helpers/fake-tables.mjs.map +1 -0
  181. package/dist/test-helpers/git-contract.d.mts +11 -0
  182. package/dist/test-helpers/git-contract.mjs +34 -0
  183. package/dist/test-helpers/git-contract.mjs.map +1 -0
  184. package/dist/test-helpers/notifier-contract.d.mts +11 -0
  185. package/dist/test-helpers/notifier-contract.mjs +34 -0
  186. package/dist/test-helpers/notifier-contract.mjs.map +1 -0
  187. package/dist/test-helpers/queue-contract.d.mts +11 -0
  188. package/dist/test-helpers/queue-contract.mjs +64 -0
  189. package/dist/test-helpers/queue-contract.mjs.map +1 -0
  190. package/dist/test-helpers/runner-contract.d.mts +11 -0
  191. package/dist/test-helpers/runner-contract.mjs +32 -0
  192. package/dist/test-helpers/runner-contract.mjs.map +1 -0
  193. package/dist/test-helpers/sql-chunks.mjs +138 -0
  194. package/dist/test-helpers/sql-chunks.mjs.map +1 -0
  195. package/dist/test-helpers/storage-contract.d.mts +11 -0
  196. package/dist/test-helpers/storage-contract.mjs +87 -0
  197. package/dist/test-helpers/storage-contract.mjs.map +1 -0
  198. package/dist/test-helpers.d.mts +13 -0
  199. package/dist/test-helpers.mjs +14 -0
  200. package/dist/tracing.mjs +63 -0
  201. package/dist/tracing.mjs.map +1 -0
  202. package/dist/types.d.mts +38 -5
  203. package/dist/types.mjs +18 -5
  204. package/dist/types.mjs.map +1 -1
  205. package/dist/urls.d.mts +12 -1
  206. package/dist/urls.mjs +22 -3
  207. package/dist/urls.mjs.map +1 -1
  208. package/dist/utils/encrypt.d.mts +48 -0
  209. package/dist/utils/encrypt.mjs +67 -13
  210. package/dist/utils/encrypt.mjs.map +1 -1
  211. package/dist/utils/hash.d.mts +17 -0
  212. package/dist/utils/hash.mjs +8 -2
  213. package/dist/utils/hash.mjs.map +1 -1
  214. package/dist/utils/http.d.mts +58 -0
  215. package/dist/utils/http.mjs +147 -0
  216. package/dist/utils/http.mjs.map +1 -0
  217. package/dist/utils/mime.d.mts +9 -0
  218. package/dist/utils/mime.mjs +33 -0
  219. package/dist/utils/mime.mjs.map +1 -0
  220. package/dist/utils/path-security.d.mts +12 -0
  221. package/dist/utils/path-security.mjs +24 -0
  222. package/dist/utils/path-security.mjs.map +1 -0
  223. package/dist/utils/redact.d.mts +17 -0
  224. package/dist/utils/redact.mjs +42 -0
  225. package/dist/utils/redact.mjs.map +1 -0
  226. package/dist/utils/timing.d.mts +19 -0
  227. package/dist/utils/timing.mjs +46 -0
  228. package/dist/utils/timing.mjs.map +1 -0
  229. package/dist/utils/ulid.d.mts +10 -0
  230. package/dist/utils/ulid.mjs +8 -8
  231. package/dist/utils/ulid.mjs.map +1 -1
  232. package/dist/utils.d.mts +12 -0
  233. package/dist/utils.mjs +13 -0
  234. package/package.json +69 -28
  235. package/dist/adapter/auth.d.mts +0 -50
  236. package/dist/adapter/auth.mjs +0 -10
  237. package/dist/adapter/auth.mjs.map +0 -1
  238. package/dist/adapter/init.d.mts +0 -58
  239. package/dist/adapter/init.mjs +0 -93
  240. package/dist/adapter/init.mjs.map +0 -1
  241. package/dist/adapters/webhook-events.mjs +0 -55
  242. package/dist/adapters/webhook-events.mjs.map +0 -1
  243. package/dist/ddl.d.mts +0 -8
  244. package/dist/ddl.mjs +0 -158
  245. package/dist/ddl.mjs.map +0 -1
  246. package/dist/index.mjs.map +0 -1
  247. package/dist/middleware/auth-gate.mjs +0 -36
  248. package/dist/middleware/auth-gate.mjs.map +0 -1
  249. package/dist/middleware/csrf.mjs +0 -40
  250. package/dist/middleware/csrf.mjs.map +0 -1
  251. package/dist/middleware/rate-limit.mjs +0 -39
  252. package/dist/middleware/rate-limit.mjs.map +0 -1
  253. package/dist/middleware/request-log.mjs +0 -28
  254. package/dist/middleware/request-log.mjs.map +0 -1
  255. package/dist/middleware/store-scope.mjs +0 -29
  256. package/dist/middleware/store-scope.mjs.map +0 -1
  257. package/dist/pages/build-detail.mjs +0 -339
  258. package/dist/pages/build-detail.mjs.map +0 -1
  259. package/dist/pages/build-diff-comments.mjs +0 -113
  260. package/dist/pages/build-diff-comments.mjs.map +0 -1
  261. package/dist/pages/build-diff-header.mjs +0 -163
  262. package/dist/pages/build-diff-header.mjs.map +0 -1
  263. package/dist/pages/build-diff-nav.mjs +0 -66
  264. package/dist/pages/build-diff-nav.mjs.map +0 -1
  265. package/dist/pages/build-diff-viewer.mjs +0 -197
  266. package/dist/pages/build-diff-viewer.mjs.map +0 -1
  267. package/dist/pages/build-diff.mjs +0 -90
  268. package/dist/pages/build-diff.mjs.map +0 -1
  269. package/dist/pages/compute-jobs.mjs +0 -198
  270. package/dist/pages/compute-jobs.mjs.map +0 -1
  271. package/dist/pages/label-detail.mjs +0 -228
  272. package/dist/pages/label-detail.mjs.map +0 -1
  273. package/dist/pages/login.mjs +0 -78
  274. package/dist/pages/login.mjs.map +0 -1
  275. package/dist/pages/project-builds.mjs +0 -231
  276. package/dist/pages/project-builds.mjs.map +0 -1
  277. package/dist/pages/project-create.mjs +0 -169
  278. package/dist/pages/project-create.mjs.map +0 -1
  279. package/dist/pages/project-settings.mjs +0 -102
  280. package/dist/pages/project-settings.mjs.map +0 -1
  281. package/dist/pages/projects.mjs +0 -184
  282. package/dist/pages/projects.mjs.map +0 -1
  283. package/dist/pages/root.mjs +0 -94
  284. package/dist/pages/root.mjs.map +0 -1
  285. package/dist/pages/settings-general.mjs +0 -192
  286. package/dist/pages/settings-general.mjs.map +0 -1
  287. package/dist/pages/settings-labels.mjs +0 -150
  288. package/dist/pages/settings-labels.mjs.map +0 -1
  289. package/dist/pages/settings-members.mjs +0 -125
  290. package/dist/pages/settings-members.mjs.map +0 -1
  291. package/dist/pages/settings-status.mjs +0 -152
  292. package/dist/pages/settings-status.mjs.map +0 -1
  293. package/dist/pages/settings-tests.mjs +0 -149
  294. package/dist/pages/settings-tests.mjs.map +0 -1
  295. package/dist/pages/settings-tokens.mjs +0 -96
  296. package/dist/pages/settings-tokens.mjs.map +0 -1
  297. package/dist/pages/settings-webhooks.mjs +0 -150
  298. package/dist/pages/settings-webhooks.mjs.map +0 -1
  299. package/dist/pages/storybook.mjs +0 -96
  300. package/dist/pages/storybook.mjs.map +0 -1
  301. package/dist/retention/purge.mjs +0 -70
  302. package/dist/retention/purge.mjs.map +0 -1
  303. package/dist/routers/admin.mjs +0 -42
  304. package/dist/routers/admin.mjs.map +0 -1
  305. package/dist/routers/assets.mjs +0 -25
  306. package/dist/routers/assets.mjs.map +0 -1
  307. package/dist/routers/auth.mjs +0 -78
  308. package/dist/routers/auth.mjs.map +0 -1
  309. package/dist/routers/builds.handlers.mjs +0 -184
  310. package/dist/routers/builds.handlers.mjs.map +0 -1
  311. package/dist/routers/builds.mjs +0 -185
  312. package/dist/routers/builds.mjs.map +0 -1
  313. package/dist/routers/comments.mjs +0 -90
  314. package/dist/routers/comments.mjs.map +0 -1
  315. package/dist/routers/health.mjs +0 -137
  316. package/dist/routers/health.mjs.map +0 -1
  317. package/dist/routers/helpers.mjs +0 -82
  318. package/dist/routers/helpers.mjs.map +0 -1
  319. package/dist/routers/htmx.mjs +0 -20
  320. package/dist/routers/htmx.mjs.map +0 -1
  321. package/dist/routers/labels.mjs +0 -110
  322. package/dist/routers/labels.mjs.map +0 -1
  323. package/dist/routers/media.mjs +0 -55
  324. package/dist/routers/media.mjs.map +0 -1
  325. package/dist/routers/members.mjs +0 -102
  326. package/dist/routers/members.mjs.map +0 -1
  327. package/dist/routers/projects.mjs +0 -122
  328. package/dist/routers/projects.mjs.map +0 -1
  329. package/dist/routers/schemas.mjs +0 -224
  330. package/dist/routers/schemas.mjs.map +0 -1
  331. package/dist/routers/settings-general.mjs +0 -95
  332. package/dist/routers/settings-general.mjs.map +0 -1
  333. package/dist/routers/settings-labels.mjs +0 -49
  334. package/dist/routers/settings-labels.mjs.map +0 -1
  335. package/dist/routers/settings-members.mjs +0 -30
  336. package/dist/routers/settings-members.mjs.map +0 -1
  337. package/dist/routers/settings-status.mjs +0 -73
  338. package/dist/routers/settings-status.mjs.map +0 -1
  339. package/dist/routers/settings-tokens.mjs +0 -40
  340. package/dist/routers/settings-tokens.mjs.map +0 -1
  341. package/dist/routers/settings-webhooks.mjs +0 -63
  342. package/dist/routers/settings-webhooks.mjs.map +0 -1
  343. package/dist/routers/settings.handlers.mjs +0 -93
  344. package/dist/routers/settings.handlers.mjs.map +0 -1
  345. package/dist/routers/settings.mjs +0 -23
  346. package/dist/routers/settings.mjs.map +0 -1
  347. package/dist/routers/snapshots.mjs +0 -144
  348. package/dist/routers/snapshots.mjs.map +0 -1
  349. package/dist/routers/status-configs.mjs +0 -101
  350. package/dist/routers/status-configs.mjs.map +0 -1
  351. package/dist/routers/storybook.mjs +0 -120
  352. package/dist/routers/storybook.mjs.map +0 -1
  353. package/dist/routers/tokens.mjs +0 -81
  354. package/dist/routers/tokens.mjs.map +0 -1
  355. package/dist/routers/ui.mjs +0 -165
  356. package/dist/routers/ui.mjs.map +0 -1
  357. package/dist/routers/webhooks.mjs +0 -90
  358. package/dist/routers/webhooks.mjs.map +0 -1
  359. package/dist/schema/baseline.mjs +0 -22
  360. package/dist/schema/baseline.mjs.map +0 -1
  361. package/dist/schema/build.mjs +0 -29
  362. package/dist/schema/build.mjs.map +0 -1
  363. package/dist/schema/comment.mjs +0 -26
  364. package/dist/schema/comment.mjs.map +0 -1
  365. package/dist/schema/label.mjs +0 -30
  366. package/dist/schema/label.mjs.map +0 -1
  367. package/dist/schema/member.mjs +0 -19
  368. package/dist/schema/member.mjs.map +0 -1
  369. package/dist/schema/project.mjs +0 -25
  370. package/dist/schema/project.mjs.map +0 -1
  371. package/dist/schema/snapshot.mjs +0 -34
  372. package/dist/schema/snapshot.mjs.map +0 -1
  373. package/dist/schema/status-config.mjs +0 -20
  374. package/dist/schema/status-config.mjs.map +0 -1
  375. package/dist/schema/token.mjs +0 -19
  376. package/dist/schema/token.mjs.map +0 -1
  377. package/dist/schema/user.mjs +0 -19
  378. package/dist/schema/user.mjs.map +0 -1
  379. package/dist/schema/webhook.mjs +0 -20
  380. package/dist/schema/webhook.mjs.map +0 -1
  381. package/dist/schema.mjs.map +0 -1
  382. package/dist/store.mjs +0 -20
  383. package/dist/store.mjs.map +0 -1
  384. package/dist/ui/document.mjs +0 -348
  385. package/dist/ui/document.mjs.map +0 -1
  386. package/dist/ui/feedback.mjs +0 -24
  387. package/dist/ui/feedback.mjs.map +0 -1
  388. package/dist/ui/forms.mjs +0 -117
  389. package/dist/ui/forms.mjs.map +0 -1
  390. package/dist/ui/styles.mjs +0 -174
  391. package/dist/ui/styles.mjs.map +0 -1
  392. package/dist/ui/theme.mjs +0 -44
  393. package/dist/ui/theme.mjs.map +0 -1
package/dist/index.mjs CHANGED
@@ -1,288 +1,8 @@
1
1
  import "node:path";
2
2
  import "node:url";
3
3
  import.meta.url;
4
- import { AdapterLifecycleError, collectCloses, collectInits, runAdapterCloses, runAdapterInits } from "./adapter/init.mjs";
5
- import { createDispatchJob } from "./capture/dispatch.mjs";
6
- import { InMemoryCaptureQueue } from "./capture/queue.mjs";
7
- import { validateConfig, validateUiConfig } from "./config.mjs";
4
+ import { DEFAULT_MAX_UPLOAD_BYTES, shelfConfigSchema, uiConfigSchema, validateConfig, validateUiConfig } from "./config.mjs";
5
+ import { brandPack, brandPacks } from "./brand-packs.mjs";
8
6
  import { createShelfLogger } from "./logger.mjs";
9
- import { rateLimit } from "./middleware/rate-limit.mjs";
10
- import { csrf } from "./middleware/csrf.mjs";
11
- import { requestLogging } from "./middleware/request-log.mjs";
12
- import { storeScope } from "./middleware/store-scope.mjs";
13
- import { authGate, resolveRequestUser } from "./middleware/auth-gate.mjs";
14
- import { registerAdmin } from "./routers/admin.mjs";
15
- import { registerAssets } from "./routers/assets.mjs";
16
- import { registerAuth } from "./routers/auth.mjs";
17
- import { registerBuilds } from "./routers/builds.mjs";
18
- import { registerHealth } from "./routers/health.mjs";
19
- import { registerLabels } from "./routers/labels.mjs";
20
- import { registerMedia } from "./routers/media.mjs";
21
- import { registerMembers } from "./routers/members.mjs";
22
- import { registerProjects } from "./routers/projects.mjs";
23
- import { registerStatusConfigs } from "./routers/status-configs.mjs";
24
- import { registerStorybook } from "./routers/storybook.mjs";
25
- import { registerTokens } from "./routers/tokens.mjs";
26
- import { registerUiPages } from "./routers/ui.mjs";
27
- import { registerWebhooks } from "./routers/webhooks.mjs";
28
- import { swaggerUI } from "@hono/swagger-ui";
29
- import { OpenAPIHono } from "@hono/zod-openapi";
30
- import { requestId } from "hono/request-id";
31
- //#region src/index.tsx
32
- /**
33
- * StoryShelf core: compose database, storage, capture, auth, and git-host
34
- * adapters into a complete self-hosted visual-testing Hono server.
35
- */
36
- function addOptionalSnapshots(snap, options) {
37
- if (options.captureRunner) snap["captureRunner"] = options.captureRunner.metadata;
38
- if (options.captureQueue) snap["captureQueue"] = options.captureQueue.metadata;
39
- if (options.auth) snap["auth"] = options.auth.metadata;
40
- }
41
- function buildAdapterSnapshot(options) {
42
- const snap = {
43
- database: options.database.metadata,
44
- storage: options.storage.metadata
45
- };
46
- addOptionalSnapshots(snap, options);
47
- for (const p of options.gitHosts ?? []) snap[`git:${p.metadata.kind}`] = p.metadata;
48
- return snap;
49
- }
50
- /** Validate config/ui and derive runtime singletons from options. */
51
- function resolveRuntime(options) {
52
- const rawConfig = options.config ? validateConfig(options.config) : {};
53
- const ui = options.ui ? validateUiConfig(options.ui) : {};
54
- const logger = options.logger ?? createShelfLogger();
55
- const authEnabled = options.auth !== void 0;
56
- const gitHosts = options.gitHosts ?? [];
57
- return {
58
- config: rawConfig.adapters ? rawConfig : {
59
- ...rawConfig,
60
- adapters: buildAdapterSnapshot(options)
61
- },
62
- ui,
63
- logger,
64
- authEnabled,
65
- gitHosts
66
- };
67
- }
68
- /** Assemble the capture queue and its enqueue hook when a runner is configured. */
69
- function setupCaptureQueue(options, config, gitHosts, logger) {
70
- if (!options.captureRunner) return {
71
- queue: null,
72
- enqueueCapture: void 0
73
- };
74
- if (!config.scratchDir) throw new Error("captureRunner is enabled but ShelfConfig.scratchDir is not set");
75
- const jobOptions = {
76
- db: options.database,
77
- storage: options.storage,
78
- runner: options.captureRunner,
79
- scratchDir: config.scratchDir,
80
- viewports: config.viewports,
81
- logger,
82
- secret: config.secret
83
- };
84
- const runJob = createDispatchJob({
85
- db: options.database,
86
- jobOptions,
87
- gitHosts,
88
- secret: config.secret,
89
- logger
90
- });
91
- const captureQueue = options.captureQueue ?? new InMemoryCaptureQueue({
92
- concurrency: config.captureConcurrency ?? 2,
93
- logger,
94
- runJob
95
- });
96
- const enqueueCapture = async (buildId, reqId) => {
97
- await captureQueue.enqueue({
98
- buildId,
99
- reqId
100
- });
101
- };
102
- return {
103
- queue: captureQueue,
104
- enqueueCapture
105
- };
106
- }
107
- /** Failures safe to expose on the init gate's 503 (no secrets). */
108
- function publicFailures(result) {
109
- return result.failures.map((failure) => ({
110
- category: failure.category,
111
- kind: failure.kind,
112
- error: failure.error
113
- }));
114
- }
115
- /**
116
- * Block requests until adapter init settles; answer 503 with the per-adapter
117
- * failures when init failed. Health probes are exempt (they report init
118
- * state themselves instead of being masked by the gate).
119
- */
120
- function initGate(getReady) {
121
- return async (c, next) => {
122
- if (c.req.path === "/api/v1/health") {
123
- await next();
124
- return;
125
- }
126
- const result = await getReady();
127
- if (!result.ok) return c.json({
128
- error: "Adapters failed to initialize",
129
- failures: publicFailures(result)
130
- }, 503);
131
- await next();
132
- };
133
- }
134
- function trackSettlement(cell, promise) {
135
- promise.then((result) => {
136
- cell.settled = result;
137
- }, () => {
138
- cell.settled = {
139
- ok: false,
140
- failures: []
141
- };
142
- });
143
- }
144
- /** Kick the eager background init run (resolves; never rejects). */
145
- function kickInit(options, ctx, logger, cell) {
146
- cell.ready = runAdapterInits(collectInits(options), ctx, logger);
147
- trackSettlement(cell, cell.ready);
148
- }
149
- /** Kick eager init and attach the `app.lifecycle` namespace (single run). */
150
- function attachLifecycle(app, options, runtime, cell) {
151
- const initCtx = {
152
- config: runtime.config,
153
- logger: runtime.logger
154
- };
155
- kickInit(options, initCtx, runtime.logger, cell);
156
- Object.assign(app, { lifecycle: createLifecycle(options, initCtx, runtime.logger, cell) });
157
- }
158
- /** Build the `app.lifecycle` namespace closing over one settlement cell. */
159
- function createLifecycle(options, ctx, logger, cell) {
160
- return {
161
- get ready() {
162
- return cell.ready;
163
- },
164
- init: async () => {
165
- const result = await runAdapterInits(collectInits(options), ctx, logger);
166
- cell.ready = Promise.resolve(result);
167
- cell.settled = result;
168
- if (!result.ok) throw new AdapterLifecycleError("init", result.failures);
169
- },
170
- close: async () => {
171
- const result = await runAdapterCloses(collectCloses(options), ctx, logger);
172
- if (!result.ok) throw new AdapterLifecycleError("close", result.failures);
173
- }
174
- };
175
- }
176
- /** Attach global middleware: ids, logging, init gate, limits, store scope, auth gate. */
177
- function wireMiddleware(app, wiring) {
178
- const { options, config, ui, logger, authEnabled, enqueueCapture, queue, gitHosts, getReady } = wiring;
179
- app.use("*", requestId());
180
- app.use("*", requestLogging(logger));
181
- app.use("*", initGate(getReady));
182
- app.use("/api/v1/*", rateLimit({
183
- windowMs: 6e4,
184
- max: 100
185
- }));
186
- app.use("/api/v1/tokens/*", rateLimit({
187
- windowMs: 6e4,
188
- max: 10
189
- }));
190
- app.use("/api/v1/webhooks/*", rateLimit({
191
- windowMs: 6e4,
192
- max: 20
193
- }));
194
- app.use("/projects/:slug/settings/*", csrf());
195
- app.use("*", storeScope({
196
- db: options.database,
197
- storage: options.storage,
198
- config,
199
- ui,
200
- logger,
201
- authEnabled,
202
- enqueueCapture,
203
- captureQueue: queue,
204
- gitHosts,
205
- resolveUser: async (c) => await resolveRequestUser(c, options.auth)
206
- }));
207
- app.use("*", authGate());
208
- }
209
- /** Register every JSON API router. */
210
- function registerApiRoutes(app, health) {
211
- registerProjects(app);
212
- registerBuilds(app);
213
- registerLabels(app);
214
- registerMedia(app);
215
- registerMembers(app);
216
- registerTokens(app);
217
- registerWebhooks(app);
218
- registerStatusConfigs(app);
219
- registerHealth(app, health);
220
- registerAdmin(app);
221
- }
222
- /** Register HTML pages, assets, and the optional auth flow. */
223
- function registerPageRoutes(app, options) {
224
- if (options.auth) registerAuth(app, options.auth);
225
- registerAssets(app);
226
- registerStorybook(app);
227
- registerUiPages(app);
228
- }
229
- /** Register the OpenAPI document and interactive docs page. */
230
- function registerDocs(app) {
231
- app.doc("/api/v1/openapi.json", {
232
- openapi: "3.0.0",
233
- info: {
234
- title: "StoryShelf API",
235
- description: "REST API for the StoryShelf visual testing platform. JSON endpoints live under /api/v1; HTML pages are served at /.",
236
- version: "1.0.0"
237
- }
238
- });
239
- app.get("/api/v1/docs", swaggerUI({ url: "/api/v1/openapi.json" }));
240
- }
241
- /** Register every API router, page set, and optional auth flow. */
242
- function registerAllRoutes(app, options, health) {
243
- registerApiRoutes(app, health);
244
- registerPageRoutes(app, options);
245
- }
246
- /** Package version injected at build via __PKG_VERSION__. */
247
- function packageVersion() {
248
- return globalThis.__PKG_VERSION__ ?? "0.0.0";
249
- }
250
- /**
251
- * Create the StoryShelf Hono router with all API routes and HTML pages.
252
- *
253
- * Adapter `init` hooks kick off eagerly in the background (the constructor
254
- * stays synchronous). Await `app.lifecycle.init()` before serving for
255
- * fail-fast startup, or let the first request gate on settlement.
256
- */
257
- function createShelfRouter(options) {
258
- const app = new OpenAPIHono();
259
- const runtime = resolveRuntime(options);
260
- const cell = {
261
- ready: Promise.resolve({
262
- ok: true,
263
- failures: []
264
- }),
265
- settled: null
266
- };
267
- attachLifecycle(app, options, runtime, cell);
268
- const { queue, enqueueCapture } = setupCaptureQueue(options, runtime.config, runtime.gitHosts, runtime.logger);
269
- wireMiddleware(app, {
270
- ...runtime,
271
- queue,
272
- enqueueCapture,
273
- options,
274
- getReady: async () => await cell.ready
275
- });
276
- registerAllRoutes(app, options, {
277
- sources: options,
278
- getSettled: () => cell.settled,
279
- bootTimeMs: Date.now(),
280
- version: packageVersion()
281
- });
282
- registerDocs(app);
283
- return app;
284
- }
285
- //#endregion
286
- export { createShelfRouter };
287
-
288
- //# sourceMappingURL=index.mjs.map
7
+ import { BUILD_STATUSES, SNAPSHOT_STATUSES, TERMINAL_BUILD_STATUSES } from "./types.mjs";
8
+ export { BUILD_STATUSES, DEFAULT_MAX_UPLOAD_BYTES, SNAPSHOT_STATUSES, TERMINAL_BUILD_STATUSES, brandPack, brandPacks, createShelfLogger, shelfConfigSchema, uiConfigSchema, validateConfig, validateUiConfig };
package/dist/logger.d.mts CHANGED
@@ -2,6 +2,26 @@ import __tsdown_shims_path from 'node:path';
2
2
  import __tsdown_shims_url from 'node:url';
3
3
  import { Logger } from "pino";
4
4
  //#region src/logger.d.ts
5
+ /**
6
+ * Create the structured JSON logger used across StoryShelf.
7
+ *
8
+ * Logging conventions:
9
+ * - Always log structured objects, never interpolate into the message:
10
+ * `logger.info({ projectId, buildId }, "build started")`.
11
+ * - Attach errors as an `err` child field rather than stringifying into the message:
12
+ * `logger.error({ err }, "capture failed")`.
13
+ * - Derive scoped child loggers for background work:
14
+ * `const captureLogger = logger.child({ buildId })`.
15
+ *
16
+ * The default sink is stdout. The level resolves as explicit option, then
17
+ * the `LOG_LEVEL` environment variable, then `"info"` — so `LOG_LEVEL=debug`
18
+ * configures a default-constructed logger with no code changes. Unknown
19
+ * levels throw at construction (fail fast on typos). Hosted observability
20
+ * platforms (Sentry, PostHog, Datadog, GCP, OTEL collector, etc.) are added
21
+ * as pino worker `transports` — they are sinks for this logger, not
22
+ * standalone loggers.
23
+ */
24
+ declare function createShelfLogger(options?: LoggerOptions): Logger;
5
25
  /** A pino worker transport to attach to the logger output. */
6
26
  interface PinoTransport {
7
27
  /** Package name or absolute path of the transport module. */
@@ -11,29 +31,18 @@ interface PinoTransport {
11
31
  }
12
32
  /** Configuration for constructing the shelf logger. */
13
33
  interface LoggerOptions {
14
- /** Minimum level to emit. Defaults to `"info"`. */
34
+ /** Minimum level to emit. Defaults to `LOG_LEVEL` env, then `"info"`. */
15
35
  level?: string;
16
36
  /** Extra pino worker transports appended to the default stdout sink. */
17
37
  transports?: PinoTransport[];
18
38
  /** Deployment environment recorded in the `env` base field. Defaults to `NODE_ENV`. */
19
39
  env?: string;
40
+ /**
41
+ * Per-line mixin merged into every log record. Used for OTEL trace
42
+ * correlation (`otelLogMixin` from `@storyshelf/observability`).
43
+ */
44
+ mixin?: () => Record<string, unknown>;
20
45
  }
21
- /**
22
- * Create the structured JSON logger used across StoryShelf.
23
- *
24
- * Logging conventions:
25
- * - Always log structured objects, never interpolate into the message:
26
- * `logger.info({ projectId, buildId }, "build started")`.
27
- * - Attach errors as an `err` child field rather than stringifying into the message:
28
- * `logger.error({ err }, "capture failed")`.
29
- * - Derive scoped child loggers for background work:
30
- * `const captureLogger = logger.child({ buildId })`.
31
- *
32
- * The default sink is stdout. Hosted observability platforms (Sentry, PostHog,
33
- * Datadog, GCP, OTEL collector, etc.) are added as pino worker `transports` —
34
- * they are sinks for this logger, not standalone loggers.
35
- */
36
- declare function createShelfLogger(options?: LoggerOptions): Logger;
37
46
  //#endregion
38
47
  export { type Logger, LoggerOptions, PinoTransport, createShelfLogger };
39
48
  //# sourceMappingURL=logger.d.mts.map
package/dist/logger.mjs CHANGED
@@ -14,9 +14,13 @@ import pino from "pino";
14
14
  * - Derive scoped child loggers for background work:
15
15
  * `const captureLogger = logger.child({ buildId })`.
16
16
  *
17
- * The default sink is stdout. Hosted observability platforms (Sentry, PostHog,
18
- * Datadog, GCP, OTEL collector, etc.) are added as pino worker `transports` —
19
- * they are sinks for this logger, not standalone loggers.
17
+ * The default sink is stdout. The level resolves as explicit option, then
18
+ * the `LOG_LEVEL` environment variable, then `"info"` — so `LOG_LEVEL=debug`
19
+ * configures a default-constructed logger with no code changes. Unknown
20
+ * levels throw at construction (fail fast on typos). Hosted observability
21
+ * platforms (Sentry, PostHog, Datadog, GCP, OTEL collector, etc.) are added
22
+ * as pino worker `transports` — they are sinks for this logger, not
23
+ * standalone loggers.
20
24
  */
21
25
  function createShelfLogger(options = {}) {
22
26
  const targets = [{
@@ -25,8 +29,9 @@ function createShelfLogger(options = {}) {
25
29
  }, ...options.transports ?? []];
26
30
  const transport = pino.transport({ targets });
27
31
  return pino({
28
- level: options.level ?? "info",
29
- base: { env: options.env ?? process.env["NODE_ENV"] }
32
+ level: options.level ?? process.env["LOG_LEVEL"] ?? "info",
33
+ base: { env: options.env ?? process.env["NODE_ENV"] },
34
+ mixin: options.mixin
30
35
  }, transport);
31
36
  }
32
37
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"logger.mjs","names":[],"sources":["../src/logger.ts"],"sourcesContent":["import pino from \"pino\";\nimport type { Logger } from \"pino\";\n\n/** Pino logger type shared across StoryShelf. */\nexport type { Logger };\n\n/** A pino worker transport to attach to the logger output. */\nexport interface PinoTransport {\n /** Package name or absolute path of the transport module. */\n target: string;\n /** Options passed to the transport worker. */\n options?: Record<string, unknown>;\n}\n\n/** Configuration for constructing the shelf logger. */\nexport interface LoggerOptions {\n /** Minimum level to emit. Defaults to `\"info\"`. */\n level?: string;\n /** Extra pino worker transports appended to the default stdout sink. */\n transports?: PinoTransport[];\n /** Deployment environment recorded in the `env` base field. Defaults to `NODE_ENV`. */\n env?: string;\n}\n\n/**\n * Create the structured JSON logger used across StoryShelf.\n *\n * Logging conventions:\n * - Always log structured objects, never interpolate into the message:\n * `logger.info({ projectId, buildId }, \"build started\")`.\n * - Attach errors as an `err` child field rather than stringifying into the message:\n * `logger.error({ err }, \"capture failed\")`.\n * - Derive scoped child loggers for background work:\n * `const captureLogger = logger.child({ buildId })`.\n *\n * The default sink is stdout. Hosted observability platforms (Sentry, PostHog,\n * Datadog, GCP, OTEL collector, etc.) are added as pino worker `transports` —\n * they are sinks for this logger, not standalone loggers.\n */\nexport function createShelfLogger(options: LoggerOptions = {}): Logger {\n const targets = [\n { target: \"pino/file\", options: { destination: 1 } },\n ...(options.transports ?? []),\n ];\n const transport = pino.transport({ targets });\n return pino(\n {\n level: options.level ?? \"info\",\n base: { env: options.env ?? process.env[\"NODE_ENV\"] },\n },\n transport,\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAuCA,SAAgB,kBAAkB,UAAyB,CAAC,GAAW;CACrE,MAAM,UAAU,CACd;EAAE,QAAQ;EAAa,SAAS,EAAE,aAAa,EAAE;CAAE,GACnD,GAAI,QAAQ,cAAc,CAAC,CAC7B;CACA,MAAM,YAAY,KAAK,UAAU,EAAE,QAAQ,CAAC;CAC5C,OAAO,KACL;EACE,OAAO,QAAQ,SAAS;EACxB,MAAM,EAAE,KAAK,QAAQ,OAAO,QAAQ,IAAI,YAAY;CACtD,GACA,SACF;AACF"}
1
+ {"version":3,"file":"logger.mjs","names":[],"sources":["../src/logger.ts"],"sourcesContent":["import pino from \"pino\";\nimport type { Logger } from \"pino\";\n\n/**\n * Create the structured JSON logger used across StoryShelf.\n *\n * Logging conventions:\n * - Always log structured objects, never interpolate into the message:\n * `logger.info({ projectId, buildId }, \"build started\")`.\n * - Attach errors as an `err` child field rather than stringifying into the message:\n * `logger.error({ err }, \"capture failed\")`.\n * - Derive scoped child loggers for background work:\n * `const captureLogger = logger.child({ buildId })`.\n *\n * The default sink is stdout. The level resolves as explicit option, then\n * the `LOG_LEVEL` environment variable, then `\"info\"` — so `LOG_LEVEL=debug`\n * configures a default-constructed logger with no code changes. Unknown\n * levels throw at construction (fail fast on typos). Hosted observability\n * platforms (Sentry, PostHog, Datadog, GCP, OTEL collector, etc.) are added\n * as pino worker `transports` — they are sinks for this logger, not\n * standalone loggers.\n */\nexport function createShelfLogger(options: LoggerOptions = {}): Logger {\n const targets = [\n { target: \"pino/file\", options: { destination: 1 } },\n ...(options.transports ?? []),\n ];\n const transport = pino.transport({ targets });\n return pino(\n {\n level: options.level ?? process.env[\"LOG_LEVEL\"] ?? \"info\",\n base: { env: options.env ?? process.env[\"NODE_ENV\"] },\n mixin: options.mixin,\n },\n transport,\n );\n}\n\n/** Pino logger type shared across StoryShelf. */\nexport type { Logger };\n\n/** A pino worker transport to attach to the logger output. */\nexport interface PinoTransport {\n /** Package name or absolute path of the transport module. */\n target: string;\n /** Options passed to the transport worker. */\n options?: Record<string, unknown>;\n}\n\n/** Configuration for constructing the shelf logger. */\nexport interface LoggerOptions {\n /** Minimum level to emit. Defaults to `LOG_LEVEL` env, then `\"info\"`. */\n level?: string;\n /** Extra pino worker transports appended to the default stdout sink. */\n transports?: PinoTransport[];\n /** Deployment environment recorded in the `env` base field. Defaults to `NODE_ENV`. */\n env?: string;\n /**\n * Per-line mixin merged into every log record. Used for OTEL trace\n * correlation (`otelLogMixin` from `@storyshelf/observability`).\n */\n mixin?: () => Record<string, unknown>;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,kBAAkB,UAAyB,CAAC,GAAW;CACrE,MAAM,UAAU,CACd;EAAE,QAAQ;EAAa,SAAS,EAAE,aAAa,EAAE;CAAE,GACnD,GAAI,QAAQ,cAAc,CAAC,CAC7B;CACA,MAAM,YAAY,KAAK,UAAU,EAAE,QAAQ,CAAC;CAC5C,OAAO,KACL;EACE,OAAO,QAAQ,SAAS,QAAQ,IAAI,gBAAgB;EACpD,MAAM,EAAE,KAAK,QAAQ,OAAO,QAAQ,IAAI,YAAY;EACpD,OAAO,QAAQ;CACjB,GACA,SACF;AACF"}
@@ -0,0 +1,29 @@
1
+ import __tsdown_shims_path from 'node:path';
2
+ import __tsdown_shims_url from 'node:url';
3
+ import { Baseline } from "../schema/baseline.mjs";
4
+ import { Snapshot } from "../schema/snapshot.mjs";
5
+ //#region src/models/baseline-status.d.ts
6
+ /** `baselineVersion` recorded when a story had no baseline at diff time. */
7
+ declare const NO_BASELINE = "none";
8
+ /**
9
+ * How a snapshot's recorded baseline compares with the baseline that applies now.
10
+ *
11
+ * - `current`: still the baseline the diff was computed against.
12
+ * - `stale`: the applicable baseline changed (or a baseline now exists for a new story).
13
+ * - `removed`: the baseline the diff used no longer exists.
14
+ * - `unknown`: legacy row without baseline tracking; never blocks review.
15
+ */
16
+ type BaselineStatus = "current" | "stale" | "removed" | "unknown";
17
+ type TrackedSnapshot = Pick<Snapshot, "baselineId" | "baselineVersion">;
18
+ /**
19
+ * Classify a snapshot against the baseline currently resolved for its story/viewport/branch.
20
+ *
21
+ * @param snapshot - Snapshot with its recorded baseline fields.
22
+ * @param current - Baseline resolved now (own branch, else default branch), or null.
23
+ */
24
+ declare function baselineStatus(snapshot: TrackedSnapshot, current: Baseline | null): BaselineStatus;
25
+ /** True when approving should be refused until the diff is recomputed (or forced). */
26
+ declare function isBaselineDrifted(status: BaselineStatus): boolean;
27
+ //#endregion
28
+ export { BaselineStatus, NO_BASELINE, baselineStatus, isBaselineDrifted };
29
+ //# sourceMappingURL=baseline-status.d.mts.map
@@ -0,0 +1,26 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ //#region src/models/baseline-status.ts
5
+ /** `baselineVersion` recorded when a story had no baseline at diff time. */
6
+ const NO_BASELINE = "none";
7
+ /**
8
+ * Classify a snapshot against the baseline currently resolved for its story/viewport/branch.
9
+ *
10
+ * @param snapshot - Snapshot with its recorded baseline fields.
11
+ * @param current - Baseline resolved now (own branch, else default branch), or null.
12
+ */
13
+ function baselineStatus(snapshot, current) {
14
+ if (snapshot.baselineVersion === null) return "unknown";
15
+ if (snapshot.baselineId === null) return current === null ? "current" : "stale";
16
+ if (current === null) return "removed";
17
+ return current.id === snapshot.baselineId && current.updatedAt === snapshot.baselineVersion ? "current" : "stale";
18
+ }
19
+ /** True when approving should be refused until the diff is recomputed (or forced). */
20
+ function isBaselineDrifted(status) {
21
+ return status === "stale" || status === "removed";
22
+ }
23
+ //#endregion
24
+ export { NO_BASELINE, baselineStatus, isBaselineDrifted };
25
+
26
+ //# sourceMappingURL=baseline-status.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"baseline-status.mjs","names":[],"sources":["../../src/models/baseline-status.ts"],"sourcesContent":["/** Detect when a snapshot's diff was computed against a baseline that has since changed. */\nimport type { Baseline } from \"../schema/baseline.ts\";\nimport type { Snapshot } from \"../schema/snapshot.ts\";\n\n/** `baselineVersion` recorded when a story had no baseline at diff time. */\nexport const NO_BASELINE = \"none\";\n\n/**\n * How a snapshot's recorded baseline compares with the baseline that applies now.\n *\n * - `current`: still the baseline the diff was computed against.\n * - `stale`: the applicable baseline changed (or a baseline now exists for a new story).\n * - `removed`: the baseline the diff used no longer exists.\n * - `unknown`: legacy row without baseline tracking; never blocks review.\n */\nexport type BaselineStatus = \"current\" | \"stale\" | \"removed\" | \"unknown\";\n\ntype TrackedSnapshot = Pick<Snapshot, \"baselineId\" | \"baselineVersion\">;\n\n/**\n * Classify a snapshot against the baseline currently resolved for its story/viewport/branch.\n *\n * @param snapshot - Snapshot with its recorded baseline fields.\n * @param current - Baseline resolved now (own branch, else default branch), or null.\n */\nexport function baselineStatus(\n snapshot: TrackedSnapshot,\n current: Baseline | null,\n): BaselineStatus {\n if (snapshot.baselineVersion === null) {\n return \"unknown\";\n }\n if (snapshot.baselineId === null) {\n return current === null ? \"current\" : \"stale\";\n }\n if (current === null) {\n return \"removed\";\n }\n const same = current.id === snapshot.baselineId && current.updatedAt === snapshot.baselineVersion;\n return same ? \"current\" : \"stale\";\n}\n\n/** True when approving should be refused until the diff is recomputed (or forced). */\nexport function isBaselineDrifted(status: BaselineStatus): boolean {\n return status === \"stale\" || status === \"removed\";\n}\n"],"mappings":";;;;;AAKA,MAAa,cAAc;;;;;;;AAoB3B,SAAgB,eACd,UACA,SACgB;CAChB,IAAI,SAAS,oBAAoB,MAC/B,OAAO;CAET,IAAI,SAAS,eAAe,MAC1B,OAAO,YAAY,OAAO,YAAY;CAExC,IAAI,YAAY,MACd,OAAO;CAGT,OADa,QAAQ,OAAO,SAAS,cAAc,QAAQ,cAAc,SAAS,kBACpE,YAAY;AAC5B;;AAGA,SAAgB,kBAAkB,QAAiC;CACjE,OAAO,WAAW,WAAW,WAAW;AAC1C"}
@@ -0,0 +1,33 @@
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 { StorageAdapter } from "../adapter/storage.mjs";
5
+ import { SecretInput } from "../utils/encrypt.mjs";
6
+ import { WebhookTables } from "./webhook.mjs";
7
+ import { Baseline } from "../schema/baseline.mjs";
8
+ import { Table } from "drizzle-orm";
9
+ //#region src/models/baseline.d.ts
10
+ /** Tables required by {@link BaselineModel}. */
11
+ interface BaselineTables {
12
+ baselines: Table;
13
+ }
14
+ /** Data operations for baseline screenshots. */
15
+ declare class BaselineModel {
16
+ private readonly db;
17
+ private readonly tables;
18
+ private readonly storage;
19
+ private readonly secret?;
20
+ private readonly webhookTables?;
21
+ constructor(db: DatabaseAdapter, tables?: BaselineTables, storage?: StorageAdapter, secret?: SecretInput, webhookTables?: WebhookTables);
22
+ getFor(projectId: string, storyId: string, viewport: string, branch: string): Promise<Baseline | null>;
23
+ resolve(projectId: string, storyId: string, viewport: string, branch: string, defaultBranch: string): Promise<Baseline | null>;
24
+ read(baseline: Baseline): Promise<Buffer>;
25
+ upsert(projectId: string, storyId: string, viewport: string, branch: string, snapshotId: string, sourcePath: string, infraHash?: string | null): Promise<Baseline>;
26
+ list(projectId: string): Promise<Baseline[]>;
27
+ removeOrphans(projectId: string, validStoryIds: Set<string>): Promise<number>;
28
+ removeForBranch(projectId: string, branch: string): Promise<number>;
29
+ removeStaleBranches(projectId: string, staleBranches: ReadonlySet<string>): Promise<number>;
30
+ }
31
+ //#endregion
32
+ export { BaselineModel, BaselineTables };
33
+ //# sourceMappingURL=baseline.d.mts.map
@@ -2,25 +2,28 @@ import "node:path";
2
2
  import "node:url";
3
3
  import.meta.url;
4
4
  import { ulid } from "../utils/ulid.mjs";
5
- import { emitWebhookEvent } from "../adapters/webhook-events.mjs";
6
- import { baselines } from "../schema/baseline.mjs";
5
+ import { emitWebhookEvent } from "../adapter/webhook-events.mjs";
7
6
  import { baselinePath } from "../paths.mjs";
8
- import { and, eq } from "drizzle-orm";
7
+ import { and, eq, getTableColumns } from "drizzle-orm";
9
8
  //#region src/models/baseline.ts
10
9
  /** Per-branch baseline screenshots with default-branch fallback. */
11
10
  /** Data operations for baseline screenshots. */
12
11
  var BaselineModel = class {
13
12
  db;
13
+ tables;
14
14
  storage;
15
15
  secret;
16
- constructor(db, storage, secret) {
16
+ webhookTables;
17
+ constructor(db, tables, storage, secret, webhookTables) {
17
18
  this.db = db;
19
+ this.tables = tables ?? { baselines: db.tables.baselines };
18
20
  this.storage = storage;
19
21
  this.secret = secret;
22
+ this.webhookTables = webhookTables;
20
23
  }
21
24
  async getFor(projectId, storyId, viewport, branch) {
22
- return (await this.db.list(baselines, {
23
- where: and(eq(baselines.projectId, projectId), eq(baselines.storyId, storyId), eq(baselines.viewportName, viewport), eq(baselines.branch, branch)),
25
+ return (await this.db.list(this.tables.baselines, {
26
+ where: and(eq(getTableColumns(this.tables.baselines)["projectId"], projectId), eq(getTableColumns(this.tables.baselines)["storyId"], storyId), eq(getTableColumns(this.tables.baselines)["viewportName"], viewport), eq(getTableColumns(this.tables.baselines)["branch"], branch)),
24
27
  limit: 1
25
28
  }))[0] ?? null;
26
29
  }
@@ -34,19 +37,20 @@ var BaselineModel = class {
34
37
  async read(baseline) {
35
38
  return await this.storage.read(baseline.screenshotPath);
36
39
  }
37
- async upsert(projectId, storyId, viewport, branch, snapshotId, sourcePath) {
40
+ async upsert(projectId, storyId, viewport, branch, snapshotId, sourcePath, infraHash) {
38
41
  const screenshotPath = baselinePath(projectId, branch, storyId, viewport);
39
42
  const source = await this.storage.read(sourcePath);
40
43
  await this.storage.write(screenshotPath, source);
41
44
  const existing = await this.getFor(projectId, storyId, viewport, branch);
42
45
  let baseline;
43
46
  if (existing) {
44
- baseline = await this.db.update(baselines, existing.id, {
47
+ baseline = await this.db.update(this.tables.baselines, existing.id, {
45
48
  snapshotId,
46
49
  screenshotPath,
50
+ infraHash: infraHash ?? existing.infraHash ?? null,
47
51
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
48
52
  });
49
- await emitWebhookEvent(this.db, projectId, "baseline:updated", {
53
+ if (this.webhookTables) await emitWebhookEvent(this.db, this.webhookTables, projectId, "baseline:updated", {
50
54
  baselineId: baseline.id,
51
55
  storyId,
52
56
  viewport,
@@ -55,7 +59,7 @@ var BaselineModel = class {
55
59
  }, this.secret);
56
60
  } else {
57
61
  const now = (/* @__PURE__ */ new Date()).toISOString();
58
- baseline = await this.db.insert(baselines, {
62
+ baseline = await this.db.insert(this.tables.baselines, {
59
63
  id: ulid(),
60
64
  projectId,
61
65
  storyId,
@@ -63,10 +67,11 @@ var BaselineModel = class {
63
67
  branch,
64
68
  snapshotId,
65
69
  screenshotPath,
70
+ infraHash: infraHash ?? null,
66
71
  createdAt: now,
67
72
  updatedAt: now
68
73
  });
69
- await emitWebhookEvent(this.db, projectId, "baseline:created", {
74
+ if (this.webhookTables) await emitWebhookEvent(this.db, this.webhookTables, projectId, "baseline:created", {
70
75
  baselineId: baseline.id,
71
76
  storyId,
72
77
  viewport,
@@ -77,15 +82,32 @@ var BaselineModel = class {
77
82
  return baseline;
78
83
  }
79
84
  async list(projectId) {
80
- return await this.db.list(baselines, { where: eq(baselines.projectId, projectId) });
85
+ return await this.db.list(this.tables.baselines, { where: eq(getTableColumns(this.tables.baselines)["projectId"], projectId) });
81
86
  }
82
87
  async removeOrphans(projectId, validStoryIds) {
83
88
  const toRemove = (await this.list(projectId)).filter((baseline) => !validStoryIds.has(baseline.storyId));
84
89
  await Promise.all(toRemove.map(async (baseline) => {
85
- await this.db.remove(baselines, baseline.id);
90
+ try {
91
+ await this.storage.delete(baseline.screenshotPath);
92
+ } catch {}
93
+ await this.db.remove(this.tables.baselines, baseline.id);
86
94
  }));
87
95
  return toRemove.length;
88
96
  }
97
+ async removeForBranch(projectId, branch) {
98
+ const toRemove = (await this.list(projectId)).filter((baseline) => baseline.branch === branch);
99
+ await Promise.all(toRemove.map(async (baseline) => {
100
+ try {
101
+ await this.storage.delete(baseline.screenshotPath);
102
+ } catch {}
103
+ await this.db.remove(this.tables.baselines, baseline.id);
104
+ }));
105
+ return toRemove.length;
106
+ }
107
+ async removeStaleBranches(projectId, staleBranches) {
108
+ if (staleBranches.size === 0) return 0;
109
+ return (await Promise.all([...staleBranches].map((branch) => this.removeForBranch(projectId, branch)))).reduce((sum, count) => sum + count, 0);
110
+ }
89
111
  };
90
112
  //#endregion
91
113
  export { BaselineModel };
@@ -1 +1 @@
1
- {"version":3,"file":"baseline.mjs","names":[],"sources":["../../src/models/baseline.ts"],"sourcesContent":["/** Per-branch baseline screenshots with default-branch fallback. */\nimport { and, eq } from \"drizzle-orm\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { StorageAdapter } from \"../adapters/storage.ts\";\nimport { emitWebhookEvent } from \"../adapters/webhook-events.ts\";\nimport { baselines } from \"../schema/baseline.ts\";\nimport type { Baseline } from \"../schema/baseline.ts\";\nimport { baselinePath } from \"../utils/paths.ts\";\nimport { ulid } from \"../utils/ulid.ts\";\n\n/** Data operations for baseline screenshots. */\nexport class BaselineModel {\n constructor(\n private readonly db: DatabaseAdapter,\n private readonly storage: StorageAdapter,\n private readonly secret?: string,\n ) {}\n\n async getFor(\n projectId: string,\n storyId: string,\n viewport: string,\n branch: string,\n ): Promise<Baseline | null> {\n const rows = await this.db.list(baselines, {\n where: and(\n eq(baselines.projectId, projectId),\n eq(baselines.storyId, storyId),\n eq(baselines.viewportName, viewport),\n eq(baselines.branch, branch),\n ),\n limit: 1,\n });\n return rows[0] ?? null;\n }\n\n async resolve(\n projectId: string,\n storyId: string,\n viewport: string,\n branch: string,\n defaultBranch: string,\n ): Promise<Baseline | null> {\n if (branch !== defaultBranch) {\n const own = await this.getFor(projectId, storyId, viewport, branch);\n if (own) {\n return own;\n }\n }\n return this.getFor(projectId, storyId, viewport, defaultBranch);\n }\n\n async read(baseline: Baseline): Promise<Buffer> {\n return await this.storage.read(baseline.screenshotPath);\n }\n\n async upsert(\n projectId: string,\n storyId: string,\n viewport: string,\n branch: string,\n snapshotId: string,\n sourcePath: string,\n ): Promise<Baseline> {\n const screenshotPath = baselinePath(projectId, branch, storyId, viewport);\n const source = await this.storage.read(sourcePath);\n await this.storage.write(screenshotPath, source);\n\n const existing = await this.getFor(projectId, storyId, viewport, branch);\n let baseline: Baseline;\n if (existing) {\n baseline = await this.db.update(baselines, existing.id, {\n snapshotId,\n screenshotPath,\n updatedAt: new Date().toISOString(),\n });\n await emitWebhookEvent(this.db, projectId, \"baseline:updated\", {\n baselineId: baseline.id,\n storyId,\n viewport,\n branch,\n snapshotId,\n }, this.secret);\n } else {\n const now = new Date().toISOString();\n baseline = await this.db.insert(baselines, {\n id: ulid(),\n projectId,\n storyId,\n viewportName: viewport,\n branch,\n snapshotId,\n screenshotPath,\n createdAt: now,\n updatedAt: now,\n });\n await emitWebhookEvent(this.db, projectId, \"baseline:created\", {\n baselineId: baseline.id,\n storyId,\n viewport,\n branch,\n snapshotId,\n }, this.secret);\n }\n return baseline;\n }\n\n async list(projectId: string): Promise<Baseline[]> {\n return await this.db.list(baselines, { where: eq(baselines.projectId, projectId) });\n }\n\n async removeOrphans(projectId: string, validStoryIds: Set<string>): Promise<number> {\n const all = await this.list(projectId);\n const toRemove = all.filter((baseline) => !validStoryIds.has(baseline.storyId));\n await Promise.all(\n toRemove.map(async (baseline) => {\n await this.db.remove(baselines, baseline.id);\n }),\n );\n return toRemove.length;\n }\n}\n"],"mappings":";;;;;;;;;;;AAWA,IAAa,gBAAb,MAA2B;CAEN;CACA;CACA;CAHnB,YACE,IACA,SACA,QACA;EAHiB,KAAA,KAAA;EACA,KAAA,UAAA;EACA,KAAA,SAAA;CAChB;CAEH,MAAM,OACJ,WACA,SACA,UACA,QAC0B;EAU1B,QAAO,MATY,KAAK,GAAG,KAAK,WAAW;GACzC,OAAO,IACL,GAAG,UAAU,WAAW,SAAS,GACjC,GAAG,UAAU,SAAS,OAAO,GAC7B,GAAG,UAAU,cAAc,QAAQ,GACnC,GAAG,UAAU,QAAQ,MAAM,CAC7B;GACA,OAAO;EACT,CAAC,EAAA,CACW,MAAM;CACpB;CAEA,MAAM,QACJ,WACA,SACA,UACA,QACA,eAC0B;EAC1B,IAAI,WAAW,eAAe;GAC5B,MAAM,MAAM,MAAM,KAAK,OAAO,WAAW,SAAS,UAAU,MAAM;GAClE,IAAI,KACF,OAAO;EAEX;EACA,OAAO,KAAK,OAAO,WAAW,SAAS,UAAU,aAAa;CAChE;CAEA,MAAM,KAAK,UAAqC;EAC9C,OAAO,MAAM,KAAK,QAAQ,KAAK,SAAS,cAAc;CACxD;CAEA,MAAM,OACJ,WACA,SACA,UACA,QACA,YACA,YACmB;EACnB,MAAM,iBAAiB,aAAa,WAAW,QAAQ,SAAS,QAAQ;EACxE,MAAM,SAAS,MAAM,KAAK,QAAQ,KAAK,UAAU;EACjD,MAAM,KAAK,QAAQ,MAAM,gBAAgB,MAAM;EAE/C,MAAM,WAAW,MAAM,KAAK,OAAO,WAAW,SAAS,UAAU,MAAM;EACvE,IAAI;EACJ,IAAI,UAAU;GACZ,WAAW,MAAM,KAAK,GAAG,OAAO,WAAW,SAAS,IAAI;IACtD;IACA;IACA,4BAAW,IAAI,KAAK,EAAA,CAAE,YAAY;GACpC,CAAC;GACD,MAAM,iBAAiB,KAAK,IAAI,WAAW,oBAAoB;IAC7D,YAAY,SAAS;IACrB;IACA;IACA;IACA;GACF,GAAG,KAAK,MAAM;EAChB,OAAO;GACL,MAAM,uBAAM,IAAI,KAAK,EAAA,CAAE,YAAY;GACnC,WAAW,MAAM,KAAK,GAAG,OAAO,WAAW;IACzC,IAAI,KAAK;IACT;IACA;IACA,cAAc;IACd;IACA;IACA;IACA,WAAW;IACX,WAAW;GACb,CAAC;GACD,MAAM,iBAAiB,KAAK,IAAI,WAAW,oBAAoB;IAC7D,YAAY,SAAS;IACrB;IACA;IACA;IACA;GACF,GAAG,KAAK,MAAM;EAChB;EACA,OAAO;CACT;CAEA,MAAM,KAAK,WAAwC;EACjD,OAAO,MAAM,KAAK,GAAG,KAAK,WAAW,EAAE,OAAO,GAAG,UAAU,WAAW,SAAS,EAAE,CAAC;CACpF;CAEA,MAAM,cAAc,WAAmB,eAA6C;EAElF,MAAM,YAAW,MADC,KAAK,KAAK,SAAS,EAAA,CAChB,QAAQ,aAAa,CAAC,cAAc,IAAI,SAAS,OAAO,CAAC;EAC9E,MAAM,QAAQ,IACZ,SAAS,IAAI,OAAO,aAAa;GAC/B,MAAM,KAAK,GAAG,OAAO,WAAW,SAAS,EAAE;EAC7C,CAAC,CACH;EACA,OAAO,SAAS;CAClB;AACF"}
1
+ {"version":3,"file":"baseline.mjs","names":[],"sources":["../../src/models/baseline.ts"],"sourcesContent":["/* oxlint-disable typescript/promise-function-async -- map callbacks return promises from async helpers */\n/** Per-branch baseline screenshots with default-branch fallback. */\nimport { and, eq, getTableColumns } from \"drizzle-orm\";\nimport type { SQLWrapper, Table } from \"drizzle-orm\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport type { StorageAdapter } from \"../adapters/storage.ts\";\nimport { emitWebhookEvent } from \"../adapters/webhook-events.ts\";\nimport type { Baseline } from \"../schema/baseline.ts\";\nimport type { SecretInput } from \"../utils/encrypt.ts\";\nimport { baselinePath } from \"../utils/paths.ts\";\nimport { ulid } from \"../utils/ulid.ts\";\nimport type { WebhookTables } from \"./webhook.ts\";\n\n/** Tables required by {@link BaselineModel}. */\nexport interface BaselineTables {\n baselines: Table;\n}\n\n/** Data operations for baseline screenshots. */\nexport class BaselineModel {\n private readonly tables: BaselineTables;\n private readonly storage: StorageAdapter;\n private readonly secret?: SecretInput;\n private readonly webhookTables?: WebhookTables;\n constructor(\n private readonly db: DatabaseAdapter,\n tables?: BaselineTables,\n storage?: StorageAdapter,\n secret?: SecretInput,\n webhookTables?: WebhookTables,\n ) {\n this.tables = tables ?? { baselines: db.tables.baselines };\n this.storage = storage as StorageAdapter;\n this.secret = secret;\n this.webhookTables = webhookTables;\n }\n\n async getFor(\n projectId: string,\n storyId: string,\n viewport: string,\n branch: string,\n ): Promise<Baseline | null> {\n const rows = (await this.db.list(this.tables.baselines, {\n where: and(\n eq(getTableColumns(this.tables.baselines)[\"projectId\"] as unknown as SQLWrapper, projectId),\n eq(getTableColumns(this.tables.baselines)[\"storyId\"] as unknown as SQLWrapper, storyId),\n eq(\n getTableColumns(this.tables.baselines)[\"viewportName\"] as unknown as SQLWrapper,\n viewport,\n ),\n eq(getTableColumns(this.tables.baselines)[\"branch\"] as unknown as SQLWrapper, branch),\n ),\n limit: 1,\n })) as unknown as Baseline[];\n return rows[0] ?? null;\n }\n\n async resolve(\n projectId: string,\n storyId: string,\n viewport: string,\n branch: string,\n defaultBranch: string,\n ): Promise<Baseline | null> {\n if (branch !== defaultBranch) {\n const own = await this.getFor(projectId, storyId, viewport, branch);\n if (own) {\n return own;\n }\n }\n return this.getFor(projectId, storyId, viewport, defaultBranch);\n }\n\n async read(baseline: Baseline): Promise<Buffer> {\n return await this.storage.read(baseline.screenshotPath);\n }\n\n async upsert(\n projectId: string,\n storyId: string,\n viewport: string,\n branch: string,\n snapshotId: string,\n sourcePath: string,\n infraHash?: string | null,\n ): Promise<Baseline> {\n const screenshotPath = baselinePath(projectId, branch, storyId, viewport);\n const source = await this.storage.read(sourcePath);\n await this.storage.write(screenshotPath, source);\n\n const existing = await this.getFor(projectId, storyId, viewport, branch);\n let baseline: Baseline;\n if (existing) {\n baseline = (await this.db.update(this.tables.baselines, existing.id, {\n snapshotId,\n screenshotPath,\n infraHash: infraHash ?? existing.infraHash ?? null,\n updatedAt: new Date().toISOString(),\n })) as unknown as Baseline;\n if (this.webhookTables) {\n await emitWebhookEvent(\n this.db,\n this.webhookTables,\n projectId,\n \"baseline:updated\",\n {\n baselineId: baseline.id,\n storyId,\n viewport,\n branch,\n snapshotId,\n },\n this.secret,\n );\n }\n } else {\n const now = new Date().toISOString();\n baseline = (await this.db.insert(this.tables.baselines, {\n id: ulid(),\n projectId,\n storyId,\n viewportName: viewport,\n branch,\n snapshotId,\n screenshotPath,\n infraHash: infraHash ?? null,\n createdAt: now,\n updatedAt: now,\n })) as unknown as Baseline;\n if (this.webhookTables) {\n await emitWebhookEvent(\n this.db,\n this.webhookTables,\n projectId,\n \"baseline:created\",\n {\n baselineId: baseline.id,\n storyId,\n viewport,\n branch,\n snapshotId,\n },\n this.secret,\n );\n }\n }\n return baseline;\n }\n\n async list(projectId: string): Promise<Baseline[]> {\n return (await this.db.list(this.tables.baselines, {\n where: eq(\n getTableColumns(this.tables.baselines)[\"projectId\"] as unknown as SQLWrapper,\n projectId,\n ),\n })) as unknown as Baseline[];\n }\n\n async removeOrphans(projectId: string, validStoryIds: Set<string>): Promise<number> {\n const all = await this.list(projectId);\n const toRemove = all.filter((baseline) => !validStoryIds.has(baseline.storyId));\n await Promise.all(\n toRemove.map(async (baseline) => {\n try {\n await this.storage.delete(baseline.screenshotPath);\n } catch {\n // ignore missing file\n }\n await this.db.remove(this.tables.baselines, baseline.id);\n }),\n );\n return toRemove.length;\n }\n\n async removeForBranch(projectId: string, branch: string): Promise<number> {\n const all = await this.list(projectId);\n const toRemove = all.filter((baseline) => baseline.branch === branch);\n await Promise.all(\n toRemove.map(async (baseline) => {\n try {\n await this.storage.delete(baseline.screenshotPath);\n } catch {\n // ignore missing file\n }\n await this.db.remove(this.tables.baselines, baseline.id);\n }),\n );\n return toRemove.length;\n }\n\n // oxlint-disable-next-line eslint/require-await -- delegates to async removeForBranch via Promise.all\n async removeStaleBranches(\n projectId: string,\n staleBranches: ReadonlySet<string>,\n ): Promise<number> {\n if (staleBranches.size === 0) {\n return 0;\n }\n const results = await Promise.all(\n [...staleBranches].map((branch) => this.removeForBranch(projectId, branch)),\n );\n return results.reduce((sum, count) => sum + count, 0);\n }\n}\n"],"mappings":";;;;;;;;;;AAmBA,IAAa,gBAAb,MAA2B;CAMN;CALnB;CACA;CACA;CACA;CACA,YACE,IACA,QACA,SACA,QACA,eACA;EALiB,KAAA,KAAA;EAMjB,KAAK,SAAS,UAAU,EAAE,WAAW,GAAG,OAAO,UAAU;EACzD,KAAK,UAAU;EACf,KAAK,SAAS;EACd,KAAK,gBAAgB;CACvB;CAEA,MAAM,OACJ,WACA,SACA,UACA,QAC0B;EAa1B,QAAO,MAZa,KAAK,GAAG,KAAK,KAAK,OAAO,WAAW;GACtD,OAAO,IACL,GAAG,gBAAgB,KAAK,OAAO,SAAS,CAAC,CAAC,cAAuC,SAAS,GAC1F,GAAG,gBAAgB,KAAK,OAAO,SAAS,CAAC,CAAC,YAAqC,OAAO,GACtF,GACE,gBAAgB,KAAK,OAAO,SAAS,CAAC,CAAC,iBACvC,QACF,GACA,GAAG,gBAAgB,KAAK,OAAO,SAAS,CAAC,CAAC,WAAoC,MAAM,CACtF;GACA,OAAO;EACT,CAAC,EAAA,CACW,MAAM;CACpB;CAEA,MAAM,QACJ,WACA,SACA,UACA,QACA,eAC0B;EAC1B,IAAI,WAAW,eAAe;GAC5B,MAAM,MAAM,MAAM,KAAK,OAAO,WAAW,SAAS,UAAU,MAAM;GAClE,IAAI,KACF,OAAO;EAEX;EACA,OAAO,KAAK,OAAO,WAAW,SAAS,UAAU,aAAa;CAChE;CAEA,MAAM,KAAK,UAAqC;EAC9C,OAAO,MAAM,KAAK,QAAQ,KAAK,SAAS,cAAc;CACxD;CAEA,MAAM,OACJ,WACA,SACA,UACA,QACA,YACA,YACA,WACmB;EACnB,MAAM,iBAAiB,aAAa,WAAW,QAAQ,SAAS,QAAQ;EACxE,MAAM,SAAS,MAAM,KAAK,QAAQ,KAAK,UAAU;EACjD,MAAM,KAAK,QAAQ,MAAM,gBAAgB,MAAM;EAE/C,MAAM,WAAW,MAAM,KAAK,OAAO,WAAW,SAAS,UAAU,MAAM;EACvE,IAAI;EACJ,IAAI,UAAU;GACZ,WAAY,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,WAAW,SAAS,IAAI;IACnE;IACA;IACA,WAAW,aAAa,SAAS,aAAa;IAC9C,4BAAW,IAAI,KAAK,EAAA,CAAE,YAAY;GACpC,CAAC;GACD,IAAI,KAAK,eACP,MAAM,iBACJ,KAAK,IACL,KAAK,eACL,WACA,oBACA;IACE,YAAY,SAAS;IACrB;IACA;IACA;IACA;GACF,GACA,KAAK,MACP;EAEJ,OAAO;GACL,MAAM,uBAAM,IAAI,KAAK,EAAA,CAAE,YAAY;GACnC,WAAY,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,WAAW;IACtD,IAAI,KAAK;IACT;IACA;IACA,cAAc;IACd;IACA;IACA;IACA,WAAW,aAAa;IACxB,WAAW;IACX,WAAW;GACb,CAAC;GACD,IAAI,KAAK,eACP,MAAM,iBACJ,KAAK,IACL,KAAK,eACL,WACA,oBACA;IACE,YAAY,SAAS;IACrB;IACA;IACA;IACA;GACF,GACA,KAAK,MACP;EAEJ;EACA,OAAO;CACT;CAEA,MAAM,KAAK,WAAwC;EACjD,OAAQ,MAAM,KAAK,GAAG,KAAK,KAAK,OAAO,WAAW,EAChD,OAAO,GACL,gBAAgB,KAAK,OAAO,SAAS,CAAC,CAAC,cACvC,SACF,EACF,CAAC;CACH;CAEA,MAAM,cAAc,WAAmB,eAA6C;EAElF,MAAM,YAAW,MADC,KAAK,KAAK,SAAS,EAAA,CAChB,QAAQ,aAAa,CAAC,cAAc,IAAI,SAAS,OAAO,CAAC;EAC9E,MAAM,QAAQ,IACZ,SAAS,IAAI,OAAO,aAAa;GAC/B,IAAI;IACF,MAAM,KAAK,QAAQ,OAAO,SAAS,cAAc;GACnD,QAAQ,CAER;GACA,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,WAAW,SAAS,EAAE;EACzD,CAAC,CACH;EACA,OAAO,SAAS;CAClB;CAEA,MAAM,gBAAgB,WAAmB,QAAiC;EAExE,MAAM,YAAW,MADC,KAAK,KAAK,SAAS,EAAA,CAChB,QAAQ,aAAa,SAAS,WAAW,MAAM;EACpE,MAAM,QAAQ,IACZ,SAAS,IAAI,OAAO,aAAa;GAC/B,IAAI;IACF,MAAM,KAAK,QAAQ,OAAO,SAAS,cAAc;GACnD,QAAQ,CAER;GACA,MAAM,KAAK,GAAG,OAAO,KAAK,OAAO,WAAW,SAAS,EAAE;EACzD,CAAC,CACH;EACA,OAAO,SAAS;CAClB;CAGA,MAAM,oBACJ,WACA,eACiB;EACjB,IAAI,cAAc,SAAS,GACzB,OAAO;EAKT,QAAO,MAHe,QAAQ,IAC5B,CAAC,GAAG,aAAa,CAAC,CAAC,KAAK,WAAW,KAAK,gBAAgB,WAAW,MAAM,CAAC,CAC5E,EAAA,CACe,QAAQ,KAAK,UAAU,MAAM,OAAO,CAAC;CACtD;AACF"}