@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,206 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { CaptureJobModel, attemptRowId } from "../models/capture-job.mjs";
5
+ import { LEASE_EXHAUSTED_ERROR } from "../capture/db-queue/claim.mjs";
6
+ import { createDatabaseCaptureQueue } from "../capture/db-queue/index.mjs";
7
+ import { describe, expect, it } from "vitest";
8
+ //#region src/test-helpers/queue-multi-instance.ts
9
+ /**
10
+ * Multi-instance suite for the database capture queue: several queue instances (one per simulated
11
+ * app instance) share ONE database, the way horizontally scaled app instances do. Database packages
12
+ * run it against their own drivers (node:sqlite, PGlite, and the gated live Postgres, MySQL and
13
+ * Turso tests):
14
+ *
15
+ * ```ts
16
+ * import { databaseQueueMultiInstanceSuite } from "@storyshelf/core/test-helpers";
17
+ * databaseQueueMultiInstanceSuite("sqlite", async () => await setUp(createSqliteDatabase(":memory:")));
18
+ * ```
19
+ *
20
+ * Every instance reads one shared manual clock, so lease expiry and retry delays are exact and no
21
+ * case sleeps. Build ids are unique per run, so a shared live database needs no cleanup between runs.
22
+ */
23
+ const LEASE_MS = 6e4;
24
+ const RETRIES = 2;
25
+ async function fleet(makeDb, size, kinds = ["capture"]) {
26
+ const db = await makeDb();
27
+ const clock = { now: Date.now() };
28
+ const queues = Array.from({ length: size }, (_unused, index) => createDatabaseCaptureQueue(db, {
29
+ kinds,
30
+ leaseMs: LEASE_MS,
31
+ retries: RETRIES,
32
+ owner: `instance-${String(index)}`,
33
+ now: () => clock.now
34
+ }));
35
+ const advance = (ms) => {
36
+ clock.now += ms;
37
+ };
38
+ return {
39
+ db,
40
+ queues,
41
+ advance,
42
+ runId: `mi-${String(clock.now)}-${Math.random().toString(36).slice(2, 8)}`
43
+ };
44
+ }
45
+ /** Poll on every instance at once, as concurrent app instances would. */
46
+ async function pollAll(queues) {
47
+ return (await Promise.all(queues.map(async (queue) => await queue.poll()))).filter((job) => job !== null);
48
+ }
49
+ /** Poll every instance until one returns the job of `buildId` (others' jobs are acked away). */
50
+ async function claimOn(queue, buildId) {
51
+ for (let tries = 0; tries < 50; tries += 1) {
52
+ const job = await queue.poll();
53
+ if (job === null || job.buildId === buildId) return job;
54
+ await queue.ack(job);
55
+ }
56
+ return null;
57
+ }
58
+ /** Run the multi-instance suite against databases from `makeDb`. */
59
+ function databaseQueueMultiInstanceSuite(label, makeDb, options = {}) {
60
+ const size = options.instances ?? 4;
61
+ const jobs = options.jobs ?? 24;
62
+ describe(`database queue, ${String(size)} instances on one database: ${label}`, () => {
63
+ exactlyOnceCase(makeDb, size, jobs, options.timeoutMs);
64
+ leaseCases(makeDb, size, options.timeoutMs);
65
+ retryCases(makeDb, size, options.timeoutMs);
66
+ cancelAndKindCases(makeDb, size, options.timeoutMs);
67
+ });
68
+ }
69
+ function exactlyOnceCase(makeDb, size, jobs, timeoutMs) {
70
+ it("runs each job exactly once across instances polling at the same time", async () => {
71
+ const { queues, runId } = await fleet(makeDb, size);
72
+ const ids = Array.from({ length: jobs }, (_unused, index) => `${runId}-${String(index)}`);
73
+ await Promise.all(ids.map(async (buildId, index) => await queues[index % size].enqueue({ buildId })));
74
+ const claims = /* @__PURE__ */ new Map();
75
+ const ours = new Set(ids);
76
+ for (let round = 0; round < jobs * 2 + 20 && claims.size < jobs; round += 1) {
77
+ const claimed = await pollAll(queues);
78
+ for (const job of claimed.filter((one) => ours.has(one.buildId))) claims.set(job.buildId, (claims.get(job.buildId) ?? 0) + 1);
79
+ await Promise.all(claimed.map(async (job) => await queues[0].ack(job)));
80
+ }
81
+ expect([...claims.keys()].toSorted()).toEqual(ids.toSorted());
82
+ expect([...claims.values()].every((count) => count === 1)).toBe(true);
83
+ const statuses = await Promise.all(ids.map(async (id) => (await queues[1].status(id))?.status));
84
+ expect(new Set(statuses)).toEqual(/* @__PURE__ */ new Set(["completed"]));
85
+ }, timeoutMs);
86
+ }
87
+ function leaseCases(makeDb, size, timeoutMs) {
88
+ it("redelivers an expired lease to another instance and fences the stale holder", async () => {
89
+ const { db, queues, advance, runId } = await fleet(makeDb, size);
90
+ const [first, second] = [queues[0], queues[1]];
91
+ const buildId = `${runId}-lease`;
92
+ await first.enqueue({ buildId });
93
+ const stale = await claimOn(first, buildId);
94
+ expect(stale?.attempt).toBe(1);
95
+ expect(await claimOn(second, buildId)).toBeNull();
96
+ advance(60001);
97
+ const winners = (await pollAll(queues)).filter((job) => job.buildId === buildId);
98
+ expect(winners).toHaveLength(1);
99
+ const fresh = winners[0];
100
+ expect(fresh.attempt).toBe(2);
101
+ const model = new CaptureJobModel(db);
102
+ const leaseBefore = (await model.get(attemptRowId(fresh.jobId, 2)))?.visibleAt;
103
+ expect(await first.extend(stale)).toBe("lost");
104
+ expect(await first.inspect(stale)).toBe("lost");
105
+ await first.nack(stale, {
106
+ requeue: true,
107
+ error: "stale"
108
+ });
109
+ const after = await model.get(fresh.jobId);
110
+ expect(after).toMatchObject({
111
+ status: "running",
112
+ attempt: 2
113
+ });
114
+ expect((await model.get(attemptRowId(fresh.jobId, 2)))?.visibleAt).toBe(leaseBefore);
115
+ expect((await model.get(attemptRowId(fresh.jobId, 1)))?.status).toBe("expired");
116
+ expect(await second.extend(fresh)).toBe("ok");
117
+ await second.ack(fresh);
118
+ await first.ack(stale);
119
+ expect((await second.status(buildId))?.status).toBe("completed");
120
+ }, timeoutMs);
121
+ }
122
+ function retryCases(makeDb, size, timeoutMs) {
123
+ it("fails a job on any instance once its retries are spent", async () => {
124
+ const { queues, advance, runId } = await fleet(makeDb, size);
125
+ const buildId = `${runId}-retry`;
126
+ await queues[0].enqueue({ buildId });
127
+ for (let attempt = 1; attempt <= 3; attempt += 1) {
128
+ const queue = queues[attempt % size];
129
+ const job = await claimOn(queue, buildId);
130
+ expect(job?.attempt).toBe(attempt);
131
+ await queue.nack(job, {
132
+ requeue: true,
133
+ delayMs: 1e3,
134
+ error: `boom ${String(attempt)}`
135
+ });
136
+ advance(1001);
137
+ }
138
+ expect(await claimOn(queues[0], buildId)).toBeNull();
139
+ expect(await queues[2 % size].status(buildId)).toMatchObject({
140
+ status: "failed",
141
+ error: `boom ${String(3)}`
142
+ });
143
+ }, timeoutMs);
144
+ it("fails a job whose every attempt lost its lease", async () => {
145
+ const { queues, advance, runId } = await fleet(makeDb, size);
146
+ const buildId = `${runId}-hung`;
147
+ await queues[0].enqueue({ buildId });
148
+ for (let attempt = 1; attempt <= 3; attempt += 1) {
149
+ const job = await claimOn(queues[attempt % size], buildId);
150
+ expect(job?.attempt).toBe(attempt);
151
+ advance(60001);
152
+ }
153
+ expect(await claimOn(queues[0], buildId)).toBeNull();
154
+ expect(await queues[1].status(buildId)).toMatchObject({
155
+ status: "failed",
156
+ error: LEASE_EXHAUSTED_ERROR
157
+ });
158
+ }, timeoutMs);
159
+ }
160
+ function cancelAndKindCases(makeDb, size, timeoutMs) {
161
+ it("lets one instance cancel a job another instance holds", async () => {
162
+ const { queues, runId } = await fleet(makeDb, size);
163
+ const [holder, other] = [queues[0], queues[1]];
164
+ const buildId = `${runId}-cancel`;
165
+ await other.enqueue({ buildId });
166
+ const job = await claimOn(holder, buildId);
167
+ expect(await holder.inspect(job)).toBe("ok");
168
+ expect(await other.cancel(buildId)).toBe(true);
169
+ expect(await holder.inspect(job)).toBe("cancelled");
170
+ expect(await holder.extend(job)).toBe("cancelled");
171
+ await holder.nack(job, {
172
+ requeue: false,
173
+ error: "cancelled"
174
+ });
175
+ expect((await other.status(buildId))?.status).toBe("cancelled");
176
+ expect(await claimOn(other, buildId)).toBeNull();
177
+ }, timeoutMs);
178
+ it("keeps job kinds independent", async () => {
179
+ const shared = await makeDb();
180
+ const reuse = () => shared;
181
+ const capture = await fleet(reuse, 2, ["capture"]);
182
+ const publish = await fleet(reuse, 2, ["publish"]);
183
+ const buildId = `${capture.runId}-kinds`;
184
+ await capture.queues[0].enqueue({ buildId });
185
+ await publish.queues[0].enqueueJob({
186
+ kind: "publish",
187
+ buildId,
188
+ data: { zip: "k" }
189
+ });
190
+ const published = await claimOn(publish.queues[1], buildId);
191
+ expect(published).toMatchObject({
192
+ kind: "publish",
193
+ data: { zip: "k" }
194
+ });
195
+ const captured = await claimOn(capture.queues[1], buildId);
196
+ expect(captured?.kind).toBe("capture");
197
+ expect(await claimOn(publish.queues[0], buildId)).toBeNull();
198
+ await publish.queues[1].ack(published);
199
+ await capture.queues[1].ack(captured);
200
+ expect((await capture.queues[0].status(buildId))?.status).toBe("completed");
201
+ }, timeoutMs);
202
+ }
203
+ //#endregion
204
+ export { databaseQueueMultiInstanceSuite };
205
+
206
+ //# sourceMappingURL=queue-multi-instance.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queue-multi-instance.mjs","names":[],"sources":["../../src/test-helpers/queue-multi-instance.ts"],"sourcesContent":["/**\n * Multi-instance suite for the database capture queue: several queue instances (one per simulated\n * app instance) share ONE database, the way horizontally scaled app instances do. Database packages\n * run it against their own drivers (node:sqlite, PGlite, and the gated live Postgres, MySQL and\n * Turso tests):\n *\n * ```ts\n * import { databaseQueueMultiInstanceSuite } from \"@storyshelf/core/test-helpers\";\n * databaseQueueMultiInstanceSuite(\"sqlite\", async () => await setUp(createSqliteDatabase(\":memory:\")));\n * ```\n *\n * Every instance reads one shared manual clock, so lease expiry and retry delays are exact and no\n * case sleeps. Build ids are unique per run, so a shared live database needs no cleanup between runs.\n */\nimport { describe, expect, it } from \"vitest\";\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport {\n createDatabaseCaptureQueue,\n LEASE_EXHAUSTED_ERROR,\n type DatabaseCaptureQueue,\n type DatabaseJob,\n} from \"../capture/db-queue/index.ts\";\nimport { attemptRowId, CaptureJobModel } from \"../models/capture-job.ts\";\nimport type { CaptureJobKind } from \"../schema/capture-job.ts\";\n\n/** A set-up database the instances of one case share. */\nexport type SharedDatabaseFactory = () => DatabaseAdapter | Promise<DatabaseAdapter>;\n\n/** Options of {@link databaseQueueMultiInstanceSuite}. */\nexport interface MultiInstanceOptions {\n /** Queue instances sharing the database (default 4). */\n instances?: number;\n /** Jobs in the exactly-once case (default 24). */\n jobs?: number;\n /** Per-case timeout in ms, for slow remote databases (default vitest's). */\n timeoutMs?: number;\n}\n\nconst LEASE_MS = 60_000;\nconst RETRIES = 2;\n\ninterface Fleet {\n db: DatabaseAdapter;\n queues: DatabaseCaptureQueue[];\n advance: (ms: number) => void;\n runId: string;\n}\n\nasync function fleet(\n makeDb: SharedDatabaseFactory,\n size: number,\n kinds: readonly CaptureJobKind[] = [\"capture\"],\n): Promise<Fleet> {\n const db = await makeDb();\n const clock = { now: Date.now() };\n const queues = Array.from({ length: size }, (_unused, index) =>\n createDatabaseCaptureQueue(db, {\n kinds,\n leaseMs: LEASE_MS,\n retries: RETRIES,\n owner: `instance-${String(index)}`,\n now: () => clock.now,\n }),\n );\n const advance = (ms: number): void => {\n clock.now += ms;\n };\n return {\n db,\n queues,\n advance,\n runId: `mi-${String(clock.now)}-${Math.random().toString(36).slice(2, 8)}`,\n };\n}\n\n/** Poll on every instance at once, as concurrent app instances would. */\nasync function pollAll(queues: DatabaseCaptureQueue[]): Promise<DatabaseJob[]> {\n const jobs = await Promise.all(queues.map(async (queue) => await queue.poll()));\n return jobs.filter((job): job is DatabaseJob => job !== null);\n}\n\n/** Poll every instance until one returns the job of `buildId` (others' jobs are acked away). */\nasync function claimOn(queue: DatabaseCaptureQueue, buildId: string): Promise<DatabaseJob | null> {\n for (let tries = 0; tries < 50; tries += 1) {\n // oxlint-disable-next-line eslint/no-await-in-loop -- sequential polls are the point\n const job = await queue.poll();\n if (job === null || job.buildId === buildId) {\n return job;\n }\n // oxlint-disable-next-line eslint/no-await-in-loop -- clear a leftover of another case\n await queue.ack(job);\n }\n return null;\n}\n\n/** Run the multi-instance suite against databases from `makeDb`. */\nexport function databaseQueueMultiInstanceSuite(\n label: string,\n makeDb: SharedDatabaseFactory,\n options: MultiInstanceOptions = {},\n): void {\n const size = options.instances ?? 4;\n const jobs = options.jobs ?? 24;\n describe(`database queue, ${String(size)} instances on one database: ${label}`, () => {\n exactlyOnceCase(makeDb, size, jobs, options.timeoutMs);\n leaseCases(makeDb, size, options.timeoutMs);\n retryCases(makeDb, size, options.timeoutMs);\n cancelAndKindCases(makeDb, size, options.timeoutMs);\n });\n}\n\nfunction exactlyOnceCase(\n makeDb: SharedDatabaseFactory,\n size: number,\n jobs: number,\n timeoutMs?: number,\n): void {\n it(\n \"runs each job exactly once across instances polling at the same time\",\n async () => {\n const { queues, runId } = await fleet(makeDb, size);\n const ids = Array.from({ length: jobs }, (_unused, index) => `${runId}-${String(index)}`);\n await Promise.all(\n ids.map(async (buildId, index) => await queues[index % size]!.enqueue({ buildId })),\n );\n const claims = new Map<string, number>();\n const ours = new Set(ids);\n for (let round = 0; round < jobs * 2 + 20 && claims.size < jobs; round += 1) {\n // oxlint-disable-next-line eslint/no-await-in-loop -- rounds of concurrent polls\n const claimed = await pollAll(queues);\n // A shared (live) database may hold other runs' jobs: settle them, count only ours.\n for (const job of claimed.filter((one) => ours.has(one.buildId))) {\n claims.set(job.buildId, (claims.get(job.buildId) ?? 0) + 1);\n }\n // oxlint-disable-next-line eslint/no-await-in-loop -- see above\n await Promise.all(claimed.map(async (job) => await queues[0]!.ack(job)));\n }\n expect([...claims.keys()].toSorted()).toEqual(ids.toSorted());\n expect([...claims.values()].every((count) => count === 1)).toBe(true);\n const statuses = await Promise.all(\n ids.map(async (id) => (await queues[1]!.status(id))?.status),\n );\n expect(new Set(statuses)).toEqual(new Set([\"completed\"]));\n },\n timeoutMs,\n );\n}\n\nfunction leaseCases(makeDb: SharedDatabaseFactory, size: number, timeoutMs?: number): void {\n it(\n \"redelivers an expired lease to another instance and fences the stale holder\",\n async () => {\n const { db, queues, advance, runId } = await fleet(makeDb, size);\n const [first, second] = [queues[0]!, queues[1]!];\n const buildId = `${runId}-lease`;\n await first.enqueue({ buildId });\n const stale = await claimOn(first, buildId);\n expect(stale?.attempt).toBe(1);\n expect(await claimOn(second, buildId)).toBeNull();\n advance(LEASE_MS + 1);\n // Every instance races for the expired lease; exactly one wins.\n const winners = (await pollAll(queues)).filter((job) => job.buildId === buildId);\n expect(winners).toHaveLength(1);\n const fresh = winners[0]!;\n expect(fresh.attempt).toBe(2);\n const model = new CaptureJobModel(db);\n const leaseBefore = (await model.get(attemptRowId(fresh.jobId, 2)))?.visibleAt;\n expect(await first.extend(stale!)).toBe(\"lost\");\n expect(await first.inspect(stale!)).toBe(\"lost\");\n await first.nack(stale!, { requeue: true, error: \"stale\" });\n const after = await model.get(fresh.jobId);\n expect(after).toMatchObject({ status: \"running\", attempt: 2 });\n expect((await model.get(attemptRowId(fresh.jobId, 2)))?.visibleAt).toBe(leaseBefore);\n expect((await model.get(attemptRowId(fresh.jobId, 1)))?.status).toBe(\"expired\");\n expect(await second.extend(fresh)).toBe(\"ok\");\n await second.ack(fresh);\n // A late finish by the stale holder costs a duplicate run, never a wrong result.\n await first.ack(stale!);\n expect((await second.status(buildId))?.status).toBe(\"completed\");\n },\n timeoutMs,\n );\n}\n\nfunction retryCases(makeDb: SharedDatabaseFactory, size: number, timeoutMs?: number): void {\n it(\n \"fails a job on any instance once its retries are spent\",\n async () => {\n const { queues, advance, runId } = await fleet(makeDb, size);\n const buildId = `${runId}-retry`;\n await queues[0]!.enqueue({ buildId });\n for (let attempt = 1; attempt <= RETRIES + 1; attempt += 1) {\n const queue = queues[attempt % size]!;\n // oxlint-disable-next-line eslint/no-await-in-loop -- attempts are sequential\n const job = await claimOn(queue, buildId);\n expect(job?.attempt).toBe(attempt);\n // oxlint-disable-next-line eslint/no-await-in-loop -- see above\n await queue.nack(job!, { requeue: true, delayMs: 1000, error: `boom ${String(attempt)}` });\n advance(1001);\n }\n expect(await claimOn(queues[0]!, buildId)).toBeNull();\n expect(await queues[2 % size]!.status(buildId)).toMatchObject({\n status: \"failed\",\n error: `boom ${String(RETRIES + 1)}`,\n });\n },\n timeoutMs,\n );\n\n it(\n \"fails a job whose every attempt lost its lease\",\n async () => {\n const { queues, advance, runId } = await fleet(makeDb, size);\n const buildId = `${runId}-hung`;\n await queues[0]!.enqueue({ buildId });\n for (let attempt = 1; attempt <= RETRIES + 1; attempt += 1) {\n // oxlint-disable-next-line eslint/no-await-in-loop -- attempts are sequential\n const job = await claimOn(queues[attempt % size]!, buildId);\n expect(job?.attempt).toBe(attempt);\n advance(LEASE_MS + 1);\n }\n expect(await claimOn(queues[0]!, buildId)).toBeNull();\n expect(await queues[1]!.status(buildId)).toMatchObject({\n status: \"failed\",\n error: LEASE_EXHAUSTED_ERROR,\n });\n },\n timeoutMs,\n );\n}\n\nfunction cancelAndKindCases(makeDb: SharedDatabaseFactory, size: number, timeoutMs?: number): void {\n it(\n \"lets one instance cancel a job another instance holds\",\n async () => {\n const { queues, runId } = await fleet(makeDb, size);\n const [holder, other] = [queues[0]!, queues[1]!];\n const buildId = `${runId}-cancel`;\n await other.enqueue({ buildId });\n const job = await claimOn(holder, buildId);\n expect(await holder.inspect(job!)).toBe(\"ok\");\n expect(await other.cancel(buildId)).toBe(true);\n expect(await holder.inspect(job!)).toBe(\"cancelled\");\n expect(await holder.extend(job!)).toBe(\"cancelled\");\n await holder.nack(job!, { requeue: false, error: \"cancelled\" });\n expect((await other.status(buildId))?.status).toBe(\"cancelled\");\n expect(await claimOn(other, buildId)).toBeNull();\n },\n timeoutMs,\n );\n\n it(\n \"keeps job kinds independent\",\n async () => {\n const shared = await makeDb();\n const reuse = (): DatabaseAdapter => shared;\n const capture = await fleet(reuse, 2, [\"capture\"]);\n const publish = await fleet(reuse, 2, [\"publish\"]);\n const buildId = `${capture.runId}-kinds`;\n await capture.queues[0]!.enqueue({ buildId });\n await publish.queues[0]!.enqueueJob({ kind: \"publish\", buildId, data: { zip: \"k\" } });\n const published = await claimOn(publish.queues[1]!, buildId);\n expect(published).toMatchObject({ kind: \"publish\", data: { zip: \"k\" } });\n const captured = await claimOn(capture.queues[1]!, buildId);\n expect(captured?.kind).toBe(\"capture\");\n expect(await claimOn(publish.queues[0]!, buildId)).toBeNull();\n await publish.queues[1]!.ack(published!);\n await capture.queues[1]!.ack(captured!);\n expect((await capture.queues[0]!.status(buildId))?.status).toBe(\"completed\");\n },\n timeoutMs,\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAsCA,MAAM,WAAW;AACjB,MAAM,UAAU;AAShB,eAAe,MACb,QACA,MACA,QAAmC,CAAC,SAAS,GAC7B;CAChB,MAAM,KAAK,MAAM,OAAO;CACxB,MAAM,QAAQ,EAAE,KAAK,KAAK,IAAI,EAAE;CAChC,MAAM,SAAS,MAAM,KAAK,EAAE,QAAQ,KAAK,IAAI,SAAS,UACpD,2BAA2B,IAAI;EAC7B;EACA,SAAS;EACT,SAAS;EACT,OAAO,YAAY,OAAO,KAAK;EAC/B,WAAW,MAAM;CACnB,CAAC,CACH;CACA,MAAM,WAAW,OAAqB;EACpC,MAAM,OAAO;CACf;CACA,OAAO;EACL;EACA;EACA;EACA,OAAO,MAAM,OAAO,MAAM,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC;CACzE;AACF;;AAGA,eAAe,QAAQ,QAAwD;CAE7E,QAAO,MADY,QAAQ,IAAI,OAAO,IAAI,OAAO,UAAU,MAAM,MAAM,KAAK,CAAC,CAAC,EAAA,CAClE,QAAQ,QAA4B,QAAQ,IAAI;AAC9D;;AAGA,eAAe,QAAQ,OAA6B,SAA8C;CAChG,KAAK,IAAI,QAAQ,GAAG,QAAQ,IAAI,SAAS,GAAG;EAE1C,MAAM,MAAM,MAAM,MAAM,KAAK;EAC7B,IAAI,QAAQ,QAAQ,IAAI,YAAY,SAClC,OAAO;EAGT,MAAM,MAAM,IAAI,GAAG;CACrB;CACA,OAAO;AACT;;AAGA,SAAgB,gCACd,OACA,QACA,UAAgC,CAAC,GAC3B;CACN,MAAM,OAAO,QAAQ,aAAa;CAClC,MAAM,OAAO,QAAQ,QAAQ;CAC7B,SAAS,mBAAmB,OAAO,IAAI,EAAE,8BAA8B,eAAe;EACpF,gBAAgB,QAAQ,MAAM,MAAM,QAAQ,SAAS;EACrD,WAAW,QAAQ,MAAM,QAAQ,SAAS;EAC1C,WAAW,QAAQ,MAAM,QAAQ,SAAS;EAC1C,mBAAmB,QAAQ,MAAM,QAAQ,SAAS;CACpD,CAAC;AACH;AAEA,SAAS,gBACP,QACA,MACA,MACA,WACM;CACN,GACE,wEACA,YAAY;EACV,MAAM,EAAE,QAAQ,UAAU,MAAM,MAAM,QAAQ,IAAI;EAClD,MAAM,MAAM,MAAM,KAAK,EAAE,QAAQ,KAAK,IAAI,SAAS,UAAU,GAAG,MAAM,GAAG,OAAO,KAAK,GAAG;EACxF,MAAM,QAAQ,IACZ,IAAI,IAAI,OAAO,SAAS,UAAU,MAAM,OAAO,QAAQ,KAAK,CAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,CACpF;EACA,MAAM,yBAAS,IAAI,IAAoB;EACvC,MAAM,OAAO,IAAI,IAAI,GAAG;EACxB,KAAK,IAAI,QAAQ,GAAG,QAAQ,OAAO,IAAI,MAAM,OAAO,OAAO,MAAM,SAAS,GAAG;GAE3E,MAAM,UAAU,MAAM,QAAQ,MAAM;GAEpC,KAAK,MAAM,OAAO,QAAQ,QAAQ,QAAQ,KAAK,IAAI,IAAI,OAAO,CAAC,GAC7D,OAAO,IAAI,IAAI,UAAU,OAAO,IAAI,IAAI,OAAO,KAAK,KAAK,CAAC;GAG5D,MAAM,QAAQ,IAAI,QAAQ,IAAI,OAAO,QAAQ,MAAM,OAAO,EAAE,CAAE,IAAI,GAAG,CAAC,CAAC;EACzE;EACA,OAAO,CAAC,GAAG,OAAO,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,IAAI,SAAS,CAAC;EAC5D,OAAO,CAAC,GAAG,OAAO,OAAO,CAAC,CAAC,CAAC,OAAO,UAAU,UAAU,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI;EACpE,MAAM,WAAW,MAAM,QAAQ,IAC7B,IAAI,IAAI,OAAO,QAAQ,MAAM,OAAO,EAAE,CAAE,OAAO,EAAE,EAAA,EAAI,MAAM,CAC7D;EACA,OAAO,IAAI,IAAI,QAAQ,CAAC,CAAC,CAAC,wBAAQ,IAAI,IAAI,CAAC,WAAW,CAAC,CAAC;CAC1D,GACA,SACF;AACF;AAEA,SAAS,WAAW,QAA+B,MAAc,WAA0B;CACzF,GACE,+EACA,YAAY;EACV,MAAM,EAAE,IAAI,QAAQ,SAAS,UAAU,MAAM,MAAM,QAAQ,IAAI;EAC/D,MAAM,CAAC,OAAO,UAAU,CAAC,OAAO,IAAK,OAAO,EAAG;EAC/C,MAAM,UAAU,GAAG,MAAM;EACzB,MAAM,MAAM,QAAQ,EAAE,QAAQ,CAAC;EAC/B,MAAM,QAAQ,MAAM,QAAQ,OAAO,OAAO;EAC1C,OAAO,OAAO,OAAO,CAAC,CAAC,KAAK,CAAC;EAC7B,OAAO,MAAM,QAAQ,QAAQ,OAAO,CAAC,CAAC,CAAC,SAAS;EAChD,QAAQ,KAAY;EAEpB,MAAM,WAAW,MAAM,QAAQ,MAAM,EAAA,CAAG,QAAQ,QAAQ,IAAI,YAAY,OAAO;EAC/E,OAAO,OAAO,CAAC,CAAC,aAAa,CAAC;EAC9B,MAAM,QAAQ,QAAQ;EACtB,OAAO,MAAM,OAAO,CAAC,CAAC,KAAK,CAAC;EAC5B,MAAM,QAAQ,IAAI,gBAAgB,EAAE;EACpC,MAAM,eAAe,MAAM,MAAM,IAAI,aAAa,MAAM,OAAO,CAAC,CAAC,EAAA,EAAI;EACrE,OAAO,MAAM,MAAM,OAAO,KAAM,CAAC,CAAC,CAAC,KAAK,MAAM;EAC9C,OAAO,MAAM,MAAM,QAAQ,KAAM,CAAC,CAAC,CAAC,KAAK,MAAM;EAC/C,MAAM,MAAM,KAAK,OAAQ;GAAE,SAAS;GAAM,OAAO;EAAQ,CAAC;EAC1D,MAAM,QAAQ,MAAM,MAAM,IAAI,MAAM,KAAK;EACzC,OAAO,KAAK,CAAC,CAAC,cAAc;GAAE,QAAQ;GAAW,SAAS;EAAE,CAAC;EAC7D,QAAQ,MAAM,MAAM,IAAI,aAAa,MAAM,OAAO,CAAC,CAAC,EAAA,EAAI,SAAS,CAAC,CAAC,KAAK,WAAW;EACnF,QAAQ,MAAM,MAAM,IAAI,aAAa,MAAM,OAAO,CAAC,CAAC,EAAA,EAAI,MAAM,CAAC,CAAC,KAAK,SAAS;EAC9E,OAAO,MAAM,OAAO,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI;EAC5C,MAAM,OAAO,IAAI,KAAK;EAEtB,MAAM,MAAM,IAAI,KAAM;EACtB,QAAQ,MAAM,OAAO,OAAO,OAAO,EAAA,EAAI,MAAM,CAAC,CAAC,KAAK,WAAW;CACjE,GACA,SACF;AACF;AAEA,SAAS,WAAW,QAA+B,MAAc,WAA0B;CACzF,GACE,0DACA,YAAY;EACV,MAAM,EAAE,QAAQ,SAAS,UAAU,MAAM,MAAM,QAAQ,IAAI;EAC3D,MAAM,UAAU,GAAG,MAAM;EACzB,MAAM,OAAO,EAAE,CAAE,QAAQ,EAAE,QAAQ,CAAC;EACpC,KAAK,IAAI,UAAU,GAAG,WAAW,GAAa,WAAW,GAAG;GAC1D,MAAM,QAAQ,OAAO,UAAU;GAE/B,MAAM,MAAM,MAAM,QAAQ,OAAO,OAAO;GACxC,OAAO,KAAK,OAAO,CAAC,CAAC,KAAK,OAAO;GAEjC,MAAM,MAAM,KAAK,KAAM;IAAE,SAAS;IAAM,SAAS;IAAM,OAAO,QAAQ,OAAO,OAAO;GAAI,CAAC;GACzF,QAAQ,IAAI;EACd;EACA,OAAO,MAAM,QAAQ,OAAO,IAAK,OAAO,CAAC,CAAC,CAAC,SAAS;EACpD,OAAO,MAAM,OAAO,IAAI,KAAK,CAAE,OAAO,OAAO,CAAC,CAAC,CAAC,cAAc;GAC5D,QAAQ;GACR,OAAO,QAAQ,OAAO,CAAW;EACnC,CAAC;CACH,GACA,SACF;CAEA,GACE,kDACA,YAAY;EACV,MAAM,EAAE,QAAQ,SAAS,UAAU,MAAM,MAAM,QAAQ,IAAI;EAC3D,MAAM,UAAU,GAAG,MAAM;EACzB,MAAM,OAAO,EAAE,CAAE,QAAQ,EAAE,QAAQ,CAAC;EACpC,KAAK,IAAI,UAAU,GAAG,WAAW,GAAa,WAAW,GAAG;GAE1D,MAAM,MAAM,MAAM,QAAQ,OAAO,UAAU,OAAQ,OAAO;GAC1D,OAAO,KAAK,OAAO,CAAC,CAAC,KAAK,OAAO;GACjC,QAAQ,KAAY;EACtB;EACA,OAAO,MAAM,QAAQ,OAAO,IAAK,OAAO,CAAC,CAAC,CAAC,SAAS;EACpD,OAAO,MAAM,OAAO,EAAE,CAAE,OAAO,OAAO,CAAC,CAAC,CAAC,cAAc;GACrD,QAAQ;GACR,OAAO;EACT,CAAC;CACH,GACA,SACF;AACF;AAEA,SAAS,mBAAmB,QAA+B,MAAc,WAA0B;CACjG,GACE,yDACA,YAAY;EACV,MAAM,EAAE,QAAQ,UAAU,MAAM,MAAM,QAAQ,IAAI;EAClD,MAAM,CAAC,QAAQ,SAAS,CAAC,OAAO,IAAK,OAAO,EAAG;EAC/C,MAAM,UAAU,GAAG,MAAM;EACzB,MAAM,MAAM,QAAQ,EAAE,QAAQ,CAAC;EAC/B,MAAM,MAAM,MAAM,QAAQ,QAAQ,OAAO;EACzC,OAAO,MAAM,OAAO,QAAQ,GAAI,CAAC,CAAC,CAAC,KAAK,IAAI;EAC5C,OAAO,MAAM,MAAM,OAAO,OAAO,CAAC,CAAC,CAAC,KAAK,IAAI;EAC7C,OAAO,MAAM,OAAO,QAAQ,GAAI,CAAC,CAAC,CAAC,KAAK,WAAW;EACnD,OAAO,MAAM,OAAO,OAAO,GAAI,CAAC,CAAC,CAAC,KAAK,WAAW;EAClD,MAAM,OAAO,KAAK,KAAM;GAAE,SAAS;GAAO,OAAO;EAAY,CAAC;EAC9D,QAAQ,MAAM,MAAM,OAAO,OAAO,EAAA,EAAI,MAAM,CAAC,CAAC,KAAK,WAAW;EAC9D,OAAO,MAAM,QAAQ,OAAO,OAAO,CAAC,CAAC,CAAC,SAAS;CACjD,GACA,SACF;CAEA,GACE,+BACA,YAAY;EACV,MAAM,SAAS,MAAM,OAAO;EAC5B,MAAM,cAA+B;EACrC,MAAM,UAAU,MAAM,MAAM,OAAO,GAAG,CAAC,SAAS,CAAC;EACjD,MAAM,UAAU,MAAM,MAAM,OAAO,GAAG,CAAC,SAAS,CAAC;EACjD,MAAM,UAAU,GAAG,QAAQ,MAAM;EACjC,MAAM,QAAQ,OAAO,EAAE,CAAE,QAAQ,EAAE,QAAQ,CAAC;EAC5C,MAAM,QAAQ,OAAO,EAAE,CAAE,WAAW;GAAE,MAAM;GAAW;GAAS,MAAM,EAAE,KAAK,IAAI;EAAE,CAAC;EACpF,MAAM,YAAY,MAAM,QAAQ,QAAQ,OAAO,IAAK,OAAO;EAC3D,OAAO,SAAS,CAAC,CAAC,cAAc;GAAE,MAAM;GAAW,MAAM,EAAE,KAAK,IAAI;EAAE,CAAC;EACvE,MAAM,WAAW,MAAM,QAAQ,QAAQ,OAAO,IAAK,OAAO;EAC1D,OAAO,UAAU,IAAI,CAAC,CAAC,KAAK,SAAS;EACrC,OAAO,MAAM,QAAQ,QAAQ,OAAO,IAAK,OAAO,CAAC,CAAC,CAAC,SAAS;EAC5D,MAAM,QAAQ,OAAO,EAAE,CAAE,IAAI,SAAU;EACvC,MAAM,QAAQ,OAAO,EAAE,CAAE,IAAI,QAAS;EACtC,QAAQ,MAAM,QAAQ,OAAO,EAAE,CAAE,OAAO,OAAO,EAAA,EAAI,MAAM,CAAC,CAAC,KAAK,WAAW;CAC7E,GACA,SACF;AACF"}
@@ -1,6 +1,7 @@
1
1
  import "node:path";
