@storyshelf/core 0.3.1 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (393) hide show
  1. package/README.md +38 -31
  2. package/dist/adapter/capture-queue.d.mts +29 -1
  3. package/dist/adapter/capture-runner.d.mts +18 -2
  4. package/dist/adapter/capture-runner.mjs +12 -1
  5. package/dist/adapter/capture-runner.mjs.map +1 -0
  6. package/dist/adapter/database.d.mts +67 -17
  7. package/dist/adapter/database.mjs +22 -55
  8. package/dist/adapter/database.mjs.map +1 -1
  9. package/dist/adapter/email-sender.d.mts +27 -0
  10. package/dist/adapter/email-sender.mjs +4 -0
  11. package/dist/adapter/metadata.d.mts +21 -15
  12. package/dist/adapter/notifier.d.mts +7 -0
  13. package/dist/adapter/notifier.mjs +6 -0
  14. package/dist/adapter/setup.d.mts +61 -0
  15. package/dist/adapter/setup.mjs +29 -0
  16. package/dist/adapter/setup.mjs.map +1 -0
  17. package/dist/adapter/webhook-events.d.mts +29 -0
  18. package/dist/adapter/webhook-events.mjs +83 -0
  19. package/dist/adapter/webhook-events.mjs.map +1 -0
  20. package/dist/adapters/notifier/events.d.mts +26 -0
  21. package/dist/adapters/notifier/events.mjs +66 -0
  22. package/dist/adapters/notifier/events.mjs.map +1 -0
  23. package/dist/adapters/notifier/format.d.mts +14 -0
  24. package/dist/adapters/notifier/format.mjs +96 -0
  25. package/dist/adapters/notifier/format.mjs.map +1 -0
  26. package/dist/adapters/notifier/provider.d.mts +32 -0
  27. package/dist/adapters/notifier/types.d.mts +56 -0
  28. package/dist/adapters/setup-runner.d.mts +23 -0
  29. package/dist/adapters/setup-runner.mjs +149 -0
  30. package/dist/adapters/setup-runner.mjs.map +1 -0
  31. package/dist/adapters/validate.d.mts +30 -0
  32. package/dist/adapters/validate.mjs +313 -0
  33. package/dist/adapters/validate.mjs.map +1 -0
  34. package/dist/auth.d.mts +94 -0
  35. package/dist/auth.mjs +4 -0
  36. package/dist/brand-packs.d.mts +16 -0
  37. package/dist/brand-packs.mjs +130 -0
  38. package/dist/brand-packs.mjs.map +1 -0
  39. package/dist/capture/adapter.d.mts +51 -1
  40. package/dist/capture/adapter.mjs.map +1 -1
  41. package/dist/capture/affected.d.mts +16 -0
  42. package/dist/capture/affected.mjs +64 -0
  43. package/dist/capture/affected.mjs.map +1 -0
  44. package/dist/capture/diff-record.mjs +30 -0
  45. package/dist/capture/diff-record.mjs.map +1 -0
  46. package/dist/capture/dispatch.d.mts +36 -0
  47. package/dist/capture/dispatch.mjs +88 -31
  48. package/dist/capture/dispatch.mjs.map +1 -1
  49. package/dist/capture/guards.mjs +31 -0
  50. package/dist/capture/guards.mjs.map +1 -0
  51. package/dist/capture/orchestrator.d.mts +41 -6
  52. package/dist/capture/orchestrator.mjs +151 -28
  53. package/dist/capture/orchestrator.mjs.map +1 -1
  54. package/dist/capture/pipeline.d.mts +30 -16
  55. package/dist/capture/pipeline.mjs +143 -39
  56. package/dist/capture/pipeline.mjs.map +1 -1
  57. package/dist/capture/recovery.d.mts +21 -0
  58. package/dist/capture/recovery.mjs +83 -0
  59. package/dist/capture/recovery.mjs.map +1 -0
  60. package/dist/capture/rediff.d.mts +35 -0
  61. package/dist/capture/rediff.mjs +77 -0
  62. package/dist/capture/rediff.mjs.map +1 -0
  63. package/dist/capture/sizing.d.mts +58 -0
  64. package/dist/capture/sizing.mjs +119 -0
  65. package/dist/capture/sizing.mjs.map +1 -0
  66. package/dist/capture/skip-checks.mjs +3 -3
  67. package/dist/capture/skip-checks.mjs.map +1 -1
  68. package/dist/capture/statics.d.mts +38 -0
  69. package/dist/capture/statics.mjs +38 -4
  70. package/dist/capture/statics.mjs.map +1 -1
  71. package/dist/capture/status-fanout.mjs +7 -2
  72. package/dist/capture/status-fanout.mjs.map +1 -1
  73. package/dist/capture/storybook.d.mts +31 -2
  74. package/dist/capture/storybook.mjs +77 -2
  75. package/dist/capture/storybook.mjs.map +1 -1
  76. package/dist/capture.d.mts +10 -4
  77. package/dist/capture.mjs +7 -2
  78. package/dist/config.d.mts +121 -4
  79. package/dist/config.mjs +55 -8
  80. package/dist/config.mjs.map +1 -1
  81. package/dist/index.d.mts +5 -56
  82. package/dist/index.mjs +4 -284
  83. package/dist/logger.d.mts +26 -17
  84. package/dist/logger.mjs +10 -5
  85. package/dist/logger.mjs.map +1 -1
  86. package/dist/models/baseline-status.d.mts +29 -0
  87. package/dist/models/baseline-status.mjs +26 -0
  88. package/dist/models/baseline-status.mjs.map +1 -0
  89. package/dist/models/baseline.d.mts +33 -0
  90. package/dist/models/baseline.mjs +35 -13
  91. package/dist/models/baseline.mjs.map +1 -1
  92. package/dist/models/build.d.mts +87 -0
  93. package/dist/models/build.mjs +83 -35
  94. package/dist/models/build.mjs.map +1 -1
  95. package/dist/models/capture-attempt.d.mts +40 -0
  96. package/dist/models/capture-attempt.mjs +85 -0
  97. package/dist/models/capture-attempt.mjs.map +1 -0
  98. package/dist/models/capture-log.d.mts +21 -0
  99. package/dist/models/capture-log.mjs +44 -0
  100. package/dist/models/capture-log.mjs.map +1 -0
  101. package/dist/models/comment.d.mts +29 -0
  102. package/dist/models/comment.mjs +11 -8
  103. package/dist/models/comment.mjs.map +1 -1
  104. package/dist/models/content-ref.d.mts +21 -0
  105. package/dist/models/content-ref.mjs +49 -0
  106. package/dist/models/content-ref.mjs.map +1 -0
  107. package/dist/models/credentials.d.mts +36 -0
  108. package/dist/models/credentials.mjs +103 -0
  109. package/dist/models/credentials.mjs.map +1 -0
  110. package/dist/models/label.d.mts +58 -0
  111. package/dist/models/label.mjs +32 -27
  112. package/dist/models/label.mjs.map +1 -1
  113. package/dist/models/member.d.mts +34 -0
  114. package/dist/models/member.mjs +20 -11
  115. package/dist/models/member.mjs.map +1 -1
  116. package/dist/models/notification-channel.d.mts +52 -0
  117. package/dist/models/notification-channel.mjs +93 -0
  118. package/dist/models/notification-channel.mjs.map +1 -0
  119. package/dist/models/notification-subscription.d.mts +44 -0
  120. package/dist/models/notification-subscription.mjs +89 -0
  121. package/dist/models/notification-subscription.mjs.map +1 -0
  122. package/dist/models/project-group-mapping.d.mts +30 -0
  123. package/dist/models/project-group-mapping.mjs +46 -0
  124. package/dist/models/project-group-mapping.mjs.map +1 -0
  125. package/dist/models/project.d.mts +54 -0
  126. package/dist/models/project.mjs +15 -11
  127. package/dist/models/project.mjs.map +1 -1
  128. package/dist/models/snapshot.d.mts +58 -0
  129. package/dist/models/snapshot.mjs +13 -7
  130. package/dist/models/snapshot.mjs.map +1 -1
  131. package/dist/models/status-config.d.mts +38 -0
  132. package/dist/models/status-config.mjs +12 -11
  133. package/dist/models/status-config.mjs.map +1 -1
  134. package/dist/models/token.d.mts +44 -0
  135. package/dist/models/token.mjs +18 -15
  136. package/dist/models/token.mjs.map +1 -1
  137. package/dist/models/user.d.mts +42 -0
  138. package/dist/models/user.mjs +65 -0
  139. package/dist/models/user.mjs.map +1 -0
  140. package/dist/models/webhook.d.mts +50 -0
  141. package/dist/models/webhook.mjs +12 -10
  142. package/dist/models/webhook.mjs.map +1 -1
  143. package/dist/models.d.mts +22 -0
  144. package/dist/models.mjs +23 -0
  145. package/dist/paths.mjs +29 -0
  146. package/dist/paths.mjs.map +1 -1
  147. package/dist/retention.d.mts +45 -0
  148. package/dist/retention.mjs +145 -0
  149. package/dist/retention.mjs.map +1 -0
  150. package/dist/schema/baseline.d.mts +1 -0
  151. package/dist/schema/build.d.mts +8 -0
  152. package/dist/schema/capture-attempt.d.mts +38 -0
  153. package/dist/schema/comment.d.mts +1 -1
  154. package/dist/schema/content-ref.d.mts +13 -0
  155. package/dist/schema/member.d.mts +2 -0
  156. package/dist/schema/notification.d.mts +35 -0
  157. package/dist/schema/project-group-mapping.d.mts +15 -0
  158. package/dist/schema/project.d.mts +4 -0
  159. package/dist/schema/snapshot.d.mts +7 -0
  160. package/dist/schema/token.d.mts +2 -0
  161. package/dist/schema/user.d.mts +11 -1
  162. package/dist/schema.d.mts +9 -46
  163. package/dist/schema.mjs +1 -55
  164. package/dist/test-helpers/create-project.d.mts +15 -0
  165. package/dist/test-helpers/create-project.mjs +28 -0
  166. package/dist/test-helpers/create-project.mjs.map +1 -0
  167. package/dist/test-helpers/database-contract.d.mts +13 -0
  168. package/dist/test-helpers/database-contract.mjs +58 -0
  169. package/dist/test-helpers/database-contract.mjs.map +1 -0
  170. package/dist/test-helpers/fake-adapters.d.mts +14 -0
  171. package/dist/test-helpers/fake-adapters.mjs +20 -0
  172. package/dist/test-helpers/fake-adapters.mjs.map +1 -0
  173. package/dist/test-helpers/fake-database.d.mts +11 -0
  174. package/dist/test-helpers/fake-database.mjs +104 -0
  175. package/dist/test-helpers/fake-database.mjs.map +1 -0
  176. package/dist/test-helpers/fake-storage.d.mts +14 -0
  177. package/dist/test-helpers/fake-storage.mjs +49 -0
  178. package/dist/test-helpers/fake-storage.mjs.map +1 -0
  179. package/dist/test-helpers/fake-tables.mjs +276 -0
  180. package/dist/test-helpers/fake-tables.mjs.map +1 -0
  181. package/dist/test-helpers/git-contract.d.mts +11 -0
  182. package/dist/test-helpers/git-contract.mjs +34 -0
  183. package/dist/test-helpers/git-contract.mjs.map +1 -0
  184. package/dist/test-helpers/notifier-contract.d.mts +11 -0
  185. package/dist/test-helpers/notifier-contract.mjs +34 -0
  186. package/dist/test-helpers/notifier-contract.mjs.map +1 -0
  187. package/dist/test-helpers/queue-contract.d.mts +11 -0
  188. package/dist/test-helpers/queue-contract.mjs +64 -0
  189. package/dist/test-helpers/queue-contract.mjs.map +1 -0
  190. package/dist/test-helpers/runner-contract.d.mts +11 -0
  191. package/dist/test-helpers/runner-contract.mjs +32 -0
  192. package/dist/test-helpers/runner-contract.mjs.map +1 -0
  193. package/dist/test-helpers/sql-chunks.mjs +138 -0
  194. package/dist/test-helpers/sql-chunks.mjs.map +1 -0
  195. package/dist/test-helpers/storage-contract.d.mts +11 -0
  196. package/dist/test-helpers/storage-contract.mjs +87 -0
  197. package/dist/test-helpers/storage-contract.mjs.map +1 -0
  198. package/dist/test-helpers.d.mts +13 -0
  199. package/dist/test-helpers.mjs +14 -0
  200. package/dist/tracing.mjs +63 -0
  201. package/dist/tracing.mjs.map +1 -0
  202. package/dist/types.d.mts +38 -5
  203. package/dist/types.mjs +18 -5
  204. package/dist/types.mjs.map +1 -1
  205. package/dist/urls.d.mts +12 -1
  206. package/dist/urls.mjs +22 -3
  207. package/dist/urls.mjs.map +1 -1
  208. package/dist/utils/encrypt.d.mts +48 -0
  209. package/dist/utils/encrypt.mjs +67 -13
  210. package/dist/utils/encrypt.mjs.map +1 -1
  211. package/dist/utils/hash.d.mts +17 -0
  212. package/dist/utils/hash.mjs +8 -2
  213. package/dist/utils/hash.mjs.map +1 -1
  214. package/dist/utils/http.d.mts +58 -0
  215. package/dist/utils/http.mjs +147 -0
  216. package/dist/utils/http.mjs.map +1 -0
  217. package/dist/utils/mime.d.mts +9 -0
  218. package/dist/utils/mime.mjs +33 -0
  219. package/dist/utils/mime.mjs.map +1 -0
  220. package/dist/utils/path-security.d.mts +12 -0
  221. package/dist/utils/path-security.mjs +24 -0
  222. package/dist/utils/path-security.mjs.map +1 -0
  223. package/dist/utils/redact.d.mts +17 -0
  224. package/dist/utils/redact.mjs +42 -0
  225. package/dist/utils/redact.mjs.map +1 -0
  226. package/dist/utils/timing.d.mts +19 -0
  227. package/dist/utils/timing.mjs +46 -0
  228. package/dist/utils/timing.mjs.map +1 -0
  229. package/dist/utils/ulid.d.mts +10 -0
  230. package/dist/utils/ulid.mjs +8 -8
  231. package/dist/utils/ulid.mjs.map +1 -1
  232. package/dist/utils.d.mts +12 -0
  233. package/dist/utils.mjs +13 -0
  234. package/package.json +69 -28
  235. package/dist/adapter/auth.d.mts +0 -50
  236. package/dist/adapter/auth.mjs +0 -10
  237. package/dist/adapter/auth.mjs.map +0 -1
  238. package/dist/adapter/init.d.mts +0 -58
  239. package/dist/adapter/init.mjs +0 -93
  240. package/dist/adapter/init.mjs.map +0 -1
  241. package/dist/adapters/webhook-events.mjs +0 -55
  242. package/dist/adapters/webhook-events.mjs.map +0 -1
  243. package/dist/ddl.d.mts +0 -8
  244. package/dist/ddl.mjs +0 -158
  245. package/dist/ddl.mjs.map +0 -1
  246. package/dist/index.mjs.map +0 -1
  247. package/dist/middleware/auth-gate.mjs +0 -36
  248. package/dist/middleware/auth-gate.mjs.map +0 -1
  249. package/dist/middleware/csrf.mjs +0 -40
  250. package/dist/middleware/csrf.mjs.map +0 -1
  251. package/dist/middleware/rate-limit.mjs +0 -39
  252. package/dist/middleware/rate-limit.mjs.map +0 -1
  253. package/dist/middleware/request-log.mjs +0 -28
  254. package/dist/middleware/request-log.mjs.map +0 -1
  255. package/dist/middleware/store-scope.mjs +0 -29
  256. package/dist/middleware/store-scope.mjs.map +0 -1
  257. package/dist/pages/build-detail.mjs +0 -339
  258. package/dist/pages/build-detail.mjs.map +0 -1
  259. package/dist/pages/build-diff-comments.mjs +0 -113
  260. package/dist/pages/build-diff-comments.mjs.map +0 -1
  261. package/dist/pages/build-diff-header.mjs +0 -163
  262. package/dist/pages/build-diff-header.mjs.map +0 -1
  263. package/dist/pages/build-diff-nav.mjs +0 -66
  264. package/dist/pages/build-diff-nav.mjs.map +0 -1
  265. package/dist/pages/build-diff-viewer.mjs +0 -197
  266. package/dist/pages/build-diff-viewer.mjs.map +0 -1
  267. package/dist/pages/build-diff.mjs +0 -90
  268. package/dist/pages/build-diff.mjs.map +0 -1
  269. package/dist/pages/compute-jobs.mjs +0 -198
  270. package/dist/pages/compute-jobs.mjs.map +0 -1
  271. package/dist/pages/label-detail.mjs +0 -228
  272. package/dist/pages/label-detail.mjs.map +0 -1
  273. package/dist/pages/login.mjs +0 -78
  274. package/dist/pages/login.mjs.map +0 -1
  275. package/dist/pages/project-builds.mjs +0 -231
  276. package/dist/pages/project-builds.mjs.map +0 -1
  277. package/dist/pages/project-create.mjs +0 -169
  278. package/dist/pages/project-create.mjs.map +0 -1
  279. package/dist/pages/project-settings.mjs +0 -102
  280. package/dist/pages/project-settings.mjs.map +0 -1
  281. package/dist/pages/projects.mjs +0 -184
  282. package/dist/pages/projects.mjs.map +0 -1
  283. package/dist/pages/root.mjs +0 -94
  284. package/dist/pages/root.mjs.map +0 -1
  285. package/dist/pages/settings-general.mjs +0 -192
  286. package/dist/pages/settings-general.mjs.map +0 -1
  287. package/dist/pages/settings-labels.mjs +0 -150
  288. package/dist/pages/settings-labels.mjs.map +0 -1
  289. package/dist/pages/settings-members.mjs +0 -125
  290. package/dist/pages/settings-members.mjs.map +0 -1
  291. package/dist/pages/settings-status.mjs +0 -152
  292. package/dist/pages/settings-status.mjs.map +0 -1
  293. package/dist/pages/settings-tests.mjs +0 -149
  294. package/dist/pages/settings-tests.mjs.map +0 -1
  295. package/dist/pages/settings-tokens.mjs +0 -96
  296. package/dist/pages/settings-tokens.mjs.map +0 -1
  297. package/dist/pages/settings-webhooks.mjs +0 -150
  298. package/dist/pages/settings-webhooks.mjs.map +0 -1
  299. package/dist/pages/storybook.mjs +0 -96
  300. package/dist/pages/storybook.mjs.map +0 -1
  301. package/dist/retention/purge.mjs +0 -67
  302. package/dist/retention/purge.mjs.map +0 -1
  303. package/dist/routers/admin.mjs +0 -42
  304. package/dist/routers/admin.mjs.map +0 -1
  305. package/dist/routers/assets.mjs +0 -25
  306. package/dist/routers/assets.mjs.map +0 -1
  307. package/dist/routers/auth.mjs +0 -78
  308. package/dist/routers/auth.mjs.map +0 -1
  309. package/dist/routers/builds.handlers.mjs +0 -184
  310. package/dist/routers/builds.handlers.mjs.map +0 -1
  311. package/dist/routers/builds.mjs +0 -185
  312. package/dist/routers/builds.mjs.map +0 -1
  313. package/dist/routers/comments.mjs +0 -90
  314. package/dist/routers/comments.mjs.map +0 -1
  315. package/dist/routers/health.mjs +0 -137
  316. package/dist/routers/health.mjs.map +0 -1
  317. package/dist/routers/helpers.mjs +0 -82
  318. package/dist/routers/helpers.mjs.map +0 -1
  319. package/dist/routers/htmx.mjs +0 -20
  320. package/dist/routers/htmx.mjs.map +0 -1
  321. package/dist/routers/labels.mjs +0 -110
  322. package/dist/routers/labels.mjs.map +0 -1
  323. package/dist/routers/media.mjs +0 -55
  324. package/dist/routers/media.mjs.map +0 -1
  325. package/dist/routers/members.mjs +0 -102
  326. package/dist/routers/members.mjs.map +0 -1
  327. package/dist/routers/projects.mjs +0 -122
  328. package/dist/routers/projects.mjs.map +0 -1
  329. package/dist/routers/schemas.mjs +0 -224
  330. package/dist/routers/schemas.mjs.map +0 -1
  331. package/dist/routers/settings-general.mjs +0 -95
  332. package/dist/routers/settings-general.mjs.map +0 -1
  333. package/dist/routers/settings-labels.mjs +0 -49
  334. package/dist/routers/settings-labels.mjs.map +0 -1
  335. package/dist/routers/settings-members.mjs +0 -30
  336. package/dist/routers/settings-members.mjs.map +0 -1
  337. package/dist/routers/settings-status.mjs +0 -73
  338. package/dist/routers/settings-status.mjs.map +0 -1
  339. package/dist/routers/settings-tokens.mjs +0 -40
  340. package/dist/routers/settings-tokens.mjs.map +0 -1
  341. package/dist/routers/settings-webhooks.mjs +0 -63
  342. package/dist/routers/settings-webhooks.mjs.map +0 -1
  343. package/dist/routers/settings.handlers.mjs +0 -93
  344. package/dist/routers/settings.handlers.mjs.map +0 -1
  345. package/dist/routers/settings.mjs +0 -23
  346. package/dist/routers/settings.mjs.map +0 -1
  347. package/dist/routers/snapshots.mjs +0 -144
  348. package/dist/routers/snapshots.mjs.map +0 -1
  349. package/dist/routers/status-configs.mjs +0 -101
  350. package/dist/routers/status-configs.mjs.map +0 -1
  351. package/dist/routers/storybook.mjs +0 -120
  352. package/dist/routers/storybook.mjs.map +0 -1
  353. package/dist/routers/tokens.mjs +0 -81
  354. package/dist/routers/tokens.mjs.map +0 -1
  355. package/dist/routers/ui.mjs +0 -165
  356. package/dist/routers/ui.mjs.map +0 -1
  357. package/dist/routers/webhooks.mjs +0 -90
  358. package/dist/routers/webhooks.mjs.map +0 -1
  359. package/dist/schema/baseline.mjs +0 -22
  360. package/dist/schema/baseline.mjs.map +0 -1
  361. package/dist/schema/build.mjs +0 -29
  362. package/dist/schema/build.mjs.map +0 -1
  363. package/dist/schema/comment.mjs +0 -26
  364. package/dist/schema/comment.mjs.map +0 -1
  365. package/dist/schema/label.mjs +0 -30
  366. package/dist/schema/label.mjs.map +0 -1
  367. package/dist/schema/member.mjs +0 -19
  368. package/dist/schema/member.mjs.map +0 -1
  369. package/dist/schema/project.mjs +0 -25
  370. package/dist/schema/project.mjs.map +0 -1
  371. package/dist/schema/snapshot.mjs +0 -34
  372. package/dist/schema/snapshot.mjs.map +0 -1
  373. package/dist/schema/status-config.mjs +0 -20
  374. package/dist/schema/status-config.mjs.map +0 -1
  375. package/dist/schema/token.mjs +0 -19
  376. package/dist/schema/token.mjs.map +0 -1
  377. package/dist/schema/user.mjs +0 -19
  378. package/dist/schema/user.mjs.map +0 -1
  379. package/dist/schema/webhook.mjs +0 -20
  380. package/dist/schema/webhook.mjs.map +0 -1
  381. package/dist/schema.mjs.map +0 -1
  382. package/dist/store.mjs +0 -20
  383. package/dist/store.mjs.map +0 -1
  384. package/dist/ui/document.mjs +0 -348
  385. package/dist/ui/document.mjs.map +0 -1
  386. package/dist/ui/feedback.mjs +0 -24
  387. package/dist/ui/feedback.mjs.map +0 -1
  388. package/dist/ui/forms.mjs +0 -117
  389. package/dist/ui/forms.mjs.map +0 -1
  390. package/dist/ui/styles.mjs +0 -174
  391. package/dist/ui/styles.mjs.map +0 -1
  392. package/dist/ui/theme.mjs +0 -44
  393. package/dist/ui/theme.mjs.map +0 -1
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @storyshelf/core
2
2
 
