@hot-updater/console 1.0.0-rc.2 → 1.0.0-rc.20

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/.output/nitro.json +1 -1
  2. package/.output/public/assets/ConsoleFeatureUnavailable-B9VYGA3N.js +1 -0
  3. package/.output/public/assets/HashValueDisplay-D880zntR.js +1 -0
  4. package/.output/public/assets/InsightsInfo-By_sPjTE.js +1 -0
  5. package/.output/public/assets/InsightsPageHeader-9_kx_LL7.js +1 -0
  6. package/.output/public/assets/NotFoundPage-Dys8C0cW.js +1 -0
  7. package/.output/public/assets/api-CtarlV0l.js +1 -0
  8. package/.output/public/assets/api-keys-DFF_QqtN.js +1 -0
  9. package/.output/public/assets/api-keys-evfsQ4sN.js +1 -0
  10. package/.output/public/assets/arrow-left-BP9D-ta_.js +1 -0
  11. package/.output/public/assets/arrow-up-right-CDP9Tevw.js +1 -0
  12. package/.output/public/assets/card-DzCPSRsh.js +1 -0
  13. package/.output/public/assets/console-features-api-yv7rm3DX.js +1 -0
  14. package/.output/public/assets/dialog-BDvCy_QO.js +1 -0
  15. package/.output/public/assets/dist-Clb6cQ9l.js +1 -0
  16. package/.output/public/assets/empty-C5x9mn_s.js +1 -0
  17. package/.output/public/assets/field-Bou7shoO.js +1 -0
  18. package/.output/public/assets/index-0eg3l8aC.js +10 -0
  19. package/.output/public/assets/input-group-DVtWFUUF.js +1 -0
  20. package/.output/public/assets/insights-2MbdiSNv.js +1 -0
  21. package/.output/public/assets/insights-C6tmCZHk.js +2 -0
  22. package/.output/public/assets/insights-CadS_aIA.js +42 -0
  23. package/.output/public/assets/insights-recovery-rpc-wvdyM4LD.js +1 -0
  24. package/.output/public/assets/insights-search-KIF7mRtB.js +1 -0
  25. package/.output/public/assets/insights-usage-rpc-qUkNtrWr.js +1 -0
  26. package/.output/public/assets/insights_.distribution-BhBs6PNA.js +1 -0
  27. package/.output/public/assets/insights_.distribution-CItZw3-8.js +1 -0
  28. package/.output/public/assets/insights_.distribution-CJUH_wAS.js +2 -0
  29. package/.output/public/assets/installations-CH2H7JQt.js +2 -0
  30. package/.output/public/assets/installations-DMeMuwsW.js +1 -0
  31. package/.output/public/assets/installations-DnvxI9Dt.js +1 -0
  32. package/.output/public/assets/loader-circle-Ca7C9YY1.js +1 -0
  33. package/.output/public/assets/mutation-x9L28_CD.js +1 -0
  34. package/.output/public/assets/preload-helper-BN-UkjOE.js +1 -0
  35. package/.output/public/assets/refresh-cw-CVGqvy8Z.js +1 -0
  36. package/.output/public/assets/routes-BhK8DuZA.js +1 -0
  37. package/.output/public/assets/routes-veKtDW39.js +2 -0
  38. package/.output/public/assets/sidebar-B45aMqRm.js +3 -0
  39. package/.output/public/assets/styles-Dn0pK1EX.css +2 -0
  40. package/.output/public/assets/table-CwLnN6KX.js +1 -0
  41. package/.output/public/assets/useCompositeListItem-CF7vsfcA.js +1 -0
  42. package/.output/public/assets/useMutation-CN1ygYEr.js +1 -0
  43. package/.output/public/assets/useOpenInteractionType-AnAMWYIY.js +1 -0
  44. package/.output/public/assets/valueToPercent-C-t8FT0y.js +1 -0
  45. package/.output/server/{__23tanstack-start-server-fn-resolver-B7ioJhtE.mjs → __23tanstack-start-server-fn-resolver-CUQLz7hs.mjs} +52 -40
  46. package/.output/server/_libs/@base-ui/react+[...].mjs +7044 -3622
  47. package/.output/server/_libs/@reduxjs/toolkit+[...].mjs +1554 -0
  48. package/.output/server/_libs/@tanstack/react-router+[...].mjs +49 -3
  49. package/.output/server/_libs/@tanstack/react-router-ssr-query+[...].mjs +4 -4
  50. package/.output/server/_libs/d3-array.mjs +231 -0
  51. package/.output/server/_libs/d3-color.mjs +330 -0
  52. package/.output/server/_libs/d3-format.mjs +252 -0
  53. package/.output/server/_libs/d3-interpolate.mjs +207 -0
  54. package/.output/server/_libs/d3-path.mjs +77 -0
  55. package/.output/server/_libs/d3-scale+[...].mjs +1907 -0
  56. package/.output/server/_libs/d3-shape.mjs +818 -0
  57. package/.output/server/_libs/decimal.js-light.mjs +884 -0
  58. package/.output/server/_libs/es-toolkit.mjs +1159 -0
  59. package/.output/server/_libs/eventemitter3.mjs +236 -0
  60. package/.output/server/_libs/immer.mjs +532 -0
  61. package/.output/server/_libs/lucide-react.mjs +46 -32
  62. package/.output/server/_libs/next-themes.mjs +2 -2
  63. package/.output/server/_libs/react-redux+[...].mjs +205 -0
  64. package/.output/server/_libs/recharts+[...].mjs +14350 -0
  65. package/.output/server/_libs/sonner.mjs +2 -2
  66. package/.output/server/_libs/tanstack__react-query.mjs +8 -3
  67. package/.output/server/_libs/verkit.mjs +99 -1
  68. package/.output/server/_runtime.mjs +10 -1
  69. package/.output/server/_ssr/ConsoleFeatureUnavailable-DoxET2AT.mjs +58 -0
  70. package/.output/server/_ssr/{HashValueDisplay-DwpGLfrl.mjs → HashValueDisplay-BplboHEb.mjs} +3 -3
  71. package/.output/server/_ssr/InsightsInfo-o2YPd5Qc.mjs +37 -0
  72. package/.output/server/_ssr/InsightsPageHeader-NExCJL1I.mjs +154 -0
  73. package/.output/server/_ssr/NotFoundPage-Cd-A8ock.mjs +37 -0
  74. package/.output/server/_ssr/api-B1Ro0uys.mjs +202 -0
  75. package/.output/server/_ssr/api-C0G9MTMI.mjs +4205 -0
  76. package/.output/server/_ssr/{api-keys-CZeTPQys.mjs → api-keys-CT3HG7KT.mjs} +49 -9
  77. package/.output/server/_ssr/api-keys-Dvvy1sem.mjs +5 -0
  78. package/.output/server/_ssr/api-keys-rpc-b1qTTFfG.mjs +52 -0
  79. package/.output/server/_ssr/api-rpc-Cbns2MGx.mjs +338 -0
  80. package/.output/server/_ssr/{auth-rpc-DUaeOWKK.mjs → auth-rpc-DChS7m9y.mjs} +6 -6
  81. package/.output/server/_ssr/auth.server-Ccqz2iVv.mjs +37 -0
  82. package/.output/server/_ssr/bundleActivity-D17Pb_h5.mjs +19 -0
  83. package/.output/server/_ssr/card-DMbcvlPz.mjs +49 -0
  84. package/.output/server/_ssr/config-DMEJdhoW.mjs +13 -0
  85. package/.output/server/_ssr/config.server-DyypN6cQ.mjs +60 -0
  86. package/.output/server/_ssr/console-access-KpPv5lin.mjs +17 -0
  87. package/.output/server/_ssr/console-features-CsbyHYfK.mjs +55 -0
  88. package/.output/server/_ssr/console-features-api-B-KVuo4p.mjs +43 -0
  89. package/.output/server/_ssr/console-features-rpc-Djyo5740.mjs +19 -0
  90. package/.output/server/_ssr/{createServerFn-B2hcXoPM.mjs → createMiddleware-B_I3Ll4M.mjs} +28 -2
  91. package/.output/server/_ssr/{createServerRpc-DB29C_5p.mjs → createServerRpc-DuNm5mmo.mjs} +2 -2
  92. package/.output/server/_ssr/databaseRows-CXxf_VIB.mjs +132 -0
  93. package/.output/server/_ssr/{deleteBundle-BTGwBgzx.mjs → deleteBundle-Dcc21Lbd.mjs} +9 -19
  94. package/.output/server/_ssr/{dialog-DS-nw-pA.mjs → dialog-cOmLTUV2.mjs} +2 -2
  95. package/.output/server/_ssr/{dist-CyzPaI5v.mjs → dist-B-r4u3Lg.mjs} +28 -5
  96. package/.output/server/_ssr/{downloadBundle-CNnYxYO2.mjs → downloadBundle-CexOFoZ4.mjs} +6 -7
  97. package/.output/server/_ssr/empty-BHoY_5jt.mjs +49 -0
  98. package/.output/server/_ssr/{table-CnZmon_Z.mjs → field-mjhcNEXD.mjs} +63 -51
  99. package/.output/server/_ssr/getBundleChildren-DbDXe9wB.mjs +22 -0
  100. package/.output/server/_ssr/{input-group-BpsnmvZw.mjs → input-group-96H30HWC.mjs} +2 -2
  101. package/.output/server/_ssr/insights-D25VDVaz.mjs +5 -0
  102. package/.output/server/_ssr/insights-D359GJFL.mjs +14 -0
  103. package/.output/server/_ssr/insights-DgFv1NsO.mjs +1238 -0
  104. package/.output/server/_ssr/insights-api-ChAKrbhG.mjs +105 -0
  105. package/.output/server/_ssr/insights-failures-WQ-ttjOx.mjs +44 -0
  106. package/.output/server/_ssr/insights-failures-rpc-BFsqlltN.mjs +21 -0
  107. package/.output/server/_ssr/insights-recovery-DkL6bxMW.mjs +28 -0
  108. package/.output/server/_ssr/insights-recovery-rpc-2P5m8F62.mjs +46 -0
  109. package/.output/server/_ssr/insights-recovery-rpc-Tj8vNC19.mjs +20 -0
  110. package/.output/server/_ssr/{insights-rpc-38DujK0G.mjs → insights-rpc-DrkVmV9o.mjs} +22 -14
  111. package/.output/server/_ssr/insights-search-BsalnNw-.mjs +25 -0
  112. package/.output/server/_ssr/insights-usage-1nVTQrLy.mjs +29 -0
  113. package/.output/server/_ssr/insights-usage-rpc-Ct_4xHhZ.mjs +29 -0
  114. package/.output/server/_ssr/insights-usage-rpc-DaYIRtFK.mjs +41 -0
  115. package/.output/server/_ssr/insightsRecovery-CpGucWN1.mjs +45 -0
  116. package/.output/server/_ssr/insightsUsage-JYpKZAF7.mjs +56 -0
  117. package/.output/server/_ssr/insights_.distribution-Bcu6uYs_.mjs +5 -0
  118. package/.output/server/_ssr/insights_.distribution-D3Csuyjw.mjs +14 -0
  119. package/.output/server/_ssr/insights_.distribution-jQZEF07J.mjs +335 -0
  120. package/.output/server/_ssr/installations-B8Vh54lH.mjs +69 -0
  121. package/.output/server/_ssr/installations-BI5kelHv.mjs +5 -0
  122. package/.output/server/_ssr/{installations-DiVKEF5T.mjs → installations-Cxikllmx.mjs} +517 -54
  123. package/.output/server/_ssr/listReleases-DqU9t1l3.mjs +79 -0
  124. package/.output/server/_ssr/releaseManagement-B3e7I8J9.mjs +11 -0
  125. package/.output/server/_ssr/{router-B9wG_9f8.mjs → router-CRkm-wHD.mjs} +117 -124
  126. package/.output/server/_ssr/routes-B3IJo_jL.mjs +292 -0
  127. package/.output/server/_ssr/{routes-D7Yh_jfj.mjs → routes-CNAi0Mmv.mjs} +176 -455
  128. package/.output/server/_ssr/runtime.server-NucbYjc7.mjs +1372 -0
  129. package/.output/server/_ssr/select-BkPAJFL7.mjs +107 -0
  130. package/.output/server/_ssr/{server-D_C2rMzf.mjs → server-DKJYsaCP.mjs} +6 -32
  131. package/.output/server/_ssr/{createSsrRpc-DjWNJ0uc.mjs → sidebar-Cw1VuC7d.mjs} +117 -63
  132. package/.output/server/_ssr/ssr.mjs +1 -1
  133. package/.output/server/_ssr/table-q0P9wz7x.mjs +53 -0
  134. package/.output/server/_ssr/updateFailures-DMU9f0pK.mjs +24 -0
  135. package/.output/server/_ssr/{uuidv7-D0QFkPL_.mjs → uuidv7-BzSO-fr-.mjs} +2 -2
  136. package/.output/server/_tanstack-start-manifest_v-Bb0gKDbb.mjs +124 -0
  137. package/.output/server/index.mjs +273 -168
  138. package/README.md +71 -65
  139. package/lib/index.d.mts +13 -11
  140. package/lib/vite-internal.mjs +6 -3
  141. package/package.json +8 -8
  142. package/src/components/AppSidebar.tsx +91 -71
  143. package/src/components/ConsoleAccessPage.tsx +5 -1
  144. package/src/components/ConsoleFeatureUnavailable.tsx +82 -0
  145. package/src/components/NotFoundPage.tsx +7 -7
  146. package/src/components/features/FeatureSlots.tsx +99 -0
  147. package/src/components/features/api-keys/apiKeysFeatureUi.tsx +15 -0
  148. package/src/components/features/bundles/BundleMetadata.tsx +7 -4
  149. package/src/components/features/channels/ChannelManagementDialog.tsx +2 -5
  150. package/src/components/features/insights/AppUsage.tsx +370 -0
  151. package/src/components/features/insights/EstimatedCount.tsx +18 -0
  152. package/src/components/features/insights/EventDetails.tsx +117 -37
  153. package/src/components/features/insights/EventHistoryCard.tsx +112 -26
  154. package/src/components/features/insights/EventHistoryList.tsx +2 -2
  155. package/src/components/features/insights/InsightsControls.tsx +265 -102
  156. package/src/components/features/insights/InsightsErrorAlert.tsx +14 -1
  157. package/src/components/features/insights/InsightsInfo.tsx +48 -0
  158. package/src/components/features/insights/InsightsOverview.tsx +176 -173
  159. package/src/components/features/insights/InsightsPageHeader.tsx +35 -20
  160. package/src/components/features/insights/InsightsPeriodSelector.tsx +89 -0
  161. package/src/components/features/insights/InstallationHistoryCard.tsx +52 -10
  162. package/src/components/features/insights/InstallationMatchesCard.tsx +1 -1
  163. package/src/components/features/insights/ReleaseActivity.tsx +188 -0
  164. package/src/components/features/insights/ReportingDevicesSummary.tsx +65 -0
  165. package/src/components/features/insights/UpdateFailures.tsx +319 -0
  166. package/src/components/features/insights/insightsFeatureUi.tsx +35 -0
  167. package/src/components/features/releases/ReleaseEditorSheet.tsx +24 -1
  168. package/src/components/ui/card.tsx +1 -1
  169. package/src/components/ui/empty.tsx +101 -0
  170. package/src/components/ui/scroll-area.tsx +52 -0
  171. package/src/components/ui/sidebar.tsx +22 -29
  172. package/src/components/ui/tabs.tsx +81 -0
  173. package/src/index.ts +12 -17
  174. package/src/lib/api-keys-api.ts +0 -28
  175. package/src/lib/api-keys-rpc.ts +27 -38
  176. package/src/lib/api-rpc.ts +197 -221
  177. package/src/lib/api.ts +11 -13
  178. package/src/lib/bundle-activity.ts +31 -0
  179. package/src/lib/console-access.ts +20 -0
  180. package/src/lib/console-features-api.ts +68 -0
  181. package/src/lib/console-features-rpc.ts +13 -0
  182. package/src/lib/console-features.ts +91 -0
  183. package/src/lib/insights-api.ts +37 -1
  184. package/src/lib/insights-failures-rpc.ts +21 -0
  185. package/src/lib/insights-failures.ts +129 -0
  186. package/src/lib/insights-recovery-rpc.ts +51 -0
  187. package/src/lib/insights-recovery.ts +68 -0
  188. package/src/lib/insights-retention.ts +21 -0
  189. package/src/lib/insights-rpc.ts +30 -12
  190. package/src/lib/insights-search.ts +58 -0
  191. package/src/lib/insights-usage-rpc.ts +29 -0
  192. package/src/lib/insights-usage.ts +74 -0
  193. package/src/lib/insights-view.ts +38 -2
  194. package/src/lib/scroll-restoration.ts +21 -0
  195. package/src/lib/server/adminInsights.ts +124 -0
  196. package/src/lib/server/auth.server.ts +13 -5
  197. package/src/lib/server/bundleActivity.ts +37 -0
  198. package/src/lib/server/config.server.ts +16 -32
  199. package/src/lib/server/console-feature-apis.ts +59 -0
  200. package/src/lib/server/console-runtime.server.ts +6 -40
  201. package/src/lib/server/deleteBundle.ts +16 -25
  202. package/src/lib/server/downloadBundle.ts +7 -10
  203. package/src/lib/server/getBundleChildren.ts +31 -122
  204. package/src/lib/server/insightsRecovery.ts +55 -0
  205. package/src/lib/server/insightsUsage.ts +63 -0
  206. package/src/lib/server/listReleases.ts +109 -44
  207. package/src/lib/server/releaseReachability.ts +4 -3
  208. package/src/lib/server/runtime.server.ts +132 -31
  209. package/src/lib/server/updateFailures.ts +31 -0
  210. package/src/routeTree.gen.ts +21 -21
  211. package/src/router.tsx +3 -2
  212. package/src/routes/-installations-search.ts +32 -8
  213. package/src/routes/-releases-search.ts +76 -29
  214. package/src/routes/__root.tsx +10 -4
  215. package/src/routes/api/bundles/$bundleId/download.ts +2 -5
  216. package/src/routes/api-keys.tsx +5 -2
  217. package/src/routes/index.tsx +167 -104
  218. package/src/routes/insights.tsx +100 -59
  219. package/src/routes/insights_.distribution.tsx +398 -0
  220. package/src/routes/installations.tsx +98 -41
  221. package/src/vite-internal.ts +6 -3
  222. package/.output/public/assets/HashValueDisplay-CB0Eskit.js +0 -1
  223. package/.output/public/assets/InsightsPageHeader-Car5LNiZ.js +0 -1
  224. package/.output/public/assets/api-keys-BzKocqRy.js +0 -1
  225. package/.output/public/assets/api-keys-api-BHcODB7q.js +0 -1
  226. package/.output/public/assets/card-Dhr_eFYj.js +0 -1
  227. package/.output/public/assets/chart-no-axes-combined-Dy4WJv2U.js +0 -1
  228. package/.output/public/assets/composite-Bp98A0Ld.js +0 -1
  229. package/.output/public/assets/createServerFn-CA8fs7QD.js +0 -3
  230. package/.output/public/assets/dialog-CUTrcPlB.js +0 -1
  231. package/.output/public/assets/dist-C_NydL0e.js +0 -1
  232. package/.output/public/assets/index-DdAz-CXv.js +0 -10
  233. package/.output/public/assets/input-group-cSOG4wh_.js +0 -1
  234. package/.output/public/assets/insights-C0PBSZ-n.js +0 -1
  235. package/.output/public/assets/installations-CE33wgbY.js +0 -2
  236. package/.output/public/assets/installations-CStZja8x.js +0 -1
  237. package/.output/public/assets/lazyRouteComponent-Ccp3bwBY.js +0 -1
  238. package/.output/public/assets/link-CI8idAUi.js +0 -1
  239. package/.output/public/assets/loader-circle-BoxLrsOu.js +0 -1
  240. package/.output/public/assets/preload-helper-CQ36nxok.js +0 -1
  241. package/.output/public/assets/refresh-cw-Bqc72UZG.js +0 -1
  242. package/.output/public/assets/routes-DGVXdzpw.js +0 -2
  243. package/.output/public/assets/routes-DTlwOr8I.js +0 -1
  244. package/.output/public/assets/shield-check-uK3MHWJZ.js +0 -1
  245. package/.output/public/assets/signing-CMf4aqDO.js +0 -1
  246. package/.output/public/assets/styles-BCvbtKsm.css +0 -2
  247. package/.output/public/assets/table-2vcV89QB.js +0 -1
  248. package/.output/public/assets/useCompositeListItem-CHLITE_4.js +0 -1
  249. package/.output/public/assets/x-DCd0j8c6.js +0 -1
  250. package/.output/server/_ssr/InsightsPageHeader-Df4Brj8u.mjs +0 -484
  251. package/.output/server/_ssr/api-keys-api-BUPQVSpk.mjs +0 -60
  252. package/.output/server/_ssr/api-keys-rpc-Cs7tS5_j.mjs +0 -136
  253. package/.output/server/_ssr/api-rpc-tAupfGQm.mjs +0 -1399
  254. package/.output/server/_ssr/auth.server-DW71WzsD.mjs +0 -47
  255. package/.output/server/_ssr/bundle-signing-rpc-BhnBw0-d.mjs +0 -16
  256. package/.output/server/_ssr/bundle-signing.server-C4NW5mWx.mjs +0 -12
  257. package/.output/server/_ssr/card-BRiPmi0C.mjs +0 -108
  258. package/.output/server/_ssr/config-Dv8qLDZ0.mjs +0 -13
  259. package/.output/server/_ssr/config.server-afDuAwaG.mjs +0 -368
  260. package/.output/server/_ssr/databaseJsonValue-d6I3nAS4.mjs +0 -45
  261. package/.output/server/_ssr/databaseRows-5bCRTxm-.mjs +0 -126
  262. package/.output/server/_ssr/getBundleChildren-C0vh4ZvE.mjs +0 -50
  263. package/.output/server/_ssr/insights-BdS364Xu.mjs +0 -460
  264. package/.output/server/_ssr/insightsContract-fjJDBeEE.mjs +0 -303
  265. package/.output/server/_ssr/installations-CtA59MQG.mjs +0 -29
  266. package/.output/server/_ssr/routes-CKrrS2q2.mjs +0 -37
  267. package/.output/server/_ssr/runtime.server-DacU1TBV.mjs +0 -438
  268. package/.output/server/_ssr/signing-Dz1WkZOf.mjs +0 -133
  269. package/.output/server/_tanstack-start-manifest_v-2MjTyxil.mjs +0 -97
  270. package/src/components/features/insights/InsightsBundleReports.tsx +0 -51
  271. package/src/components/features/signing/BundleSigningPage.tsx +0 -139
  272. package/src/lib/bundle-signing-api.ts +0 -14
  273. package/src/lib/bundle-signing-rpc.ts +0 -16
  274. package/src/lib/server/bundle-signing.server.ts +0 -23
  275. package/src/routes/signing.tsx +0 -37
