@storyshelf/core 0.1.2 → 0.3.1

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 (275) hide show
  1. package/README.md +50 -46
  2. package/dist/adapter/auth.d.mts +4 -4
  3. package/dist/adapter/auth.mjs.map +1 -1
  4. package/dist/{adapters → adapter}/capture-queue.d.mts +6 -4
  5. package/dist/adapter/capture-queue.mjs +4 -0
  6. package/dist/adapter/capture-runner.d.mts +7 -7
  7. package/dist/adapter/database.d.mts +20 -9
  8. package/dist/adapter/database.mjs +60 -1
  9. package/dist/adapter/database.mjs.map +1 -0
  10. package/dist/adapter/git-host/comments.d.mts +41 -0
  11. package/dist/adapter/git-host/comments.mjs +54 -0
  12. package/dist/adapter/git-host/comments.mjs.map +1 -0
  13. package/dist/adapter/git-host/helpers.d.mts +1 -0
  14. package/dist/adapter/git-host/helpers.mjs +1 -0
  15. package/dist/adapter/git-host/helpers.mjs.map +1 -1
  16. package/dist/adapter/git-host.d.mts +3 -7
  17. package/dist/adapter/init.d.mts +58 -0
  18. package/dist/adapter/init.mjs +93 -0
  19. package/dist/adapter/init.mjs.map +1 -0
  20. package/dist/adapter/metadata.d.mts +83 -0
  21. package/dist/adapter/metadata.mjs +4 -0
  22. package/dist/adapter/storage.d.mts +15 -4
  23. package/dist/adapters/webhook-events.mjs +20 -4
  24. package/dist/adapters/webhook-events.mjs.map +1 -1
  25. package/dist/capture/adapter.d.mts +7 -1
  26. package/dist/capture/adapter.mjs +3 -0
  27. package/dist/capture/adapter.mjs.map +1 -1
  28. package/dist/capture/dispatch.mjs +150 -0
  29. package/dist/capture/dispatch.mjs.map +1 -0
  30. package/dist/capture/orchestrator.d.mts +9 -0
  31. package/dist/capture/orchestrator.mjs +11 -44
  32. package/dist/capture/orchestrator.mjs.map +1 -1
  33. package/dist/capture/pipeline.d.mts +4 -1
  34. package/dist/capture/pipeline.mjs +6 -6
  35. package/dist/capture/pipeline.mjs.map +1 -1
  36. package/dist/capture/queue.d.mts +4 -1
  37. package/dist/capture/queue.mjs +3 -1
  38. package/dist/capture/queue.mjs.map +1 -1
  39. package/dist/capture/skip-checks.mjs +76 -0
  40. package/dist/capture/skip-checks.mjs.map +1 -0
  41. package/dist/capture/statics.mjs +124 -0
  42. package/dist/capture/statics.mjs.map +1 -0
  43. package/dist/capture/status-fanout.mjs +1 -0
  44. package/dist/capture/status-fanout.mjs.map +1 -1
  45. package/dist/capture/storybook.d.mts +1 -0
  46. package/dist/capture/storybook.mjs +1 -0
  47. package/dist/capture/storybook.mjs.map +1 -1
  48. package/dist/capture.d.mts +8 -0
  49. package/dist/capture.mjs +9 -0
  50. package/dist/config.d.mts +37 -22
  51. package/dist/config.mjs +28 -2
  52. package/dist/config.mjs.map +1 -1
  53. package/dist/ddl.d.mts +2 -1
  54. package/dist/ddl.mjs +2 -1
  55. package/dist/ddl.mjs.map +1 -1
  56. package/dist/diff/engine.d.mts +8 -0
  57. package/dist/diff/engine.mjs +8 -0
  58. package/dist/diff/engine.mjs.map +1 -1
  59. package/dist/diff/options.d.mts +3 -1
  60. package/dist/diff.d.mts +5 -0
  61. package/dist/diff.mjs +6 -0
  62. package/dist/index.d.mts +34 -24
  63. package/dist/index.mjs +211 -252
  64. package/dist/index.mjs.map +1 -1
  65. package/dist/logger.d.mts +3 -3
  66. package/dist/logger.mjs.map +1 -1
  67. package/dist/middleware/auth-gate.mjs +36 -0
  68. package/dist/middleware/auth-gate.mjs.map +1 -0
  69. package/dist/middleware/csrf.mjs +1 -0
  70. package/dist/middleware/csrf.mjs.map +1 -1
  71. package/dist/middleware/rate-limit.mjs +1 -0
  72. package/dist/middleware/rate-limit.mjs.map +1 -1
  73. package/dist/middleware/request-log.mjs +28 -0
  74. package/dist/middleware/request-log.mjs.map +1 -0
  75. package/dist/middleware/store-scope.mjs +29 -0
  76. package/dist/middleware/store-scope.mjs.map +1 -0
  77. package/dist/models/baseline.mjs +9 -5
  78. package/dist/models/baseline.mjs.map +1 -1
  79. package/dist/models/build.mjs +5 -1
  80. package/dist/models/build.mjs.map +1 -1
  81. package/dist/models/comment.mjs +4 -1
  82. package/dist/models/comment.mjs.map +1 -1
  83. package/dist/models/label.mjs +3 -1
  84. package/dist/models/label.mjs.map +1 -1
  85. package/dist/models/member.mjs +2 -1
  86. package/dist/models/member.mjs.map +1 -1
  87. package/dist/models/project.mjs +2 -1
  88. package/dist/models/project.mjs.map +1 -1
  89. package/dist/models/snapshot.mjs +2 -1
  90. package/dist/models/snapshot.mjs.map +1 -1
  91. package/dist/models/status-config.mjs +2 -1
  92. package/dist/models/status-config.mjs.map +1 -1
  93. package/dist/models/token.mjs +2 -1
  94. package/dist/models/token.mjs.map +1 -1
  95. package/dist/models/webhook.mjs +12 -3
  96. package/dist/models/webhook.mjs.map +1 -1
  97. package/dist/pages/build-detail.mjs +10 -4
  98. package/dist/pages/build-detail.mjs.map +1 -1
  99. package/dist/pages/build-diff-comments.mjs +113 -0
  100. package/dist/pages/build-diff-comments.mjs.map +1 -0
  101. package/dist/pages/build-diff-header.mjs +163 -0
  102. package/dist/pages/build-diff-header.mjs.map +1 -0
  103. package/dist/pages/build-diff-nav.mjs +66 -0
  104. package/dist/pages/build-diff-nav.mjs.map +1 -0
  105. package/dist/pages/build-diff-viewer.mjs +197 -0
  106. package/dist/pages/build-diff-viewer.mjs.map +1 -0
  107. package/dist/pages/build-diff.mjs +66 -413
  108. package/dist/pages/build-diff.mjs.map +1 -1
  109. package/dist/pages/compute-jobs.mjs +3 -1
  110. package/dist/pages/compute-jobs.mjs.map +1 -1
  111. package/dist/pages/label-detail.mjs +4 -3
  112. package/dist/pages/label-detail.mjs.map +1 -1
  113. package/dist/pages/login.mjs +1 -0
  114. package/dist/pages/login.mjs.map +1 -1
  115. package/dist/pages/project-builds.mjs +6 -3
  116. package/dist/pages/project-builds.mjs.map +1 -1
  117. package/dist/pages/project-create.mjs +1 -0
  118. package/dist/pages/project-create.mjs.map +1 -1
  119. package/dist/pages/project-settings.mjs +1 -0
  120. package/dist/pages/project-settings.mjs.map +1 -1
  121. package/dist/pages/projects.mjs +12 -6
  122. package/dist/pages/projects.mjs.map +1 -1
  123. package/dist/pages/root.mjs +7 -3
  124. package/dist/pages/root.mjs.map +1 -1
  125. package/dist/pages/settings-general.mjs +1 -0
  126. package/dist/pages/settings-general.mjs.map +1 -1
  127. package/dist/pages/settings-labels.mjs +2 -1
  128. package/dist/pages/settings-labels.mjs.map +1 -1
  129. package/dist/pages/settings-members.mjs +2 -1
  130. package/dist/pages/settings-members.mjs.map +1 -1
  131. package/dist/pages/settings-status.mjs +3 -1
  132. package/dist/pages/settings-status.mjs.map +1 -1
  133. package/dist/pages/settings-tests.mjs +4 -3
  134. package/dist/pages/settings-tests.mjs.map +1 -1
  135. package/dist/pages/settings-tokens.mjs +1 -0
  136. package/dist/pages/settings-tokens.mjs.map +1 -1
  137. package/dist/pages/settings-webhooks.mjs +1 -0
  138. package/dist/pages/settings-webhooks.mjs.map +1 -1
  139. package/dist/pages/storybook.mjs +38 -1
  140. package/dist/pages/storybook.mjs.map +1 -1
  141. package/dist/{utils/paths.d.mts → paths.d.mts} +5 -0
  142. package/dist/{utils/paths.mjs → paths.mjs} +5 -0
  143. package/dist/paths.mjs.map +1 -0
  144. package/dist/retention/purge.mjs +2 -1
  145. package/dist/retention/purge.mjs.map +1 -1
  146. package/dist/routers/admin.mjs +2 -1
  147. package/dist/routers/admin.mjs.map +1 -1
  148. package/dist/routers/assets.mjs +1 -0
  149. package/dist/routers/assets.mjs.map +1 -1
  150. package/dist/routers/auth.mjs +1 -0
  151. package/dist/routers/auth.mjs.map +1 -1
  152. package/dist/routers/builds.handlers.mjs +114 -19
  153. package/dist/routers/builds.handlers.mjs.map +1 -1
  154. package/dist/routers/builds.mjs +55 -34
  155. package/dist/routers/builds.mjs.map +1 -1
  156. package/dist/routers/comments.mjs +1 -0
  157. package/dist/routers/comments.mjs.map +1 -1
  158. package/dist/routers/health.mjs +137 -0
  159. package/dist/routers/health.mjs.map +1 -0
  160. package/dist/routers/helpers.mjs +8 -2
  161. package/dist/routers/helpers.mjs.map +1 -1
  162. package/dist/routers/htmx.mjs +2 -0
  163. package/dist/routers/htmx.mjs.map +1 -1
  164. package/dist/routers/labels.mjs +2 -1
  165. package/dist/routers/labels.mjs.map +1 -1
  166. package/dist/routers/media.mjs +3 -2
  167. package/dist/routers/media.mjs.map +1 -1
  168. package/dist/routers/members.mjs +1 -0
  169. package/dist/routers/members.mjs.map +1 -1
  170. package/dist/routers/projects.mjs +2 -1
  171. package/dist/routers/projects.mjs.map +1 -1
  172. package/dist/routers/schemas.mjs +25 -0
  173. package/dist/routers/schemas.mjs.map +1 -1
  174. package/dist/routers/settings-general.mjs +95 -0
  175. package/dist/routers/settings-general.mjs.map +1 -0
  176. package/dist/routers/settings-labels.mjs +49 -0
  177. package/dist/routers/settings-labels.mjs.map +1 -0
  178. package/dist/routers/settings-members.mjs +30 -0
  179. package/dist/routers/settings-members.mjs.map +1 -0
  180. package/dist/routers/settings-status.mjs +73 -0
  181. package/dist/routers/settings-status.mjs.map +1 -0
  182. package/dist/routers/settings-tokens.mjs +40 -0
  183. package/dist/routers/settings-tokens.mjs.map +1 -0
  184. package/dist/routers/settings-webhooks.mjs +63 -0
  185. package/dist/routers/settings-webhooks.mjs.map +1 -0
  186. package/dist/routers/settings.handlers.mjs +93 -0
  187. package/dist/routers/settings.handlers.mjs.map +1 -0
  188. package/dist/routers/settings.mjs +14 -246
  189. package/dist/routers/settings.mjs.map +1 -1
  190. package/dist/routers/snapshots.mjs +2 -1
  191. package/dist/routers/snapshots.mjs.map +1 -1
  192. package/dist/routers/status-configs.mjs +2 -1
  193. package/dist/routers/status-configs.mjs.map +1 -1
  194. package/dist/routers/storybook.mjs +12 -4
  195. package/dist/routers/storybook.mjs.map +1 -1
  196. package/dist/routers/tokens.mjs +2 -1
  197. package/dist/routers/tokens.mjs.map +1 -1
  198. package/dist/routers/ui.mjs +5 -4
  199. package/dist/routers/ui.mjs.map +1 -1
  200. package/dist/routers/webhooks.mjs +7 -6
  201. package/dist/routers/webhooks.mjs.map +1 -1
  202. package/dist/schema/baseline.d.mts +18 -0
  203. package/dist/schema/baseline.mjs +22 -0
  204. package/dist/schema/baseline.mjs.map +1 -0
  205. package/dist/schema/build.d.mts +26 -0
  206. package/dist/schema/build.mjs +29 -0
  207. package/dist/schema/build.mjs.map +1 -0
  208. package/dist/schema/comment.d.mts +19 -0
  209. package/dist/schema/comment.mjs +26 -0
  210. package/dist/schema/comment.mjs.map +1 -0
  211. package/dist/schema/label.d.mts +25 -0
  212. package/dist/schema/label.mjs +30 -0
  213. package/dist/schema/label.mjs.map +1 -0
  214. package/dist/schema/member.d.mts +15 -0
  215. package/dist/schema/member.mjs +19 -0
  216. package/dist/schema/member.mjs.map +1 -0
  217. package/dist/schema/project.d.mts +34 -0
  218. package/dist/schema/project.mjs +25 -0
  219. package/dist/schema/project.mjs.map +1 -0
  220. package/dist/schema/snapshot.d.mts +30 -0
  221. package/dist/schema/snapshot.mjs +34 -0
  222. package/dist/schema/snapshot.mjs.map +1 -0
  223. package/dist/schema/status-config.d.mts +16 -0
  224. package/dist/schema/status-config.mjs +20 -0
  225. package/dist/schema/status-config.mjs.map +1 -0
  226. package/dist/schema/token.d.mts +15 -0
  227. package/dist/schema/token.mjs +19 -0
  228. package/dist/schema/token.mjs.map +1 -0
  229. package/dist/schema/user.d.mts +17 -0
  230. package/dist/schema/user.mjs +19 -0
  231. package/dist/schema/user.mjs.map +1 -0
  232. package/dist/schema/webhook.d.mts +16 -0
  233. package/dist/schema/webhook.mjs +20 -0
  234. package/dist/schema/webhook.mjs.map +1 -0
  235. package/dist/schema.d.mts +52 -4433
  236. package/dist/schema.mjs +37 -139
  237. package/dist/schema.mjs.map +1 -1
  238. package/dist/store.mjs +2 -0
  239. package/dist/store.mjs.map +1 -1
  240. package/dist/types.d.mts +13 -2
  241. package/dist/types.mjs +8 -0
  242. package/dist/types.mjs.map +1 -1
  243. package/dist/ui/document.mjs +1 -0
  244. package/dist/ui/document.mjs.map +1 -1
  245. package/dist/ui/feedback.mjs +24 -0
  246. package/dist/ui/feedback.mjs.map +1 -0
  247. package/dist/ui/{components.mjs → forms.mjs} +6 -16
  248. package/dist/ui/forms.mjs.map +1 -0
  249. package/dist/ui/styles.mjs +1 -0
  250. package/dist/ui/styles.mjs.map +1 -1
  251. package/dist/ui/theme.mjs +2 -0
  252. package/dist/ui/theme.mjs.map +1 -1
  253. package/dist/urls.d.mts +8 -0
  254. package/dist/urls.mjs +7 -0
  255. package/dist/urls.mjs.map +1 -1
  256. package/dist/utils/hash.mjs +3 -0
  257. package/dist/utils/hash.mjs.map +1 -1
  258. package/dist/utils/ulid.mjs +2 -0
  259. package/dist/utils/ulid.mjs.map +1 -1
  260. package/package.json +47 -45
  261. package/dist/adapters/metadata.d.mts +0 -24
  262. package/dist/models/baseline.d.mts +0 -20
  263. package/dist/models/build.d.mts +0 -50
  264. package/dist/models/comment.d.mts +0 -20
  265. package/dist/models/label.d.mts +0 -49
  266. package/dist/models/member.d.mts +0 -27
  267. package/dist/models/project.d.mts +0 -42
  268. package/dist/models/snapshot.d.mts +0 -46
  269. package/dist/models/token.d.mts +0 -33
  270. package/dist/models/webhook.d.mts +0 -37
  271. package/dist/retention/purge.d.mts +0 -28
  272. package/dist/ui/components.mjs.map +0 -1
  273. package/dist/ui/document.d.mts +0 -8
  274. package/dist/utils/paths.mjs.map +0 -1
  275. package/dist/utils/ulid.d.mts +0 -8