3
- The heart of StoryShelf: the Hono router, adapter interfaces, models, capture pipeline, diff engine, and retention logic. Compose pluggable adapters into a complete self-hosted visual testing server.
3
+ The domain layer of StoryShelf: adapter interfaces, models, capture pipeline, diff engine, and retention logic — with no HTTP dependency, so workers import it without pulling a server. The Hono router lives in `@storyshelf/app`. Compose pluggable adapters into a complete self-hosted visual testing server.
4
4
 
5
5
  ## Install
6
6
 
@@ -17,17 +17,22 @@ npm install @storyshelf/core
17
17
  ## Quick start
18
18
 
19
19
  ```ts
20
- import { createShelfRouter } from "@storyshelf/core";
20
+ import { createShelfApp } from "@storyshelf/app";
21
21
 
22
- const app = createShelfRouter({
22
+ const app = createShelfApp({
23
23
  database, // DatabaseAdapter
24
24
  storage, // StorageAdapter
25
25
  captureRunner, // CaptureRunner (optional)
26
- auth, // AuthAdapter (optional)
26
+ auth, // Auth from @storyshelf/auth (optional)
27
27
  gitHosts, // GitHostProvider[] (optional)
28
28
  logger, // pino Logger (optional; built internally if omitted)
29
29
  ui: { name: "My Shelf" }, // UIConfig (optional)
30
- config: { captureConcurrency: 2, purgeTtlDays: 30 }, // ShelfConfig (optional)
30
+ config: {
31
+ captureConcurrency: 2,
32
+ purgeTtlDays: 30,
33
+ branchTtlDays: 30,
34
+ branchGcIntervalMs: 86_400_000,
35
+ }, // ShelfConfig (optional)
31
36
  });
32
37
 
33
38
  // The returned app is a Hono instance; serve it with any Hono adapter.
@@ -36,21 +41,21 @@ serve({ fetch: app.fetch, port: 3000 });
36
41
 
37
42
  ## API
38
43
 
39
- ### `createShelfRouter(options: ShelfOptions): Hono`
44
+ ### `createShelfApp(options: ShelfOptions): Hono`
40
45
 
41
46
  Assembles the router from the provided adapters. `ShelfOptions`:
42
47
 
43
- | Option | Type | Description |
44
- | --------------- | ------------------- | ---------------------------------------------------------------------------------------- |
45
- | `database` | `DatabaseAdapter` | **Required.** Data access. |
46
- | `storage` | `StorageAdapter` | **Required.** Blob storage for screenshots, diffs, storybook archives. |
47
- | `captureRunner` | `CaptureRunner` | Optional. Enables the async capture pipeline (pure renderer). |
48
- | `captureQueue` | `CaptureQueue` | Optional. Queue adapter; defaults to `InMemoryCaptureQueue`. |
49
- | `auth` | `AuthAdapter` | Optional. Enables auth and the login UI. |
50
- | `gitHosts` | `GitHostProvider[]` | Optional. Git-host adapters (GitHub/GitLab) for status checks, merge gates, PR comments. |
51
- | `logger` | `Logger` (pino) | Optional. Shared logger. Construct a fallback via `createShelfLogger()`. |
52
- | `ui` | `UIConfig` | Optional. UI branding. |
53
- | `config` | `ShelfConfig` | Optional. Server behavior. |
48
+ | Option | Type | Description |
49
+ | --------------- | --------------------------- | ---------------------------------------------------------------------------------------- |
50
+ | `database` | `DatabaseAdapter` | **Required.** Data access. |
51
+ | `storage` | `StorageAdapter` | **Required.** Blob storage for screenshots, diffs, storybook archives. |
52
+ | `captureRunner` | `CaptureRunner` | Optional. Enables the async capture pipeline (pure renderer). |
53
+ | `captureQueue` | `CaptureQueue` | Optional. Queue adapter; defaults to `InMemoryCaptureQueue`. |
54
+ | `auth` | `Auth` (`@storyshelf/auth`) | Optional. Enables auth and the login UI. |
55
+ | `gitHosts` | `GitHostProvider[]` | Optional. Git-host adapters (GitHub/GitLab) for status checks, merge gates, PR comments. |
56
+ | `logger` | `Logger` (pino) | Optional. Shared logger. Construct a fallback via `createShelfLogger()`. |
57
+ | `ui` | `UIConfig` | Optional. UI branding. |
58
+ | `config` | `ShelfConfig` | Optional. Server behavior. |
54
59
 
55
60
  ### `ShelfConfig`
56
61
 
@@ -61,6 +66,8 @@ interface ShelfConfig {
61
66
  captureConcurrency?: number; // concurrent capture jobs (default 2)
62
67
  scratchDir?: string; // capture working directory (required with captureRunner)
63
68
  purgeTtlDays?: number; // purge builds older than N days
69
+ branchTtlDays?: number | null; // branch baseline TTL, days; null disables GC (default 30)
70
+ branchGcIntervalMs?: number; // branch GC interval, ms; daily via interval clock (default 86_400_000)
64
71
  maxUploadBytes?: number; // single-zip upload cap (default 1 GiB)
65
72
  maxInlineUnzipSize?: number; // inline statics extraction cap; unset = capture-only
66
73
  viewports?: Viewport[]; // capture viewports
@@ -73,7 +80,7 @@ interface ShelfConfig {
73
80
  interface UIConfig {
74
81
  name?: string;
75
82
  logo?: string;
76
- favicon?: string;
83
+ favicon?: string; // deprecated: ignored, the app serves its own mark
77
84
  lightTheme?: BrandTheme;
78
85
  darkTheme?: BrandTheme;
79
86
  }
@@ -85,25 +92,25 @@ All adapters are constructor-injected (no AsyncLocalStorage). See `docs/architec
85
92
 
