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

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 (285) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/ConsoleFeatureUnavailable-D0LPL1Gy.js +1 -0
  3. package/.output/public/assets/HashValueDisplay-BmuB4FW-.js +1 -0
  4. package/.output/public/assets/InsightsInfo--v4SC07c.js +1 -0
  5. package/.output/public/assets/InsightsPageHeader-dU83CAQY.js +1 -0
  6. package/.output/public/assets/NotFoundPage-B5mhGG2a.js +1 -0
  7. package/.output/public/assets/api-D-vOVlQ4.js +1 -0
  8. package/.output/public/assets/api-keys-Csh8ARKU.js +1 -0
  9. package/.output/public/assets/api-keys-OMPUoA_A.js +1 -0
  10. package/.output/public/assets/arrow-left-Bwq9X1eS.js +1 -0
  11. package/.output/public/assets/arrow-up-right-BkOVn4if.js +1 -0
  12. package/.output/public/assets/card-C_7d6O9a.js +1 -0
  13. package/.output/public/assets/console-features-api-C1jRZTGw.js +1 -0
  14. package/.output/public/assets/dialog-DE8wFN_P.js +1 -0
  15. package/.output/public/assets/dist-C7ENGzpS.js +1 -0
  16. package/.output/public/assets/empty-CVd5NlQD.js +1 -0
  17. package/.output/public/assets/field-C06q1iC9.js +1 -0
  18. package/.output/public/assets/index-BeWlQtO6.js +11 -0
  19. package/.output/public/assets/input-group-Bi61biIA.js +1 -0
  20. package/.output/public/assets/insights-BjnAtC0f.js +2 -0
  21. package/.output/public/assets/insights-D86m11yH.js +42 -0
  22. package/.output/public/assets/insights-DpYstMoY.js +1 -0
  23. package/.output/public/assets/insights-recovery-rpc-BIm5_ALc.js +1 -0
  24. package/.output/public/assets/insights-search-KIF7mRtB.js +1 -0
  25. package/.output/public/assets/insights-usage-rpc-VHj8vyBt.js +1 -0
  26. package/.output/public/assets/insights_.distribution-ADcrX48c.js +2 -0
  27. package/.output/public/assets/insights_.distribution-CuuJUN6Y.js +1 -0
  28. package/.output/public/assets/insights_.distribution-DJRjuXsO.js +1 -0
  29. package/.output/public/assets/installations-BSBBzqWs.js +2 -0
  30. package/.output/public/assets/installations-DqIE6WcO.js +1 -0
  31. package/.output/public/assets/installations-UsELbDvw.js +1 -0
  32. package/.output/public/assets/loader-circle-CnCBxzHp.js +1 -0
  33. package/.output/public/assets/mutation-Csp7xUNh.js +1 -0
  34. package/.output/public/assets/preload-helper-B-cOmpLH.js +1 -0
  35. package/.output/public/assets/refresh-cw-FF1NtzlQ.js +1 -0
  36. package/.output/public/assets/routes-CoN9XHP_.js +2 -0
  37. package/.output/public/assets/routes-DT1jB0Zi.js +1 -0
  38. package/.output/public/assets/sidebar-D8mJRF15.js +3 -0
  39. package/.output/public/assets/styles-Bwt7v6U5.css +2 -0
  40. package/.output/public/assets/table-WBgfvgXg.js +1 -0
  41. package/.output/public/assets/useCompositeListItem-CloBmDwn.js +1 -0
  42. package/.output/public/assets/useMutation-DO8cvsTx.js +1 -0
  43. package/.output/public/assets/useOpenInteractionType-ReGr9GzJ.js +1 -0
  44. package/.output/public/assets/valueToPercent-BvPAi4bP.js +1 -0
  45. package/.output/server/{__23tanstack-start-server-fn-resolver-B7ioJhtE.mjs → __23tanstack-start-server-fn-resolver-DMRD4e4f.mjs} +51 -47
  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 +3803 -4023
  49. package/.output/server/_libs/@tanstack/react-router-ssr-query+[...].mjs +141 -98
  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__history.mjs +68 -41
  67. package/.output/server/_libs/tanstack__query-core.mjs +1618 -309
  68. package/.output/server/_libs/tanstack__react-query.mjs +262 -22
  69. package/.output/server/_libs/verkit.mjs +99 -1
  70. package/.output/server/_runtime.mjs +10 -1
  71. package/.output/server/_ssr/ConsoleFeatureUnavailable-Du7jNPNS.mjs +58 -0
  72. package/.output/server/_ssr/{HashValueDisplay-DwpGLfrl.mjs → HashValueDisplay-GQvFkXoi.mjs} +3 -3
  73. package/.output/server/_ssr/InsightsInfo-Do7QSaFR.mjs +37 -0
  74. package/.output/server/_ssr/InsightsPageHeader-DSUWUWRc.mjs +154 -0
  75. package/.output/server/_ssr/NotFoundPage-DsF4Vrhg.mjs +37 -0
  76. package/.output/server/_ssr/api-BEiVs0H7.mjs +203 -0
  77. package/.output/server/_ssr/api-keys-BrvBWGRf.mjs +5 -0
  78. package/.output/server/_ssr/{api-keys-CZeTPQys.mjs → api-keys-K5v32nf7.mjs} +49 -9
  79. package/.output/server/_ssr/api-keys-rpc-B96bBllm.mjs +52 -0
  80. package/.output/server/_ssr/api-rpc-DcUe280B.mjs +296 -0
  81. package/.output/server/_ssr/{auth-rpc-DUaeOWKK.mjs → auth-rpc-C68W56DP.mjs} +6 -6
  82. package/.output/server/_ssr/auth.server-DM1J6NwR.mjs +5712 -0
  83. package/.output/server/_ssr/bundleActivity-CpuY9CTP.mjs +19 -0
  84. package/.output/server/_ssr/card-CPAEKx0w.mjs +49 -0
  85. package/.output/server/_ssr/config-nsIcsHh8.mjs +15 -0
  86. package/.output/server/_ssr/config.server-DDCZk3Sq.mjs +36 -0
  87. package/.output/server/_ssr/console-access-Da-2Qd7q.mjs +17 -0
  88. package/.output/server/_ssr/console-features-Dzu2iUGD.mjs +55 -0
  89. package/.output/server/_ssr/console-features-api-xAsSgFa_.mjs +43 -0
  90. package/.output/server/_ssr/console-features-rpc-qARGpB24.mjs +19 -0
  91. package/.output/server/_ssr/{createServerFn-B2hcXoPM.mjs → createMiddleware-BdBf_ezN.mjs} +67 -56
  92. package/.output/server/_ssr/{createServerRpc-DB29C_5p.mjs → createServerRpc-B_y2lrwA.mjs} +2 -2
  93. package/.output/server/_ssr/{databaseJsonValue-d6I3nAS4.mjs → databaseJsonValue-Bhxciy7Z.mjs} +28 -5
  94. package/.output/server/_ssr/databaseRows-DwMAjaF8.mjs +74 -0
  95. package/.output/server/_ssr/{dialog-DS-nw-pA.mjs → dialog-q__BJ7mQ.mjs} +2 -2
  96. package/.output/server/_ssr/{dist-CyzPaI5v.mjs → dist-Cxo9ghYv.mjs} +140 -6
  97. package/.output/server/_ssr/{downloadBundle-CNnYxYO2.mjs → downloadBundle-BW9I-pMQ.mjs} +10 -10
  98. package/.output/server/_ssr/empty-DcBTAooP.mjs +49 -0
  99. package/.output/server/_ssr/{empty-plugin-adapters-B5QvGpEl.mjs → empty-plugin-adapters-CYdinE-i.mjs} +1 -1
  100. package/.output/server/_ssr/{table-CnZmon_Z.mjs → field-QCDBVpXA.mjs} +63 -51
  101. package/.output/server/_ssr/getBundleChildren-DIAGCF4a.mjs +22 -0
  102. package/.output/server/_ssr/{input-group-BpsnmvZw.mjs → input-group-hCk2nUoU.mjs} +2 -2
  103. package/.output/server/_ssr/insights-BfBO0RFr.mjs +1238 -0
  104. package/.output/server/_ssr/insights-DsAU3-TB.mjs +14 -0
  105. package/.output/server/_ssr/insights-RIfg447W.mjs +5 -0
  106. package/.output/server/_ssr/insights-api-CDn-QQHU.mjs +105 -0
  107. package/.output/server/_ssr/insights-failures-BBGMhuUr.mjs +44 -0
  108. package/.output/server/_ssr/insights-failures-rpc-rA2dctK2.mjs +21 -0
  109. package/.output/server/_ssr/insights-recovery-Dpk6Mfb9.mjs +28 -0
  110. package/.output/server/_ssr/insights-recovery-rpc-BCaF-xSd.mjs +46 -0
  111. package/.output/server/_ssr/insights-recovery-rpc-CcgYyUHk.mjs +20 -0
  112. package/.output/server/_ssr/{insights-rpc-38DujK0G.mjs → insights-rpc-keycmwzw.mjs} +22 -14
  113. package/.output/server/_ssr/insights-search-BRg5VsNe.mjs +25 -0
  114. package/.output/server/_ssr/insights-usage-cbw9n2Fq.mjs +29 -0
  115. package/.output/server/_ssr/insights-usage-rpc-BKgV1QDf.mjs +29 -0
  116. package/.output/server/_ssr/insights-usage-rpc-D-JYachy.mjs +41 -0
  117. package/.output/server/_ssr/insightsRecovery-B7oMc8d8.mjs +45 -0
  118. package/.output/server/_ssr/insightsUsage-Oa7kk5ek.mjs +56 -0
  119. package/.output/server/_ssr/insights_.distribution-BIqOqRW0.mjs +335 -0
  120. package/.output/server/_ssr/insights_.distribution-CuUrDa7P.mjs +5 -0
  121. package/.output/server/_ssr/insights_.distribution-RT_6x_ud.mjs +14 -0
  122. package/.output/server/_ssr/{installations-DiVKEF5T.mjs → installations-BO5CjX2z.mjs} +517 -54
  123. package/.output/server/_ssr/installations-BjaHiIfh.mjs +5 -0
  124. package/.output/server/_ssr/installations-Dt5luA-N.mjs +69 -0
  125. package/.output/server/_ssr/listReleases-BitHzOYB.mjs +79 -0
  126. package/.output/server/_ssr/releaseManagement-B3e7I8J9.mjs +11 -0
  127. package/.output/server/_ssr/{router-B9wG_9f8.mjs → router-DfVAixxe.mjs} +135 -139
  128. package/.output/server/_ssr/{routes-D7Yh_jfj.mjs → routes-C6LstNp8.mjs} +176 -455
  129. package/.output/server/_ssr/routes-Q3Uhmwwz.mjs +292 -0
  130. package/.output/server/_ssr/runtime.server-fL0o6I2b.mjs +1459 -0
  131. package/.output/server/_ssr/schema-BKY9SqaY.mjs +25 -0
  132. package/.output/server/_ssr/select-CUlInmjk.mjs +107 -0
  133. package/.output/server/_ssr/server-C43wq9ft.mjs +8330 -0
  134. package/.output/server/_ssr/{createSsrRpc-DjWNJ0uc.mjs → sidebar-DKbx2ach.mjs} +117 -63
  135. package/.output/server/_ssr/ssr.mjs +1 -1
  136. package/.output/server/_ssr/start-BXUOv_n8.mjs +4 -0
  137. package/.output/server/_ssr/table-NGWV8B-S.mjs +53 -0
  138. package/.output/server/_ssr/updateFailures-Jwe2Baab.mjs +24 -0
  139. package/.output/server/_ssr/{uuidv7-D0QFkPL_.mjs → uuidv7-C8bwiZr8.mjs} +2 -2
  140. package/.output/server/_tanstack-start-manifest_v-WQ5qGn8n.mjs +124 -0
  141. package/.output/server/index.mjs +267 -162
  142. package/LICENSE +13 -14
  143. package/README.md +82 -72
  144. package/lib/index.d.mts +23 -11
  145. package/lib/vite-internal.mjs +13 -3
  146. package/lib/vite.d.mts +2 -0
  147. package/package.json +14 -14
  148. package/src/components/AppSidebar.tsx +100 -71
  149. package/src/components/ConsoleAccessPage.tsx +5 -1
  150. package/src/components/ConsoleFeatureUnavailable.tsx +80 -0
  151. package/src/components/NotFoundPage.tsx +7 -7
  152. package/src/components/features/FeatureSlots.tsx +99 -0
  153. package/src/components/features/api-keys/apiKeysFeatureUi.tsx +15 -0
  154. package/src/components/features/bundles/BundleMetadata.tsx +8 -5
  155. package/src/components/features/bundles/RolloutCohortsDialog.tsx +1 -1
  156. package/src/components/features/channels/ChannelManagementDialog.tsx +2 -5
  157. package/src/components/features/insights/AppUsage.tsx +370 -0
  158. package/src/components/features/insights/EstimatedCount.tsx +18 -0
  159. package/src/components/features/insights/EventDetails.tsx +117 -37
  160. package/src/components/features/insights/EventHistoryCard.tsx +112 -26
  161. package/src/components/features/insights/EventHistoryList.tsx +2 -2
  162. package/src/components/features/insights/InsightsControls.tsx +265 -102
  163. package/src/components/features/insights/InsightsErrorAlert.tsx +14 -1
  164. package/src/components/features/insights/InsightsInfo.tsx +48 -0
  165. package/src/components/features/insights/InsightsOverview.tsx +176 -173
  166. package/src/components/features/insights/InsightsPageHeader.tsx +35 -20
  167. package/src/components/features/insights/InsightsPeriodSelector.tsx +89 -0
  168. package/src/components/features/insights/InstallationHistoryCard.tsx +52 -10
  169. package/src/components/features/insights/InstallationMatchesCard.tsx +1 -1
  170. package/src/components/features/insights/ReleaseActivity.tsx +188 -0
  171. package/src/components/features/insights/ReportingDevicesSummary.tsx +65 -0
  172. package/src/components/features/insights/UpdateFailures.tsx +319 -0
  173. package/src/components/features/insights/insightsFeatureUi.tsx +35 -0
  174. package/src/components/features/releases/ReleaseEditorSheet.tsx +30 -7
  175. package/src/components/ui/card.tsx +1 -1
  176. package/src/components/ui/empty.tsx +101 -0
  177. package/src/components/ui/scroll-area.tsx +52 -0
  178. package/src/components/ui/sidebar.tsx +22 -29
  179. package/src/components/ui/tabs.tsx +81 -0
  180. package/src/index.ts +26 -19
  181. package/src/lib/api-keys-api.ts +0 -28
  182. package/src/lib/api-keys-rpc.ts +27 -38
  183. package/src/lib/api-rpc.ts +189 -266
  184. package/src/lib/api.ts +19 -102
  185. package/src/lib/bundle-activity.ts +31 -0
  186. package/src/lib/console-access.ts +20 -0
  187. package/src/lib/console-features-api.ts +68 -0
  188. package/src/lib/console-features-rpc.ts +13 -0
  189. package/src/lib/console-features.ts +91 -0
  190. package/src/lib/insights-api.ts +37 -1
  191. package/src/lib/insights-failures-rpc.ts +21 -0
  192. package/src/lib/insights-failures.ts +129 -0
  193. package/src/lib/insights-recovery-rpc.ts +51 -0
  194. package/src/lib/insights-recovery.ts +68 -0
  195. package/src/lib/insights-retention.ts +21 -0
  196. package/src/lib/insights-rpc.ts +30 -12
  197. package/src/lib/insights-search.ts +58 -0
  198. package/src/lib/insights-usage-rpc.ts +29 -0
  199. package/src/lib/insights-usage.ts +74 -0
  200. package/src/lib/insights-view.ts +38 -2
  201. package/src/lib/scroll-restoration.ts +21 -0
  202. package/src/lib/server/adminInsights.ts +124 -0
  203. package/src/lib/server/auth.server.ts +13 -5
  204. package/src/lib/server/bundleActivity.ts +37 -0
  205. package/src/lib/server/config.server.ts +21 -74
  206. package/src/lib/server/console-feature-apis.ts +59 -0
  207. package/src/lib/server/console-runtime.server.ts +82 -38
  208. package/src/lib/server/downloadBundle.ts +16 -15
  209. package/src/lib/server/getBundleChildren.ts +31 -122
  210. package/src/lib/server/insightsRecovery.ts +55 -0
  211. package/src/lib/server/insightsUsage.ts +63 -0
  212. package/src/lib/server/listReleases.ts +109 -44
  213. package/src/lib/server/releaseReachability.ts +8 -7
  214. package/src/lib/server/runtime.server.ts +120 -31
  215. package/src/lib/server/updateFailures.ts +31 -0
  216. package/src/routeTree.gen.ts +44 -44
  217. package/src/router.tsx +3 -2
  218. package/src/routes/-installations-search.ts +32 -8
  219. package/src/routes/-releases-search.ts +76 -29
  220. package/src/routes/__root.tsx +10 -4
  221. package/src/routes/api/bundles/$bundleId/download.ts +2 -5
  222. package/src/routes/api-keys.tsx +5 -2
  223. package/src/routes/index.tsx +167 -104
  224. package/src/routes/insights.tsx +100 -59
  225. package/src/routes/insights_.distribution.tsx +398 -0
  226. package/src/routes/installations.tsx +98 -41
  227. package/src/vite-internal.ts +15 -3
  228. package/src/vite.ts +2 -0
  229. package/.output/public/assets/HashValueDisplay-CB0Eskit.js +0 -1
  230. package/.output/public/assets/InsightsPageHeader-Car5LNiZ.js +0 -1
  231. package/.output/public/assets/api-keys-BzKocqRy.js +0 -1
  232. package/.output/public/assets/api-keys-api-BHcODB7q.js +0 -1
  233. package/.output/public/assets/card-Dhr_eFYj.js +0 -1
  234. package/.output/public/assets/chart-no-axes-combined-Dy4WJv2U.js +0 -1
  235. package/.output/public/assets/composite-Bp98A0Ld.js +0 -1
  236. package/.output/public/assets/createServerFn-CA8fs7QD.js +0 -3
  237. package/.output/public/assets/dialog-CUTrcPlB.js +0 -1
  238. package/.output/public/assets/dist-C_NydL0e.js +0 -1
  239. package/.output/public/assets/index-DdAz-CXv.js +0 -10
  240. package/.output/public/assets/input-group-cSOG4wh_.js +0 -1
  241. package/.output/public/assets/insights-C0PBSZ-n.js +0 -1
  242. package/.output/public/assets/installations-CE33wgbY.js +0 -2
  243. package/.output/public/assets/installations-CStZja8x.js +0 -1
  244. package/.output/public/assets/lazyRouteComponent-Ccp3bwBY.js +0 -1
  245. package/.output/public/assets/link-CI8idAUi.js +0 -1
  246. package/.output/public/assets/loader-circle-BoxLrsOu.js +0 -1
  247. package/.output/public/assets/preload-helper-CQ36nxok.js +0 -1
  248. package/.output/public/assets/refresh-cw-Bqc72UZG.js +0 -1
  249. package/.output/public/assets/routes-DGVXdzpw.js +0 -2
  250. package/.output/public/assets/routes-DTlwOr8I.js +0 -1
  251. package/.output/public/assets/shield-check-uK3MHWJZ.js +0 -1
  252. package/.output/public/assets/signing-CMf4aqDO.js +0 -1
  253. package/.output/public/assets/styles-BCvbtKsm.css +0 -2
  254. package/.output/public/assets/table-2vcV89QB.js +0 -1
  255. package/.output/public/assets/useCompositeListItem-CHLITE_4.js +0 -1
  256. package/.output/public/assets/x-DCd0j8c6.js +0 -1
  257. package/.output/server/_ssr/InsightsPageHeader-Df4Brj8u.mjs +0 -484
  258. package/.output/server/_ssr/api-keys-api-BUPQVSpk.mjs +0 -60
  259. package/.output/server/_ssr/api-keys-rpc-Cs7tS5_j.mjs +0 -136
  260. package/.output/server/_ssr/api-rpc-tAupfGQm.mjs +0 -1399
  261. package/.output/server/_ssr/auth.server-DW71WzsD.mjs +0 -47
  262. package/.output/server/_ssr/bundle-signing-rpc-BhnBw0-d.mjs +0 -16
  263. package/.output/server/_ssr/bundle-signing.server-C4NW5mWx.mjs +0 -12
  264. package/.output/server/_ssr/card-BRiPmi0C.mjs +0 -108
  265. package/.output/server/_ssr/config-Dv8qLDZ0.mjs +0 -13
  266. package/.output/server/_ssr/config.server-afDuAwaG.mjs +0 -368
  267. package/.output/server/_ssr/databaseRows-5bCRTxm-.mjs +0 -126
  268. package/.output/server/_ssr/deleteBundle-BTGwBgzx.mjs +0 -66
  269. package/.output/server/_ssr/getBundleChildren-C0vh4ZvE.mjs +0 -50
  270. package/.output/server/_ssr/insights-BdS364Xu.mjs +0 -460
  271. package/.output/server/_ssr/insightsContract-fjJDBeEE.mjs +0 -303
  272. package/.output/server/_ssr/installations-CtA59MQG.mjs +0 -29
  273. package/.output/server/_ssr/routes-CKrrS2q2.mjs +0 -37
  274. package/.output/server/_ssr/runtime.server-DacU1TBV.mjs +0 -438
  275. package/.output/server/_ssr/server-D_C2rMzf.mjs +0 -7468
  276. package/.output/server/_ssr/signing-Dz1WkZOf.mjs +0 -133
  277. package/.output/server/_ssr/start-DpViQGnt.mjs +0 -4
  278. package/.output/server/_tanstack-start-manifest_v-2MjTyxil.mjs +0 -97
  279. package/src/components/features/insights/InsightsBundleReports.tsx +0 -51
  280. package/src/components/features/signing/BundleSigningPage.tsx +0 -139
  281. package/src/lib/bundle-signing-api.ts +0 -14
  282. package/src/lib/bundle-signing-rpc.ts +0 -16
  283. package/src/lib/server/bundle-signing.server.ts +0 -23
  284. package/src/lib/server/deleteBundle.ts +0 -145
  285. package/src/routes/signing.tsx +0 -37