package/README.md CHANGED
@@ -20,12 +20,12 @@ npm install @storyshelf/core
20
20
  import { createShelfRouter } from "@storyshelf/core";
21
21
 
22
22
  const app = createShelfRouter({
23
- database, // DatabaseAdapter
24
- storage, // StorageAdapter
25
- captureRunner, // CaptureRunner (optional)
26
- auth, // AuthAdapter (optional)
27
- gitHosts, // GitHostProvider[] (optional)
28
- logger, // pino Logger (optional; built internally if omitted)
23
+ database, // DatabaseAdapter
24
+ storage, // StorageAdapter
25
+ captureRunner, // CaptureRunner (optional)
26
+ auth, // AuthAdapter (optional)
27
+ gitHosts, // GitHostProvider[] (optional)
28
+ logger, // pino Logger (optional; built internally if omitted)
29
29
  ui: { name: "My Shelf" }, // UIConfig (optional)
30
30
  config: { captureConcurrency: 2, purgeTtlDays: 30 }, // ShelfConfig (optional)
31
31
  });
@@ -40,27 +40,30 @@ serve({ fetch: app.fetch, port: 3000 });
40
40
 
41
41
  Assembles the router from the provided adapters. `ShelfOptions`:
42
42
 
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. |
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. |
54
54
 