86
93
  - `DatabaseAdapter` (`core/adapter/database`) — `insert`, `update`, `get`, `remove`, `list`, `count`, `all`, `migrate`, `close`. Also exports `ListOptions` and the `createDrizzleAdapter` factory.
87
94
  - `StorageAdapter` (`core/adapter/storage`) — `read`, `write`, `delete`, `exists`, `list(prefix)`.
88
- - `AuthAdapter` (`core/adapter/auth`) — `check(request)`, `createSession(user)`, `destroySession(sessionId)`, optional `handleCallback(callback)`. Also exports `AuthUser`, `AuthCallback`, and the shared `SESSION_COOKIE`.
95
+ - `Auth` (`@storyshelf/auth`) — the shelf authentication singleton: `check(request)`, `handler` (mount at `/api/auth/*`), `loginMethods()`, invites, sessions, passkeys. `AuthUser` and the shared `SESSION_COOKIE` live in `core/types`.
89
96
  - `CaptureRunner` (`core/adapter/capture-runner`) — a **pure capture renderer**: `render(input) => RenderResult`, `cancel(buildId)`. Also exports `RenderedSnapshot`, `RenderResult`, `StoryEntry`, `StorySourceAdapter`, `Viewport`.
90
97
  - `CaptureQueue` (`core/adapter/capture-queue`) — `enqueue({ buildId, reqId? })`, plus `status`, `active`, `recent`. Also exports `CaptureJob`, `QueueEntry`, `JobStatus`.