2
2
  import "node:url";
3
3
  import.meta.url;
4
+ import { isCaptureTarget } from "../adapters/capture-target.mjs";
4
5
  import { describe, expect, it } from "vitest";
5
6
  //#region src/test-helpers/runner-contract.ts
6
7
  /**
@@ -19,6 +20,16 @@ function runnerContractSuite(label, make) {
19
20
  expect(runner.metadata.category).toBe("capture-runner");
20
21
  expect(runner.metadata.kind.length).toBeGreaterThan(0);
21
22
  });
23
+ it("lists its targets as unique, valid capture targets, default first", async () => {
24
+ const targets = (await make()).metadata.targets;
25
+ if (targets === void 0) return;
26
+ expect(targets.length).toBeGreaterThan(0);
27
+ for (const entry of targets) {
28
+ expect(isCaptureTarget(entry.target), entry.target).toBe(true);
29
+ expect(["loopback", "url"]).toContain(entry.reach);
30
+ }
31
+ expect(new Set(targets.map((entry) => entry.target)).size).toBe(targets.length);
32
+ });
22
33
  it("exposes render and cancel", async () => {
23
34
  const runner = await make();
24
35
  expect(typeof runner.render).toBe("function");
@@ -1 +1 @@
1
- {"version":3,"file":"runner-contract.mjs","names":[],"sources":["../../src/test-helpers/runner-contract.ts"],"sourcesContent":["/**\n * Published runner contract suite for first- and third-party adapters.\n *\n * Structural conformance (identity, render/cancel presence). Behavioral\n * rendering stays per-package behind real or mocked browsers — the\n * orchestrator owns timeouts, byte guards, and failure caps, so runners\n * only promise to return PNG buffers and honor `cancel`.\n */\nimport { describe, expect, it } from \"vitest\";\nimport type { CaptureRunner } from \"../adapters/capture-runner.ts\";\n\n/** Build the adapter under test (sync or async factories both work). */\nexport type RunnerFactory = () => CaptureRunner | Promise<CaptureRunner>;\n\n/** Run the runner contract against a factory. */\nexport function runnerContractSuite(label: string, make: RunnerFactory): void {\n describe(`runner contract: ${label}`, () => {\n it(\"exposes capture-runner metadata\", async () => {\n const runner = await make();\n expect(runner.metadata.category).toBe(\"capture-runner\");\n expect(runner.metadata.kind.length).toBeGreaterThan(0);\n });\n\n it(\"exposes render and cancel\", async () => {\n const runner = await make();\n expect(typeof runner.render).toBe(\"function\");\n expect(typeof runner.cancel).toBe(\"function\");\n });\n });\n}\n"],"mappings":";;;;;;;;;;;;;;AAeA,SAAgB,oBAAoB,OAAe,MAA2B;CAC5E,SAAS,oBAAoB,eAAe;EAC1C,GAAG,mCAAmC,YAAY;GAChD,MAAM,SAAS,MAAM,KAAK;GAC1B,OAAO,OAAO,SAAS,QAAQ,CAAC,CAAC,KAAK,gBAAgB;GACtD,OAAO,OAAO,SAAS,KAAK,MAAM,CAAC,CAAC,gBAAgB,CAAC;EACvD,CAAC;EAED,GAAG,6BAA6B,YAAY;GAC1C,MAAM,SAAS,MAAM,KAAK;GAC1B,OAAO,OAAO,OAAO,MAAM,CAAC,CAAC,KAAK,UAAU;GAC5C,OAAO,OAAO,OAAO,MAAM,CAAC,CAAC,KAAK,UAAU;EAC9C,CAAC;CACH,CAAC;AACH"}
1
+ {"version":3,"file":"runner-contract.mjs","names":[],"sources":["../../src/test-helpers/runner-contract.ts"],"sourcesContent":["/**\n * Published runner contract suite for first- and third-party adapters.\n *\n * Structural conformance (identity, render/cancel presence). Behavioral\n * rendering stays per-package behind real or mocked browsers — the\n * orchestrator owns timeouts, byte guards, and failure caps, so runners\n * only promise to return PNG buffers and honor `cancel`.\n */\nimport { describe, expect, it } from \"vitest\";\nimport type { CaptureRunner } from \"../adapters/capture-runner.ts\";\nimport { isCaptureTarget } from \"../adapters/capture-target.ts\";\n\n/** Build the adapter under test (sync or async factories both work). */\nexport type RunnerFactory = () => CaptureRunner | Promise<CaptureRunner>;\n\n/** Run the runner contract against a factory. */\nexport function runnerContractSuite(label: string, make: RunnerFactory): void {\n describe(`runner contract: ${label}`, () => {\n it(\"exposes capture-runner metadata\", async () => {\n const runner = await make();\n expect(runner.metadata.category).toBe(\"capture-runner\");\n expect(runner.metadata.kind.length).toBeGreaterThan(0);\n });\n\n it(\"lists its targets as unique, valid capture targets, default first\", async () => {\n const targets = (await make()).metadata.targets;\n if (targets === undefined) {\n return;\n }\n expect(targets.length).toBeGreaterThan(0);\n for (const entry of targets) {\n expect(isCaptureTarget(entry.target), entry.target).toBe(true);\n expect([\"loopback\", \"url\"]).toContain(entry.reach);\n }\n expect(new Set(targets.map((entry) => entry.target)).size).toBe(targets.length);\n });\n\n it(\"exposes render and cancel\", async () => {\n const runner = await make();\n expect(typeof runner.render).toBe(\"function\");\n expect(typeof runner.cancel).toBe(\"function\");\n });\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;AAgBA,SAAgB,oBAAoB,OAAe,MAA2B;CAC5E,SAAS,oBAAoB,eAAe;EAC1C,GAAG,mCAAmC,YAAY;GAChD,MAAM,SAAS,MAAM,KAAK;GAC1B,OAAO,OAAO,SAAS,QAAQ,CAAC,CAAC,KAAK,gBAAgB;GACtD,OAAO,OAAO,SAAS,KAAK,MAAM,CAAC,CAAC,gBAAgB,CAAC;EACvD,CAAC;EAED,GAAG,qEAAqE,YAAY;GAClF,MAAM,WAAW,MAAM,KAAK,EAAA,CAAG,SAAS;GACxC,IAAI,YAAY,KAAA,GACd;GAEF,OAAO,QAAQ,MAAM,CAAC,CAAC,gBAAgB,CAAC;GACxC,KAAK,MAAM,SAAS,SAAS;IAC3B,OAAO,gBAAgB,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,CAAC,KAAK,IAAI;IAC7D,OAAO,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,UAAU,MAAM,KAAK;GACnD;GACA,OAAO,IAAI,IAAI,QAAQ,KAAK,UAAU,MAAM,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,QAAQ,MAAM;EAChF,CAAC;EAED,GAAG,6BAA6B,YAAY;GAC1C,MAAM,SAAS,MAAM,KAAK;GAC1B,OAAO,OAAO,OAAO,MAAM,CAAC,CAAC,KAAK,UAAU;GAC5C,OAAO,OAAO,OAAO,MAAM,CAAC,CAAC,KAAK,UAAU;EAC9C,CAAC;CACH,CAAC;AACH"}
@@ -24,7 +24,10 @@ function matchChunk(chunks, row, table) {
24
24
  const text = chunks.map((c) => textOf(c) ?? "").join("");
25
25
  if (/is not null/iu.test(text)) return isNullMatches(chunks, row, table, true);
26
26
  if (/is null/iu.test(text)) return isNullMatches(chunks, row, table, false);
27
+ if (text.includes(" like ")) return likeMatches(chunks, row, table);
27
28
  if (text.includes(" in ")) return inArrayMatches(chunks, row, table);
29
+ if (text.includes(" >= ")) return !ltMatches(chunks, row, table);
30
+ if (text.includes(" <= ")) return lteMatches(chunks, row, table);
28
31
  if (text.includes(" < ")) return ltMatches(chunks, row, table);
29
32
  return eqMatches(chunks, row, table);
30
33
  }
@@ -45,6 +48,21 @@ function eqMatches(chunks, row, table) {
45
48
  if (columnName === void 0) return false;
46
49
  return row[columnKey(table, columnName)] === argument;
47
50
  }
51
+ /** Match `column like 'pattern'`: `%` is any run of characters, `_` any one; everything else literal. */
52
+ function likeMatches(chunks, row, table) {
53
+ let columnName;
54
+ let pattern;
55
+ for (const chunk of chunks) if (typeof chunk === "string") pattern = chunk;
56
+ else if (chunk !== null && typeof chunk === "object") {
57
+ const node = chunk;
58
+ if (typeof node.name === "string" && node.table !== void 0) columnName = node.name;
59
+ else if ("brand" in node) pattern = node.value;
60
+ }
61
+ const cell = columnName === void 0 ? void 0 : row[columnKey(table, columnName)];
62
+ if (typeof cell !== "string" || typeof pattern !== "string") return false;
63
+ const source = pattern.replaceAll(/[.*+?^${}()|[\]\\]/gu, String.raw`\$&`);
64
+ return new RegExp(`^${source.replaceAll("%", ".*").replaceAll("_", ".")}$`, "su").test(cell);
65
+ }
48
66
  function inArrayMatches(chunks, row, table) {
49
67
  let columnName;
50
68
  const values = [];
@@ -78,6 +96,11 @@ function inArrayMatches(chunks, row, table) {
78
96
  const key = columnKey(table, columnName);
79
97
  return values.includes(row[key]);
80
98
  }
99
+ /** `column <= value`: the cell is present and not greater than the argument. */
100
+ function lteMatches(chunks, row, table) {
101
+ if (ltMatches(chunks, row, table)) return true;
102
+ return eqMatches(chunks, row, table);
103
+ }
81
104
  function ltMatches(chunks, row, table) {
82
105
  let columnName;
83
106
  let argument;
@@ -1 +1 @@
1
- {"version":3,"file":"sql-chunks.mjs","names":[],"sources":["../../src/test-helpers/sql-chunks.ts"],"sourcesContent":["import { getTableColumns, type SQL, type Table } from \"drizzle-orm\";\n\n/* eslint-disable require-await, no-unnecessary-type-assertion, no-unnecessary-type-parameters, non-nullable-type-assertion-style */\n\n/** A drizzle SQL chunk inspected for matching and ordering. */\nexport interface SqlChunk {\n value?: unknown;\n name?: string;\n table?: unknown;\n brand?: unknown;\n queryChunks?: SqlChunk[];\n}\n\n/** Flatten the string parts of a drizzle SQL chunk into a single string. */\nfunction textOf(chunk: SqlChunk | undefined): string | undefined {\n if (\n chunk === undefined ||\n !Array.isArray(chunk.value) ||\n !chunk.value.every((part) => typeof part === \"string\")\n ) {\n return undefined;\n }\n return chunk.value.join(\"\");\n}\n\n/** Test whether an in-memory row satisfies a drizzle where-clause. */\nexport function whereMatches(where: SQL, row: Record<string, unknown>, table: Table): boolean {\n let current = where.queryChunks as unknown as SqlChunk[];\n let wrapped = current[1]?.queryChunks;\n while (wrapped !== undefined && textOf(current[0]) === \"(\" && textOf(current.at(-1)) === \")\") {\n current = wrapped;\n wrapped = current[1]?.queryChunks;\n }\n // Drizzle's `and(singleCondition)` emits the condition directly, without\n // the enclosing parens produced for two or more clauses, so unwrap it.\n if (current.length === 1 && current[0]?.queryChunks !== undefined) {\n current = current[0].queryChunks as unknown as SqlChunk[];\n }\n if (current.some((chunk) => textOf(chunk)?.trim() === \"and\")) {\n return current\n .filter((chunk) => textOf(chunk) === undefined)\n .every((chunk) => matchChunk(chunk.queryChunks ?? [], row, table));\n }\n return matchChunk(current, row, table);\n}\n\nfunction matchChunk(chunks: SqlChunk[], row: Record<string, unknown>, table: Table): boolean {\n const text = chunks.map((c) => textOf(c) ?? \"\").join(\"\");\n if (/is not null/iu.test(text)) {\n return isNullMatches(chunks, row, table, true);\n }\n if (/is null/iu.test(text)) {\n return isNullMatches(chunks, row, table, false);\n }\n if (text.includes(\" in \")) {\n return inArrayMatches(chunks, row, table);\n }\n if (text.includes(\" < \")) {\n return ltMatches(chunks, row, table);\n }\n return eqMatches(chunks, row, table);\n}\n\n/** Match `is null` / `is not null` predicates (no value argument). */\nfunction isNullMatches(\n chunks: SqlChunk[],\n row: Record<string, unknown>,\n table: Table,\n negated: boolean,\n): boolean {\n let columnName: string | undefined;\n for (const chunk of chunks) {\n if (typeof chunk.name === \"string\" && chunk.table !== undefined) {\n columnName = chunk.name;\n }\n }\n if (columnName === undefined) {\n return false;\n }\n const cell = row[columnKey(table, columnName)];\n const missing = cell === null || cell === undefined;\n return negated ? !missing : missing;\n}\n\nfunction eqMatches(chunks: SqlChunk[], row: Record<string, unknown>, table: Table): boolean {\n let columnName: string | undefined;\n let argument: unknown;\n for (const chunk of chunks) {\n if (typeof chunk.name === \"string\" && chunk.table !== undefined) {\n columnName = chunk.name;\n } else if (\"brand\" in chunk) {\n argument = chunk.value;\n }\n }\n if (columnName === undefined) {\n return false;\n }\n return row[columnKey(table, columnName)] === argument;\n}\n\n/* eslint-disable complexity, max-depth -- handles drizzle's varied chunk shapes */\nfunction inArrayMatches(chunks: SqlChunk[], row: Record<string, unknown>, table: Table): boolean {\n let columnName: string | undefined;\n const values: unknown[] = [];\n const collect = (nodes: unknown[]): void => {\n for (const raw of nodes as SqlChunk[]) {\n const chunk = raw as SqlChunk & Record<string, unknown>;\n // Handle array-like chunk (inArray values stored as array of chunks)\n if (\n Array.isArray(chunk) ||\n (chunk && typeof chunk === \"object\" && Object.keys(chunk).every((k) => /^\\d+$/u.test(k)))\n ) {\n collect(Object.values(chunk as unknown as Record<string, unknown>) as unknown[]);\n continue;\n }\n if (typeof chunk.name === \"string\" && chunk.table !== undefined) {\n columnName = chunk.name as string;\n }\n if (\"brand\" in chunk && (chunk as Record<string, unknown>)[\"value\"] !== undefined) {\n const val = (chunk as Record<string, unknown>)[\"value\"];\n if (Array.isArray(val)) {\n values.push(...(val as unknown[]));\n } else {\n values.push(val);\n }\n }\n if (chunk.queryChunks) {\n collect(chunk.queryChunks as unknown[]);\n }\n const val = (chunk as Record<string, unknown>)[\"value\"];\n if (Array.isArray(val)) {\n for (const item of val as unknown[]) {\n if (\n item &&\n typeof item === \"object\" &&\n \"queryChunks\" in (item as Record<string, unknown>)\n ) {\n collect(((item as Record<string, unknown>)[\"queryChunks\"] as unknown[]) ?? []);\n }\n if (item && typeof item === \"object\" && \"value\" in (item as Record<string, unknown>)) {\n const iv = (item as Record<string, unknown>)[\"value\"];\n if (iv !== undefined) {\n values.push(iv);\n }\n }\n if (typeof item === \"string\") {\n values.push(item);\n }\n }\n }\n }\n };\n collect(chunks as unknown[]);\n if (columnName === undefined) {\n return false;\n }\n const key = columnKey(table, columnName);\n return values.includes(row[key]);\n}\n/* eslint-enable complexity, max-depth */\n\nfunction ltMatches(chunks: SqlChunk[], row: Record<string, unknown>, table: Table): boolean {\n let columnName: string | undefined;\n let argument: unknown;\n for (const chunk of chunks) {\n if (typeof chunk.name === \"string\" && chunk.table !== undefined) {\n columnName = chunk.name;\n } else if (\"brand\" in chunk) {\n argument = chunk.value;\n }\n }\n if (columnName === undefined) {\n return false;\n }\n const key = columnKey(table, columnName);\n const cell = row[key];\n if (typeof cell === \"string\" && typeof argument === \"string\") {\n return cell < argument;\n }\n if (typeof cell === \"number\" && typeof argument === \"number\") {\n return cell < argument;\n }\n return String(cell) < String(argument);\n}\n\nfunction compareCells(left: unknown, right: unknown): number {\n if (typeof left === \"string\" && typeof right === \"string\") {\n if (left === right) {\n return 0;\n }\n return left < right ? -1 : 1;\n }\n if (typeof left === \"number\" && typeof right === \"number\") {\n return left - right;\n }\n const leftText = String(left);\n const rightText = String(right);\n if (leftText === rightText) {\n return 0;\n }\n return leftText < rightText ? -1 : 1;\n}\n\n/** Collect every raw string part of a drizzle SQL chunk tree. */\nfunction sqlText(nodes: SqlChunk[]): string {\n return nodes\n .map((node) => {\n const own = textOf(node) ?? \"\";\n const nested = node.queryChunks ? sqlText(node.queryChunks as unknown as SqlChunk[]) : \"\";\n return own + nested;\n })\n .join(\"\");\n}\n\n/** Find the ordered column reference in a drizzle order-by expression. */\nfunction findOrderColumn(nodes: SqlChunk[]): string | undefined {\n for (const node of nodes) {\n if (typeof node.name === \"string\" && node.table !== undefined) {\n return node.name;\n }\n if (node.queryChunks) {\n const nested = findOrderColumn(node.queryChunks as unknown as SqlChunk[]);\n if (nested !== undefined) {\n return nested;\n }\n }\n }\n return undefined;\n}\n\n/** Sort rows per a drizzle order-by expression (single-column asc/desc). */\nexport function orderRows(rows: unknown[], orderBy: SQL, table: Table): unknown[] {\n const chunks = orderBy.queryChunks as unknown as SqlChunk[];\n const columnName = findOrderColumn(chunks);\n if (columnName === undefined) {\n return rows;\n }\n const key = columnKey(table, columnName);\n const descending = /desc/iu.test(sqlText(chunks));\n return rows.toSorted((left, right) => {\n const order = compareCells(\n (left as Record<string, unknown>)[key],\n (right as Record<string, unknown>)[key],\n );\n return descending ? -order : order;\n });\n}\n\nfunction columnKey(table: Table, dbName: string): string {\n for (const [property, column] of Object.entries(getTableColumns(table))) {\n if (column.name === dbName) {\n return property;\n }\n }\n return dbName;\n}\n/* eslint-enable require-await, no-unnecessary-type-assertion, no-unnecessary-type-parameters, non-nullable-type-assertion-style */\n"],"mappings":";;;;;;AAcA,SAAS,OAAO,OAAiD;CAC/D,IACE,UAAU,KAAA,KACV,CAAC,MAAM,QAAQ,MAAM,KAAK,KAC1B,CAAC,MAAM,MAAM,OAAO,SAAS,OAAO,SAAS,QAAQ,GAErD;CAEF,OAAO,MAAM,MAAM,KAAK,EAAE;AAC5B;;AAGA,SAAgB,aAAa,OAAY,KAA8B,OAAuB;CAC5F,IAAI,UAAU,MAAM;CACpB,IAAI,UAAU,QAAQ,EAAE,EAAE;CAC1B,OAAO,YAAY,KAAA,KAAa,OAAO,QAAQ,EAAE,MAAM,OAAO,OAAO,QAAQ,GAAG,EAAE,CAAC,MAAM,KAAK;EAC5F,UAAU;EACV,UAAU,QAAQ,EAAE,EAAE;CACxB;CAGA,IAAI,QAAQ,WAAW,KAAK,QAAQ,EAAE,EAAE,gBAAgB,KAAA,GACtD,UAAU,QAAQ,EAAE,CAAC;CAEvB,IAAI,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC,EAAE,KAAK,MAAM,KAAK,GACzD,OAAO,QACJ,QAAQ,UAAU,OAAO,KAAK,MAAM,KAAA,CAAS,CAAC,CAC9C,OAAO,UAAU,WAAW,MAAM,eAAe,CAAC,GAAG,KAAK,KAAK,CAAC;CAErE,OAAO,WAAW,SAAS,KAAK,KAAK;AACvC;AAEA,SAAS,WAAW,QAAoB,KAA8B,OAAuB;CAC3F,MAAM,OAAO,OAAO,KAAK,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE;CACvD,IAAI,gBAAgB,KAAK,IAAI,GAC3B,OAAO,cAAc,QAAQ,KAAK,OAAO,IAAI;CAE/C,IAAI,YAAY,KAAK,IAAI,GACvB,OAAO,cAAc,QAAQ,KAAK,OAAO,KAAK;CAEhD,IAAI,KAAK,SAAS,MAAM,GACtB,OAAO,eAAe,QAAQ,KAAK,KAAK;CAE1C,IAAI,KAAK,SAAS,KAAK,GACrB,OAAO,UAAU,QAAQ,KAAK,KAAK;CAErC,OAAO,UAAU,QAAQ,KAAK,KAAK;AACrC;;AAGA,SAAS,cACP,QACA,KACA,OACA,SACS;CACT,IAAI;CACJ,KAAK,MAAM,SAAS,QAClB,IAAI,OAAO,MAAM,SAAS,YAAY,MAAM,UAAU,KAAA,GACpD,aAAa,MAAM;CAGvB,IAAI,eAAe,KAAA,GACjB,OAAO;CAET,MAAM,OAAO,IAAI,UAAU,OAAO,UAAU;CAC5C,MAAM,UAAU,SAAS,QAAQ,SAAS,KAAA;CAC1C,OAAO,UAAU,CAAC,UAAU;AAC9B;AAEA,SAAS,UAAU,QAAoB,KAA8B,OAAuB;CAC1F,IAAI;CACJ,IAAI;CACJ,KAAK,MAAM,SAAS,QAClB,IAAI,OAAO,MAAM,SAAS,YAAY,MAAM,UAAU,KAAA,GACpD,aAAa,MAAM;MACd,IAAI,WAAW,OACpB,WAAW,MAAM;CAGrB,IAAI,eAAe,KAAA,GACjB,OAAO;CAET,OAAO,IAAI,UAAU,OAAO,UAAU,OAAO;AAC/C;AAGA,SAAS,eAAe,QAAoB,KAA8B,OAAuB;CAC/F,IAAI;CACJ,MAAM,SAAoB,CAAC;CAC3B,MAAM,WAAW,UAA2B;EAC1C,KAAK,MAAM,OAAO,OAAqB;GACrC,MAAM,QAAQ;GAEd,IACE,MAAM,QAAQ,KAAK,KAClB,SAAS,OAAO,UAAU,YAAY,OAAO,KAAK,KAAK,CAAC,CAAC,OAAO,MAAM,SAAS,KAAK,CAAC,CAAC,GACvF;IACA,QAAQ,OAAO,OAAO,KAA2C,CAAc;IAC/E;GACF;GACA,IAAI,OAAO,MAAM,SAAS,YAAY,MAAM,UAAU,KAAA,GACpD,aAAa,MAAM;GAErB,IAAI,WAAW,SAAU,MAAkC,aAAa,KAAA,GAAW;IACjF,MAAM,MAAO,MAAkC;IAC/C,IAAI,MAAM,QAAQ,GAAG,GACnB,OAAO,KAAK,GAAI,GAAiB;SAEjC,OAAO,KAAK,GAAG;GAEnB;GACA,IAAI,MAAM,aACR,QAAQ,MAAM,WAAwB;GAExC,MAAM,MAAO,MAAkC;GAC/C,IAAI,MAAM,QAAQ,GAAG,GACnB,KAAK,MAAM,QAAQ,KAAkB;IACnC,IACE,QACA,OAAO,SAAS,YAChB,iBAAkB,MAElB,QAAU,KAAiC,kBAAgC,CAAC,CAAC;IAE/E,IAAI,QAAQ,OAAO,SAAS,YAAY,WAAY,MAAkC;KACpF,MAAM,KAAM,KAAiC;KAC7C,IAAI,OAAO,KAAA,GACT,OAAO,KAAK,EAAE;IAElB;IACA,IAAI,OAAO,SAAS,UAClB,OAAO,KAAK,IAAI;GAEpB;EAEJ;CACF;CACA,QAAQ,MAAmB;CAC3B,IAAI,eAAe,KAAA,GACjB,OAAO;CAET,MAAM,MAAM,UAAU,OAAO,UAAU;CACvC,OAAO,OAAO,SAAS,IAAI,IAAI;AACjC;AAGA,SAAS,UAAU,QAAoB,KAA8B,OAAuB;CAC1F,IAAI;CACJ,IAAI;CACJ,KAAK,MAAM,SAAS,QAClB,IAAI,OAAO,MAAM,SAAS,YAAY,MAAM,UAAU,KAAA,GACpD,aAAa,MAAM;MACd,IAAI,WAAW,OACpB,WAAW,MAAM;CAGrB,IAAI,eAAe,KAAA,GACjB,OAAO;CAGT,MAAM,OAAO,IADD,UAAU,OAAO,UACV;CACnB,IAAI,OAAO,SAAS,YAAY,OAAO,aAAa,UAClD,OAAO,OAAO;CAEhB,IAAI,OAAO,SAAS,YAAY,OAAO,aAAa,UAClD,OAAO,OAAO;CAEhB,OAAO,OAAO,IAAI,IAAI,OAAO,QAAQ;AACvC;AAEA,SAAS,aAAa,MAAe,OAAwB;CAC3D,IAAI,OAAO,SAAS,YAAY,OAAO,UAAU,UAAU;EACzD,IAAI,SAAS,OACX,OAAO;EAET,OAAO,OAAO,QAAQ,KAAK;CAC7B;CACA,IAAI,OAAO,SAAS,YAAY,OAAO,UAAU,UAC/C,OAAO,OAAO;CAEhB,MAAM,WAAW,OAAO,IAAI;CAC5B,MAAM,YAAY,OAAO,KAAK;CAC9B,IAAI,aAAa,WACf,OAAO;CAET,OAAO,WAAW,YAAY,KAAK;AACrC;;AAGA,SAAS,QAAQ,OAA2B;CAC1C,OAAO,MACJ,KAAK,SAAS;EAGb,QAFY,OAAO,IAAI,KAAK,OACb,KAAK,cAAc,QAAQ,KAAK,WAAoC,IAAI;CAEzF,CAAC,CAAC,CACD,KAAK,EAAE;AACZ;;AAGA,SAAS,gBAAgB,OAAuC;CAC9D,KAAK,MAAM,QAAQ,OAAO;EACxB,IAAI,OAAO,KAAK,SAAS,YAAY,KAAK,UAAU,KAAA,GAClD,OAAO,KAAK;EAEd,IAAI,KAAK,aAAa;GACpB,MAAM,SAAS,gBAAgB,KAAK,WAAoC;GACxE,IAAI,WAAW,KAAA,GACb,OAAO;EAEX;CACF;AAEF;;AAGA,SAAgB,UAAU,MAAiB,SAAc,OAAyB;CAChF,MAAM,SAAS,QAAQ;CACvB,MAAM,aAAa,gBAAgB,MAAM;CACzC,IAAI,eAAe,KAAA,GACjB,OAAO;CAET,MAAM,MAAM,UAAU,OAAO,UAAU;CACvC,MAAM,aAAa,SAAS,KAAK,QAAQ,MAAM,CAAC;CAChD,OAAO,KAAK,UAAU,MAAM,UAAU;EACpC,MAAM,QAAQ,aACX,KAAiC,MACjC,MAAkC,IACrC;EACA,OAAO,aAAa,CAAC,QAAQ;CAC/B,CAAC;AACH;AAEA,SAAS,UAAU,OAAc,QAAwB;CACvD,KAAK,MAAM,CAAC,UAAU,WAAW,OAAO,QAAQ,gBAAgB,KAAK,CAAC,GACpE,IAAI,OAAO,SAAS,QAClB,OAAO;CAGX,OAAO;AACT"}
1
+ {"version":3,"file":"sql-chunks.mjs","names":[],"sources":["../../src/test-helpers/sql-chunks.ts"],"sourcesContent":["import { getTableColumns, type SQL, type Table } from \"drizzle-orm\";\n\n/* eslint-disable require-await, no-unnecessary-type-assertion, no-unnecessary-type-parameters, non-nullable-type-assertion-style */\n\n/** A drizzle SQL chunk inspected for matching and ordering. */\nexport interface SqlChunk {\n value?: unknown;\n name?: string;\n table?: unknown;\n brand?: unknown;\n queryChunks?: SqlChunk[];\n}\n\n/** Flatten the string parts of a drizzle SQL chunk into a single string. */\nfunction textOf(chunk: SqlChunk | undefined): string | undefined {\n if (\n chunk === undefined ||\n !Array.isArray(chunk.value) ||\n !chunk.value.every((part) => typeof part === \"string\")\n ) {\n return undefined;\n }\n return chunk.value.join(\"\");\n}\n\n/** Test whether an in-memory row satisfies a drizzle where-clause. */\nexport function whereMatches(where: SQL, row: Record<string, unknown>, table: Table): boolean {\n let current = where.queryChunks as unknown as SqlChunk[];\n let wrapped = current[1]?.queryChunks;\n while (wrapped !== undefined && textOf(current[0]) === \"(\" && textOf(current.at(-1)) === \")\") {\n current = wrapped;\n wrapped = current[1]?.queryChunks;\n }\n // Drizzle's `and(singleCondition)` emits the condition directly, without\n // the enclosing parens produced for two or more clauses, so unwrap it.\n if (current.length === 1 && current[0]?.queryChunks !== undefined) {\n current = current[0].queryChunks as unknown as SqlChunk[];\n }\n if (current.some((chunk) => textOf(chunk)?.trim() === \"and\")) {\n return current\n .filter((chunk) => textOf(chunk) === undefined)\n .every((chunk) => matchChunk(chunk.queryChunks ?? [], row, table));\n }\n return matchChunk(current, row, table);\n}\n\nfunction matchChunk(chunks: SqlChunk[], row: Record<string, unknown>, table: Table): boolean {\n const text = chunks.map((c) => textOf(c) ?? \"\").join(\"\");\n if (/is not null/iu.test(text)) {\n return isNullMatches(chunks, row, table, true);\n }\n if (/is null/iu.test(text)) {\n return isNullMatches(chunks, row, table, false);\n }\n if (text.includes(\" like \")) {\n return likeMatches(chunks, row, table);\n }\n if (text.includes(\" in \")) {\n return inArrayMatches(chunks, row, table);\n }\n if (text.includes(\" >= \")) {\n return !ltMatches(chunks, row, table);\n }\n if (text.includes(\" <= \")) {\n return lteMatches(chunks, row, table);\n }\n if (text.includes(\" < \")) {\n return ltMatches(chunks, row, table);\n }\n return eqMatches(chunks, row, table);\n}\n\n/** Match `is null` / `is not null` predicates (no value argument). */\nfunction isNullMatches(\n chunks: SqlChunk[],\n row: Record<string, unknown>,\n table: Table,\n negated: boolean,\n): boolean {\n let columnName: string | undefined;\n for (const chunk of chunks) {\n if (typeof chunk.name === \"string\" && chunk.table !== undefined) {\n columnName = chunk.name;\n }\n }\n if (columnName === undefined) {\n return false;\n }\n const cell = row[columnKey(table, columnName)];\n const missing = cell === null || cell === undefined;\n return negated ? !missing : missing;\n}\n\nfunction eqMatches(chunks: SqlChunk[], row: Record<string, unknown>, table: Table): boolean {\n let columnName: string | undefined;\n let argument: unknown;\n for (const chunk of chunks) {\n if (typeof chunk.name === \"string\" && chunk.table !== undefined) {\n columnName = chunk.name;\n } else if (\"brand\" in chunk) {\n argument = chunk.value;\n }\n }\n if (columnName === undefined) {\n return false;\n }\n return row[columnKey(table, columnName)] === argument;\n}\n\n/** Match `column like 'pattern'`: `%` is any run of characters, `_` any one; everything else literal. */\nfunction likeMatches(chunks: SqlChunk[], row: Record<string, unknown>, table: Table): boolean {\n let columnName: string | undefined;\n let pattern: unknown;\n for (const chunk of chunks as unknown[]) {\n if (typeof chunk === \"string\") {\n pattern = chunk;\n } else if (chunk !== null && typeof chunk === \"object\") {\n const node = chunk as SqlChunk;\n if (typeof node.name === \"string\" && node.table !== undefined) {\n columnName = node.name;\n } else if (\"brand\" in node) {\n pattern = node.value;\n }\n }\n }\n const cell = columnName === undefined ? undefined : row[columnKey(table, columnName)];\n if (typeof cell !== \"string\" || typeof pattern !== \"string\") {\n return false;\n }\n const source = pattern.replaceAll(/[.*+?^${}()|[\\]\\\\]/gu, String.raw`\\$&`);\n return new RegExp(`^${source.replaceAll(\"%\", \".*\").replaceAll(\"_\", \".\")}$`, \"su\").test(cell);\n}\n\n/* eslint-disable complexity, max-depth -- handles drizzle's varied chunk shapes */\nfunction inArrayMatches(chunks: SqlChunk[], row: Record<string, unknown>, table: Table): boolean {\n let columnName: string | undefined;\n const values: unknown[] = [];\n const collect = (nodes: unknown[]): void => {\n for (const raw of nodes as SqlChunk[]) {\n const chunk = raw as SqlChunk & Record<string, unknown>;\n // Handle array-like chunk (inArray values stored as array of chunks)\n if (\n Array.isArray(chunk) ||\n (chunk && typeof chunk === \"object\" && Object.keys(chunk).every((k) => /^\\d+$/u.test(k)))\n ) {\n collect(Object.values(chunk as unknown as Record<string, unknown>) as unknown[]);\n continue;\n }\n if (typeof chunk.name === \"string\" && chunk.table !== undefined) {\n columnName = chunk.name as string;\n }\n if (\"brand\" in chunk && (chunk as Record<string, unknown>)[\"value\"] !== undefined) {\n const val = (chunk as Record<string, unknown>)[\"value\"];\n if (Array.isArray(val)) {\n values.push(...(val as unknown[]));\n } else {\n values.push(val);\n }\n }\n if (chunk.queryChunks) {\n collect(chunk.queryChunks as unknown[]);\n }\n const val = (chunk as Record<string, unknown>)[\"value\"];\n if (Array.isArray(val)) {\n for (const item of val as unknown[]) {\n if (\n item &&\n typeof item === \"object\" &&\n \"queryChunks\" in (item as Record<string, unknown>)\n ) {\n collect(((item as Record<string, unknown>)[\"queryChunks\"] as unknown[]) ?? []);\n }\n if (item && typeof item === \"object\" && \"value\" in (item as Record<string, unknown>)) {\n const iv = (item as Record<string, unknown>)[\"value\"];\n if (iv !== undefined) {\n values.push(iv);\n }\n }\n if (typeof item === \"string\") {\n values.push(item);\n }\n }\n }\n }\n };\n collect(chunks as unknown[]);\n if (columnName === undefined) {\n return false;\n }\n const key = columnKey(table, columnName);\n return values.includes(row[key]);\n}\n/* eslint-enable complexity, max-depth */\n\n/** `column <= value`: the cell is present and not greater than the argument. */\nfunction lteMatches(chunks: SqlChunk[], row: Record<string, unknown>, table: Table): boolean {\n if (ltMatches(chunks, row, table)) {\n return true;\n }\n return eqMatches(chunks, row, table);\n}\n\nfunction ltMatches(chunks: SqlChunk[], row: Record<string, unknown>, table: Table): boolean {\n let columnName: string | undefined;\n let argument: unknown;\n for (const chunk of chunks) {\n if (typeof chunk.name === \"string\" && chunk.table !== undefined) {\n columnName = chunk.name;\n } else if (\"brand\" in chunk) {\n argument = chunk.value;\n }\n }\n if (columnName === undefined) {\n return false;\n }\n const key = columnKey(table, columnName);\n const cell = row[key];\n if (typeof cell === \"string\" && typeof argument === \"string\") {\n return cell < argument;\n }\n if (typeof cell === \"number\" && typeof argument === \"number\") {\n return cell < argument;\n }\n return String(cell) < String(argument);\n}\n\nfunction compareCells(left: unknown, right: unknown): number {\n if (typeof left === \"string\" && typeof right === \"string\") {\n if (left === right) {\n return 0;\n }\n return left < right ? -1 : 1;\n }\n if (typeof left === \"number\" && typeof right === \"number\") {\n return left - right;\n }\n const leftText = String(left);\n const rightText = String(right);\n if (leftText === rightText) {\n return 0;\n }\n return leftText < rightText ? -1 : 1;\n}\n\n/** Collect every raw string part of a drizzle SQL chunk tree. */\nfunction sqlText(nodes: SqlChunk[]): string {\n return nodes\n .map((node) => {\n const own = textOf(node) ?? \"\";\n const nested = node.queryChunks ? sqlText(node.queryChunks as unknown as SqlChunk[]) : \"\";\n return own + nested;\n })\n .join(\"\");\n}\n\n/** Find the ordered column reference in a drizzle order-by expression. */\nfunction findOrderColumn(nodes: SqlChunk[]): string | undefined {\n for (const node of nodes) {\n if (typeof node.name === \"string\" && node.table !== undefined) {\n return node.name;\n }\n if (node.queryChunks) {\n const nested = findOrderColumn(node.queryChunks as unknown as SqlChunk[]);\n if (nested !== undefined) {\n return nested;\n }\n }\n }\n return undefined;\n}\n\n/** Sort rows per a drizzle order-by expression (single-column asc/desc). */\nexport function orderRows(rows: unknown[], orderBy: SQL, table: Table): unknown[] {\n const chunks = orderBy.queryChunks as unknown as SqlChunk[];\n const columnName = findOrderColumn(chunks);\n if (columnName === undefined) {\n return rows;\n }\n const key = columnKey(table, columnName);\n const descending = /desc/iu.test(sqlText(chunks));\n return rows.toSorted((left, right) => {\n const order = compareCells(\n (left as Record<string, unknown>)[key],\n (right as Record<string, unknown>)[key],\n );\n return descending ? -order : order;\n });\n}\n\nfunction columnKey(table: Table, dbName: string): string {\n for (const [property, column] of Object.entries(getTableColumns(table))) {\n if (column.name === dbName) {\n return property;\n }\n }\n return dbName;\n}\n/* eslint-enable require-await, no-unnecessary-type-assertion, no-unnecessary-type-parameters, non-nullable-type-assertion-style */\n"],"mappings":"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"}
@@ -1,8 +1,8 @@
1
1
  import "node:path";
2
2
  import "node:url";
3
3
  import.meta.url;
4
- import { describe, expect, it } from "vitest";
5
4
  import { Readable } from "node:stream";
5
+ import { describe, expect, it } from "vitest";
6
6
  //#region src/test-helpers/storage-contract.ts
7
7
  /**
8
8
  * Published storage contract suite for first- and third-party adapters.
@@ -1,13 +1,18 @@
1
1
  import __tsdown_shims_path from 'node:path';
2
2
  import __tsdown_shims_url from 'node:url';
3
+ import { Deferred, Latch, ReleaseQueue, createLatch, createReleaseQueue, deferred } from "./test-helpers/deferred.mjs";
4
+ import { DesignContractSamples, designContractSuite } from "./test-helpers/design-contract.mjs";
3
5
  import { createTestBuild, createTestProject } from "./test-helpers/create-project.mjs";
4
6
  import { REQUIRED_TABLES, databaseContractSuite } from "./test-helpers/database-contract.mjs";
5
- import { gitContractSuite } from "./test-helpers/git-contract.mjs";
7
+ import { FAKE_PNG, FakeDesign, fakeUrl, makeFakeDesign } from "./test-helpers/fake-design.mjs";
6
8
  import { makeDatabase } from "./test-helpers/fake-database.mjs";
7
9
  import { FakeStorage, makeStorage } from "./test-helpers/fake-storage.mjs";
8
10
  import { stubTables } from "./test-helpers/fake-adapters.mjs";
9
- import { notifierContractSuite } from "./test-helpers/notifier-contract.mjs";
10
- import { queueContractSuite } from "./test-helpers/queue-contract.mjs";
11
+ import { notifierContractSuite, stateNotifierContractSuite } from "./test-helpers/notifier-contract.mjs";
12
+ import { PollableContractOptions, QueueContractOptions, QueueFactory, queueContractSuite } from "./test-helpers/queue-contract.mjs";
13
+ import { MultiInstanceOptions, SharedDatabaseFactory, databaseQueueMultiInstanceSuite } from "./test-helpers/queue-multi-instance.mjs";
14
+ import { ManualClock, createManualClock } from "./test-helpers/manual-clock.mjs";
11
15
  import { runnerContractSuite } from "./test-helpers/runner-contract.mjs";
12
16
  import { storageContractSuite } from "./test-helpers/storage-contract.mjs";
13
- export { type FakeStorage, REQUIRED_TABLES, createTestBuild, createTestProject, databaseContractSuite, gitContractSuite, makeDatabase, makeStorage, notifierContractSuite, queueContractSuite, runnerContractSuite, storageContractSuite, stubTables };
17
+ import { FakeStateNotifier, makeFakeStateNotifier } from "./test-helpers/fake-notifier.mjs";
18
+ export { type Deferred, type DesignContractSamples, FAKE_PNG, type FakeDesign, type FakeStateNotifier, type FakeStorage, type Latch, type ManualClock, type MultiInstanceOptions, type PollableContractOptions, type QueueContractOptions, type QueueFactory, REQUIRED_TABLES, type ReleaseQueue, type SharedDatabaseFactory, createLatch, createManualClock, createReleaseQueue, createTestBuild, createTestProject, databaseContractSuite, databaseQueueMultiInstanceSuite, deferred, designContractSuite, fakeUrl, makeDatabase, makeFakeDesign, makeFakeStateNotifier, makeStorage, notifierContractSuite, queueContractSuite, runnerContractSuite, stateNotifierContractSuite, storageContractSuite, stubTables };
@@ -1,14 +1,19 @@
1
1
  import "node:path";
2
2
  import "node:url";
3
3
  import.meta.url;
4
+ import { createLatch, createReleaseQueue, deferred } from "./test-helpers/deferred.mjs";
5
+ import { designContractSuite } from "./test-helpers/design-contract.mjs";
4
6
  import { createTestBuild, createTestProject } from "./test-helpers/create-project.mjs";
5
7
  import { REQUIRED_TABLES, databaseContractSuite } from "./test-helpers/database-contract.mjs";
6
- import { gitContractSuite } from "./test-helpers/git-contract.mjs";
8
+ import { FAKE_PNG, fakeUrl, makeFakeDesign } from "./test-helpers/fake-design.mjs";
7
9
  import { makeDatabase } from "./test-helpers/fake-database.mjs";
8
10
  import { makeStorage } from "./test-helpers/fake-storage.mjs";
9
11
  import { stubTables } from "./test-helpers/fake-adapters.mjs";
10
- import { notifierContractSuite } from "./test-helpers/notifier-contract.mjs";
12
+ import { notifierContractSuite, stateNotifierContractSuite } from "./test-helpers/notifier-contract.mjs";
11
13
  import { queueContractSuite } from "./test-helpers/queue-contract.mjs";
14
+ import { databaseQueueMultiInstanceSuite } from "./test-helpers/queue-multi-instance.mjs";
15
+ import { createManualClock } from "./test-helpers/manual-clock.mjs";
12
16
  import { runnerContractSuite } from "./test-helpers/runner-contract.mjs";
13
17
  import { storageContractSuite } from "./test-helpers/storage-contract.mjs";
14
- export { REQUIRED_TABLES, createTestBuild, createTestProject, databaseContractSuite, gitContractSuite, makeDatabase, makeStorage, notifierContractSuite, queueContractSuite, runnerContractSuite, storageContractSuite, stubTables };
18
+ import { makeFakeStateNotifier } from "./test-helpers/fake-notifier.mjs";
19
+ export { FAKE_PNG, REQUIRED_TABLES, createLatch, createManualClock, createReleaseQueue, createTestBuild, createTestProject, databaseContractSuite, databaseQueueMultiInstanceSuite, deferred, designContractSuite, fakeUrl, makeDatabase, makeFakeDesign, makeFakeStateNotifier, makeStorage, notifierContractSuite, queueContractSuite, runnerContractSuite, stateNotifierContractSuite, storageContractSuite, stubTables };
package/dist/types.d.mts CHANGED
@@ -2,7 +2,7 @@ import __tsdown_shims_path from 'node:path';
2
2
  import __tsdown_shims_url from 'node:url';
3
3
  //#region src/types.d.ts
4
4
  /** All lifecycle statuses a build can carry. */
5
- declare const BUILD_STATUSES: readonly ["pending", "capturing", "comparing", "reviewing", "approved", "rejected", "failed"];
5
+ declare const BUILD_STATUSES: readonly ["pending", "capturing", "comparing", "reviewing", "approved", "rejected", "failed", "cancelled"];
6
6
  /** Lifecycle status of a build. */
7
7
  type BuildStatus = (typeof BUILD_STATUSES)[number];
8
8
  /** All review statuses a snapshot can carry. */
@@ -11,10 +11,15 @@ declare const SNAPSHOT_STATUSES: readonly ["pending", "new", "unchanged", "chang
11
11
  type SnapshotStatus = (typeof SNAPSHOT_STATUSES)[number];
12
12
  /**
13
13
  * Build statuses considered terminal for retention purging.
14
- * Only builds that have finished review (`approved`, `rejected`, `failed`)
14
+ * Only builds that have finished review (`approved`, `rejected`, `failed`, `cancelled`)
15
15
  * are eligible for TTL-based cleanup; `pending`/`reviewing` builds are kept.
16
16
  */
17
17
  declare const TERMINAL_BUILD_STATUSES: readonly BuildStatus[];
18
+ /**
19
+ * Statuses of a build whose capture or compare work may still run: a user may cancel it (the build
20
+ * then ends `cancelled`, a terminal status no capture write moves it out of again).
21
+ */
22
+ declare const CANCELLABLE_BUILD_STATUSES: readonly BuildStatus[];
18
23
  /** All site-wide user roles. */
19
24
  declare const SITE_ROLES: readonly ["admin", "member", "viewer"];
20
25
  /** Site-wide role of a user. */
@@ -45,10 +50,16 @@ interface AuthUser {
45
50
  */
46
51
  providerId?: string;
47
52
  }
53
+ /** Build-level review verdicts (ADR 0013, decision 3). */
54
+ declare const REVIEW_VERDICTS: readonly ["approved", "rejected"];
55
+ /** A reviewer's verdict on a build. */
56
+ type ReviewVerdict = (typeof REVIEW_VERDICTS)[number];
48
57
  /** All per-project membership roles. */
49
58
  declare const PROJECT_ROLES: readonly ["admin", "approver", "developer", "viewer"];
50
59
  /** Per-project role of a member. */
51
60
  type ProjectRole = (typeof PROJECT_ROLES)[number];
61
+ /** Narrow an untrusted string to a {@link ProjectRole}. */
62
+ declare function isProjectRole(value: unknown): value is ProjectRole;
52
63
  /** Label type keys seeded for every new project. */
53
64
  declare const SEEDED_LABEL_KEYS: readonly ["branch", "persistent", "pr", "mr", "jira", "linear", "figma", "custom"];
54
65
  /** Label type keys reserved for internal bookkeeping (cannot be created by users). */
@@ -59,5 +70,5 @@ declare const RESERVED_LABEL_KEYS: readonly ["build"];
59
70
  */
60
71
  declare const PERSISTENT_LABEL_KEY = "persistent";
61
72
  //#endregion
62
- export { AuthUser, BUILD_STATUSES, BuildStatus, PERSISTENT_LABEL_KEY, PROJECT_ROLES, ProjectRole, RESERVED_LABEL_KEYS, SEEDED_LABEL_KEYS, SESSION_COOKIE, SITE_ROLES, SNAPSHOT_STATUSES, SiteRole, SnapshotStatus, TERMINAL_BUILD_STATUSES };
73
+ export { AuthUser, BUILD_STATUSES, BuildStatus, CANCELLABLE_BUILD_STATUSES, PERSISTENT_LABEL_KEY, PROJECT_ROLES, ProjectRole, RESERVED_LABEL_KEYS, REVIEW_VERDICTS, ReviewVerdict, SEEDED_LABEL_KEYS, SESSION_COOKIE, SITE_ROLES, SNAPSHOT_STATUSES, SiteRole, SnapshotStatus, TERMINAL_BUILD_STATUSES, isProjectRole };
63
74
  //# sourceMappingURL=types.d.mts.map
package/dist/types.mjs CHANGED
@@ -10,7 +10,8 @@ const BUILD_STATUSES = [
10
10
  "reviewing",
11
11
  "approved",
12
12
  "rejected",
13
- "failed"
13
+ "failed",
14
+ "cancelled"
14
15
  ];
15
16
  /** All review statuses a snapshot can carry. */
16
17
  const SNAPSHOT_STATUSES = [
@@ -23,13 +24,23 @@ const SNAPSHOT_STATUSES = [
23
24
  ];
24
25
  /**
25
26
  * Build statuses considered terminal for retention purging.
26
- * Only builds that have finished review (`approved`, `rejected`, `failed`)
27
+ * Only builds that have finished review (`approved`, `rejected`, `failed`, `cancelled`)
27
28
  * are eligible for TTL-based cleanup; `pending`/`reviewing` builds are kept.
28
29
  */
29
30
  const TERMINAL_BUILD_STATUSES = [
30
31
  "approved",
31
32
  "rejected",
32
- "failed"
33
+ "failed",
34
+ "cancelled"
35
+ ];
36
+ /**
37
+ * Statuses of a build whose capture or compare work may still run: a user may cancel it (the build
38
+ * then ends `cancelled`, a terminal status no capture write moves it out of again).
39
+ */
40
+ const CANCELLABLE_BUILD_STATUSES = [
41
+ "pending",
42
+ "capturing",
43
+ "comparing"
33
44
  ];
34
45
  /** All site-wide user roles. */
35
46
  const SITE_ROLES = [
@@ -39,6 +50,8 @@ const SITE_ROLES = [
39
50
  ];
40
51
  /** Shared session cookie name used by auth (ADR 0008). */
41
52
  const SESSION_COOKIE = "storyshelf_session";
53
+ /** Build-level review verdicts (ADR 0013, decision 3). */
54
+ const REVIEW_VERDICTS = ["approved", "rejected"];
42
55
  /** All per-project membership roles. */
43
56
  const PROJECT_ROLES = [
44
57
  "admin",
@@ -46,6 +59,10 @@ const PROJECT_ROLES = [
46
59
  "developer",
47
60
  "viewer"
48
61
  ];
62
+ /** Narrow an untrusted string to a {@link ProjectRole}. */
63
+ function isProjectRole(value) {
64
+ return typeof value === "string" && PROJECT_ROLES.includes(value);
65
+ }
49
66
  /** Label type keys seeded for every new project. */
50
67
  const SEEDED_LABEL_KEYS = [
51
68
  "branch",
@@ -65,6 +82,6 @@ const RESERVED_LABEL_KEYS = ["build"];
65
82
  */
66
83
  const PERSISTENT_LABEL_KEY = "persistent";
67
84
  //#endregion
68
- export { BUILD_STATUSES, PERSISTENT_LABEL_KEY, PROJECT_ROLES, RESERVED_LABEL_KEYS, SEEDED_LABEL_KEYS, SESSION_COOKIE, SITE_ROLES, SNAPSHOT_STATUSES, TERMINAL_BUILD_STATUSES };
85
+ export { BUILD_STATUSES, CANCELLABLE_BUILD_STATUSES, PERSISTENT_LABEL_KEY, PROJECT_ROLES, RESERVED_LABEL_KEYS, REVIEW_VERDICTS, SEEDED_LABEL_KEYS, SESSION_COOKIE, SITE_ROLES, SNAPSHOT_STATUSES, TERMINAL_BUILD_STATUSES, isProjectRole };
69
86
 
70
87
  //# sourceMappingURL=types.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.mjs","names":[],"sources":["../src/types.ts"],"sourcesContent":["/** All lifecycle statuses a build can carry. */\nexport const BUILD_STATUSES = [\n \"pending\",\n \"capturing\",\n \"comparing\",\n \"reviewing\",\n \"approved\",\n \"rejected\",\n \"failed\",\n] as const;\n\n/** Lifecycle status of a build. */\nexport type BuildStatus = (typeof BUILD_STATUSES)[number];\n\n/** All review statuses a snapshot can carry. */\nexport const SNAPSHOT_STATUSES = [\n \"pending\",\n \"new\",\n \"unchanged\",\n \"changed\",\n \"approved\",\n \"rejected\",\n] as const;\n\n/** Review status of a snapshot. */\nexport type SnapshotStatus = (typeof SNAPSHOT_STATUSES)[number];\n\n/**\n * Build statuses considered terminal for retention purging.\n * Only builds that have finished review (`approved`, `rejected`, `failed`)\n * are eligible for TTL-based cleanup; `pending`/`reviewing` builds are kept.\n */\nexport const TERMINAL_BUILD_STATUSES: readonly BuildStatus[] = [\"approved\", \"rejected\", \"failed\"];\n\n/** All site-wide user roles. */\nexport const SITE_ROLES = [\"admin\", \"member\", \"viewer\"] as const;\n\n/** Site-wide role of a user. */\nexport type SiteRole = (typeof SITE_ROLES)[number];\n\n/** Shared session cookie name used by auth (ADR 0008). */\nexport const SESSION_COOKIE = \"storyshelf_session\";\n\n/** An authenticated user of the platform. */\nexport interface AuthUser {\n /** User ID. */\n id: string;\n /** User email. */\n email: string;\n /** Display name. */\n name: string;\n /** Optional avatar URL. */\n avatarUrl?: string;\n /** Site-wide role. */\n role: SiteRole;\n /**\n * Identity-provider group memberships (names or IDs, provider-dependent).\n * Populated when the provider exposes group claims; absent otherwise.\n * Drives group-to-role mapping at login.\n */\n groups?: string[];\n /**\n * Login method that minted this identity.\n * Matches an engine login method id; absent for single-method deployments.\n */\n providerId?: string;\n}\n\n/** All per-project membership roles. */\nexport const PROJECT_ROLES = [\"admin\", \"approver\", \"developer\", \"viewer\"] as const;\n\n/** Per-project role of a member. */\nexport type ProjectRole = (typeof PROJECT_ROLES)[number];\n\n/** Label type keys seeded for every new project. */\nexport const SEEDED_LABEL_KEYS = [\n \"branch\",\n \"persistent\",\n \"pr\",\n \"mr\",\n \"jira\",\n \"linear\",\n \"figma\",\n \"custom\",\n] as const;\n\n/** Label type keys reserved for internal bookkeeping (cannot be created by users). */\nexport const RESERVED_LABEL_KEYS = [\"build\"] as const;\n\n/**\n * Label key that marks a build as exempt from retention purging.\n * Attach `persistent=true` to keep a build indefinitely, even after its TTL expires.\n */\nexport const PERSISTENT_LABEL_KEY = \"persistent\";\n"],"mappings":";;;;;AACA,MAAa,iBAAiB;CAC5B;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;AAMA,MAAa,oBAAoB;CAC/B;CACA;CACA;CACA;CACA;CACA;AACF;;;;;;AAUA,MAAa,0BAAkD;CAAC;CAAY;CAAY;AAAQ;;AAGhG,MAAa,aAAa;CAAC;CAAS;CAAU;AAAQ;;AAMtD,MAAa,iBAAiB;;AA4B9B,MAAa,gBAAgB;CAAC;CAAS;CAAY;CAAa;AAAQ;;AAMxE,MAAa,oBAAoB;CAC/B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;AAGA,MAAa,sBAAsB,CAAC,OAAO;;;;;AAM3C,MAAa,uBAAuB"}
1
+ {"version":3,"file":"types.mjs","names":[],"sources":["../src/types.ts"],"sourcesContent":["/** All lifecycle statuses a build can carry. */\nexport const BUILD_STATUSES = [\n \"pending\",\n \"capturing\",\n \"comparing\",\n \"reviewing\",\n \"approved\",\n \"rejected\",\n \"failed\",\n \"cancelled\",\n] as const;\n\n/** Lifecycle status of a build. */\nexport type BuildStatus = (typeof BUILD_STATUSES)[number];\n\n/** All review statuses a snapshot can carry. */\nexport const SNAPSHOT_STATUSES = [\n \"pending\",\n \"new\",\n \"unchanged\",\n \"changed\",\n \"approved\",\n \"rejected\",\n] as const;\n\n/** Review status of a snapshot. */\nexport type SnapshotStatus = (typeof SNAPSHOT_STATUSES)[number];\n\n/**\n * Build statuses considered terminal for retention purging.\n * Only builds that have finished review (`approved`, `rejected`, `failed`, `cancelled`)\n * are eligible for TTL-based cleanup; `pending`/`reviewing` builds are kept.\n */\nexport const TERMINAL_BUILD_STATUSES: readonly BuildStatus[] = [\n \"approved\",\n \"rejected\",\n \"failed\",\n \"cancelled\",\n];\n\n/**\n * Statuses of a build whose capture or compare work may still run: a user may cancel it (the build\n * then ends `cancelled`, a terminal status no capture write moves it out of again).\n */\nexport const CANCELLABLE_BUILD_STATUSES: readonly BuildStatus[] = [\n \"pending\",\n \"capturing\",\n \"comparing\",\n];\n\n/** All site-wide user roles. */\nexport const SITE_ROLES = [\"admin\", \"member\", \"viewer\"] as const;\n\n/** Site-wide role of a user. */\nexport type SiteRole = (typeof SITE_ROLES)[number];\n\n/** Shared session cookie name used by auth (ADR 0008). */\nexport const SESSION_COOKIE = \"storyshelf_session\";\n\n/** An authenticated user of the platform. */\nexport interface AuthUser {\n /** User ID. */\n id: string;\n /** User email. */\n email: string;\n /** Display name. */\n name: string;\n /** Optional avatar URL. */\n avatarUrl?: string;\n /** Site-wide role. */\n role: SiteRole;\n /**\n * Identity-provider group memberships (names or IDs, provider-dependent).\n * Populated when the provider exposes group claims; absent otherwise.\n * Drives group-to-role mapping at login.\n */\n groups?: string[];\n /**\n * Login method that minted this identity.\n * Matches an engine login method id; absent for single-method deployments.\n */\n providerId?: string;\n}\n\n/** Build-level review verdicts (ADR 0013, decision 3). */\nexport const REVIEW_VERDICTS = [\"approved\", \"rejected\"] as const;\n\n/** A reviewer's verdict on a build. */\nexport type ReviewVerdict = (typeof REVIEW_VERDICTS)[number];\n\n/** All per-project membership roles. */\nexport const PROJECT_ROLES = [\"admin\", \"approver\", \"developer\", \"viewer\"] as const;\n\n/** Per-project role of a member. */\nexport type ProjectRole = (typeof PROJECT_ROLES)[number];\n\n/** Narrow an untrusted string to a {@link ProjectRole}. */\nexport function isProjectRole(value: unknown): value is ProjectRole {\n return typeof value === \"string\" && (PROJECT_ROLES as readonly string[]).includes(value);\n}\n\n/** Label type keys seeded for every new project. */\nexport const SEEDED_LABEL_KEYS = [\n \"branch\",\n \"persistent\",\n \"pr\",\n \"mr\",\n \"jira\",\n \"linear\",\n \"figma\",\n \"custom\",\n] as const;\n\n/** Label type keys reserved for internal bookkeeping (cannot be created by users). */\nexport const RESERVED_LABEL_KEYS = [\"build\"] as const;\n\n/**\n * Label key that marks a build as exempt from retention purging.\n * Attach `persistent=true` to keep a build indefinitely, even after its TTL expires.\n */\nexport const PERSISTENT_LABEL_KEY = \"persistent\";\n"],"mappings":";;;;;AACA,MAAa,iBAAiB;CAC5B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;AAMA,MAAa,oBAAoB;CAC/B;CACA;CACA;CACA;CACA;CACA;AACF;;;;;;AAUA,MAAa,0BAAkD;CAC7D;CACA;CACA;CACA;AACF;;;;;AAMA,MAAa,6BAAqD;CAChE;CACA;CACA;AACF;;AAGA,MAAa,aAAa;CAAC;CAAS;CAAU;AAAQ;;AAMtD,MAAa,iBAAiB;;AA4B9B,MAAa,kBAAkB,CAAC,YAAY,UAAU;;AAMtD,MAAa,gBAAgB;CAAC;CAAS;CAAY;CAAa;AAAQ;;AAMxE,SAAgB,cAAc,OAAsC;CAClE,OAAO,OAAO,UAAU,YAAa,cAAoC,SAAS,KAAK;AACzF;;AAGA,MAAa,oBAAoB;CAC/B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;AAGA,MAAa,sBAAsB,CAAC,OAAO;;;;;AAM3C,MAAa,uBAAuB"}
package/dist/urls.d.mts CHANGED
@@ -10,10 +10,14 @@ interface UrlBuilder {
10
10
  buildsList(slug: string): string;
11
11
  build(slug: string, buildId: string): string;
12
12
  buildDiff(slug: string, buildId: string): string;
13
+ /** Review page of one snapshot: the build's diff page with that snapshot selected. */
14
+ snapshot(slug: string, buildId: string, snapshotId: string): string;
13
15
  library(slug: string): string;
14
16
  jobs(slug: string): string;
15
17
  labels(slug: string): string;
16
18
  label(slug: string, key: string, value: string): string;
19
+ /** Stable component page, keyed by the story file's import path. */
20
+ component(slug: string, importPath: string): string;
17
21
  storybook(slug: string): string;
18
22
  storybookLabel(slug: string, key: string, value: string): string;
19
23
  storybookBuild(slug: string, buildId: string): string;