55
55
  ### `ShelfConfig`
56
56
 
57
57
  ```ts
58
58
  interface ShelfConfig {
59
- secret?: string; // session signing secret
59
+ secret?: string; // session signing secret
60
60
  publishedBaseDomain?: string; // for published storybook URLs
61
- captureConcurrency?: number; // concurrent capture jobs (default 2)
62
- purgeTtlDays?: number; // purge builds older than N days
63
- viewports?: Viewport[]; // capture viewports
61
+ captureConcurrency?: number; // concurrent capture jobs (default 2)
62
+ scratchDir?: string; // capture working directory (required with captureRunner)
63
+ purgeTtlDays?: number; // purge builds older than N days
64
+ maxUploadBytes?: number; // single-zip upload cap (default 1 GiB)
65
+ maxInlineUnzipSize?: number; // inline statics extraction cap; unset = capture-only
66
+ viewports?: Viewport[]; // capture viewports
64
67
  }
65
68
  ```
66
69
 
@@ -78,33 +81,34 @@ interface UIConfig {
78
81
 
79
82
  ### Adapter interfaces
80
83
 
81
- All adapters are constructor-injected (no AsyncLocalStorage). See `docs/architecture.md` for the entity model and workflow.
84
+ All adapters are constructor-injected (no AsyncLocalStorage). See `docs/architecture.md` for the entity model and workflow. Import each from its subpath — the barrel (`@storyshelf/core`) exports only the router and its types.
82
85
 
83
- - `DatabaseAdapter` — `insert`, `update`, `get`, `remove`, `list`, `count`, `all`, `migrate`, `close`. Also exports `ListOptions`.
84
- - `StorageAdapter` — `read`, `write`, `delete`, `exists`, `list(prefix)`.
85
- - `AuthAdapter` — `check(request)`, `createSession(user)`, `destroySession(sessionId)`, optional `handleCallback(callback)`. Also exports `AuthUser`, `AuthCallback`, and the shared `SESSION_COOKIE`.
86
- - `CaptureRunner` — a **pure capture renderer**: `render(input) => RenderResult`, `cancel(buildId)`. Also exports `JobStatus`, `RenderedSnapshot`, `RenderResult`.
87
- - `GitHostProvider` / `GitHostAdapter` — set commit status checks, detect merges, and upsert PR comments. Real providers ship in `@storyshelf/git-github` and `@storyshelf/git-gitlab`. Also exports `CheckStatus`.
86
+ - `DatabaseAdapter` (`core/adapter/database`) — `insert`, `update`, `get`, `remove`, `list`, `count`, `all`, `migrate`, `close`. Also exports `ListOptions` and the `createDrizzleAdapter` factory.
87
+ - `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`.
89
+ - `CaptureRunner` (`core/adapter/capture-runner`) — a **pure capture renderer**: `render(input) => RenderResult`, `cancel(buildId)`. Also exports `RenderedSnapshot`, `RenderResult`, `StoryEntry`, `StorySourceAdapter`, `Viewport`.
90
+ - `CaptureQueue` (`core/adapter/capture-queue`) — `enqueue({ buildId, reqId? })`, plus `status`, `active`, `recent`. Also exports `CaptureJob`, `QueueEntry`, `JobStatus`.
91
+ - `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`.
88
92
 
89
93
  ### Logging
90
94
 
91
- `core` uses **pino** for structured JSON logging. `createShelfLogger({ level, transports, env })` 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.
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.
92
96
 
93
97
  ### Capture, diff, and retention
94
98
 
95
- - `executeCaptureJob({ buildId, reqId }, deps)` — 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`.
96
- - `persistCapture(ctx: CaptureContext)` — writes screenshots, diffs against the branch baseline, creates snapshots, and finalizes a build from a pure renderer's `captures`. Also exports `CaptureContext` and the `StorySourceAdapter`/`StoryEntry`/`Viewport` types.
97
- - `StorybookAdapter` — reads a built Storybook's `index.json`/`stories.json`.
98
- - `diffImages(baseline: Buffer, current: Buffer, options: DiffOptions): DiffResult` — pixelmatch-based diff. Also exports `DiffOptions`, `DiffResult`.
99
- - `CaptureQueue` — the **capture queue adapter**: `enqueue({ buildId, reqId? })`, plus `status`, `active`, `recent`. `enqueue` returns once a build is queued (async, "queued"); the job runs in a worker. Default is `InMemoryCaptureQueue` (in-process, concurrency-limited, for long-lived hosts); supply a remote queue with a separate worker for serverless. Also exports `CaptureJob`, `QueueEntry`, `JobStatus`.
100
- - `Retention` — `new Retention(db, storage, logger?)`, with `purge(project, { ttlDays, keepLatestPerBranch })`.
99
+ Import from `core/capture`, `core/diff`, and the model entries — never from the barrel:
100
+
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`.
102
+ - `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
+ - `StorybookAdapter` (`core/capture`) — reads a built Storybook's `index.json`/`stories.json`.
104
+ - `InMemoryCaptureQueue` (`core/capture`) — in-process, concurrency-limited queue for long-lived hosts; supply a remote queue with a separate worker for serverless.
105
+ - `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`.)
101
107
 
102
108
  ### Helpers
103
109
 
104
- - `createUrlBuilder(baseUrl, publishedBaseDomain?)` — type-safe URL builder. Also exports `UrlBuilder`.
105
- - `ulid`, `slugify`.
106
- - Path helpers: `screenshotPath`, `diffPath`, `baselinePath`, `storybookDir`, `storybookZipPath`.
107
- - `RenderedContent` type for the server-rendered UI.
110
+ - `createUrlBuilder(baseUrl, publishedBaseDomain?)` (`core/urls`) — type-safe URL builder. Also exports `UrlBuilder`.
111
+ - Path helpers (`core/paths`): `screenshotPath`, `diffPath`, `baselinePath`, `storybookDir`, `storybookZipPath`.
108
112
 
109
113
  ## How it fits in
110
114
 
@@ -116,13 +120,13 @@ See `docs/architecture.md` and the ADRs in `docs/adr/`.
116
120
 
117
121
  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:
118
122
 
119
- | Platform | Database | Storage | Capture queue | Server entry |
120
- |----------|----------|---------|---------------|--------------|
121
- | **Vercel** | `@storyshelf/db-turso` | `@storyshelf/storage-s3` (R2/S3) | Remote `CaptureQueue` + worker | Hono + `@hono/vercel-edge` |
122
- | **Cloudflare Workers** | `@storyshelf/db-turso` | `@storyshelf/storage-s3` (R2) | Workers Queues `CaptureQueue` | Hono + Workers entry |
123
- | **Azure Functions** | `@storyshelf/db-turso` | `@storyshelf/storage-s3` (Blob) | Azure Queues `CaptureQueue` | Hono + Azure handler |
124
- | **AWS Lambda** | `@storyshelf/db-turso` | `@storyshelf/storage-s3` | SQS `CaptureQueue` | Hono + Lambda handler |
125
- | **Deno Deploy** | `@storyshelf/db-turso` | `@storyshelf/storage-s3` | Custom `CaptureQueue` (Deno KV) | Hono + Deno entry |
126
- | **Bun / Node (VPS, Fly, Railway, Render)** | `db-sqlite` / `db-turso` | `storage-local` / `storage-s3` | `InMemoryCaptureQueue` | `storyshelf-server serve` |
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` |
127
131
 
128
132
  All clouds are equal — pick the adapters that match your infrastructure. See the **Deployment** guide for recipes including a minimal Turso + S3 + `InMemoryCaptureQueue` example.
@@ -1,7 +1,7 @@
1
1
  import __tsdown_shims_path from 'node:path';
2
2
  import __tsdown_shims_url from 'node:url';
3
- import { AdapterMetadata } from "../adapters/metadata.mjs";
4
3
  import { ProjectRole, SiteRole } from "../types.mjs";
4
+ import { Adapter } from "./metadata.mjs";
5
5
  //#region src/adapters/auth.d.ts
6
6
  /** Shared session cookie name used by all auth adapters (ADR 0008). */
7
7
  declare const SESSION_COOKIE = "storyshelf_session";
@@ -33,9 +33,9 @@ interface ProjectAccess {
33
33
  role: ProjectRole;
34
34
  }
35
35
  /** Pluggable authentication abstraction (ADR 0008). */
36
- interface AuthAdapter {
37
- /** Adapter identity. */
38
- readonly metadata?: AdapterMetadata;
36
+ interface AuthAdapter extends Adapter<{
37
+ readonly category: "auth";
38
+ }> {
39
39
  /** Resolve the current user from a request, or null if unauthenticated. */
40
40
  check(request: Request): Promise<AuthUser | null>;
41
41
  /** Create a session for a user and return a session token. */
@@ -1 +1 @@
1
- {"version":3,"file":"auth.mjs","names":[],"sources":["../../src/adapters/auth.ts"],"sourcesContent":["import type { AdapterMetadata } from \"./metadata.ts\";\nimport type { ProjectRole, SiteRole } from \"../types.ts\";\n\n/** Shared session cookie name used by all auth adapters (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\n/** Data passed to an auth adapter when an OAuth callback is received. */\nexport interface AuthCallback {\n /** Provider name. */\n provider: string;\n /** Authorization code from the provider. */\n code: string;\n /** Anti-CSRF state value. */\n state: string;\n}\n\n/** Access controls granted to a user for a specific project. */\nexport interface ProjectAccess {\n /** Project role. */\n role: ProjectRole;\n}\n\n/** Pluggable authentication abstraction (ADR 0008). */\nexport interface AuthAdapter {\n /** Adapter identity. */\n readonly metadata?: AdapterMetadata;\n /** Resolve the current user from a request, or null if unauthenticated. */\n check(request: Request): Promise<AuthUser | null>;\n /** Create a session for a user and return a session token. */\n createSession(user: AuthUser): Promise<string>;\n /** Destroy a session by its id. */\n destroySession(sessionId: string): Promise<void>;\n /** Handle an OAuth callback and return the resolved user, if supported. */\n handleCallback?(callback: AuthCallback): Promise<AuthUser | null>;\n}\n"],"mappings":";;;;;AAIA,MAAa,iBAAiB"}
1
+ {"version":3,"file":"auth.mjs","names":[],"sources":["../../src/adapters/auth.ts"],"sourcesContent":["import type { ProjectRole, SiteRole } from \"../types.ts\";\n/**\n * Auth adapter interface: authenticate users and resolve project access.\n */\nimport type { Adapter } from \"./metadata.ts\";\n\n/** Shared session cookie name used by all auth adapters (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\n/** Data passed to an auth adapter when an OAuth callback is received. */\nexport interface AuthCallback {\n /** Provider name. */\n provider: string;\n /** Authorization code from the provider. */\n code: string;\n /** Anti-CSRF state value. */\n state: string;\n}\n\n/** Access controls granted to a user for a specific project. */\nexport interface ProjectAccess {\n /** Project role. */\n role: ProjectRole;\n}\n\n/** Pluggable authentication abstraction (ADR 0008). */\nexport interface AuthAdapter extends Adapter<{ readonly category: \"auth\" }> {\n /** Resolve the current user from a request, or null if unauthenticated. */\n check(request: Request): Promise<AuthUser | null>;\n /** Create a session for a user and return a session token. */\n createSession(user: AuthUser): Promise<string>;\n /** Destroy a session by its id. */\n destroySession(sessionId: string): Promise<void>;\n /** Handle an OAuth callback and return the resolved user, if supported. */\n handleCallback?(callback: AuthCallback): Promise<AuthUser | null>;\n}\n"],"mappings":";;;;;AAOA,MAAa,iBAAiB"}
@@ -1,8 +1,10 @@
1
1
  import __tsdown_shims_path from 'node:path';
2
2
  import __tsdown_shims_url from 'node:url';
3
- import { AdapterMetadata } from "./metadata.mjs";
3
+ import { Adapter } from "./metadata.mjs";
4
4
  //#region src/adapters/capture-queue.d.ts
5
+ /** Lifecycle status of a queued capture job. */
5
6
  type JobStatus = "queued" | "running" | "completed" | "failed" | "cancelled";
7
+ /** Observable state of a single capture queue entry. */
6
8
  interface QueueEntry {
7
9
  buildId: string;
8
10
  status: JobStatus;
@@ -32,9 +34,9 @@ interface CaptureJob {
32
34
  *
33
35
  * `status`/`active`/`recent` back the live queue view regardless of transport.
34
36
  */
35
- interface CaptureQueue {
36
- /** Adapter identity. */
37
- readonly metadata?: AdapterMetadata;
37
+ interface CaptureQueue extends Adapter<{
38
+ readonly category: "capture-queue";
39
+ }> {
38
40
  /** Submit a build for capture. Resolves once the build is queued. */
39
41
  enqueue(job: CaptureJob): Promise<void>;
40
42
  /** Return the current status entry for a build, or null if untracked. */
@@ -0,0 +1,4 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ export {};
@@ -1,8 +1,8 @@
1
1
  import __tsdown_shims_path from 'node:path';
2
2
  import __tsdown_shims_url from 'node:url';
3
- import { AdapterMetadata } from "../adapters/metadata.mjs";
4
- import { StoryEntry, Viewport } from "../capture/adapter.mjs";
5
- import { JobStatus } from "../adapters/capture-queue.mjs";
3
+ import { JobStatus } from "./capture-queue.mjs";
4
+ import { StoryEntry, StorySourceAdapter, Viewport } from "../capture/adapter.mjs";
5
+ import { Adapter } from "./metadata.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. */
@@ -41,9 +41,9 @@ interface RenderResult {
41
41
  * orchestrator's job (see `capture/orchestrator.ts`), keeping every adapter
42
42
  * implementation free of server concerns.
43
43
  */
44
- interface CaptureRunner {
45
- /** Adapter identity. */
46
- readonly metadata?: AdapterMetadata;
44
+ interface CaptureRunner extends Adapter<{
45
+ readonly category: "capture-runner";
46
+ }> {
47
47
  /**
48
48
  * Render configured viewports for the given stories of an extracted
49
49
  * Storybook and return the screenshot buffers.
@@ -70,5 +70,5 @@ interface CaptureRunner {
70
70
  cancel(buildId: string): Promise<void>;
71
71
  }
72
72
  //#endregion
73
- export { CaptureRunner, type JobStatus, RenderFailure, RenderResult, RenderedSnapshot };
73
+ export { CaptureRunner, type JobStatus, RenderFailure, RenderResult, RenderedSnapshot, type StoryEntry, type StorySourceAdapter, type Viewport };
74
74
  //# sourceMappingURL=capture-runner.d.mts.map
@@ -1,6 +1,6 @@
1
1
  import __tsdown_shims_path from 'node:path';
2
2
  import __tsdown_shims_url from 'node:url';
3
- import { AdapterMetadata } from "../adapters/metadata.mjs";
3
+ import { Adapter, AdapterMetadata } from "./metadata.mjs";
4
4
  import { SQL } from "drizzle-orm";
5
5
  import { AnySQLiteTable } from "drizzle-orm/sqlite-core";
6
6
  //#region src/adapters/database.d.ts
@@ -16,9 +16,9 @@ interface ListOptions {
16
16
  offset?: number;
17
17
  }
18
18
  /** Database abstraction over Drizzle tables, agnostic of the underlying driver. */
19
- interface DatabaseAdapter {
20
- /** Adapter identity. */
21
- readonly metadata?: AdapterMetadata;
19
+ interface DatabaseAdapter extends Adapter<{
20
+ readonly category: "database";
21
+ }> {
22
22
  /** Insert a row and return the inserted record. */
23
23
  insert<T extends AnySQLiteTable>(table: T, values: T["$inferInsert"]): Promise<T["$inferSelect"]>;
24
24
  /** Update a row by id and return the updated record. */
@@ -33,11 +33,22 @@ interface DatabaseAdapter {
33
33
  count(table: AnySQLiteTable, where?: SQL): Promise<number>;
34
34
  /** Run an arbitrary SQL query and return typed rows. */
35
35
  all<T>(query: SQL): Promise<T[]>;
36
- /** Apply any pending schema migrations. */
37
- migrate(): Promise<void>;
38
- /** Close the underlying database connection. */
39
- close(): Promise<void>;
40
36
  }
37
+ /** Driver-supplied identity plus lifecycle hooks. */
38
+ interface DrizzleAdapterOptions {
39
+ metadata: AdapterMetadata & {
40
+ readonly category: "database";
41
+ };
42
+ migrate: () => Promise<void> | void;
43
+ close: () => Promise<void> | void;
44
+ }
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;
41
52
  //#endregion
42
- export { DatabaseAdapter, ListOptions };
53
+ export { DatabaseAdapter, DrizzleAdapterOptions, ListOptions, createDrizzleAdapter };
43
54
  //# sourceMappingURL=database.d.mts.map
@@ -1,4 +1,63 @@
1
1
  import "node:path";
2
2
  import "node:url";
3
3
  import.meta.url;
4
- export {};
4
+ import { eq, getTableColumns } from "drizzle-orm";
5
+ //#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
+ };
59
+ }
60
+ //#endregion
61
+ export { createDrizzleAdapter };
62
+
63
+ //# sourceMappingURL=database.mjs.map
@@ -0,0 +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"}
@@ -0,0 +1,41 @@
1
+ import __tsdown_shims_path from 'node:path';
2
+ import __tsdown_shims_url from 'node:url';
3
+ import { Logger } from "pino";
4
+ //#region src/adapters/git-host/comments.d.ts
5
+ /** A review thread (PR or MR) hosting a single StoryShelf comment. */
6
+ interface ReviewThread {
7
+ /** List existing comments as id/body pairs. */
8
+ list(): Promise<{
9
+ id: string | number;
10
+ body: string;
11
+ }[]>;
12
+ /** Replace the body of an existing comment, returning its id. */
13
+ update(id: string | number, body: string): Promise<string>;
14
+ /** Create a comment, returning its id. */
15
+ create(body: string): Promise<string>;
16
+ }
17
+ /** Dependencies for an idempotent review-comment upsert. */
18
+ interface UpsertReviewCommentOptions {
19
+ /** Canonical build/review URL — anchors the idempotency marker. */
20
+ url: string;
21
+ /** Markdown body appended below the marker. */
22
+ markdown: string;
23
+ /** Known thread number, skipping the lookup when provided. */
24
+ prNumber?: number;
25
+ /** Commit SHA used for the thread lookup and log context. */
26
+ sha: string;
27
+ logger?: Logger;
28
+ /** Resolve the thread number for the commit (PR number or MR iid). */
29
+ resolveNumber: () => Promise<number | undefined>;
30
+ /** Open the thread's comment store once the number is known. */
31
+ thread: (number: number) => ReviewThread;
32
+ }
33
+ /**
34
+ * Create or update the single StoryShelf review comment for a build,
35
+ * idempotent via the url marker. Returns the comment id, or `""` when no
36
+ * review thread exists for the commit.
37
+ */
38
+ declare function upsertReviewComment(opts: UpsertReviewCommentOptions): Promise<string>;
39
+ //#endregion
40
+ export { ReviewThread, UpsertReviewCommentOptions, upsertReviewComment };
41
+ //# sourceMappingURL=comments.d.mts.map
@@ -0,0 +1,54 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { commentMarker } from "./helpers.mjs";
5
+ //#region src/adapters/git-host/comments.ts
6
+ /** Resolve the thread number, preferring the explicit value. */
7
+ async function resolveThreadNumber(opts) {
8
+ return opts.prNumber ?? await opts.resolveNumber();
9
+ }
10
+ /** Update the marked comment if present, else create it. */
11
+ async function findOrCreateComment(thread, marker, body, prNumber, logger) {
12
+ const existing = (await thread.list()).find((comment) => comment.body.includes(marker));
13
+ if (existing) {
14
+ const id = await thread.update(existing.id, body);
15
+ logger?.info({
16
+ prNumber,
17
+ commentId: id
18
+ }, "review comment updated");
19
+ return id;
20
+ }
21
+ const id = await thread.create(body);
22
+ logger?.info({
23
+ prNumber,
24
+ commentId: id
25
+ }, "review comment created");
26
+ return id;
27
+ }
28
+ /**
29
+ * Create or update the single StoryShelf review comment for a build,
30
+ * idempotent via the url marker. Returns the comment id, or `""` when no
31
+ * review thread exists for the commit.
32
+ */
33
+ async function upsertReviewComment(opts) {
34
+ const marker = commentMarker(opts.url);
35
+ const body = `${marker}\n${opts.markdown}`;
36
+ const number = await resolveThreadNumber(opts);
37
+ if (number === void 0) {
38
+ opts.logger?.debug({ sha: opts.sha }, "no review thread found for comment, skipping");
39
+ return "";
40
+ }
41
+ try {
42
+ return await findOrCreateComment(opts.thread(number), marker, body, number, opts.logger);
43
+ } catch (error) {
44
+ opts.logger?.error({
45
+ err: error,
46
+ prNumber: number
47
+ }, "failed to upsert review comment");
48
+ throw error;
49
+ }
50
+ }
51
+ //#endregion
52
+ export { upsertReviewComment };
53
+
54
+ //# sourceMappingURL=comments.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"comments.mjs","names":[],"sources":["../../../src/adapters/git-host/comments.ts"],"sourcesContent":["import type { Logger } from \"pino\";\nimport { commentMarker } from \"./helpers.ts\";\n\n/** A review thread (PR or MR) hosting a single StoryShelf comment. */\nexport interface ReviewThread {\n /** List existing comments as id/body pairs. */\n list(): Promise<{ id: string | number; body: string }[]>;\n /** Replace the body of an existing comment, returning its id. */\n update(id: string | number, body: string): Promise<string>;\n /** Create a comment, returning its id. */\n create(body: string): Promise<string>;\n}\n\n/** Dependencies for an idempotent review-comment upsert. */\nexport interface UpsertReviewCommentOptions {\n /** Canonical build/review URL — anchors the idempotency marker. */\n url: string;\n /** Markdown body appended below the marker. */\n markdown: string;\n /** Known thread number, skipping the lookup when provided. */\n prNumber?: number;\n /** Commit SHA used for the thread lookup and log context. */\n sha: string;\n logger?: Logger;\n /** Resolve the thread number for the commit (PR number or MR iid). */\n resolveNumber: () => Promise<number | undefined>;\n /** Open the thread's comment store once the number is known. */\n thread: (number: number) => ReviewThread;\n}\n\n/** Resolve the thread number, preferring the explicit value. */\nasync function resolveThreadNumber(opts: UpsertReviewCommentOptions): Promise<number | undefined> {\n return opts.prNumber ?? (await opts.resolveNumber());\n}\n\n/** Update the marked comment if present, else create it. */\nasync function findOrCreateComment(\n thread: ReviewThread,\n marker: string,\n body: string,\n prNumber: number,\n logger?: Logger,\n): Promise<string> {\n const existing = (await thread.list()).find((comment) => comment.body.includes(marker));\n if (existing) {\n const id = await thread.update(existing.id, body);\n logger?.info({ prNumber, commentId: id }, \"review comment updated\");\n return id;\n }\n const id = await thread.create(body);\n logger?.info({ prNumber, commentId: id }, \"review comment created\");\n return id;\n}\n\n/**\n * Create or update the single StoryShelf review comment for a build,\n * idempotent via the url marker. Returns the comment id, or `\"\"` when no\n * review thread exists for the commit.\n */\nexport async function upsertReviewComment(opts: UpsertReviewCommentOptions): Promise<string> {\n const marker = commentMarker(opts.url);\n const body = `${marker}\\n${opts.markdown}`;\n const number = await resolveThreadNumber(opts);\n if (number === undefined) {\n opts.logger?.debug({ sha: opts.sha }, \"no review thread found for comment, skipping\");\n return \"\";\n }\n try {\n return await findOrCreateComment(opts.thread(number), marker, body, number, opts.logger);\n } catch (error) {\n opts.logger?.error({ err: error, prNumber: number }, \"failed to upsert review comment\");\n throw error;\n }\n}\n"],"mappings":";;;;;;AA+BA,eAAe,oBAAoB,MAA+D;CAChG,OAAO,KAAK,YAAa,MAAM,KAAK,cAAc;AACpD;;AAGA,eAAe,oBACb,QACA,QACA,MACA,UACA,QACiB;CACjB,MAAM,YAAY,MAAM,OAAO,KAAK,EAAA,CAAG,MAAM,YAAY,QAAQ,KAAK,SAAS,MAAM,CAAC;CACtF,IAAI,UAAU;EACZ,MAAM,KAAK,MAAM,OAAO,OAAO,SAAS,IAAI,IAAI;EAChD,QAAQ,KAAK;GAAE;GAAU,WAAW;EAAG,GAAG,wBAAwB;EAClE,OAAO;CACT;CACA,MAAM,KAAK,MAAM,OAAO,OAAO,IAAI;CACnC,QAAQ,KAAK;EAAE;EAAU,WAAW;CAAG,GAAG,wBAAwB;CAClE,OAAO;AACT;;;;;;AAOA,eAAsB,oBAAoB,MAAmD;CAC3F,MAAM,SAAS,cAAc,KAAK,GAAG;CACrC,MAAM,OAAO,GAAG,OAAO,IAAI,KAAK;CAChC,MAAM,SAAS,MAAM,oBAAoB,IAAI;CAC7C,IAAI,WAAW,KAAA,GAAW;EACxB,KAAK,QAAQ,MAAM,EAAE,KAAK,KAAK,IAAI,GAAG,8CAA8C;EACpF,OAAO;CACT;CACA,IAAI;EACF,OAAO,MAAM,oBAAoB,KAAK,OAAO,MAAM,GAAG,QAAQ,MAAM,QAAQ,KAAK,MAAM;CACzF,SAAS,OAAO;EACd,KAAK,QAAQ,MAAM;GAAE,KAAK;GAAO,UAAU;EAAO,GAAG,iCAAiC;EACtF,MAAM;CACR;AACF"}
@@ -6,6 +6,7 @@ import { CheckStatus } from "../git-host.mjs";
6
6
  declare function describeStatus(status: CheckStatus): string;
7
7
  /** Markdown comment body helpers — single comment per build, idempotent via marker. */
8
8
  declare function commentMarker(url: string): string;
9
+ /** Build the Markdown body for a StoryShelf status comment. */
9
10
  declare function buildCommentMarkdown(status: CheckStatus, url: string, context: string): string;
10
11
  //#endregion
11
12
  export { buildCommentMarkdown, commentMarker, describeStatus };
@@ -15,6 +15,7 @@ function describeStatus(status) {
15
15
  function commentMarker(url) {
16
16
  return `<!-- storyshelf:${url} -->`;
17
17
  }
18
+ /** Build the Markdown body for a StoryShelf status comment. */
18
19
  function buildCommentMarkdown(status, url, context) {
19
20
  switch (status) {
20
21
  case "pending": return `Visual tests pending for \`${context}\` — [View build](${url})`;
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.mjs","names":[],"sources":["../../../src/adapters/git-host/helpers.ts"],"sourcesContent":["import type { CheckStatus } from \"./index.ts\";\n\n/** Human description for a check status — shared across GitHub/GitLab. */\nexport function describeStatus(status: CheckStatus): string {\n switch (status) {\n case \"pending\": {\n return \"Visual tests pending\";\n }\n case \"success\": {\n return \"Visual tests passed\";\n }\n case \"failure\": {\n return \"Visual changes detected or tests failed\";\n }\n default: {\n return \"Visual changes detected or tests failed\";\n }\n }\n}\n\n/** Markdown comment body helpers — single comment per build, idempotent via marker. */\nexport function commentMarker(url: string): string {\n return `<!-- storyshelf:${url} -->`;\n}\n\nexport function buildCommentMarkdown(status: CheckStatus, url: string, context: string): string {\n switch (status) {\n case \"pending\": {\n return `Visual tests pending for \\`${context}\\` — [View build](${url})`;\n }\n case \"success\": {\n return `Visual tests passed for \\`${context}\\` — [View build](${url})`;\n }\n case \"failure\": {\n return `Visual changes detected for \\`${context}\\` — [View build](${url})`;\n }\n default: {\n return `Visual tests for \\`${context}\\` — [View build](${url})`;\n }\n }\n}\n"],"mappings":";;;;;AAGA,SAAgB,eAAe,QAA6B;CAC1D,QAAQ,QAAR;EACE,KAAK,WACH,OAAO;EAET,KAAK,WACH,OAAO;EAET,KAAK,WACH,OAAO;EAET,SACE,OAAO;CAEX;AACF;;AAGA,SAAgB,cAAc,KAAqB;CACjD,OAAO,mBAAmB,IAAI;AAChC;AAEA,SAAgB,qBAAqB,QAAqB,KAAa,SAAyB;CAC9F,QAAQ,QAAR;EACE,KAAK,WACH,OAAO,8BAA8B,QAAQ,oBAAoB,IAAI;EAEvE,KAAK,WACH,OAAO,6BAA6B,QAAQ,oBAAoB,IAAI;EAEtE,KAAK,WACH,OAAO,iCAAiC,QAAQ,oBAAoB,IAAI;EAE1E,SACE,OAAO,sBAAsB,QAAQ,oBAAoB,IAAI;CAEjE;AACF"}
1
+ {"version":3,"file":"helpers.mjs","names":[],"sources":["../../../src/adapters/git-host/helpers.ts"],"sourcesContent":["import type { CheckStatus } from \"./index.ts\";\n\n/** Human description for a check status — shared across GitHub/GitLab. */\nexport function describeStatus(status: CheckStatus): string {\n switch (status) {\n case \"pending\": {\n return \"Visual tests pending\";\n }\n case \"success\": {\n return \"Visual tests passed\";\n }\n case \"failure\": {\n return \"Visual changes detected or tests failed\";\n }\n default: {\n return \"Visual changes detected or tests failed\";\n }\n }\n}\n\n/** Markdown comment body helpers — single comment per build, idempotent via marker. */\nexport function commentMarker(url: string): string {\n return `<!-- storyshelf:${url} -->`;\n}\n\n/** Build the Markdown body for a StoryShelf status comment. */\nexport function buildCommentMarkdown(status: CheckStatus, url: string, context: string): string {\n switch (status) {\n case \"pending\": {\n return `Visual tests pending for \\`${context}\\` — [View build](${url})`;\n }\n case \"success\": {\n return `Visual tests passed for \\`${context}\\` — [View build](${url})`;\n }\n case \"failure\": {\n return `Visual changes detected for \\`${context}\\` — [View build](${url})`;\n }\n default: {\n return `Visual tests for \\`${context}\\` — [View build](${url})`;\n }\n }\n}\n"],"mappings":";;;;;AAGA,SAAgB,eAAe,QAA6B;CAC1D,QAAQ,QAAR;EACE,KAAK,WACH,OAAO;EAET,KAAK,WACH,OAAO;EAET,KAAK,WACH,OAAO;EAET,SACE,OAAO;CAEX;AACF;;AAGA,SAAgB,cAAc,KAAqB;CACjD,OAAO,mBAAmB,IAAI;AAChC;;AAGA,SAAgB,qBAAqB,QAAqB,KAAa,SAAyB;CAC9F,QAAQ,QAAR;EACE,KAAK,WACH,OAAO,8BAA8B,QAAQ,oBAAoB,IAAI;EAEvE,KAAK,WACH,OAAO,6BAA6B,QAAQ,oBAAoB,IAAI;EAEtE,KAAK,WACH,OAAO,iCAAiC,QAAQ,oBAAoB,IAAI;EAE1E,SACE,OAAO,sBAAsB,QAAQ,oBAAoB,IAAI;CAEjE;AACF"}
@@ -1,14 +1,12 @@
1
1
  import __tsdown_shims_path from 'node:path';
2
2
  import __tsdown_shims_url from 'node:url';
3
- import { GitAdapterMetadata } from "../adapters/metadata.mjs";
3
+ import { Adapter, GitAdapterMetadata } from "./metadata.mjs";
4
4
  import { Logger } from "pino";
5
5
  //#region src/adapters/git-host/index.d.ts
6
6
  /** Status of a git-host provider check. */
7
7
  type CheckStatus = "pending" | "success" | "failure";
8
8
  /** Descriptor — registered at startup, validates per-project config. */
9
- interface GitHostProvider {
10
- /** Adapter identity (name, version, description, kind="github", logo, schema). */
11
- readonly metadata: GitAdapterMetadata;
9
+ interface GitHostProvider extends Adapter<GitAdapterMetadata> {
12
10
  /** Create a configured instance bound to a project config + decrypted token. */
13
11
  create(opts: {
14
12
  config: unknown;
@@ -17,9 +15,7 @@ interface GitHostProvider {
17
15
  }): GitHostAdapter;
18
16
  }
19
17
  /** Runtime — bound to a project config + token, posts statuses/comments. */
20
- interface GitHostAdapter {
21
- /** Adapter identity. */
22
- readonly metadata: GitAdapterMetadata;
18
+ interface GitHostAdapter extends Adapter<GitAdapterMetadata> {
23
19
  /** Set the status of a commit. */
24
20
  setStatus(opts: {
25
21
  context: string;
@@ -0,0 +1,58 @@
1
+ import __tsdown_shims_path from 'node:path';
2
+ import __tsdown_shims_url from 'node:url';
3
+ import { CaptureQueue } from "./capture-queue.mjs";
4
+ import { CaptureRunner } from "./capture-runner.mjs";
5
+ import { DatabaseAdapter } from "./database.mjs";
6
+ import { GitHostProvider } from "./git-host.mjs";
7
+ import { StorageAdapter } from "./storage.mjs";
8
+ import { AdapterInitContext } from "./metadata.mjs";
9
+ import { AuthAdapter } from "./auth.mjs";
10
+ import { Logger } from "pino";
11
+ //#region src/adapters/init.d.ts
12
+ /** Adapters that can take part in lifecycle runs. */
13
+ interface AdapterInitSources {
14
+ database: DatabaseAdapter;
15
+ storage: StorageAdapter;
16
+ captureRunner?: CaptureRunner;
17
+ captureQueue?: CaptureQueue;
18
+ auth?: AuthAdapter;
19
+ gitHosts?: GitHostProvider[];
20
+ }
21
+ /** One collected hook bound to its adapter identity. */
22
+ interface HookEntry {
23
+ readonly category: string;
24
+ readonly kind: string;
25
+ readonly name: string;
26
+ readonly run: (ctx: AdapterInitContext) => Promise<void>;
27
+ }
28
+ /** One failed hook, with its adapter identity attached. */
29
+ interface AdapterInitFailure {
30
+ readonly category: string;
31
+ readonly kind: string;
32
+ readonly name: string;
33
+ readonly error: string;
34
+ }
35
+ /** Settled outcome of a lifecycle run (never rejects — use `ok`). */
36
+ interface AdapterInitResult {
37
+ readonly ok: boolean;
38
+ readonly failures: AdapterInitFailure[];
39
+ }
40
+ /** Lifecycle phase a run covers. */
41
+ type LifecyclePhase = "init" | "close";
42
+ /** Thrown when an `app.lifecycle.init()` or `close()` run has failures. */
43
+ declare class AdapterLifecycleError extends Error {
44
+ readonly phase: LifecyclePhase;
45
+ readonly failures: AdapterInitFailure[];
46
+ constructor(phase: LifecyclePhase, failures: AdapterInitFailure[]);
47
+ }
48
+ /** Collect every available `init` hook in deterministic order. */
49
+ declare function collectInits(sources: AdapterInitSources): HookEntry[];
50
+ /** Collect every available `close` hook in deterministic order. */
51
+ declare function collectCloses(sources: AdapterInitSources): HookEntry[];
52
+ /** Run init hooks concurrently; failures are collected, never thrown. */
53
+ declare function runAdapterInits(entries: HookEntry[], ctx: AdapterInitContext, logger: Logger): Promise<AdapterInitResult>;
54
+ /** Run close hooks concurrently; failures are collected, never thrown. */
55
+ declare function runAdapterCloses(entries: HookEntry[], ctx: AdapterInitContext, logger: Logger): Promise<AdapterInitResult>;
56
+ //#endregion
57
+ export { AdapterInitFailure, AdapterInitResult, AdapterInitSources, AdapterLifecycleError, HookEntry, LifecyclePhase, collectCloses, collectInits, runAdapterCloses, runAdapterInits };
58
+ //# sourceMappingURL=init.d.mts.map