91
98
  - `GitHostProvider` / `GitHostAdapter` (`core/adapter/git-host`) — set commit status checks, detect merges, and upsert PR comments. Real providers ship in `@storyshelf/git-github` and `@storyshelf/git-gitlab`. Also exports `CheckStatus`.
92
99
 
93
100
  ### Logging
94
101
 
95
- `core` uses **pino** for structured JSON logging. `createShelfLogger({ level, transports, env })` (from `core/logger`) builds a logger writing to stdout by default, with optional extra pino worker transports (Sentry, PostHog, Datadog, GCP, OTEL collector, etc.). Pass the resulting `Logger` to `createShelfRouter({ logger })` (or construct it at your composition root) so request and background logs share one stream. The capture orchestrator derives a `reqId`-scoped child for background capture work, correlating each capture back to the triggering HTTP request. See ADR 0014.
102
+ `core` uses **pino** for structured JSON logging. `createShelfLogger({ level, transports, env })` (from `core/logger`) builds a logger writing to stdout by default, with optional extra pino worker transports (Sentry, PostHog, Datadog, GCP, OTEL collector, etc.). Pass the resulting `Logger` to `createShelfApp({ logger })` (or construct it at your composition root) so request and background logs share one stream. The capture orchestrator derives a `reqId`-scoped child for background capture work, correlating each capture back to the triggering HTTP request. See ADR 0014.
96
103
 
97
104
  ### Capture, diff, and retention
98
105
 
99
106
  Import from `core/capture`, `core/diff`, and the model entries — never from the barrel:
100
107
 
101
- - `executeCaptureJob({ buildId, reqId }, deps)` (`core/capture`) — the capture **orchestrator**: loads the build, marks it `capturing`, extracts the uploaded archive into `scratchDir`, discovers stories, delegates rendering to a pure `CaptureRunner`, and persists. `createShelfRouter` wires it into a `CaptureQueue` when `capture` is supplied (and requires `ShelfConfig.scratchDir`). Also exports `CaptureJobOptions`.
108
+ - `executeCaptureJob({ buildId, reqId }, deps)` (`core/capture`) — the capture **orchestrator**: loads the build, marks it `capturing`, extracts the uploaded archive into `scratchDir`, discovers stories, delegates rendering to a pure `CaptureRunner`, and persists. `createShelfApp` wires it into a `CaptureQueue` when `capture` is supplied (and requires `ShelfConfig.scratchDir`). Also exports `CaptureJobOptions`.
102
109
  - `persistCapture(ctx: CaptureContext)` (`core/capture`) — writes screenshots, diffs against the branch baseline, creates snapshots, and finalizes a build from a pure renderer's `captures`. Also exports `CaptureContext`.
103
110
  - `StorybookAdapter` (`core/capture`) — reads a built Storybook's `index.json`/`stories.json`.
104
111
  - `InMemoryCaptureQueue` (`core/capture`) — in-process, concurrency-limited queue for long-lived hosts; supply a remote queue with a separate worker for serverless.
105
112
  - `diffImages(baseline: Buffer, current: Buffer, options: DiffOptions): DiffResult` (`core/diff`) — pixelmatch-based diff. Also exports `DiffOptions`, `DiffResult`.
106
- - Models, schema, and row types back every entity; retention runs inside the router. (Models are private implementation details with no public entry — reach row types via `core/schema`.)
113
+ - Models, schema, and row types back every entity; retention runs inside the router (`Retention.purge` for builds + `Retention.purgeStaleBranches` for branch GC via `retention-timer.ts` daily sweep). (Models are private implementation details with no public entry — reach row types via `core/schema`.)
107
114
 
108
115
  ### Helpers
109
116
 
@@ -112,7 +119,7 @@ Import from `core/capture`, `core/diff`, and the model entries — never from th
112
119
 
113
120
  ## How it fits in
114
121
 
115
- `core` is the framework everything else plugs into: `createShelfRouter` takes database, storage, capture, and auth adapters (from the `db-*`, `storage-*`, and `auth-*` packages) and produces a complete Hono server. The web UI is server-rendered `hono/jsx` + HTMX, and custom UIs can consume the JSON API under `/api/v1`.
122
+ `core` is the framework everything else plugs into: `createShelfApp` takes database, storage, capture, and auth adapters (from the `db-*`, `storage-*`, and `auth-*` packages) and produces a complete Hono server. The web UI is server-rendered `hono/jsx` + HTMX, and custom UIs can consume the JSON API under `/api/v1`.
116
123
 
117
124
  See `docs/architecture.md` and the ADRs in `docs/adr/`.
118
125
 
@@ -120,13 +127,13 @@ See `docs/architecture.md` and the ADRs in `docs/adr/`.
120
127
 
121
128
  The core router is runtime-agnostic (Web `Request`/`Response`, `fetch`, `crypto`, `URL`). The only Node-specific piece is the in-process `InMemoryCaptureQueue`, which suits long-lived Node servers; serverless runtimes swap in a remote `CaptureQueue` (e.g. `@storyshelf/queue-sqs`) plus a separate worker. You assemble a server for any platform — `storyshelf server init` generates a scaffold with the adapters you choose:
122
129
 
123
- | Platform | Database | Storage | Capture queue | Server entry |
124
- | ------------------------------------------ | ------------------------ | -------------------------------- | ------------------------------- | -------------------------- |
125
- | **Vercel** | `@storyshelf/db-turso` | `@storyshelf/storage-s3` (R2/S3) | Remote `CaptureQueue` + worker | Hono + `@hono/vercel-edge` |
126
- | **Cloudflare Workers** | `@storyshelf/db-turso` | `@storyshelf/storage-s3` (R2) | Workers Queues `CaptureQueue` | Hono + Workers entry |
127
- | **Azure Functions** | `@storyshelf/db-turso` | `@storyshelf/storage-s3` (Blob) | Azure Queues `CaptureQueue` | Hono + Azure handler |
128
- | **AWS Lambda** | `@storyshelf/db-turso` | `@storyshelf/storage-s3` | SQS `CaptureQueue` | Hono + Lambda handler |
129
- | **Deno Deploy** | `@storyshelf/db-turso` | `@storyshelf/storage-s3` | Custom `CaptureQueue` (Deno KV) | Hono + Deno entry |
130
- | **Bun / Node (VPS, Fly, Railway, Render)** | `db-sqlite` / `db-turso` | `storage-local` / `storage-s3` | `InMemoryCaptureQueue` | `storyshelf-server serve` |
130
+ | Platform | Database | Storage | Capture queue | Server entry |
131
+ | ------------------------------------------ | ------------------------------------- | -------------------------------- | ------------------------------- | -------------------------- |
132
+ | **Vercel** | `@storyshelf/db-sqlite/turso` | `@storyshelf/storage-s3` (R2/S3) | Remote `CaptureQueue` + worker | Hono + `@hono/vercel-edge` |
133
+ | **Cloudflare Workers** | `@storyshelf/db-sqlite/turso` | `@storyshelf/storage-s3` (R2) | Workers Queues `CaptureQueue` | Hono + Workers entry |
134
+ | **Azure Functions** | `@storyshelf/db-sqlite/turso` | `@storyshelf/storage-s3` (Blob) | Azure Queues `CaptureQueue` | Hono + Azure handler |
135
+ | **AWS Lambda** | `@storyshelf/db-sqlite/turso` | `@storyshelf/storage-s3` | SQS `CaptureQueue` | Hono + Lambda handler |
136
+ | **Deno Deploy** | `@storyshelf/db-sqlite/turso` | `@storyshelf/storage-s3` | Custom `CaptureQueue` (Deno KV) | Hono + Deno entry |
137
+ | **Bun / Node (VPS, Fly, Railway, Render)** | `db-sqlite` (+ `./bun-sqlite` on Bun) | `storage-local` / `storage-s3` | `InMemoryCaptureQueue` | `storyshelf-server serve` |
131
138
 