@@ -1,27 +1,24 @@
1
1
  import {
2
- DatabasePluginInputError,
3
- type Bundle,
4
- type ChannelDeleteInput,
5
- type ChannelInsertInput,
6
- type DatabaseBundleQueryOptions,
7
- deleteRelease as deleteReleaseMutation,
8
- preflightReleasePolicy,
9
- promoteRelease as promoteReleaseMutation,
2
+ type ChannelDeleteResult,
3
+ type ChannelRow,
4
+ type ReleaseFilter,
10
5
  type ReleasePolicyPatch,
11
- updateReleasePolicy,
6
+ rowToBundle,
12
7
  } from "@hot-updater/plugin-core";
13
8
  import { createServerFn } from "@tanstack/react-start";
14
9
 
10
+ import { consoleAccess } from "./console-access";
15
11
  import { DEFAULT_PAGE_LIMIT } from "./constants";
16
12
  import { withPublicBundleMutationErrors } from "./public-bundle-error";
17
- import { listReleases } from "./server/listReleases";
13
+ import { listReleases, readReleaseFilter } from "./server/listReleases";
18
14
  import { addReleaseReachability } from "./server/releaseReachability";
19
15
 
20
16
  type GetBundlesInput = {
21
17
  platform?: "ios" | "android";
22
- page?: number;
23
- limit?: string;
18
+ limit?: number;
19
+ /** Bundles older than this id: the next page. */
24
20
  after?: string;
21
+ /** Bundles newer than this id: the previous page. */
25
22
  before?: string;
26
23
  };