package/src/lib/api.ts CHANGED
@@ -1,21 +1,12 @@
1
1
  import type {
2
- ChannelDeleteInput,
3
- ChannelInsertInput,
2
+ ReleaseFilter,
4
3
  ReleasePolicyPatch,
5
4
  } from "@hot-updater/plugin-core";
6
- import {
7
- type QueryClient,
8
- type QueryKey,
9
- useMutation,
10
- useQuery,
11
- useQueryClient,
12
- } from "@tanstack/react-query";
5
+ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
13
6
 
14
7
  import {
15
8
  createChannel as createChannelApi,
16
9
  deleteChannel as deleteChannelApi,
17
- deleteBundle as deleteBundleApi,
18
- deleteBundles as deleteBundlesApi,
19
10
  deleteRelease as deleteReleaseApi,
20
11
  getBundle,
21
12
  getBundleChildCounts,
@@ -34,14 +25,13 @@ import {
34
25
 
35
26
  type BundleFilters = {
36
27
  platform?: "ios" | "android";
37
- page?: number;
38
- limit?: string;
28
+ limit?: number;
29
+ /** Bundles older than this id: the next page. */
39
30
  after?: string;
31
+ /** Bundles newer than this id: the previous page. */
40
32
  before?: string;
41
33
  };
42
34
 
43
- type BundlesQueryData = Awaited<ReturnType<typeof getBundles>>;
44
-
45
35
  const bundleListQueryKey = ["bundles"] as const;
46
36
  const releaseListQueryKey = ["releases"] as const;
47
37
 
@@ -71,51 +61,13 @@ export const queryKeys = {
71
61
  };
72
62
 
73
63
  export type ReleaseFilters = {
74
- afterReleaseId?: string;
64
+ /** One of the filter sets the release indexes serve; none lists every release. */
65
+ filter?: ReleaseFilter;
66
+ /** Releases older than this id: the next page. */
75
67
  beforeReleaseId?: string;
76
- bundleId?: string;
77
- channelId?: string;
78
- enabled?: boolean;
79
- platform?: "ios" | "android";
68
+ /** Releases newer than this id: the previous page. */
69
+ afterReleaseId?: string;
80
70
  limit?: number;
81
- page?: number;
82
- targetAppVersion?: string;
83
- };
84
-
85
- function removeBundleFromQueryData(
86
- data: BundlesQueryData | undefined,
87
- bundleId: string,
88
- ) {
89
- if (!data) {
90
- return data;
91
- }
92
-
93
- return {
94
- ...data,
95
- data: data.data.filter((bundle) => bundle.id !== bundleId),
96
- };
97
- }
98
-
99
- function removeBundlesFromQueryData(
100
- data: BundlesQueryData | undefined,
101
- bundleIds: readonly string[],
102
- ) {
103
- if (!data) {
104
- return data;
105
- }
106
-
107
- const bundleIdSet = new Set(bundleIds);
108
- return {
109
- ...data,
110
- data: data.data.filter((bundle) => !bundleIdSet.has(bundle.id)),
111
- };
112
- }
113
-
114
- const invalidateInBackground = (
115
- queryClient: QueryClient,
116
- queryKey: QueryKey,
117
- ) => {
118
- void queryClient.invalidateQueries({ queryKey }).catch(() => undefined);
119
71
  };
120
72
 
121
73
  // Query Hooks
@@ -213,7 +165,7 @@ export function useCreateChannelMutation() {
213
165
  const queryClient = useQueryClient();
214
166
 
215
167
  return useMutation({
216
- mutationFn: (input: ChannelInsertInput) =>
168
+ mutationFn: (input: { name: string }) =>
217
169
  createChannelApi({ data: input }).then((response) => response.data),
218
170
  onSuccess: async () => {
219
171
  await queryClient.invalidateQueries({ queryKey: queryKeys.channels });
@@ -225,7 +177,7 @@ export function useDeleteChannelMutation() {
225
177
  const queryClient = useQueryClient();
226
178
 
227
179
  return useMutation({
228
- mutationFn: (input: ChannelDeleteInput) =>
180
+ mutationFn: (input: { id: string }) =>
229
181
  deleteChannelApi({ data: input }).then((response) => response.data),
230
182
  onSettled: async () => {
231
183
  await queryClient.invalidateQueries({ queryKey: queryKeys.channels });
@@ -233,48 +185,6 @@ export function useDeleteChannelMutation() {
233
185
  });
234
186
  }
235
187
 
236
- export function useDeleteBundleMutation() {
237
- const queryClient = useQueryClient();
238
-
239
- return useMutation({
240
- mutationFn: (params: { bundleId: string }) =>
241
- deleteBundleApi({ data: params }),
242
- onSuccess: (_, vars) => {
243
- queryClient.removeQueries({ queryKey: queryKeys.bundle(vars.bundleId) });
244
- queryClient.setQueriesData(
245
- { queryKey: queryKeys.bundles.all },
246
- (data: BundlesQueryData | undefined) =>
247
- removeBundleFromQueryData(data, vars.bundleId),
248
- );
249
-
250
- invalidateInBackground(queryClient, queryKeys.bundles.all);
251
- invalidateInBackground(queryClient, queryKeys.bundleChildren.all);
252
- },
253
- });
254
- }
255
-
256
- export function useDeleteBundlesMutation() {
257
- const queryClient = useQueryClient();
258
-
259
- return useMutation({
260
- mutationFn: (params: { bundleIds: string[] }) =>
261
- deleteBundlesApi({ data: params }),
262
- onSuccess: (_, vars) => {
263
- for (const bundleId of vars.bundleIds) {
264
- queryClient.removeQueries({ queryKey: queryKeys.bundle(bundleId) });
265
- }
266
- queryClient.setQueriesData(
267
- { queryKey: queryKeys.bundles.all },
268
- (data: BundlesQueryData | undefined) =>
269
- removeBundlesFromQueryData(data, vars.bundleIds),
270
- );
271
-
272
- invalidateInBackground(queryClient, queryKeys.bundles.all);
273
- invalidateInBackground(queryClient, queryKeys.bundleChildren.all);
274
- },
275
- });
276
- }
277
-
278
188
  export function useUpdateReleaseMutation() {
279
189
  const queryClient = useQueryClient();
280
190
  return useMutation({
@@ -315,10 +225,17 @@ export function useDeleteReleaseMutation() {
315
225
  queryClient.removeQueries({
316
226
  queryKey: queryKeys.release(input.releaseId),
317
227
  });
228
+ // Core deletes the artifact with its last release, and the patches
229
+ // built on it, so the bundle reads can change too.
318
230
  await Promise.all([
319
231
  queryClient.invalidateQueries({ queryKey: queryKeys.releases.all }),
320
232
  queryClient.invalidateQueries({ queryKey: ["release-catalog"] }),
321
233
  queryClient.invalidateQueries({ queryKey: queryKeys.channels }),
234
+ queryClient.invalidateQueries({ queryKey: queryKeys.bundles.all }),
235
+ queryClient.invalidateQueries({ queryKey: ["bundle"] }),
236
+ queryClient.invalidateQueries({
237
+ queryKey: queryKeys.bundleChildren.all,
238
+ }),
322
239
  ]);
323
240
  },
324
241
  });
@@ -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
+ }
@@ -0,0 +1,20 @@
1
+ import { createMiddleware } from "@tanstack/react-start";
2
+
3
+ /**
4
+ * Refuses a caller the console does not authorize before a server function
5
+ * reads its input. TanStack Start runs a function's middleware before the
6
+ * function's own validator, so an anonymous or unlisted caller gets the access
7
+ * check's 401 or 403, never an input error, whatever it sends. Every server
8
+ * function except the sign-in reads in `auth-rpc.ts` uses it, and
9
+ * `console-access.spec.ts` checks that.
10
+ */
11
+ export const consoleAccess = createMiddleware({ type: "function" }).server(
12
+ async ({ next }) => {
13
+ const [{ getRequest }, { requireConsoleAccess }] = await Promise.all([
14
+ import("@tanstack/react-start/server"),
15
+ import("./server/auth.server"),
16
+ ]);
17
+ await requireConsoleAccess(getRequest());
18
+ return next();
19
+ },
20
+ );
@@ -0,0 +1,68 @@
1
+ import {
2
+ type QueryClient,
3
+ queryOptions,
4
+ useQuery,
5
+ } from "@tanstack/react-query";
6
+ import { notFound } from "@tanstack/react-router";
7
+
8
+ import { type ConsoleFeature, consoleFeatures } from "./console-features";
9
+ import { getConsoleFeaturesRpc } from "./console-features-rpc";
10
+
11
+ export const consoleFeaturesQueryKey = ["console", "features"] as const;
12
+
13
+ export const consoleFeaturesQueryOptions = () =>
14
+ queryOptions({
15
+ queryKey: consoleFeaturesQueryKey,
16
+ queryFn: () => getConsoleFeaturesRpc(),
17
+ // A server's plugins change only when it restarts: one read serves a visit.
18
+ staleTime: Infinity,
19
+ });
20
+
21
+ /** The features the console serves; the authorized layout loads them first. */
22
+ export const useConsoleFeatures = () => useQuery(consoleFeaturesQueryOptions());
23
+
24
+ /** Whether `feature` is on; false until the features load. */
25
+ export const useConsoleFeature = (feature: ConsoleFeature): boolean =>
26
+ useConsoleFeatures().data?.features[feature] === true;
27
+
28
+ /** A feature route's not-found data: the feature its guard found off. */
29
+ type ConsoleFeatureNotFoundData = { readonly feature: ConsoleFeature };
30
+
31
+ /** The feature in a route's not-found data, when a feature guard threw it. */
32
+ export const notFoundFeature = (data: unknown): ConsoleFeature | undefined => {
33
+ const feature =
34
+ typeof data === "object" && data !== null
35
+ ? Reflect.get(data, "feature")
36
+ : undefined;
37
+ return typeof feature === "string" && Object.hasOwn(consoleFeatures, feature)
38
+ ? (feature as ConsoleFeature)
39
+ : undefined;
40
+ };
41
+
42
+ /**
43
+ * A feature route's `beforeLoad`: not found, naming the feature, when the
44
+ * console does not serve it. A failed features read lets the route load: the
45
+ * root layout answers a visitor it does not authorize, and the route's own
46
+ * reads report any other failure. The failure leaves the cache, which a
47
+ * server render sends to the browser: a refused visitor's failure is the 401
48
+ * or 403 Response, which the render cannot serialize.
49
+ */
50
+ export const requireConsoleFeature = async (
51
+ queryClient: QueryClient,
52
+ feature: ConsoleFeature,
53
+ ): Promise<void> => {
54
+ const featureSet = await queryClient
55
+ .ensureQueryData(consoleFeaturesQueryOptions())
56
+ .catch(() => {
57
+ queryClient.removeQueries({
58
+ queryKey: consoleFeaturesQueryKey,
59
+ exact: true,
60
+ });
61
+ return undefined;
62
+ });
63
+ if (featureSet !== undefined && !featureSet.features[feature]) {
64
+ throw notFound({
65
+ data: { feature } satisfies ConsoleFeatureNotFoundData,
66
+ });
67
+ }
68
+ };
@@ -0,0 +1,13 @@
1
+ import { createServerFn } from "@tanstack/react-start";
2
+
3
+ import { consoleAccess } from "./console-access";
4
+ import type { ConsoleFeatureSet } from "./console-features";
5
+
6
+ /** The features this console serves, for navigation and route guards. */
7
+ export const getConsoleFeaturesRpc = createServerFn({ method: "GET" })
8
+ .middleware([consoleAccess])
9
+ .handler(async (): Promise<ConsoleFeatureSet> => {
10
+ const { prepareConfig } = await import("./server/config.server");
11
+ const { runtime } = await prepareConfig();
12
+ return { features: await runtime.features(), remote: runtime.remote };
13
+ });
@@ -0,0 +1,91 @@
1
+ /**
2
+ * The console's features and the server plugin each one needs. The console
3
+ * serves a feature only when its config lists the plugin, as the server runs
4
+ * it, and against a self-hosted server (`standaloneRepository`) only the
5
+ * features its admin API serves (`remote`); the rest read the database.
6
+ * Navigation, route guards, and server functions all read this registry. Its
7
+ * other halves, keyed by the same features, hold what serves each feature on
8
+ * the server (`server/console-feature-apis.ts`) and what each adds to pages
9
+ * (`components/features/console-feature-ui.tsx`).
10
+ */
11
+ export const consoleFeatures = {
12
+ /** Event history and installation lookups, which the admin API serves too. */
13
+ insights: { plugin: "insights", label: "Insights", remote: true },
14
+ /** App usage, distribution, and release activity, read from the database. */
15
+ insightsAnalytics: { plugin: "insights", label: "Insights", remote: false },
16
+ /** API key management: `apiKeys()` mounts no admin routes to manage them. */
17
+ apiKeys: { plugin: "apiKeys", label: "API keys", remote: false },
18
+ } as const satisfies Readonly<
19
+ Record<
20
+ string,
21
+ {
22
+ readonly plugin: string;
23
+ /** How navigation and the not-installed page name the feature. */
24
+ readonly label: string;
25
+ readonly remote: boolean;
26
+ }
27
+ >
28
+ >;
29
+
30
+ export type ConsoleFeature = keyof typeof consoleFeatures;
31
+
32
+ /** Whether each feature is on. */
33
+ export type ConsoleFeatures = Readonly<Record<ConsoleFeature, boolean>>;
34
+
35
+ /** What the console serves, as its client reads it. */
36
+ export type ConsoleFeatureSet = {
37
+ readonly features: ConsoleFeatures;
38
+ /** Whether the console reaches a self-hosted server through its admin API. */
39
+ readonly remote: boolean;
40
+ };
41
+
42
+ const featureIds = Object.keys(consoleFeatures) as ConsoleFeature[];
43
+
44
+ /** The features on for the plugins the console's config lists, from where the console reads them. */
45
+ export const resolveConsoleFeatures = (
46
+ plugins: readonly string[],
47
+ { remote }: { readonly remote: boolean },
48
+ ): ConsoleFeatures => {
49
+ const installed = new Set(plugins);
50
+ return Object.fromEntries(
51
+ featureIds.map((feature) => {
52
+ const { plugin, remote: servedRemotely } = consoleFeatures[feature];
53
+ return [feature, installed.has(plugin) && (servedRemotely || !remote)];
54
+ }),
55
+ ) as Record<ConsoleFeature, boolean>;
56
+ };
57
+
58
+ /** Why a feature is off: its plugin is missing, or it reads a database the console does not open. */
59
+ export const unavailableReason = (
60
+ feature: ConsoleFeature,
61
+ { remote }: { readonly remote: boolean },
62
+ ): "plugin" | "remote" =>
63
+ remote && !consoleFeatures[feature].remote ? "remote" : "plugin";
64
+
65
+ /**
66
+ * A server function refused a feature the console does not serve: the
67
+ * features' 404. The client recognizes it by `name`, which survives
68
+ * serialization where the class does not.
69
+ */
70
+ export class ConsoleFeatureUnavailableError extends Error {
71
+ override readonly name = "ConsoleFeatureUnavailableError";
72
+ readonly status = 404;
73
+
74
+ constructor(
75
+ readonly feature: ConsoleFeature,
76
+ { remote }: { readonly remote: boolean },
77
+ ) {
78
+ super(
79
+ unavailableReason(feature, { remote }) === "remote"
80
+ ? "The console reads this from the database, and it reaches a self-hosted server through its admin API instead."
81
+ : `The server runs without the ${consoleFeatures[feature].plugin}() plugin.`,
82
+ );
83
+ }
84
+ }
85
+
86
+ export const isConsoleFeatureUnavailableError = (
87
+ error: unknown,
88
+ ): error is Error & { readonly feature: ConsoleFeature } =>
89
+ error instanceof Error &&
90
+ error.name === "ConsoleFeatureUnavailableError" &&
91
+ featureIds.includes(Reflect.get(error, "feature") as ConsoleFeature);
@@ -1,9 +1,19 @@
1
- import type { InsightsEventPageInput } from "@hot-updater/server";
1
+ import type { InsightsEventPageInput } from "@hot-updater/server/plugins/insights";
2
2
  import { useQuery } from "@tanstack/react-query";
3
3
 
4
+ import {
5
+ readUpdateFailuresInput,
6
+ type UpdateFailuresInput,
7
+ } from "./insights-failures";
8
+ import { getUpdateFailuresRpc } from "./insights-failures-rpc";
9
+ import {
10
+ DEFAULT_INSIGHTS_RETENTION,
11
+ type InsightsRetentionDays,
12
+ } from "./insights-retention";
4
13
  import {
5
14
  findInsightsInstallationsRpc,
6
15
  getInsightsInstallationRpc,
16
+ getInsightsRetentionRpc,
7
17
  getReportingInstallationsRpc,
8
18
  listInsightsEventsRpc,
9
19
  listInsightsInstallationEventsRpc,
@@ -98,3 +108,29 @@ export const useInsightsInstallationEventsQuery = (
98
108
  refetchOnWindowFocus: true,
99
109
  staleTime: STALE_TIME_MS,
100
110
  });
111
+
112
+ /** A release's or a channel's update failures; lifetime for a release without a window. */
113
+ export const useUpdateFailuresQuery = (
114
+ input: UpdateFailuresInput,
115
+ enabled = true,
116
+ ) =>
117
+ useQuery({
118
+ queryKey: ["insights", "update-failures", input],
119
+ queryFn: () =>
120
+ getUpdateFailuresRpc({ data: readUpdateFailuresInput(input) }),
121
+ enabled,
122
+ refetchOnWindowFocus: true,
123
+ staleTime: STALE_TIME_MS,
124
+ });
125
+
126
+ /**
127
+ * How long the server keeps Insights rows; the defaults until it answers, or
128
+ * when it does not report them.
129
+ */
130
+ export const useInsightsRetention = (): InsightsRetentionDays =>
131
+ useQuery({
132
+ queryKey: ["insights", "retention"],
133
+ queryFn: () => getInsightsRetentionRpc(),
134
+ // A server's periods change only when it restarts.
135
+ staleTime: Infinity,
136
+ }).data ?? DEFAULT_INSIGHTS_RETENTION;
@@ -0,0 +1,21 @@
1
+ import { createServerFn } from "@tanstack/react-start";
2
+
3
+ import { consoleAccess } from "./console-access";
4
+ import { readUpdateFailuresInput } from "./insights-failures";
5
+
6
+ export const getUpdateFailuresRpc = createServerFn({ method: "GET" })
7
+ .middleware([consoleAccess])
8
+ .validator(readUpdateFailuresInput)
9
+ .handler(async ({ data }) => {
10
+ const [{ prepareConfig }, { getUpdateFailuresReport }, { requireFeature }] =
11
+ await Promise.all([
12
+ import("./server/config.server"),
13
+ import("./server/updateFailures"),
14
+ import("./server/runtime.server"),
15
+ ]);
16
+ const { runtime } = await prepareConfig();
17
+ return getUpdateFailuresReport(
18
+ await requireFeature(runtime, "insights"),
19
+ data,
20
+ );
21
+ });
@@ -0,0 +1,129 @@
1
+ import type {
2
+ InsightsFailureBreakdown,
3
+ InsightsUpdateFailures,
4
+ } from "@hot-updater/server/plugins/insights";
5
+
6
+ import { recoveryWindows } from "./insights-recovery";
7
+ import type { InsightsWindow } from "./insights-rpc";
8
+ import type { InsightsEventRow } from "./insights-view";
9
+
10
+ export type { InsightsFailureBreakdown };
11
+
12
+ export type UpdateFailuresInput = {
13
+ readonly platform: "ios" | "android";
14
+ readonly channel: string;
15
+ /** A release's failures; the channel's without it. */
16
+ readonly releaseId?: string;
17
+ /** The period that ends with the current hour; a release's since its first report without it. */
18
+ readonly window?: InsightsWindow;
19
+ };
20
+
21
+ export type UpdateFailuresReport = InsightsUpdateFailures & {
22
+ /** Where the period starts, or null since the release's first report. */
23
+ readonly startMs: number | null;
24
+ readonly endMs: number;
25
+ };
26
+
27
+ export function readUpdateFailuresInput(
28
+ input: UpdateFailuresInput,
29
+ ): UpdateFailuresInput {
30
+ if (
31
+ !input ||
32
+ (input.platform !== "ios" && input.platform !== "android") ||
33
+ typeof input.channel !== "string" ||
34
+ !input.channel.trim() ||
35
+ input.channel.length > 1_024 ||
36
+ (input.releaseId !== undefined &&
37
+ (typeof input.releaseId !== "string" ||
38
+ !input.releaseId.trim() ||
39
+ input.releaseId.length > 36)) ||
40
+ (input.window === undefined
41
+ ? input.releaseId === undefined
42
+ : !Object.hasOwn(recoveryWindows, input.window))
43
+ ) {
44
+ throw new Error("Choose a platform, channel, and time window.");
45
+ }
46
+ return input;
47
+ }
48
+
49
+ /**
50
+ * The failure rate of update attempts: failed download and install reports,
51
+ * over those plus download reports. Null without either.
52
+ */
53
+ export const failureRate = ({
54
+ failedUpdates,
55
+ downloads,
56
+ }: Pick<InsightsUpdateFailures, "failedUpdates" | "downloads">):
57
+ | number
58
+ | null =>
59
+ failedUpdates + downloads === 0
60
+ ? null
61
+ : failedUpdates / (failedUpdates + downloads);
62
+
63
+ /** Of the downloads that tried a patch, the share that fell back to files or the archive. */
64
+ export const patchFallbackRate = ({
65
+ patchDownloads,
66
+ patchFallbacks,
67
+ }: Pick<InsightsUpdateFailures, "patchDownloads" | "patchFallbacks">):
68
+ | number
69
+ | null =>
70
+ patchDownloads + patchFallbacks === 0
71
+ ? null
72
+ : patchFallbacks / (patchDownloads + patchFallbacks);
73
+
74
+ /** Installations whose update check failed, over the active ones; estimates, so at most 100%. */
75
+ export const checkFailureRate = ({
76
+ failedInstallations,
77
+ activeInstallations,
78
+ }: NonNullable<InsightsUpdateFailures["checks"]>): number | null =>
79
+ activeInstallations === 0
80
+ ? null
81
+ : Math.min(1, failedInstallations / activeInstallations);
82
+
83
+ export const formatRate = (rate: number | null): string =>
84
+ rate === null ? "—" : `${(rate * 100).toFixed(2)}%`;
85
+
86
+ const STAGES: Readonly<Record<string, string>> = {
87
+ check: "Update check",
88
+ download: "Download",
89
+ install: "Install",
90
+ };
91
+
92
+ const REASONS: Readonly<Record<string, string>> = {
93
+ network: "Network",
94
+ http: "HTTP error",
95
+ invalid_response: "Invalid response",
96
+ hash_mismatch: "Hash mismatch",
97
+ signature: "Signature",
98
+ patch: "Patch",
99
+ extract: "Extract",
100
+ storage: "Storage",
101
+ };
102
+
103
+ export const failureStageLabel = (stage: string): string =>
104
+ STAGES[stage] ?? "Unknown stage";
105
+
106
+ export const failureReasonLabel = (reason: string): string =>
107
+ REASONS[reason] ?? "Unknown reason";
108
+
109
+ /** What else the client knew: its resource, HTTP status, origin code, and transport. */
110
+ export const failureDetailParts = (detail: {
111
+ readonly resource?: string | null;
112
+ readonly httpStatus?: number | null;
113
+ readonly originCode?: string | null;
114
+ readonly transport?: string | null;
115
+ }): string[] => [
116
+ ...(detail.httpStatus == null ? [] : [`HTTP ${detail.httpStatus}`]),
117
+ ...(detail.originCode == null ? [] : [detail.originCode]),
118
+ ...(detail.transport == null ? [] : [detail.transport]),
119
+ ...(detail.resource == null ? [] : [detail.resource]),
120
+ ];
121
+
122
+ /** An update failure as one line: where, why, and what else the client knew. */
123
+ export const describeFailure = (
124
+ failure: NonNullable<InsightsEventRow["failure"]>,
125
+ ): string =>
126
+ [
127
+ `${failureStageLabel(failure.stage)} failed: ${failureReasonLabel(failure.reason)}`,
128
+ ...failureDetailParts(failure),
129
+ ].join(" · ");
@@ -0,0 +1,51 @@
1
+ import { createServerFn } from "@tanstack/react-start";
2
+
3
+ import type { BundleActivityInput } from "./bundle-activity";
4
+ import { consoleAccess } from "./console-access";
5
+ import { readRecoveryInput } from "./insights-recovery";
6
+
7
+ export function readBundleActivityInput(
8
+ inputs: readonly BundleActivityInput[],
9
+ ) {
10
+ if (!Array.isArray(inputs) || inputs.length > 20)
11
+ throw new Error("Choose up to 20 bundles.");
12
+ return inputs.map((input) => {
13
+ readRecoveryInput({ ...input, window: "30d" });
14
+ if (!input.releaseId) throw new Error("Choose a bundle ID.");
15
+ return input;
16
+ });
17
+ }
18
+
19
+ export const getBundleActivityRpc = createServerFn({ method: "GET" })
20
+ .middleware([consoleAccess])
21
+ .validator(readBundleActivityInput)
22
+ .handler(async ({ data }) => {
23
+ const [{ prepareConfig }, { getBundleActivity }, { requireFeature }] =
24
+ await Promise.all([
25
+ import("./server/config.server"),
26
+ import("./server/bundleActivity"),
27
+ import("./server/runtime.server"),
28
+ ]);
29
+ const { runtime } = await prepareConfig();
30
+ return getBundleActivity(
31
+ await requireFeature(runtime, "insightsAnalytics"),
32
+ data,
33
+ );
34
+ });
35
+
36
+ export const getRecoveryReportRpc = createServerFn({ method: "GET" })
37
+ .middleware([consoleAccess])
38
+ .validator(readRecoveryInput)
39
+ .handler(async ({ data }) => {
40
+ const [{ prepareConfig }, { getRecoveryReport }, { requireFeature }] =
41
+ await Promise.all([
42
+ import("./server/config.server"),
43
+ import("./server/insightsRecovery"),
44
+ import("./server/runtime.server"),
45
+ ]);
46
+ const { runtime } = await prepareConfig();
47
+ return getRecoveryReport(
48
+ await requireFeature(runtime, "insightsAnalytics"),
49
+ data,
50
+ );
51
+ });