132
139
  All clouds are equal — pick the adapters that match your infrastructure. See the **Deployment** guide for recipes including a minimal Turso + S3 + `InMemoryCaptureQueue` example.
@@ -18,6 +18,20 @@ interface CaptureJob {
18
18
  buildId: string;
19
19
  /** Request id used to correlate the background job with the request that queued it. */
20
20
  reqId?: string;
21
+ /**
22
+ * W3C `traceparent` of the enqueueing request. Workers continue this trace
23
+ * instead of starting a root span; absent means start a new trace.
24
+ */
25
+ traceparent?: string;
26
+ }
27
+ /** A job polled from a queue with transport metadata. */
28
+ interface PollableJob extends CaptureJob {
29
+ /** Transport receipt handle (SQS, Redis pop token, etc.). */
30
+ receipt?: string;
31
+ /** Number of delivery attempts already made (0 = first delivery). */
32
+ attempts?: number;
33
+ /** Raw transport message for debugging. */
34
+ raw?: unknown;
21
35
  }
22
36
  /**
23
37
  * A capture job queue, decoupled from the runtime.
@@ -46,6 +60,20 @@ interface CaptureQueue extends Adapter<{
46
60
  /** The most recent queue entries, newest first. */
47
61
  recent(limit: number): Promise<QueueEntry[]>;
48
62
  }
63
+ /** Extension for queues that support worker-side polling. */
64
+ interface PollableCaptureQueue extends CaptureQueue {
65
+ /** Poll for a single job; returns null if none available within waitMs. */
66
+ poll(options?: {
67
+ waitMs?: number;
68
+ }): Promise<PollableJob | null>;
69
+ /** Acknowledge successful processing of a polled job. */
70
+ ack(job: PollableJob): Promise<void>;
71
+ /** Negatively acknowledge; requeue with optional delay when possible. */
72
+ nack(job: PollableJob, options?: {
73
+ requeue?: boolean;
74
+ delayMs?: number;
75
+ }): Promise<void>;
76
+ }
49
77
  //#endregion
50
- export { CaptureJob, CaptureQueue, JobStatus, QueueEntry };
78
+ export { CaptureJob, CaptureQueue, JobStatus, PollableCaptureQueue, PollableJob, QueueEntry };
51
79
  //# sourceMappingURL=capture-queue.d.mts.map
@@ -1,8 +1,8 @@
1
1
  import __tsdown_shims_path from 'node:path';
2
2
  import __tsdown_shims_url from 'node:url';
3
- import { JobStatus } from "./capture-queue.mjs";
4
3
  import { StoryEntry, StorySourceAdapter, Viewport } from "../capture/adapter.mjs";
5
4
  import { Adapter } from "./metadata.mjs";
5
+ import { JobStatus } from "./capture-queue.mjs";
6
6
  import { Logger } from "pino";
7
7
  //#region src/adapters/capture-runner.d.ts
8
8
  /** A screenshot produced by a capture renderer. */
@@ -11,6 +11,8 @@ interface RenderedSnapshot {
11
11
  story: StoryEntry;
12
12
  /** Viewport name the screenshot was captured at. */
13
13
  viewportName: string;
14
+ /** Exact viewport dimensions captured at; falls back to the project list lookup in the pipeline. */
15
+ viewport?: Viewport;
14
16
  /** PNG screenshot bytes. */
15
17
  screenshot: Buffer;
16
18
  }
@@ -41,8 +43,12 @@ interface RenderResult {
41
43
  * orchestrator's job (see `capture/orchestrator.ts`), keeping every adapter
42
44
  * implementation free of server concerns.
43
45
  */
46
+ type BrowserName = "chromium" | "firefox" | "webkit" | "chrome";
47
+ /** Browser names accepted for capture rendering (Chromium default). */
48
+ declare const BROWSER_NAMES: readonly ["chromium", "firefox", "webkit", "chrome"];
44
49
  interface CaptureRunner extends Adapter<{
45
50
  readonly category: "capture-runner";
51
+ readonly supportedBrowsers?: readonly BrowserName[];
46
52
  }> {
47
53
  /**
48
54
  * Render configured viewports for the given stories of an extracted
@@ -65,10 +71,20 @@ interface CaptureRunner extends Adapter<{
65
71
  executePlay?: boolean;
66
72
  /** Timeout for play function execution in ms. */
67
73
  playTimeoutMs?: number;
74
+ /** Whether to run a11y checks (axe) before screenshot; annotated as non-blocking. */
75
+ runA11y?: boolean;
76
+ /** Browser to use for rendering (chromium default). */
77
+ browser?: BrowserName;
78
+ /**
79
+ * Test/extension seam: override the story source adapter (defaults to
80
+ * `StorybookAdapter`). Only the renderer uses it; discovery stays the
81
+ * orchestrator's job.
82
+ */
83
+ adapter?: StorySourceAdapter;
68
84
  }): Promise<RenderResult>;
69
85
  /** Cancel a pending or in-flight render for a build. */
70
86
  cancel(buildId: string): Promise<void>;
71
87
  }
72
88
  //#endregion
73
- export { CaptureRunner, type JobStatus, RenderFailure, RenderResult, RenderedSnapshot, type StoryEntry, type StorySourceAdapter, type Viewport };
89
+ export { BROWSER_NAMES, BrowserName, CaptureRunner, type JobStatus, RenderFailure, RenderResult, RenderedSnapshot, type StoryEntry, type StorySourceAdapter, type Viewport };
74
90
  //# sourceMappingURL=capture-runner.d.mts.map
@@ -1,4 +1,15 @@
1
1
  import "node:path";
2
2
  import "node:url";
3
3
  import.meta.url;
4
- export {};
4
+ //#region src/adapters/capture-runner.ts
5
+ /** Browser names accepted for capture rendering (Chromium default). */
6
+ const BROWSER_NAMES = [
7
+ "chromium",
8
+ "firefox",
9
+ "webkit",
10
+ "chrome"
11
+ ];
12
+ //#endregion
13
+ export { BROWSER_NAMES };
14
+
15
+ //# sourceMappingURL=capture-runner.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capture-runner.mjs","names":[],"sources":["../../src/adapters/capture-runner.ts"],"sourcesContent":["/**\n * Capture-runner adapter interface: render story screenshots for a build.\n */\nimport type { Logger } from \"pino\";\nimport type { StoryEntry, StorySourceAdapter, Viewport } from \"../capture/adapter.ts\";\nimport type { Adapter } from \"./metadata.ts\";\n\nexport type { JobStatus } from \"./capture-queue.ts\";\nexport type { StoryEntry, StorySourceAdapter, Viewport } from \"../capture/adapter.ts\";\n\n/** A screenshot produced by a capture renderer. */\nexport interface RenderedSnapshot {\n /** The rendered story (echoed back from the render request). */\n story: StoryEntry;\n /** Viewport name the screenshot was captured at. */\n viewportName: string;\n /** Exact viewport dimensions captured at; falls back to the project list lookup in the pipeline. */\n viewport?: Viewport;\n /** PNG screenshot bytes. */\n screenshot: Buffer;\n}\n\n/** A story/viewport that a renderer could not capture. */\nexport interface RenderFailure {\n /** Story that failed to render. */\n storyId: string;\n /** Viewport that failed to render. */\n viewportName: string;\n /** Error message. */\n error: string;\n}\n\n/** The outcome of a render request. */\nexport interface RenderResult {\n /** Screenshots the renderer produced successfully. */\n captures: RenderedSnapshot[];\n /** Story/viewport pairs the renderer could not capture. */\n failures: RenderFailure[];\n}\n\n/**\n * A pure capture renderer.\n *\n * Adapters implementing `CaptureRunner` render screenshots from an\n * already-extracted Storybook directory and return PNG buffers. They are\n * intentionally pure: they perform **no** database, storage, or build-state\n * management. Loading the build, extracting the uploaded archive, persisting\n * snapshots/baselines/diffs, and advancing build status are the capture\n * orchestrator's job (see `capture/orchestrator.ts`), keeping every adapter\n * implementation free of server concerns.\n */\nexport type BrowserName = \"chromium\" | \"firefox\" | \"webkit\" | \"chrome\";\n\n/** Browser names accepted for capture rendering (Chromium default). */\nexport const BROWSER_NAMES = [\"chromium\", \"firefox\", \"webkit\", \"chrome\"] as const;\n\nexport interface CaptureRunner extends Adapter<{\n readonly category: \"capture-runner\";\n readonly supportedBrowsers?: readonly BrowserName[];\n}> {\n /**\n * Render configured viewports for the given stories of an extracted\n * Storybook and return the screenshot buffers.\n *\n * @param input - Render request with the extracted Storybook directory.\n */\n render(input: {\n /** Build being captured, used to correlate cancels with in-flight work. */\n buildId: string;\n /** Root of the already-extracted Storybook build. */\n storybookDir: string;\n /** Stories to render. */\n stories: StoryEntry[];\n /** Viewports to capture. */\n viewports: Viewport[];\n /** Optional logger for render-time diagnostics. */\n logger?: Logger;\n /** Whether to execute Storybook play functions before screenshot. */\n executePlay?: boolean;\n /** Timeout for play function execution in ms. */\n playTimeoutMs?: number;\n /** Whether to run a11y checks (axe) before screenshot; annotated as non-blocking. */\n runA11y?: boolean;\n /** Browser to use for rendering (chromium default). */\n browser?: BrowserName;\n /**\n * Test/extension seam: override the story source adapter (defaults to\n * `StorybookAdapter`). Only the renderer uses it; discovery stays the\n * orchestrator's job.\n */\n adapter?: StorySourceAdapter;\n }): Promise<RenderResult>;\n\n /** Cancel a pending or in-flight render for a build. */\n cancel(buildId: string): Promise<void>;\n}\n"],"mappings":";;;;;AAsDA,MAAa,gBAAgB;CAAC;CAAY;CAAW;CAAU;AAAQ"}
@@ -1,9 +1,36 @@
1
1
  import __tsdown_shims_path from 'node:path';