27
24
 
@@ -46,15 +43,13 @@ type DeleteBundlesInput = {
46
43
  };
47
44
 
48
45
  type GetReleasesInput = {
49
- afterReleaseId?: string;
46
+ /** One of the filter sets the release indexes serve. */
47
+ filter?: ReleaseFilter;
48
+ /** Releases older than this id: the next page. */
50
49
  beforeReleaseId?: string;
51
- bundleId?: string;
52
- channelId?: string;
53
- enabled?: boolean;
54
- platform?: "ios" | "android";
50
+ /** Releases newer than this id: the previous page. */
51
+ afterReleaseId?: string;
55
52
  limit?: number;
56
- page?: number;
57
- targetAppVersion?: string;
58
53
  };
59
54
 
60
55
  type ReleaseMutationInput = {
@@ -68,78 +63,78 @@ type DeleteReleaseInput = {
68
63
  releaseId: string;
69
64
  };
70
65
 
66
+ const MAX_PAGE_LIMIT = 100;
67
+
68
+ const pageLimit = (limit: number | undefined) => {
69
+ const value = limit ?? DEFAULT_PAGE_LIMIT;
70
+ if (!Number.isSafeInteger(value) || value < 1 || value > MAX_PAGE_LIMIT) {
71
+ throw new Error(`limit must be between 1 and ${MAX_PAGE_LIMIT}.`);
72
+ }
73
+ return value;
74
+ };
75
+
76
+ const prepare = async () => {
77
+ const { prepareConfig } = await import("./server/config.server");
78
+ return prepareConfig();
79
+ };
80
+
71
81
  export const getReleases = createServerFn({ method: "GET" })
72
- .inputValidator((input: GetReleasesInput | undefined) => input)
82
+ .middleware([consoleAccess])
83
+ .validator((input: GetReleasesInput | undefined) => ({
84
+ filter: readReleaseFilter(input?.filter),
85
+ ...(input?.afterReleaseId === undefined
86
+ ? {}
87
+ : { afterReleaseId: input.afterReleaseId }),
88
+ ...(input?.beforeReleaseId === undefined
89
+ ? {}
90
+ : { beforeReleaseId: input.beforeReleaseId }),
91
+ limit: pageLimit(input?.limit),
92
+ }))
73
93
  .handler(async ({ data }) => {
74
- const { prepareConfig } = await import("./server/config.server");
75
- const { config } = await prepareConfig();
76
- const result = await listReleases(config.database.models.releases, {
77
- ...(data?.afterReleaseId === undefined
78
- ? {}
79
- : { afterReleaseId: data.afterReleaseId }),
80
- ...(data?.beforeReleaseId === undefined
81
- ? {}
82
- : { beforeReleaseId: data.beforeReleaseId }),
83
- ...(data?.bundleId === undefined ? {} : { bundleId: data.bundleId }),
84
- ...(data?.channelId === undefined ? {} : { channelId: data.channelId }),
85
- ...(data?.enabled === undefined ? {} : { enabled: data.enabled }),
86
- ...(data?.page === undefined ? {} : { page: data.page }),
87
- ...(data?.platform === undefined ? {} : { platform: data.platform }),
88
- ...(data?.targetAppVersion === undefined
89
- ? {}
90
- : { targetAppVersion: data.targetAppVersion }),
91
- limit: data?.limit ?? DEFAULT_PAGE_LIMIT,
92
- });
93
- const releases = await addReleaseReachability(
94
- config.database.models.releaseCatalogs,
95
- result.data,
96
- );
94
+ const { core } = await prepare();
95
+ const result = await listReleases(core, data);
97
96
  return {
98
97
  ...result,
99
- data: releases,
98
+ data: await addReleaseReachability(core, result.data),
100
99
  };
101
100
  });
102
101
 
103
102
  export const getRelease = createServerFn({ method: "GET" })
104
- .inputValidator((input: { releaseId: string }) => input)
105
- .handler(async ({ data }) => {
106
- const { prepareConfig } = await import("./server/config.server");
107
- const { config } = await prepareConfig();
108
- return config.database.models.releases.findById(data.releaseId);
109
- });
103
+ .middleware([consoleAccess])
104
+ .validator((input: { releaseId: string }) => input)
105
+ .handler(async ({ data }) =>
106
+ (await prepare()).core.getRelease(data.releaseId),
107
+ );
110
108
 
111
109
  export const updateRelease = createServerFn({ method: "POST" })
112
- .inputValidator((input: ReleaseMutationInput) => input)
110
+ .middleware([consoleAccess])
111
+ .validator((input: ReleaseMutationInput) => input)
113
112
  .handler(async ({ data }) => {
114
- const { prepareConfig } = await import("./server/config.server");
115
- const { config } = await prepareConfig();
116
- return withPublicBundleMutationErrors(() =>
117
- updateReleasePolicy({ database: config.database, ...data }),
118
- );
113
+ const { core } = await prepare();
114
+ return withPublicBundleMutationErrors(() => core.updateReleasePolicy(data));
119
115
  });
120
116
 
121
117
  export const preflightRelease = createServerFn({ method: "POST" })
122
- .inputValidator((input: ReleaseMutationInput) => input)
118
+ .middleware([consoleAccess])
119
+ .validator((input: ReleaseMutationInput) => input)
123
120
  .handler(async ({ data }) => {
124
- const { prepareConfig } = await import("./server/config.server");
125
- const { config } = await prepareConfig();
121
+ const { core } = await prepare();
126
122
  return withPublicBundleMutationErrors(() =>
127
- preflightReleasePolicy({ database: config.database, ...data }),
123
+ core.preflightReleasePolicy(data),
128
124
  );
129
125
  });
130
126
 
131
127
  export const deleteRelease = createServerFn({ method: "POST" })
132
- .inputValidator((input: DeleteReleaseInput) => input)
128
+ .middleware([consoleAccess])
129
+ .validator((input: DeleteReleaseInput) => input)
133
130
  .handler(async ({ data }) => {
134
- const { prepareConfig } = await import("./server/config.server");
135
- const { config } = await prepareConfig();
136
- return withPublicBundleMutationErrors(() =>
137
- deleteReleaseMutation({ database: config.database, ...data }),
138
- );
131
+ const { core } = await prepare();
132
+ return withPublicBundleMutationErrors(() => core.deleteRelease(data));
139
133
  });
140
134
 
141
135
  export const promoteRelease = createServerFn({ method: "POST" })
142
- .inputValidator(
136
+ .middleware([consoleAccess])
137
+ .validator(
143
138
  (input: {
144
139
  action: "copy" | "move";
145
140
  expectedRevision?: number;
@@ -148,54 +143,60 @@ export const promoteRelease = createServerFn({ method: "POST" })
148
143
  }) => input,
149
144
  )
150
145
  .handler(async ({ data }) => {
151
- const { prepareConfig } = await import("./server/config.server");
152
- const { config } = await prepareConfig();
153
- return withPublicBundleMutationErrors(() =>
154
- promoteReleaseMutation({ database: config.database, ...data }),
155
- );
146
+ const { core } = await prepare();
147
+ return withPublicBundleMutationErrors(() => core.promoteRelease(data));
156
148
  });
157
149
 
158
150
  export const getReleaseCatalogDiagnostics = createServerFn({ method: "GET" })
159
- .inputValidator((input: { scopeKey: string }) => input)
160
- .handler(async ({ data }) => {
161
- const { prepareConfig } = await import("./server/config.server");
162
- const { config } = await prepareConfig();
163
- return config.database.models.releaseCatalogs.findByScopeKey(data.scopeKey);
164
- });
151
+ .middleware([consoleAccess])
152
+ .validator((input: { scopeKey: string }) => input)
153
+ .handler(async ({ data }) =>
154
+ (await prepare()).core.getReleaseCatalogRow(data.scopeKey),
155
+ );
165
156
 
166
157
  // GET /api/config
167
- export const getConfig = createServerFn().handler(async () => {
168
- try {
169
- const { prepareConfig } = await import("./server/config.server");
170
- const { config } = await prepareConfig();
171
- return { console: config.console };
172
- } catch (error) {
173
- console.error("Error during config retrieval:", error);
174
- throw error;
175
- }
176
- });
158
+ export const getConfig = createServerFn()
159
+ .middleware([consoleAccess])
160
+ .handler(async () => {
161
+ try {
162
+ const { config } = await prepare();
163
+ return { console: config.console };
164
+ } catch (error) {
165
+ console.error("Error during config retrieval:", error);
166
+ throw error;
167
+ }
168
+ });
177
169
 
178
170
  // GET /api/channels
179
- export const getChannels = createServerFn().handler(async () => {
180
- try {
181
- const { prepareConfig } = await import("./server/config.server");
182
- const { databaseClient } = await prepareConfig();
183
- const channels = await databaseClient.getChannels();
184
- return channels ?? [];
185
- } catch (error) {
186
- console.error("Error during channel retrieval:", error);
187
- throw error;
188
- }
189
- });
171
+ export const getChannels = createServerFn()
172
+ .middleware([consoleAccess])
173
+ .handler(async (): Promise<ChannelRow[]> => {
174
+ try {
175
+ return await (await prepare()).core.listChannels();
176
+ } catch (error) {
177
+ console.error("Error during channel retrieval:", error);
178
+ throw error;
179
+ }
180
+ });
190
181
 
191
182
  // POST /api/channels
192
183
  export const createChannel = createServerFn({ method: "POST" })
193
- .inputValidator((input: ChannelInsertInput) => input)
184
+ .middleware([consoleAccess])
185
+ .validator((input: { name: string }) => {
186
+ const name = typeof input?.name === "string" ? input.name.trim() : "";
187
+ if (name.length === 0) throw new Error("Channel name is required.");
188
+ return { name };
189
+ })
194
190
  .handler(async ({ data }) => {
195
191
  try {
196
- const { prepareConfig } = await import("./server/config.server");
197
- const { databaseClient } = await prepareConfig();
198
- return { data: await databaseClient.insertChannel(data) };
192
+ const { core } = await prepare();
193
+ const existing = await core.findChannelByName(data.name);
194
+ if (existing !== null) {
195
+ return { data: { row: existing, inserted: false } };
196
+ }
197
+ return {
198
+ data: { row: await core.ensureChannel(data.name), inserted: true },
199
+ };
199
200
  } catch (error) {
200
201
  console.error("Error during channel creation:", error);
201
202
  throw error;
@@ -204,12 +205,11 @@ export const createChannel = createServerFn({ method: "POST" })
204
205
 
205
206
  // DELETE /api/channels/:id
206
207
  export const deleteChannel = createServerFn({ method: "POST" })
207
- .inputValidator((input: ChannelDeleteInput) => input)
208
- .handler(async ({ data }) => {
208
+ .middleware([consoleAccess])
209
+ .validator((input: { id: string }) => input)
210
+ .handler(async ({ data }): Promise<{ data: ChannelDeleteResult }> => {
209
211
  try {
210
- const { prepareConfig } = await import("./server/config.server");
211
- const { databaseClient } = await prepareConfig();
212
- return { data: await databaseClient.deleteChannel(data) };
212
+ return { data: await (await prepare()).core.deleteChannel(data.id) };
213
213
  } catch (error) {
214
214
  console.error("Error during channel deletion:", error);
215
215
  throw error;
@@ -217,78 +217,71 @@ export const deleteChannel = createServerFn({ method: "POST" })
217
217
  });
218
218
 
219
219
  // GET /api/config-loaded
220
- export const getConfigLoaded = createServerFn().handler(async () => {
221
- try {
222
- const [{ getRequest }, { requireConsoleAccess }] = await Promise.all([
223
- import("@tanstack/react-start/server"),
224
- import("./server/auth.server"),
225
- ]);
226
- await requireConsoleAccess(getRequest());
227
- const { isConfigLoaded } = await import("./server/config.server");
228
- const configLoaded = isConfigLoaded();
229
- return { configLoaded };
230
- } catch (error) {
231
- console.error("Error during config loaded retrieval:", error);
232
- throw error;
233
- }
234
- });
220
+ export const getConfigLoaded = createServerFn()
221
+ .middleware([consoleAccess])
222
+ .handler(async () => {
223
+ try {
224
+ const { isConfigLoaded } = await import("./server/config.server");
225
+ const configLoaded = isConfigLoaded();
226
+ return { configLoaded };
227
+ } catch (error) {
228
+ console.error("Error during config loaded retrieval:", error);
229
+ throw error;
230
+ }
231
+ });
235
232
 
236
- // GET /api/bundles
233
+ // GET /api/bundles: newest first, one page by key; the total is one counter row.
237
234
  export const getBundles = createServerFn({ method: "GET" })
238
- .inputValidator((input: GetBundlesInput | undefined) => input)
235
+ .middleware([consoleAccess])
236
+ .validator((input: GetBundlesInput | undefined) => {
237
+ if (input?.after !== undefined && input.before !== undefined) {
238
+ throw new Error("Page by after or before, not both.");
239
+ }
240
+ return {
241
+ ...(input?.platform === undefined ? {} : { platform: input.platform }),
242
+ ...(input?.after === undefined ? {} : { after: input.after }),
243
+ ...(input?.before === undefined ? {} : { before: input.before }),
244
+ limit: pageLimit(input?.limit),
245
+ };
246
+ })
239
247
  .handler(async ({ data }) => {
240
248
  try {
241
- const { prepareConfig } = await import("./server/config.server");
242
- const query = {
243
- platform: data?.platform ?? undefined,
244
- page:
245
- typeof data?.page === "number" &&
246
- Number.isInteger(data.page) &&
247
- data.page > 1
248
- ? data.page
249
- : undefined,
250
- limit: data?.limit ? Number(data.limit) : DEFAULT_PAGE_LIMIT,
251
- after: data?.after ?? undefined,
252
- before: data?.before ?? undefined,
249
+ const { core } = await prepare();
250
+ const platform =
251
+ data.platform === undefined ? {} : { platform: data.platform };
252
+ const [page, total] = await Promise.all([
253
+ data.before === undefined
254
+ ? core.listBundles({
255
+ ...platform,
256
+ order: "desc",
257
+ limit: data.limit,
258
+ ...(data.after === undefined ? {} : { after: data.after }),
259
+ })
260
+ : core
261
+ .listBundles({
262
+ ...platform,
263
+ order: "asc",
264
+ limit: data.limit,
265
+ after: data.before,
266
+ })
267
+ .then((rows) => rows.reverse()),
268
+ core.countBundles(data.platform),
269
+ ]);
270
+ const full = page.length === data.limit;
271
+ const first = page[0]?.bundle.id;
272
+ const last = page.at(-1)?.bundle.id;
273
+ return {
274
+ data: page.map(({ bundle, patches }) => rowToBundle(bundle, patches)),
275
+ total,
276
+ // A full page may have more past it; a short one ended the range.
277
+ ...(last !== undefined && (data.before !== undefined || full)
278
+ ? { next: last }
279
+ : {}),
280
+ ...(first !== undefined &&
281
+ (data.after !== undefined || (data.before !== undefined && full))
282
+ ? { previous: first }
283
+ : {}),
253
284
  };
254
- if (
255
- (query.after !== undefined && query.before !== undefined) ||
256
- (query.page !== undefined &&
257
- (query.after !== undefined || query.before !== undefined))
258
- ) {
259
- throw new DatabasePluginInputError("invalid-pagination");
260
- }
261
- const pagination =
262
- query.page !== undefined
263
- ? { page: query.page }
264
- : query.after !== undefined
265
- ? { cursor: { after: query.after } }
266
- : query.before !== undefined
267
- ? { cursor: { before: query.before } }
268
- : {};
269
-
270
- const { databaseClient } = await prepareConfig();
271
- const bundleQueryOptions: DatabaseBundleQueryOptions = {
272
- where: {
273
- platform: query.platform,
274
- },
275
- limit: query.limit,
276
- ...pagination,
277
- };
278
- const bundles = await databaseClient.getBundles(bundleQueryOptions);
279
-
280
- return (
281
- bundles ?? {
282
- data: [],
283
- pagination: {
284
- total: 0,
285
- hasNextPage: false,
286
- hasPreviousPage: false,
287
- currentPage: 1,
288
- totalPages: 0,
289
- },
290
- }
291
- );
292
285
  } catch (error) {
293
286
  console.error("Error during bundle retrieval:", error);
294
287
  throw error;
@@ -297,13 +290,14 @@ export const getBundles = createServerFn({ method: "GET" })
297
290
 
298
291
  // GET /api/bundles/:bundleId
299
292
  export const getBundle = createServerFn({ method: "GET" })
300
- .inputValidator((input: GetBundleInput) => input)
293
+ .middleware([consoleAccess])
294
+ .validator((input: GetBundleInput) => input)
301
295
  .handler(async ({ data }) => {
302
296
  try {
303
- const { prepareConfig } = await import("./server/config.server");
304
- const { databaseClient } = await prepareConfig();
305
- const bundle = await databaseClient.getBundleById(data.bundleId);
306
- return bundle ?? null;
297
+ const detail = await (await prepare()).core.getBundle(data.bundleId);
298
+ return detail === null
299
+ ? null
300
+ : rowToBundle(detail.bundle, detail.patches);
307
301
  } catch (error) {
308
302
  console.error("Error during bundle retrieval:", error);
309
303
  throw error;
@@ -311,17 +305,13 @@ export const getBundle = createServerFn({ method: "GET" })
311
305
  });
312
306
 
313
307
  export const getBundleChildren = createServerFn({ method: "GET" })
314
- .inputValidator((input: GetBundleChildrenInput) => input)
308
+ .middleware([consoleAccess])
309
+ .validator((input: GetBundleChildrenInput) => input)
315
310
  .handler(async ({ data }) => {
316
311
  try {
317
- const { prepareConfig } = await import("./server/config.server");
318
- const { getBundleChildren: getBundleChildrenWithConfig } =
319
- await import("./server/getBundleChildren");
320
- const { databaseClient } = await prepareConfig();
321
-
322
- return await getBundleChildrenWithConfig(data, {
323
- databaseClient,
324
- });
312
+ const [{ core }, { getBundleChildren: readBundleChildren }] =
313
+ await Promise.all([prepare(), import("./server/getBundleChildren")]);
314
+ return await readBundleChildren(core, data.baseBundleId);
325
315
  } catch (error) {
326
316
  console.error("Error during bundle children retrieval:", error);
327
317
  throw error;
@@ -329,50 +319,36 @@ export const getBundleChildren = createServerFn({ method: "GET" })
329
319
  });
330
320
 
331
321
  export const getBundleChildCounts = createServerFn({ method: "GET" })
332
- .inputValidator((input: GetBundleChildCountsInput) => input)
333
- .handler(async ({ data }) => {
334
- try {
335
- const { prepareConfig } = await import("./server/config.server");
336
- const { getBundleChildCounts: getBundleChildCountsWithConfig } =
337
- await import("./server/getBundleChildren");
338
- const { databaseClient } = await prepareConfig();
339
-
340
- return await getBundleChildCountsWithConfig(data.bundleIds, {
341
- databaseClient,
342
- });
343
- } catch (error) {
344
- console.error("Error during bundle child count retrieval:", error);
345
- throw error;
322
+ .middleware([consoleAccess])
323
+ .validator((input: GetBundleChildCountsInput) => {
324
+ if (!Array.isArray(input?.bundleIds) || input.bundleIds.length > 100) {
325
+ throw new Error("Choose up to 100 bundles.");
346
326
  }
347
- });
348
-
349
- // POST /api/bundles
350
- export const createBundle = createServerFn({ method: "POST" })
351
- .inputValidator((input: Bundle) => input)
327
+ return input;
328
+ })
352
329
  .handler(async ({ data }) => {
353
330
  try {
354
- const { prepareConfig } = await import("./server/config.server");
355
- const { databaseClient } = await prepareConfig();
356
- await databaseClient.insertBundle(data);
357
- return { success: true, bundleId: data.id };
331
+ const [{ core }, { getBundleChildCounts: readBundleChildCounts }] =
332
+ await Promise.all([prepare(), import("./server/getBundleChildren")]);
333
+ return await readBundleChildCounts(core, data.bundleIds);
358
334
  } catch (error) {
359
- console.error("Error during bundle creation:", error);
335
+ console.error("Error during bundle child count retrieval:", error);
360
336
  throw error;
361
337
  }
362
338
  });
363
339
 
364
340
  // DELETE /api/bundles/:bundleId
365
341
  export const deleteBundle = createServerFn({ method: "POST" })
366
- .inputValidator((input: DeleteBundleInput) => input)
342
+ .middleware([consoleAccess])
343
+ .validator((input: DeleteBundleInput) => input)
367
344
  .handler(async ({ data }) => {
368
345
  try {
369
- const { prepareConfig } = await import("./server/config.server");
370
346
  const { deleteBundle: deleteBundleWithStorage } =
371
347
  await import("./server/deleteBundle");
372
- const { databaseClient, storagePlugin } = await prepareConfig();
348
+ const { core, storagePlugin } = await prepare();
373
349
 
374
350
  await deleteBundleWithStorage(data, {
375
- databaseClient,
351
+ core,
376
352
  storagePlugin,
377
353
  waitForStorageCleanup: false,
378
354
  });
@@ -385,16 +361,16 @@ export const deleteBundle = createServerFn({ method: "POST" })
385
361
  });
386
362
 
387
363
  export const deleteBundles = createServerFn({ method: "POST" })
388
- .inputValidator((input: DeleteBundlesInput) => input)
364
+ .middleware([consoleAccess])
365
+ .validator((input: DeleteBundlesInput) => input)
389
366
  .handler(async ({ data }) => {
390
367
  try {
391
- const { prepareConfig } = await import("./server/config.server");
392
368
  const { deleteBundles: deleteBundlesWithStorage } =
393
369
  await import("./server/deleteBundle");
394
- const { databaseClient, storagePlugin } = await prepareConfig();
370
+ const { core, storagePlugin } = await prepare();
395
371
 
396
372
  const result = await deleteBundlesWithStorage(data, {
397
- databaseClient,
373
+ core,
398
374
  storagePlugin,
399
375
  waitForStorageCleanup: false,
400
376
  });
package/src/lib/api.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import type {
2
- ChannelDeleteInput,
3
- ChannelInsertInput,
2
+ ReleaseFilter,
4
3
  ReleasePolicyPatch,
5
4
  } from "@hot-updater/plugin-core";
6
5
  import {
@@ -34,9 +33,10 @@ import {
34
33
 
35
34
  type BundleFilters = {
36
35
  platform?: "ios" | "android";
37
- page?: number;
38
- limit?: string;
36
+ limit?: number;
37
+ /** Bundles older than this id: the next page. */
39
38
  after?: string;
39
+ /** Bundles newer than this id: the previous page. */
40
40
  before?: string;
41
41
  };
42
42
 
@@ -71,15 +71,13 @@ export const queryKeys = {
71
71
  };
72
72
 
73
73
  export type ReleaseFilters = {
74
- afterReleaseId?: string;
74
+ /** One of the filter sets the release indexes serve; none lists every release. */
75
+ filter?: ReleaseFilter;
76
+ /** Releases older than this id: the next page. */
75
77
  beforeReleaseId?: string;
76
- bundleId?: string;
77
- channelId?: string;
78
- enabled?: boolean;
79
- platform?: "ios" | "android";
78
+ /** Releases newer than this id: the previous page. */
79
+ afterReleaseId?: string;
80
80
  limit?: number;
81
- page?: number;
82
- targetAppVersion?: string;
83
81
  };
84
82
 
85
83
  function removeBundleFromQueryData(
@@ -213,7 +211,7 @@ export function useCreateChannelMutation() {
213
211
  const queryClient = useQueryClient();
214
212
 
215
213
  return useMutation({
216
- mutationFn: (input: ChannelInsertInput) =>
214
+ mutationFn: (input: { name: string }) =>
217
215
  createChannelApi({ data: input }).then((response) => response.data),
218
216
  onSuccess: async () => {
219
217
  await queryClient.invalidateQueries({ queryKey: queryKeys.channels });
@@ -225,7 +223,7 @@ export function useDeleteChannelMutation() {
225
223
  const queryClient = useQueryClient();
226
224
 
227
225
  return useMutation({
228
- mutationFn: (input: ChannelDeleteInput) =>
226
+ mutationFn: (input: { id: string }) =>
229
227
  deleteChannelApi({ data: input }).then((response) => response.data),
230
228
  onSettled: async () => {
231
229
  await queryClient.invalidateQueries({ queryKey: queryKeys.channels });
@@ -0,0 +1,31 @@
1
+ import { useQuery } from "@tanstack/react-query";
2
+
3
+ import type { RecoveryInput } from "./insights-recovery";
4
+ import { getBundleActivityRpc } from "./insights-recovery-rpc";
5
+
6
+ export type BundleActivityInput = Pick<
7
+ RecoveryInput,
8
+ "platform" | "channel"
9
+ > & { readonly releaseId: string };
10
+
11
+ export type BundleActivityReport = {
12
+ readonly downloads: number;
13
+ /** Each installation once for each UTC day it launched the release. */
14
+ readonly activeDays: number;
15
+ readonly failedLaunches: number;
16
+ readonly measuredAtMs: number;
17
+ readonly coverage: import("@hot-updater/server/plugins/insights").InsightsCoverage;
18
+ };
19
+
20
+ /** Release activity for bundle rows; mounted only where the console reads it. */
21
+ export function useBundleActivityQuery(inputs: readonly BundleActivityInput[]) {
22
+ const sorted = [...inputs].sort((a, b) =>
23
+ a.releaseId.localeCompare(b.releaseId),
24
+ );
25
+ return useQuery({
26
+ queryKey: ["bundle-activity", sorted],
27
+ queryFn: () => getBundleActivityRpc({ data: sorted }),
28
+ enabled: sorted.length > 0,
29
+ staleTime: 30_000,
30
+ });
31
+ }