2
2
  import __tsdown_shims_url from 'node:url';
3
3
  import { Adapter, AdapterMetadata } from "./metadata.mjs";
4
- import { SQL } from "drizzle-orm";
5
- import { AnySQLiteTable } from "drizzle-orm/sqlite-core";
4
+ import { SQL, Table } from "drizzle-orm";
6
5
  //#region src/adapters/database.d.ts
6
+ /** Type-safe table map enforced on every DatabaseAdapter. */
7
+ interface Tables {
8
+ projects: Table;
9
+ projectStatusConfigs: Table;
10
+ builds: Table;
11
+ captureAttempts: Table;
12
+ captureLogs: Table;
13
+ snapshots: Table;
14
+ baselines: Table;
15
+ comments: Table;
16
+ labelTypes: Table;
17
+ buildLabels: Table;
18
+ tokens: Table;
19
+ webhooks: Table;
20
+ notificationChannels: Table;
21
+ notificationSubscriptions: Table;
22
+ users: Table;
23
+ userInviteTokens: Table;
24
+ projectMembers: Table;
25
+ projectGroupMappings: Table;
26
+ contentRefs: Table;
27
+ }
28
+ /** Table keys every DatabaseAdapter must expose (contract + validation share this). */
29
+ declare const REQUIRED_TABLE_KEYS: string[];
30
+ /** Largest page any adapter serves in one list call. */
31
+ declare const MAX_LIST_LIMIT = 1000;
32
+ /** Clamp a list limit into [1, MAX_LIST_LIMIT]; undefined stays undefined. */
33
+ declare function clampListLimit(limit?: number): number | undefined;
7
34
  /** Options that narrow and page a list query. */
8
35
  interface ListOptions {
9
36
  /** SQL WHERE condition. */
@@ -15,40 +42,63 @@ interface ListOptions {
15
42
  /** Number of rows to skip. */
16
43
  offset?: number;
17
44
  }
18
- /** Database abstraction over Drizzle tables, agnostic of the underlying driver. */
45
+ /**
46
+ * Transactional CRUD subset available inside {@link DatabaseAdapter.transact}.
47
+ * Mirrors the adapter surface minus metadata, lifecycle, and raw `all`
48
+ * (opaque to transactions on some drivers).
49
+ */
50
+ interface TxStore {
51
+ /** Insert a row and return the inserted record. */
52
+ insert<T extends Table>(table: T, values: T["$inferInsert"]): Promise<T["$inferSelect"]>;
53
+ /** Update a row by id and return the updated record. */
54
+ update<T extends Table>(table: T, id: string, values: Partial<T["$inferInsert"]>): Promise<T["$inferSelect"]>;
55
+ /** Fetch a single row by id, or null if not found. */
56
+ get<T extends Table>(table: T, id: string): Promise<T["$inferSelect"] | null>;
57
+ /** Delete a row by id. */
58
+ remove(table: Table, id: string): Promise<void>;
59
+ /** List rows matching the given options. */
60
+ list<T extends Table>(table: T, opts?: ListOptions): Promise<T["$inferSelect"][]>;
61
+ /** Count rows matching an optional where condition. */
62
+ count(table: Table, where?: SQL): Promise<number>;
63
+ }
64
+ /** Database abstraction over Drizzle tables, agnostic of dialect and driver. */
19
65
  interface DatabaseAdapter extends Adapter<{
20
66
  readonly category: "database";
21
67
  }> {
68
+ /** Table handles for this adapter's dialect, enforced with type safety. */
69
+ readonly tables: Tables;
22
70
  /** Insert a row and return the inserted record. */
23
- insert<T extends AnySQLiteTable>(table: T, values: T["$inferInsert"]): Promise<T["$inferSelect"]>;
71
+ insert<T extends Table>(table: T, values: T["$inferInsert"]): Promise<T["$inferSelect"]>;
24
72
  /** Update a row by id and return the updated record. */
25
- update<T extends AnySQLiteTable>(table: T, id: string, values: Partial<T["$inferInsert"]>): Promise<T["$inferSelect"]>;
73
+ update<T extends Table>(table: T, id: string, values: Partial<T["$inferInsert"]>): Promise<T["$inferSelect"]>;
26
74
  /** Fetch a single row by id, or null if not found. */
27
- get<T extends AnySQLiteTable>(table: T, id: string): Promise<T["$inferSelect"] | null>;
75
+ get<T extends Table>(table: T, id: string): Promise<T["$inferSelect"] | null>;
28
76
  /** Delete a row by id. */
29
- remove(table: AnySQLiteTable, id: string): Promise<void>;
77
+ remove(table: Table, id: string): Promise<void>;
30
78
  /** List rows matching the given options. */
31
- list<T extends AnySQLiteTable>(table: T, opts?: ListOptions): Promise<T["$inferSelect"][]>;
79
+ list<T extends Table>(table: T, opts?: ListOptions): Promise<T["$inferSelect"][]>;
32
80
  /** Count rows matching an optional where condition. */
33
- count(table: AnySQLiteTable, where?: SQL): Promise<number>;
81
+ count(table: Table, where?: SQL): Promise<number>;
34
82
  /** Run an arbitrary SQL query and return typed rows. */
35
83
  all<T>(query: SQL): Promise<T[]>;
84
+ /**
85
+ * Run `fn` inside a transaction when the driver supports one.
86
+ * Commits on resolve, rolls back on throw. Drivers without transaction
87
+ * support omit this; callers must use `?.` and accept sequential execution.
88
+ */
89
+ transact?<R>(fn: (tx: TxStore) => Promise<R>): Promise<R>;
36
90
  }
37
91
  /** Driver-supplied identity plus lifecycle hooks. */
38
92
  interface DrizzleAdapterOptions {
39
93
  metadata: AdapterMetadata & {
40
94
  readonly category: "database";
41
95
  };
96
+ tables: Tables;
42
97
  migrate: () => Promise<void> | void;
43
98
  close: () => Promise<void> | void;
99
+ /** Cheap liveness probe (e.g. `SELECT 1`); omitted when the driver has none. */
100
+ ping?: () => Promise<void> | void;
44
101
  }
45
- /**
46
- * Build a {@link DatabaseAdapter} over any SQLite-compatible Drizzle
47
- * dialect. Sync results (node:sqlite) and promises (libSQL) are both
48
- * awaited, so drivers only supply connection setup, metadata, and the
49
- * driver-specific migrate/close.
50
- */
51
- declare function createDrizzleAdapter(db: unknown, options: DrizzleAdapterOptions): DatabaseAdapter;
52
102
  //#endregion
53
- export { DatabaseAdapter, DrizzleAdapterOptions, ListOptions, createDrizzleAdapter };
103
+ export { DatabaseAdapter, DrizzleAdapterOptions, ListOptions, MAX_LIST_LIMIT, REQUIRED_TABLE_KEYS, Tables, TxStore, clampListLimit };
54
104
  //# sourceMappingURL=database.d.mts.map
@@ -1,63 +1,30 @@
1
1
  import "node:path";
2
2
  import "node:url";
3
3
  import.meta.url;
4
- import { eq, getTableColumns } from "drizzle-orm";
5
4
  //#region src/adapters/database.ts
6
- function idOf(table) {
7
- const id = getTableColumns(table)["id"];
8
- if (!id) throw new Error("Table has no id column");
9
- return id;
10
- }
11
- function applyListOptions(query, opts) {
12
- let current = query;
13
- if (opts.where) current = current.where(opts.where);
14
- if (opts.orderBy) current = current.orderBy(opts.orderBy);
15
- if (opts.limit !== void 0) current = current.limit(opts.limit);
16
- if (opts.offset !== void 0) current = current.offset(opts.offset);
17
- return current;
18
- }
19
- /**
20
- * Build the database lifecycle: migrations run in `init`, the connection
21
- * closes in `close`. Close tolerates repeated calls (drivers may not).
22
- */
23
- function buildLifecycle(options) {
24
- let closed = false;
25
- return {
26
- init: async () => {
27
- await options.migrate();
28
- },
29
- close: async () => {
30
- if (closed) return;
31
- closed = true;
32
- await options.close();
33
- }
34
- };
35
- }
36
- /**
37
- * Build a {@link DatabaseAdapter} over any SQLite-compatible Drizzle
38
- * dialect. Sync results (node:sqlite) and promises (libSQL) are both
39
- * awaited, so drivers only supply connection setup, metadata, and the
40
- * driver-specific migrate/close.
41
- */
42
- function createDrizzleAdapter(db, options) {
43
- const drizzle = db;
44
- return {
45
- metadata: options.metadata,
46
- lifecycle: buildLifecycle(options),
47
- insert: async (table, values) => await drizzle.insert(table).values(values).returning().get(),
48
- update: async (table, id, values) => await drizzle.update(table).set(values).where(eq(idOf(table), id)).returning().get(),
49
- get: async (table, id) => await drizzle.select().from(table).where(eq(idOf(table), id)).limit(1).get() ?? null,
50
- remove: async (table, id) => {
51
- await drizzle.delete(table).where(eq(idOf(table), id)).run();
52
- },
53
- list: async (table, opts = {}) => {
54
- return await applyListOptions(drizzle.select().from(table), opts).all();
55
- },
56
- count: async (table, where) => await drizzle.$count(table, where),
57
- all: async (query) => await drizzle.all(query)
58
- };
5
+ /** Table keys every DatabaseAdapter must expose (contract + validation share this). */
6
+ const REQUIRED_TABLE_KEYS = [
7
+ "projects",
8
+ "builds",
9
+ "snapshots",
10
+ "baselines",
11
+ "comments",
12
+ "labelTypes",
13
+ "buildLabels",
14
+ "tokens",
15
+ "webhooks",
16
+ "users",
17
+ "projectMembers"
18
+ ];
19
+ /** Largest page any adapter serves in one list call. */
20
+ const MAX_LIST_LIMIT = 1e3;
21
+ /** Clamp a list limit into [1, MAX_LIST_LIMIT]; undefined stays undefined. */
22
+ function clampListLimit(limit) {
23
+ if (limit === void 0) return;
24
+ if (!Number.isFinite(limit)) return MAX_LIST_LIMIT;
25
+ return Math.min(Math.max(Math.floor(limit), 1), MAX_LIST_LIMIT);
59
26
  }
60
27
  //#endregion
61
- export { createDrizzleAdapter };
28
+ export { MAX_LIST_LIMIT, REQUIRED_TABLE_KEYS, clampListLimit };
62
29
 
63
30
  //# sourceMappingURL=database.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"database.mjs","names":[],"sources":["../../src/adapters/database.ts"],"sourcesContent":["/**\n * Database adapter interface: query builders over explicitly typed tables.\n */\nimport type { SQL } from \"drizzle-orm\";\nimport { eq, getTableColumns } from \"drizzle-orm\";\nimport type { AnySQLiteTable, SQLiteColumn } from \"drizzle-orm/sqlite-core\";\nimport type { Adapter, AdapterLifecycle, AdapterMetadata } from \"./metadata.ts\";\n\n/** Options that narrow and page a list query. */\nexport interface ListOptions {\n /** SQL WHERE condition. */\n where?: SQL;\n /** SQL ORDER BY condition. */\n orderBy?: SQL;\n /** Maximum number of rows to return. */\n limit?: number;\n /** Number of rows to skip. */\n offset?: number;\n}\n\n/** Database abstraction over Drizzle tables, agnostic of the underlying driver. */\nexport interface DatabaseAdapter extends Adapter<{ readonly category: \"database\" }> {\n /** Insert a row and return the inserted record. */\n insert<T extends AnySQLiteTable>(table: T, values: T[\"$inferInsert\"]): Promise<T[\"$inferSelect\"]>;\n /** Update a row by id and return the updated record. */\n update<T extends AnySQLiteTable>(\n table: T,\n id: string,\n values: Partial<T[\"$inferInsert\"]>,\n ): Promise<T[\"$inferSelect\"]>;\n /** Fetch a single row by id, or null if not found. */\n get<T extends AnySQLiteTable>(table: T, id: string): Promise<T[\"$inferSelect\"] | null>;\n /** Delete a row by id. */\n remove(table: AnySQLiteTable, id: string): Promise<void>;\n /** List rows matching the given options. */\n list<T extends AnySQLiteTable>(table: T, opts?: ListOptions): Promise<T[\"$inferSelect\"][]>;\n /** Count rows matching an optional where condition. */\n count(table: AnySQLiteTable, where?: SQL): Promise<number>;\n /** Run an arbitrary SQL query and return typed rows. */\n all<T>(query: SQL): Promise<T[]>;\n}\n\n/** A value or a promise of one (sync node:sqlite vs async libSQL drivers). */\ntype MaybePromise<T> = T | Promise<T>;\n\n/** Chainable select surface shared by the sync and async Drizzle dialects. */\ninterface DrizzleSelectChain {\n where(where: SQL): DrizzleSelectChain;\n orderBy(order: SQL): DrizzleSelectChain;\n limit(n: number): DrizzleSelectChain;\n offset(n: number): DrizzleSelectChain;\n get(): MaybePromise<unknown>;\n all(): MaybePromise<unknown[]>;\n}\n\n/**\n * Minimal Drizzle surface consumed by {@link createDrizzleAdapter}. Drivers\n * pass their dialect instance as `unknown`; the cast lives here, once, so\n * driver packages stay fully typed. Covered by the adapter contract tests.\n */\ninterface DrizzleLike {\n insert(table: AnySQLiteTable): {\n values(values: unknown): { returning(): { get(): MaybePromise<unknown> } };\n };\n update(table: AnySQLiteTable): {\n set(values: unknown): { where(where: SQL): { returning(): { get(): MaybePromise<unknown> } } };\n };\n select(): { from(table: AnySQLiteTable): DrizzleSelectChain };\n delete(table: AnySQLiteTable): { where(where: SQL): { run(): MaybePromise<unknown> } };\n $count(table: AnySQLiteTable, where?: SQL): MaybePromise<number>;\n all<T>(query: SQL): MaybePromise<T[]>;\n}\n\n/** Driver-supplied identity plus lifecycle hooks. */\nexport interface DrizzleAdapterOptions {\n metadata: AdapterMetadata & { readonly category: \"database\" };\n migrate: () => Promise<void> | void;\n close: () => Promise<void> | void;\n}\n\nfunction idOf(table: AnySQLiteTable): SQLiteColumn {\n const id = getTableColumns(table)[\"id\"];\n if (!id) {\n throw new Error(\"Table has no id column\");\n }\n return id;\n}\n\nfunction applyListOptions(query: DrizzleSelectChain, opts: ListOptions): DrizzleSelectChain {\n let current = query;\n if (opts.where) {\n current = current.where(opts.where);\n }\n if (opts.orderBy) {\n current = current.orderBy(opts.orderBy);\n }\n if (opts.limit !== undefined) {\n current = current.limit(opts.limit);\n }\n if (opts.offset !== undefined) {\n current = current.offset(opts.offset);\n }\n return current;\n}\n\n/**\n * Build the database lifecycle: migrations run in `init`, the connection\n * closes in `close`. Close tolerates repeated calls (drivers may not).\n */\nfunction buildLifecycle(options: DrizzleAdapterOptions): AdapterLifecycle {\n let closed = false;\n return {\n init: async () => {\n await options.migrate();\n },\n close: async () => {\n if (closed) {\n return;\n }\n closed = true;\n await options.close();\n },\n };\n}\n\n/**\n * Build a {@link DatabaseAdapter} over any SQLite-compatible Drizzle\n * dialect. Sync results (node:sqlite) and promises (libSQL) are both\n * awaited, so drivers only supply connection setup, metadata, and the\n * driver-specific migrate/close.\n */\nexport function createDrizzleAdapter(db: unknown, options: DrizzleAdapterOptions): DatabaseAdapter {\n const drizzle = db as DrizzleLike;\n return {\n metadata: options.metadata,\n lifecycle: buildLifecycle(options),\n insert: async <T extends AnySQLiteTable>(\n table: T,\n values: T[\"$inferInsert\"],\n ): Promise<T[\"$inferSelect\"]> =>\n (await drizzle.insert(table).values(values).returning().get()) as T[\"$inferSelect\"],\n update: async <T extends AnySQLiteTable>(\n table: T,\n id: string,\n values: Partial<T[\"$inferInsert\"]>,\n ): Promise<T[\"$inferSelect\"]> =>\n (await drizzle\n .update(table)\n .set(values)\n .where(eq(idOf(table), id))\n .returning()\n .get()) as T[\"$inferSelect\"],\n get: async <T extends AnySQLiteTable>(\n table: T,\n id: string,\n ): Promise<T[\"$inferSelect\"] | null> =>\n ((await drizzle\n .select()\n .from(table)\n .where(eq(idOf(table), id))\n .limit(1)\n .get()) as T[\"$inferSelect\"] | undefined) ?? null,\n remove: async (table: AnySQLiteTable, id: string): Promise<void> => {\n await drizzle\n .delete(table)\n .where(eq(idOf(table), id))\n .run();\n },\n list: async <T extends AnySQLiteTable>(\n table: T,\n opts: ListOptions = {},\n ): Promise<T[\"$inferSelect\"][]> => {\n const query = applyListOptions(drizzle.select().from(table), opts);\n const rows: unknown = await query.all();\n return rows as T[\"$inferSelect\"][];\n },\n count: async (table: AnySQLiteTable, where?: SQL): Promise<number> =>\n await drizzle.$count(table, where),\n all: async <T>(query: SQL): Promise<T[]> => await drizzle.all(query),\n };\n}\n"],"mappings":";;;;;AAgFA,SAAS,KAAK,OAAqC;CACjD,MAAM,KAAK,gBAAgB,KAAK,CAAC,CAAC;CAClC,IAAI,CAAC,IACH,MAAM,IAAI,MAAM,wBAAwB;CAE1C,OAAO;AACT;AAEA,SAAS,iBAAiB,OAA2B,MAAuC;CAC1F,IAAI,UAAU;CACd,IAAI,KAAK,OACP,UAAU,QAAQ,MAAM,KAAK,KAAK;CAEpC,IAAI,KAAK,SACP,UAAU,QAAQ,QAAQ,KAAK,OAAO;CAExC,IAAI,KAAK,UAAU,KAAA,GACjB,UAAU,QAAQ,MAAM,KAAK,KAAK;CAEpC,IAAI,KAAK,WAAW,KAAA,GAClB,UAAU,QAAQ,OAAO,KAAK,MAAM;CAEtC,OAAO;AACT;;;;;AAMA,SAAS,eAAe,SAAkD;CACxE,IAAI,SAAS;CACb,OAAO;EACL,MAAM,YAAY;GAChB,MAAM,QAAQ,QAAQ;EACxB;EACA,OAAO,YAAY;GACjB,IAAI,QACF;GAEF,SAAS;GACT,MAAM,QAAQ,MAAM;EACtB;CACF;AACF;;;;;;;AAQA,SAAgB,qBAAqB,IAAa,SAAiD;CACjG,MAAM,UAAU;CAChB,OAAO;EACL,UAAU,QAAQ;EAClB,WAAW,eAAe,OAAO;EACjC,QAAQ,OACN,OACA,WAEC,MAAM,QAAQ,OAAO,KAAK,CAAC,CAAC,OAAO,MAAM,CAAC,CAAC,UAAU,CAAC,CAAC,IAAI;EAC9D,QAAQ,OACN,OACA,IACA,WAEC,MAAM,QACJ,OAAO,KAAK,CAAC,CACb,IAAI,MAAM,CAAC,CACX,MAAM,GAAG,KAAK,KAAK,GAAG,EAAE,CAAC,CAAC,CAC1B,UAAU,CAAC,CACX,IAAI;EACT,KAAK,OACH,OACA,OAEE,MAAM,QACL,OAAO,CAAC,CACR,KAAK,KAAK,CAAC,CACX,MAAM,GAAG,KAAK,KAAK,GAAG,EAAE,CAAC,CAAC,CAC1B,MAAM,CAAC,CAAC,CACR,IAAI,KAAwC;EACjD,QAAQ,OAAO,OAAuB,OAA8B;GAClE,MAAM,QACH,OAAO,KAAK,CAAC,CACb,MAAM,GAAG,KAAK,KAAK,GAAG,EAAE,CAAC,CAAC,CAC1B,IAAI;EACT;EACA,MAAM,OACJ,OACA,OAAoB,CAAC,MACY;GAGjC,OAAO,MAFO,iBAAiB,QAAQ,OAAO,CAAC,CAAC,KAAK,KAAK,GAAG,IAC7B,CAAC,CAAC,IAAI;EAExC;EACA,OAAO,OAAO,OAAuB,UACnC,MAAM,QAAQ,OAAO,OAAO,KAAK;EACnC,KAAK,OAAU,UAA6B,MAAM,QAAQ,IAAI,KAAK;CACrE;AACF"}
1
+ {"version":3,"file":"database.mjs","names":[],"sources":["../../src/adapters/database.ts"],"sourcesContent":["/**\n * Database adapter interface: query builders over explicitly typed tables.\n *\n * Canonical home of the database contract — {@link DatabaseAdapter},\n * {@link Tables}, {@link TxStore}, {@link ListOptions}, and\n * {@link DrizzleAdapterOptions}. Driver machinery lives in the database\n * packages (`@storyshelf/db-sqlite`, `@storyshelf/db-postgres`).\n */\nimport type { SQL } from \"drizzle-orm\";\nimport type { Table } from \"drizzle-orm\";\nimport type { Adapter, AdapterMetadata } from \"./metadata.ts\";\n\n/** Type-safe table map enforced on every DatabaseAdapter. */\nexport interface Tables {\n projects: Table;\n projectStatusConfigs: Table;\n builds: Table;\n captureAttempts: Table;\n captureLogs: Table;\n snapshots: Table;\n baselines: Table;\n comments: Table;\n labelTypes: Table;\n buildLabels: Table;\n tokens: Table;\n webhooks: Table;\n notificationChannels: Table;\n notificationSubscriptions: Table;\n users: Table;\n userInviteTokens: Table;\n projectMembers: Table;\n projectGroupMappings: Table;\n contentRefs: Table;\n}\n\n/** Table keys every DatabaseAdapter must expose (contract + validation share this). */\nexport const REQUIRED_TABLE_KEYS = [\n \"projects\",\n \"builds\",\n \"snapshots\",\n \"baselines\",\n \"comments\",\n \"labelTypes\",\n \"buildLabels\",\n \"tokens\",\n \"webhooks\",\n \"users\",\n \"projectMembers\",\n];\n\n/** Largest page any adapter serves in one list call. */\nexport const MAX_LIST_LIMIT = 1000;\n\n/** Clamp a list limit into [1, MAX_LIST_LIMIT]; undefined stays undefined. */\nexport function clampListLimit(limit?: number): number | undefined {\n if (limit === undefined) {\n return undefined;\n }\n if (!Number.isFinite(limit)) {\n return MAX_LIST_LIMIT;\n }\n return Math.min(Math.max(Math.floor(limit), 1), MAX_LIST_LIMIT);\n}\n\n/** Options that narrow and page a list query. */\nexport interface ListOptions {\n /** SQL WHERE condition. */\n where?: SQL;\n /** SQL ORDER BY condition. */\n orderBy?: SQL;\n /** Maximum number of rows to return. */\n limit?: number;\n /** Number of rows to skip. */\n offset?: number;\n}\n\n/**\n * Transactional CRUD subset available inside {@link DatabaseAdapter.transact}.\n * Mirrors the adapter surface minus metadata, lifecycle, and raw `all`\n * (opaque to transactions on some drivers).\n */\nexport interface TxStore {\n /** Insert a row and return the inserted record. */\n insert<T extends Table>(table: T, values: T[\"$inferInsert\"]): Promise<T[\"$inferSelect\"]>;\n /** Update a row by id and return the updated record. */\n update<T extends Table>(\n table: T,\n id: string,\n values: Partial<T[\"$inferInsert\"]>,\n ): Promise<T[\"$inferSelect\"]>;\n /** Fetch a single row by id, or null if not found. */\n get<T extends Table>(table: T, id: string): Promise<T[\"$inferSelect\"] | null>;\n /** Delete a row by id. */\n remove(table: Table, id: string): Promise<void>;\n /** List rows matching the given options. */\n list<T extends Table>(table: T, opts?: ListOptions): Promise<T[\"$inferSelect\"][]>;\n /** Count rows matching an optional where condition. */\n count(table: Table, where?: SQL): Promise<number>;\n}\n\n/** Database abstraction over Drizzle tables, agnostic of dialect and driver. */\nexport interface DatabaseAdapter extends Adapter<{ readonly category: \"database\" }> {\n /** Table handles for this adapter's dialect, enforced with type safety. */\n readonly tables: Tables;\n /** Insert a row and return the inserted record. */\n insert<T extends Table>(table: T, values: T[\"$inferInsert\"]): Promise<T[\"$inferSelect\"]>;\n /** Update a row by id and return the updated record. */\n update<T extends Table>(\n table: T,\n id: string,\n values: Partial<T[\"$inferInsert\"]>,\n ): Promise<T[\"$inferSelect\"]>;\n /** Fetch a single row by id, or null if not found. */\n get<T extends Table>(table: T, id: string): Promise<T[\"$inferSelect\"] | null>;\n /** Delete a row by id. */\n remove(table: Table, id: string): Promise<void>;\n /** List rows matching the given options. */\n list<T extends Table>(table: T, opts?: ListOptions): Promise<T[\"$inferSelect\"][]>;\n /** Count rows matching an optional where condition. */\n count(table: Table, where?: SQL): Promise<number>;\n /** Run an arbitrary SQL query and return typed rows. */\n all<T>(query: SQL): Promise<T[]>;\n /**\n * Run `fn` inside a transaction when the driver supports one.\n * Commits on resolve, rolls back on throw. Drivers without transaction\n * support omit this; callers must use `?.` and accept sequential execution.\n */\n transact?<R>(fn: (tx: TxStore) => Promise<R>): Promise<R>;\n}\n\n/** Driver-supplied identity plus lifecycle hooks. */\nexport interface DrizzleAdapterOptions {\n metadata: AdapterMetadata & { readonly category: \"database\" };\n tables: Tables;\n migrate: () => Promise<void> | void;\n close: () => Promise<void> | void;\n /** Cheap liveness probe (e.g. `SELECT 1`); omitted when the driver has none. */\n ping?: () => Promise<void> | void;\n}\n"],"mappings":";;;;;AAoCA,MAAa,sBAAsB;CACjC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;AAGA,MAAa,iBAAiB;;AAG9B,SAAgB,eAAe,OAAoC;CACjE,IAAI,UAAU,KAAA,GACZ;CAEF,IAAI,CAAC,OAAO,SAAS,KAAK,GACxB,OAAO;CAET,OAAO,KAAK,IAAI,KAAK,IAAI,KAAK,MAAM,KAAK,GAAG,CAAC,GAAG,cAAc;AAChE"}
@@ -0,0 +1,27 @@
1
+ import __tsdown_shims_path from 'node:path';
2
+ import __tsdown_shims_url from 'node:url';
3
+ import { Adapter } from "./metadata.mjs";
4
+ //#region src/adapters/email-sender.d.ts
5
+ /** A single email message (transport-agnostic). */
6
+ interface EmailMessage {
7
+ to: string;
8
+ subject: string;
9
+ text: string;
10
+ html?: string;
11
+ from?: string;
12
+ replyTo?: string;
13
+ }
14
+ /**
15
+ * Minimal email transport: implemented once in `@storyshelf/notify-email`,
16
+ * consumed by notification fan-out and (optionally) the auth engine for
17
+ * invites and password resets. Best-effort delivery; throws on failure so
18
+ * callers can log and continue.
19
+ */
20
+ interface EmailSender extends Adapter<{
21
+ readonly category: "notifier";
22
+ }> {
23
+ send(message: EmailMessage): Promise<void>;
24
+ }
25
+ //#endregion
26
+ export { EmailMessage, EmailSender };
27
+ //# sourceMappingURL=email-sender.d.mts.map
@@ -0,0 +1,4 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ export {};