@hot-updater/console 1.0.0-rc.4 → 1.0.0-rc.41

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 (330) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/ConsoleFeatureUnavailable-DRzeMGRM.js +1 -0
  3. package/.output/public/assets/EventDetails-SB5ygJzS.js +1 -0
  4. package/.output/public/assets/HashValueDisplay-BFX_kY3-.js +1 -0
  5. package/.output/public/assets/InsightsInfo-btYppp0B.js +1 -0
  6. package/.output/public/assets/InsightsPageHeader-Ba8psD8t.js +1 -0
  7. package/.output/public/assets/NotFoundPage-CyRG2eca.js +1 -0
  8. package/.output/public/assets/alert-dialog-Bbk_4-WJ.js +1 -0
  9. package/.output/public/assets/api-DUgI6yOI.js +1 -0
  10. package/.output/public/assets/api-keys-CIO9L_wu.js +1 -0
  11. package/.output/public/assets/api-keys-CmkNKOhS.js +1 -0
  12. package/.output/public/assets/arrow-left-CnMYpqB7.js +1 -0
  13. package/.output/public/assets/arrow-up-right-BtffZA6e.js +1 -0
  14. package/.output/public/assets/chevron-right-BqD5wtFb.js +1 -0
  15. package/.output/public/assets/clipboard-DS9ucUQL.js +1 -0
  16. package/.output/public/assets/console-features-api-BMsI3Bj8.js +1 -0
  17. package/.output/public/assets/dialog-Bqbb7B0N.js +1 -0
  18. package/.output/public/assets/dist-LxKJB-Vu.js +1 -0
  19. package/.output/public/assets/empty-CuT2UhFp.js +1 -0
  20. package/.output/public/assets/field-BBDpjIqu.js +1 -0
  21. package/.output/public/assets/index-BEfQ2_7f.js +11 -0
  22. package/.output/public/assets/input-group-ja_30qaF.js +1 -0
  23. package/.output/public/assets/insights-B0ib9qOE.js +2 -0
  24. package/.output/public/assets/insights-BIMJx-0p.js +1 -0
  25. package/.output/public/assets/insights-CndqcWEj.js +42 -0
  26. package/.output/public/assets/insights-api-B6Ewc-OG.js +1 -0
  27. package/.output/public/assets/insights-usage-rpc-DYEhsCKT.js +1 -0
  28. package/.output/public/assets/insights_.distribution-B26PMy4k.js +2 -0
  29. package/.output/public/assets/insights_.distribution-BBDtqqYS.js +1 -0
  30. package/.output/public/assets/insights_.distribution-DhHmYS8k.js +1 -0
  31. package/.output/public/assets/installations-C3cMuwRx.js +1 -0
  32. package/.output/public/assets/installations-C3yrjwDh.js +2 -0
  33. package/.output/public/assets/installations-D3edlN75.js +1 -0
  34. package/.output/public/assets/key-round-wS_SIZ6b.js +1 -0
  35. package/.output/public/assets/list-CgrYiadM.js +1 -0
  36. package/.output/public/assets/refresh-cw-BYgoNCco.js +1 -0
  37. package/.output/public/assets/remote-config-0i-gDkeU.js +1 -0
  38. package/.output/public/assets/remote-config-An1ROIOQ.js +6 -0
  39. package/.output/public/assets/remote-config-CoeWgA9i.js +1 -0
  40. package/.output/public/assets/routes-DNoaJIQV.js +2 -0
  41. package/.output/public/assets/routes-cWBDtghZ.js +1 -0
  42. package/.output/public/assets/search-DaTDPW25.js +1 -0
  43. package/.output/public/assets/select-CZMQKT7F.js +1 -0
  44. package/.output/public/assets/sidebar-DNVyJPfX.js +3 -0
  45. package/.output/public/assets/smartphone-DpTFgtKf.js +1 -0
  46. package/.output/public/assets/styles-D8mxMvCB.css +2 -0
  47. package/.output/public/assets/switch-CU4-fupc.js +1 -0
  48. package/.output/public/assets/table-Gm4qRWwE.js +1 -0
  49. package/.output/public/assets/tabs-R1I8CRjR.js +1 -0
  50. package/.output/public/assets/undo-2-CmrHjwFc.js +1 -0
  51. package/.output/public/assets/useCompositeListItem-BaPeC9ZO.js +1 -0
  52. package/.output/public/assets/valueToPercent-CH8GCHaL.js +1 -0
  53. package/.output/server/__23tanstack-start-server-fn-resolver-DTAqlLRd.mjs +178 -0
  54. package/.output/server/_libs/@base-ui/react+[...].mjs +6450 -2741
  55. package/.output/server/_libs/@tanstack/react-router+[...].mjs +3802 -4022
  56. package/.output/server/_libs/@tanstack/react-router-ssr-query+[...].mjs +140 -97
  57. package/.output/server/_libs/lucide-react.mjs +235 -31
  58. package/.output/server/_libs/next-themes.mjs +1 -1
  59. package/.output/server/_libs/react-redux+[...].mjs +1 -1
  60. package/.output/server/_libs/recharts+[...].mjs +483 -901
  61. package/.output/server/_libs/sonner.mjs +1 -1
  62. package/.output/server/_libs/tanstack__history.mjs +68 -41
  63. package/.output/server/_libs/tanstack__query-core.mjs +2010 -309
  64. package/.output/server/_libs/tanstack__react-query.mjs +448 -21
  65. package/.output/server/_libs/verkit.mjs +102 -1
  66. package/.output/server/_ssr/BundleIdDisplay-BNoVW3Dv.mjs +13 -0
  67. package/.output/server/_ssr/ConsoleFeatureUnavailable-BPFa-cmJ.mjs +58 -0
  68. package/.output/server/_ssr/EventDetails-9CeshfdR.mjs +344 -0
  69. package/.output/server/_ssr/HashValueDisplay-DXZlkcps.mjs +55 -0
  70. package/.output/server/_ssr/InsightsInfo-DLNdJQ0m.mjs +75 -0
  71. package/.output/server/_ssr/InsightsPageHeader-DZGVDgvv.mjs +145 -0
  72. package/.output/server/_ssr/NotFoundPage-mYHHg-2s.mjs +37 -0
  73. package/.output/server/_ssr/{dialog-DUaIgpgd.mjs → alert-dialog-Chkim7vZ.mjs} +6 -83
  74. package/.output/server/_ssr/api-C-Il5-sa.mjs +203 -0
  75. package/.output/server/_ssr/api-keys-01FGqj4d.mjs +5 -0
  76. package/.output/server/_ssr/{api-keys-DJml_XoK.mjs → api-keys-B_f9h7ln.mjs} +48 -10
  77. package/.output/server/_ssr/api-keys-rpc-Cf4YCyFc.mjs +52 -0
  78. package/.output/server/_ssr/api-rpc-DRqwxxT_.mjs +296 -0
  79. package/.output/server/_ssr/{auth-BF8mbAgF.mjs → auth-UP0wXs4i.mjs} +1 -1
  80. package/.output/server/_ssr/{auth-rpc-D_hv52-J.mjs → auth-rpc-D8nFpU9C.mjs} +6 -6
  81. package/.output/server/_ssr/auth.server-COZoXu4i.mjs +5425 -0
  82. package/.output/server/_ssr/bundleActivity-M5gzBFNg.mjs +18 -0
  83. package/.output/server/_ssr/config-WVX4IxSR.mjs +15 -0
  84. package/.output/server/_ssr/config.server-Dm2l7w0O.mjs +36 -0
  85. package/.output/server/_ssr/console-access--o9FXikU.mjs +17 -0
  86. package/.output/server/_ssr/console-features-BftSkf3f.mjs +60 -0
  87. package/.output/server/_ssr/console-features-api-CYclUQ4h.mjs +43 -0
  88. package/.output/server/_ssr/console-features-rpc-1WaAkPM_.mjs +19 -0
  89. package/.output/server/_ssr/{createServerFn-B2hcXoPM.mjs → createMiddleware-Cm6Tn0_Y.mjs} +67 -56
  90. package/.output/server/_ssr/{createServerRpc-DB29C_5p.mjs → createServerRpc-RLR4H-Fn.mjs} +2 -2
  91. package/.output/server/_ssr/databaseConfig-C5SkAyUy.mjs +5 -0
  92. package/.output/server/_ssr/{databaseJsonValue-d6I3nAS4.mjs → databaseJsonValue-Csbuzc9Q.mjs} +28 -5
  93. package/.output/server/_ssr/databaseRows-BUuNU-qt.mjs +74 -0
  94. package/.output/server/_ssr/dialog-mfSANqjC.mjs +84 -0
  95. package/.output/server/_ssr/{dist-CyzPaI5v.mjs → dist-wmnTk7w6.mjs} +140 -6
  96. package/.output/server/_ssr/{downloadBundle-B3ert-pY.mjs → downloadBundle-DViuIa9U.mjs} +10 -10
  97. package/.output/server/_ssr/empty-BPayFad3.mjs +60 -0
  98. package/.output/server/_ssr/{empty-plugin-adapters-B5QvGpEl.mjs → empty-plugin-adapters-CPtwX8a7.mjs} +1 -1
  99. package/.output/server/_ssr/failureReports-DKDhVsXA.mjs +33 -0
  100. package/.output/server/_ssr/field-DjZ5t7rH.mjs +189 -0
  101. package/.output/server/_ssr/getBundleChildren-CmjDg6Re.mjs +22 -0
  102. package/.output/server/_ssr/{input-group-Bb8ueylQ.mjs → input-group-Dx-MEkNM.mjs} +3 -52
  103. package/.output/server/_ssr/insights-BopxyJjk.mjs +14 -0
  104. package/.output/server/_ssr/insights-Bvm03d61.mjs +5 -0
  105. package/.output/server/_ssr/insights-CrLAHSDy.mjs +2315 -0
  106. package/.output/server/_ssr/insights-api-Bg8ZV_h_.mjs +122 -0
  107. package/.output/server/_ssr/insights-errors-BjnFUyZj.mjs +34 -0
  108. package/.output/server/_ssr/insights-failures-rpc-tyy5m9Cc.mjs +36 -0
  109. package/.output/server/_ssr/insights-failures-xaa41Buf.mjs +46 -0
  110. package/.output/server/_ssr/{insights-recovery-DSFM-YTL.mjs → insights-recovery-BWQhiZaF.mjs} +9 -2
  111. package/.output/server/_ssr/insights-recovery-rpc-CPJOiJAV.mjs +32 -0
  112. package/.output/server/_ssr/{insights-rpc-DrIQCjpD.mjs → insights-rpc-DoC3hla9.mjs} +22 -14
  113. package/.output/server/_ssr/insights-usage-BpBM0U0c.mjs +29 -0
  114. package/.output/server/_ssr/insights-usage-rpc-CFuTYp3T.mjs +29 -0
  115. package/.output/server/_ssr/insights-usage-rpc-O2ruP4iE.mjs +41 -0
  116. package/.output/server/_ssr/insightsUsage-CzhVjsc8.mjs +56 -0
  117. package/.output/server/_ssr/insights_.distribution-B3BVPD00.mjs +16 -0
  118. package/.output/server/_ssr/insights_.distribution-BMoisxRG.mjs +347 -0
  119. package/.output/server/_ssr/insights_.distribution-BiCcFyrG.mjs +7 -0
  120. package/.output/server/_ssr/{installations-YumqpNpa.mjs → installations-Cx3jIrwl.mjs} +280 -364
  121. package/.output/server/_ssr/installations-DTGPaKXx.mjs +69 -0
  122. package/.output/server/_ssr/installations-osiAuNKr.mjs +5 -0
  123. package/.output/server/_ssr/listReleases-BfUBlCHo.mjs +79 -0
  124. package/.output/server/_ssr/release-adoption-005tQRpo.mjs +55 -0
  125. package/.output/server/_ssr/release-adoption-rpc-BVySDR0L.mjs +41 -0
  126. package/.output/server/_ssr/releaseAdoption-2PJPA09N.mjs +114 -0
  127. package/.output/server/_ssr/releaseManagement-BTCWEY8-.mjs +11 -0
  128. package/.output/server/_ssr/remote-config-CUoAxUMy.mjs +430 -0
  129. package/.output/server/_ssr/remote-config-Cc5P4J-r.mjs +17 -0
  130. package/.output/server/_ssr/remote-config-CjdxcZ9B.mjs +5 -0
  131. package/.output/server/_ssr/remote-config-D2GmbGwI.mjs +2594 -0
  132. package/.output/server/_ssr/remote-config-rpc-kjoZ9PRS.mjs +115 -0
  133. package/.output/server/_ssr/rolldown-runtime-rSIU-vHC.mjs +13 -0
  134. package/.output/server/_ssr/{router-iP95Vqi2.mjs → router-DwGVewL-.mjs} +141 -139
  135. package/.output/server/_ssr/{routes-ivsV2TDu.mjs → routes-C4yxv3JU.mjs} +192 -685
  136. package/.output/server/_ssr/routes-D3_aegLY.mjs +409 -0
  137. package/.output/server/_ssr/runtime.server-BoWf4QhU.mjs +1716 -0
  138. package/.output/server/_ssr/schema-DrOtkRVD.mjs +361 -0
  139. package/.output/server/_ssr/select-DkzM2miy.mjs +107 -0
  140. package/.output/server/_ssr/server-BW0gzB0p.mjs +8325 -0
  141. package/.output/server/_ssr/{createSsrRpc-NHF2PxuK.mjs → sidebar-DPcOijvL.mjs} +107 -47
  142. package/.output/server/_ssr/ssr.mjs +1 -1
  143. package/.output/server/_ssr/start-BfFbHKMK.mjs +4 -0
  144. package/.output/server/_ssr/switch-BqJ2mAwC.mjs +17 -0
  145. package/.output/server/_ssr/{table-D1mL8fJu.mjs → table-R7RY92q9.mjs} +2 -2
  146. package/.output/server/_ssr/tabs-trDcbRkT.mjs +45 -0
  147. package/.output/server/_ssr/updateFailures-H6pnnYK4.mjs +40 -0
  148. package/.output/server/_ssr/{uuidv7-D0QFkPL_.mjs → uuidv7-ctA_XVZA.mjs} +2 -2
  149. package/.output/server/_tanstack-start-manifest_v-BwInaUTw.mjs +139 -0
  150. package/.output/server/index.mjs +315 -154
  151. package/LICENSE +13 -14
  152. package/README.md +82 -72
  153. package/lib/index.d.mts +23 -11
  154. package/lib/vite-internal.mjs +13 -3
  155. package/lib/vite.d.mts +2 -0
  156. package/package.json +14 -14
  157. package/src/components/AppSidebar.tsx +100 -71
  158. package/src/components/ConsoleAccessPage.tsx +5 -1
  159. package/src/components/ConsoleFeatureUnavailable.tsx +80 -0
  160. package/src/components/NotFoundPage.tsx +7 -7
  161. package/src/components/features/FeatureSlots.tsx +103 -0
  162. package/src/components/features/api-keys/apiKeysFeatureUi.tsx +15 -0
  163. package/src/components/features/bundles/BundleMetadata.tsx +8 -5
  164. package/src/components/features/bundles/RolloutCohortsDialog.tsx +1 -1
  165. package/src/components/features/channels/ChannelManagementDialog.tsx +2 -5
  166. package/src/components/features/insights/AppUsage.tsx +366 -0
  167. package/src/components/features/insights/BuiltinBundle.tsx +36 -0
  168. package/src/components/features/insights/ComparedBundlesChart.tsx +310 -0
  169. package/src/components/features/insights/EstimatedCount.tsx +18 -0
  170. package/src/components/features/insights/EventDetails.tsx +400 -39
  171. package/src/components/features/insights/EventHistoryCard.tsx +112 -26
  172. package/src/components/features/insights/EventHistoryList.tsx +2 -2
  173. package/src/components/features/insights/FailureReports.tsx +407 -0
  174. package/src/components/features/insights/InsightsControls.tsx +265 -88
  175. package/src/components/features/insights/InsightsErrorAlert.tsx +14 -1
  176. package/src/components/features/insights/InsightsInfo.tsx +48 -0
  177. package/src/components/features/insights/InsightsOverview.tsx +604 -355
  178. package/src/components/features/insights/InsightsPageHeader.tsx +35 -20
  179. package/src/components/features/insights/InsightsPeriodSelector.tsx +89 -0
  180. package/src/components/features/insights/InstallationHistoryCard.tsx +66 -11
  181. package/src/components/features/insights/InstallationMatchesCard.tsx +1 -1
  182. package/src/components/features/insights/ReleaseActivity.tsx +308 -0
  183. package/src/components/features/insights/ReportingDevicesSummary.tsx +48 -0
  184. package/src/components/features/insights/RollbackButton.tsx +118 -0
  185. package/src/components/features/insights/UpdateFailures.tsx +382 -0
  186. package/src/components/features/insights/insightsFeatureUi.tsx +35 -0
  187. package/src/components/features/releases/ReleaseEditorSheet.tsx +26 -12
  188. package/src/components/features/remote-config/ConditionDialog.tsx +581 -0
  189. package/src/components/features/remote-config/ConditionsCard.tsx +244 -0
  190. package/src/components/features/remote-config/ParameterDialog.tsx +407 -0
  191. package/src/components/features/remote-config/ParametersCard.tsx +244 -0
  192. package/src/components/features/remote-config/PreviewCard.tsx +284 -0
  193. package/src/components/features/remote-config/PublishDialog.tsx +319 -0
  194. package/src/components/features/remote-config/RemoteConfigPage.tsx +353 -0
  195. package/src/components/features/remote-config/RuleSummary.tsx +21 -0
  196. package/src/components/features/remote-config/ValueInput.tsx +148 -0
  197. package/src/components/features/remote-config/VersionsCard.tsx +361 -0
  198. package/src/components/features/remote-config/remoteConfigFeatureUi.tsx +15 -0
  199. package/src/components/features/remote-config/useDateTimeText.ts +16 -0
  200. package/src/components/ui/card.tsx +1 -1
  201. package/src/components/ui/scroll-area.tsx +52 -0
  202. package/src/components/ui/sidebar.tsx +22 -29
  203. package/src/components/ui/tabs.tsx +81 -0
  204. package/src/index.ts +26 -19
  205. package/src/lib/api-keys-api.ts +0 -28
  206. package/src/lib/api-keys-rpc.ts +27 -38
  207. package/src/lib/api-rpc.ts +189 -266
  208. package/src/lib/api.ts +19 -102
  209. package/src/lib/bundle-activity.ts +24 -4
  210. package/src/lib/console-access.ts +20 -0
  211. package/src/lib/console-features-api.ts +68 -0
  212. package/src/lib/console-features-rpc.ts +13 -0
  213. package/src/lib/console-features.ts +97 -0
  214. package/src/lib/insights-api.ts +56 -2
  215. package/src/lib/insights-errors.ts +64 -0
  216. package/src/lib/insights-failures-rpc.ts +36 -0
  217. package/src/lib/insights-failures.ts +130 -0
  218. package/src/lib/insights-recovery-rpc.ts +13 -17
  219. package/src/lib/insights-recovery.ts +10 -27
  220. package/src/lib/insights-retention.ts +21 -0
  221. package/src/lib/insights-rpc.ts +30 -12
  222. package/src/lib/insights-search.ts +93 -0
  223. package/src/lib/insights-usage-rpc.ts +29 -0
  224. package/src/lib/insights-usage.ts +76 -0
  225. package/src/lib/insights-view.ts +34 -5
  226. package/src/lib/release-adoption-api.ts +214 -0
  227. package/src/lib/release-adoption-rpc.ts +49 -0
  228. package/src/lib/release-adoption.ts +164 -0
  229. package/src/lib/remote-config-api.ts +119 -0
  230. package/src/lib/remote-config-draft.ts +652 -0
  231. package/src/lib/remote-config-rpc.ts +196 -0
  232. package/src/lib/scroll-restoration.ts +21 -0
  233. package/src/lib/server/adminInsights.ts +124 -0
  234. package/src/lib/server/auth.server.ts +13 -5
  235. package/src/lib/server/bundleActivity.ts +27 -28
  236. package/src/lib/server/config.server.ts +21 -74
  237. package/src/lib/server/console-feature-apis.ts +70 -0
  238. package/src/lib/server/console-runtime.server.ts +82 -38
  239. package/src/lib/server/downloadBundle.ts +16 -15
  240. package/src/lib/server/failureReports.ts +49 -0
  241. package/src/lib/server/getBundleChildren.ts +31 -122
  242. package/src/lib/server/insightsUsage.ts +63 -0
  243. package/src/lib/server/listReleases.ts +109 -44
  244. package/src/lib/server/releaseAdoption.ts +154 -0
  245. package/src/lib/server/releaseReachability.ts +8 -7
  246. package/src/lib/server/remoteConfig.ts +191 -0
  247. package/src/lib/server/runtime.server.ts +120 -31
  248. package/src/lib/server/updateFailures.ts +65 -0
  249. package/src/routeTree.gen.ts +65 -44
  250. package/src/router.tsx +3 -2
  251. package/src/routes/-installations-search.ts +32 -8
  252. package/src/routes/-releases-search.ts +76 -29
  253. package/src/routes/__root.tsx +10 -4
  254. package/src/routes/api/bundles/$bundleId/download.ts +2 -5
  255. package/src/routes/api-keys.tsx +5 -2
  256. package/src/routes/index.tsx +210 -152
  257. package/src/routes/insights.tsx +133 -18
  258. package/src/routes/insights_.distribution.tsx +422 -0
  259. package/src/routes/installations.tsx +98 -41
  260. package/src/routes/remote-config.tsx +42 -0
  261. package/src/styles.css +9 -0
  262. package/src/vite-internal.ts +15 -3
  263. package/src/vite.ts +2 -0
  264. package/.output/public/assets/InsightsPageHeader-C-Xc2MB9.js +0 -1
  265. package/.output/public/assets/api-keys-C1sgC6ko.js +0 -1
  266. package/.output/public/assets/api-keys-api-LchP65vQ.js +0 -1
  267. package/.output/public/assets/badge-BuMDU0Rf.js +0 -1
  268. package/.output/public/assets/card-BEvx_QKh.js +0 -1
  269. package/.output/public/assets/chart-no-axes-combined-C0G0fQ3J.js +0 -1
  270. package/.output/public/assets/composite-CHTH1Xhh.js +0 -1
  271. package/.output/public/assets/createServerFn-DHc2erub.js +0 -3
  272. package/.output/public/assets/dialog-dyZ1SphZ.js +0 -1
  273. package/.output/public/assets/dist-Bh1gaDfB.js +0 -1
  274. package/.output/public/assets/field-79SVH26s.js +0 -1
  275. package/.output/public/assets/index-BNkxg4jz.js +0 -10
  276. package/.output/public/assets/input-group-BbHfaBzQ.js +0 -1
  277. package/.output/public/assets/insights-D_7uSKfc.js +0 -1
  278. package/.output/public/assets/installations-BzoHWuXw.js +0 -1
  279. package/.output/public/assets/installations-DvjbUqd1.js +0 -2
  280. package/.output/public/assets/lazyRouteComponent-BN0MS05o.js +0 -1
  281. package/.output/public/assets/link-BG06whwB.js +0 -1
  282. package/.output/public/assets/loader-circle-Bbp8LGVC.js +0 -1
  283. package/.output/public/assets/preload-helper-uTix4PVD.js +0 -1
  284. package/.output/public/assets/refresh-cw-DZgiAOMk.js +0 -1
  285. package/.output/public/assets/routes-BFE-oRIT.js +0 -1
  286. package/.output/public/assets/routes-DaPMY31P.js +0 -2
  287. package/.output/public/assets/shield-check-DoIiaaXU.js +0 -1
  288. package/.output/public/assets/signing-BTRhaF5D.js +0 -1
  289. package/.output/public/assets/styles-C7U0oVke.css +0 -2
  290. package/.output/public/assets/table-BBEzJ7Xl.js +0 -1
  291. package/.output/public/assets/useCompositeListItem-BkPDaqyj.js +0 -42
  292. package/.output/server/__23tanstack-start-server-fn-resolver-Dr2ixDgQ.mjs +0 -146
  293. package/.output/server/_ssr/InsightsPageHeader-C5Has8Ry.mjs +0 -60
  294. package/.output/server/_ssr/api-keys-api-CrCZyvnl.mjs +0 -60
  295. package/.output/server/_ssr/api-keys-rpc-CFaNf-q0.mjs +0 -136
  296. package/.output/server/_ssr/api-rpc-B61KL3Eo.mjs +0 -1399
  297. package/.output/server/_ssr/auth.server-DF_shCeq.mjs +0 -47
  298. package/.output/server/_ssr/badge-BgsCP9Dp.mjs +0 -28
  299. package/.output/server/_ssr/bundle-signing-rpc-BBipZnFs.mjs +0 -16
  300. package/.output/server/_ssr/bundle-signing.server-Co_zHdJx.mjs +0 -12
  301. package/.output/server/_ssr/bundleActivity-D3_fIda2.mjs +0 -27
  302. package/.output/server/_ssr/card-DrbxCPtG.mjs +0 -86
  303. package/.output/server/_ssr/chart-qxa-_30a.mjs +0 -162
  304. package/.output/server/_ssr/config-Dv8qLDZ0.mjs +0 -13
  305. package/.output/server/_ssr/config.server-CBr6PDj5.mjs +0 -368
  306. package/.output/server/_ssr/databaseRows-5bCRTxm-.mjs +0 -126
  307. package/.output/server/_ssr/deleteBundle-BAq3JuGr.mjs +0 -66
  308. package/.output/server/_ssr/field-CqNUlqUq.mjs +0 -74
  309. package/.output/server/_ssr/getBundleChildren-COMt16m_.mjs +0 -50
  310. package/.output/server/_ssr/insights-B-DUbksX.mjs +0 -502
  311. package/.output/server/_ssr/insights-recovery-rpc-P71FH66n.mjs +0 -37
  312. package/.output/server/_ssr/insightsContract-fjJDBeEE.mjs +0 -303
  313. package/.output/server/_ssr/insightsRecovery-CqcosG7B.mjs +0 -149
  314. package/.output/server/_ssr/installations-BQrppti7.mjs +0 -29
  315. package/.output/server/_ssr/routes-BDabsmc7.mjs +0 -37
  316. package/.output/server/_ssr/runtime.server-jmZOKIQM.mjs +0 -438
  317. package/.output/server/_ssr/server-p7yrVBfD.mjs +0 -7468
  318. package/.output/server/_ssr/signing-CxCbnFCt.mjs +0 -134
  319. package/.output/server/_ssr/start-DpViQGnt.mjs +0 -4
  320. package/.output/server/_tanstack-start-manifest_v-Cwpu0C9Q.mjs +0 -100
  321. package/src/components/features/bundles/BundleActivityChart.tsx +0 -95
  322. package/src/components/features/bundles/BundleInsightsSummary.tsx +0 -121
  323. package/src/components/features/signing/BundleSigningPage.tsx +0 -139
  324. package/src/lib/bundle-signing-api.ts +0 -14
  325. package/src/lib/bundle-signing-rpc.ts +0 -16
  326. package/src/lib/server/bundle-signing.server.ts +0 -23
  327. package/src/lib/server/deleteBundle.ts +0 -145
  328. package/src/lib/server/insightsHistory.ts +0 -52
  329. package/src/lib/server/insightsRecovery.ts +0 -186
  330. package/src/routes/signing.tsx +0 -37
@@ -0,0 +1,154 @@
1
+ import type { HotUpdaterCoreApi, ReleaseRow } from "@hot-updater/plugin-core";
2
+ import type {
3
+ InsightsBundleEventFilter,
4
+ InsightsModel,
5
+ } from "@hot-updater/server/plugins/insights";
6
+
7
+ import { insightsPeriodEnd, recoveryWindows } from "../insights-recovery";
8
+ import {
9
+ ADOPTION_CANDIDATES,
10
+ type AdoptionRelease,
11
+ type AdoptionReleaseInput,
12
+ type AdoptionReleaseResult,
13
+ type AdoptionReleasesInput,
14
+ type BundleEventsInput,
15
+ type BundleEventsSeries,
16
+ readAdoptionReleaseInput,
17
+ readAdoptionReleasesInput,
18
+ readBundleEventsInput,
19
+ } from "../release-adoption";
20
+
21
+ type ReleaseReads = Pick<
22
+ HotUpdaterCoreApi,
23
+ "findChannelByName" | "getRelease" | "listReleases"
24
+ >;
25
+
26
+ /** Rows read past a focused release to find the bundle before it. */
27
+ const PREVIOUS_ROWS = 4;
28
+
29
+ const labelOf = (release: ReleaseRow): AdoptionRelease => ({
30
+ releaseId: release.id,
31
+ bundleId: release.bundle_id!,
32
+ deployedAtMs: release.created_at_ms,
33
+ targetAppVersion: release.target_app_version,
34
+ enabled: release.enabled,
35
+ revision: release.revision,
36
+ });
37
+
38
+ // A rollback to the built-in bundle has nothing to apply.
39
+ const bundles = (releases: readonly ReleaseRow[]) =>
40
+ releases
41
+ .filter((release) => release.kind === "BUNDLE" && release.bundle_id)
42
+ .map(labelOf);
43
+
44
+ /**
45
+ * The newest bundle deployments of a channel and platform, newest first: one
46
+ * read of the channel and one of its newest releases.
47
+ */
48
+ export async function listAdoptionReleases(
49
+ core: ReleaseReads,
50
+ input: AdoptionReleasesInput,
51
+ ): Promise<readonly AdoptionRelease[]> {
52
+ const { platform, channel } = readAdoptionReleasesInput(input);
53
+ const row = await core.findChannelByName(channel);
54
+ if (row === null) return [];
55
+ return bundles(
56
+ await core.listReleases({
57
+ filter: { kind: "channelPlatform", channelId: row.id, platform },
58
+ order: "desc",
59
+ limit: ADOPTION_CANDIDATES,
60
+ }),
61
+ );
62
+ }
63
+
64
+ /**
65
+ * One bundle deployment of the channel and platform, for a focus the newest
66
+ * ones leave out, and with `withPrevious` the bundle deployed before it.
67
+ */
68
+ export async function getAdoptionRelease(
69
+ core: ReleaseReads,
70
+ input: AdoptionReleaseInput,
71
+ ): Promise<AdoptionReleaseResult> {
72
+ const { platform, channel, releaseId, withPrevious } =
73
+ readAdoptionReleaseInput(input);
74
+ const [row, release] = await Promise.all([
75
+ core.findChannelByName(channel),
76
+ core.getRelease(releaseId),
77
+ ]);
78
+ if (
79
+ row === null ||
80
+ release === null ||
81
+ release.kind !== "BUNDLE" ||
82
+ !release.bundle_id ||
83
+ release.platform !== platform ||
84
+ release.channel_id !== row.id
85
+ )
86
+ return withPrevious ? { release: null, previous: null } : { release: null };
87
+ if (!withPrevious) return { release: labelOf(release) };
88
+ // Newest first, `after` a release lists older ones.
89
+ const older = await core.listReleases({
90
+ filter: { kind: "channelPlatform", channelId: row.id, platform },
91
+ order: "desc",
92
+ after: release.id,
93
+ limit: PREVIOUS_ROWS,
94
+ });
95
+ return { release: labelOf(release), previous: bundles(older)[0] ?? null };
96
+ }
97
+
98
+ /**
99
+ * One bundle's counts of one type in each interval of the period, from
100
+ * their hourly counts, never the events or the release table. Downloads are
101
+ * the bundle's download hours, which count the downloads a launch or crash
102
+ * implied too. Launches add two series: apply reports, and the kept
103
+ * UNCHANGED reports that moved an installation to the bundle with no apply
104
+ * report.
105
+ */
106
+ export async function getBundleEvents(
107
+ model: Pick<InsightsModel, "countEventSeries">,
108
+ input: BundleEventsInput,
109
+ now = Date.now(),
110
+ ): Promise<BundleEventsSeries> {
111
+ const { platform, channel, window, endMs, bundleId, type } =
112
+ readBundleEventsInput(input);
113
+ const { durationMs, intervalMs } = recoveryWindows[window];
114
+ const end = Math.min(endMs, insightsPeriodEnd(now));
115
+ const timeRange = { start: Math.max(0, end - durationMs), end };
116
+ const series = (filter: InsightsBundleEventFilter) =>
117
+ model.countEventSeries({ filter, timeRange, intervalMs });
118
+ const points =
119
+ type === "RECOVERED"
120
+ ? await series({ platform, channel, type, fromBundleId: bundleId })
121
+ : type === "DOWNLOADED"
122
+ ? await series({
123
+ platform,
124
+ channel,
125
+ type: "UPDATE_DOWNLOADED",
126
+ toBundleId: bundleId,
127
+ })
128
+ : await Promise.all([
129
+ series({
130
+ platform,
131
+ channel,
132
+ type: "UPDATE_APPLIED",
133
+ toBundleId: bundleId,
134
+ }),
135
+ series({
136
+ platform,
137
+ channel,
138
+ type: "UNCHANGED",
139
+ toBundleId: bundleId,
140
+ }),
141
+ ]).then(([applied, moved]) =>
142
+ applied.map((point, index) => ({
143
+ startMs: point.startMs,
144
+ events: point.events + (moved[index]?.events ?? 0),
145
+ })),
146
+ );
147
+ return {
148
+ bundleId,
149
+ type,
150
+ measuredAtMs: now,
151
+ points,
152
+ total: points.reduce((sum, point) => sum + point.events, 0),
153
+ };
154
+ }
@@ -1,12 +1,12 @@
1
- import {
2
- isReleaseEligibleForCohort,
3
- NUMERIC_COHORT_SIZE,
4
- } from "@hot-updater/core";
5
1
  import type {
6
2
  CompiledReleaseCatalog,
7
- ReleaseCatalogModel,
3
+ HotUpdaterCoreApi,
8
4
  ReleaseRow,
9
5
  } from "@hot-updater/plugin-core";
6
+ import {
7
+ isReleaseEligibleForCohort,
8
+ NUMERIC_COHORT_SIZE,
9
+ } from "@hot-updater/protocol";
10
10
 
11
11
  export type ReleaseReachabilityRow = ReleaseRow & {
12
12
  readonly currentlyUnreachable: boolean;
@@ -63,15 +63,16 @@ export function collectCurrentlyReachableReleaseIds(
63
63
  return reachableReleaseIds;
64
64
  }
65
65
 
66
+ /** Marks each release its scope's compiled catalog no longer serves: one catalog read per scope. */
66
67
  export async function addReleaseReachability(
67
- releaseCatalogs: Pick<ReleaseCatalogModel, "findByScopeKey">,
68
+ core: Pick<HotUpdaterCoreApi, "getReleaseCatalogRow">,
68
69
  releases: readonly ReleaseRow[],
69
70
  ): Promise<readonly ReleaseReachabilityRow[]> {
70
71
  const scopeKeys = [...new Set(releases.map(({ scope_key }) => scope_key))];
71
72
  const reachableByScope = new Map(
72
73
  await Promise.all(
73
74
  scopeKeys.map(async (scopeKey) => {
74
- const row = await releaseCatalogs.findByScopeKey(scopeKey);
75
+ const row = await core.getReleaseCatalogRow(scopeKey);
75
76
  const reachableReleaseIds =
76
77
  row === null
77
78
  ? new Set<string>()
@@ -0,0 +1,191 @@
1
+ import type { RemoteDatabase } from "@hot-updater/plugin-core";
2
+ import {
3
+ type RemoteConfigActive,
4
+ type RemoteConfigApi,
5
+ type RemoteConfigPublishResult,
6
+ type RemoteConfigTemplateIssue,
7
+ RemoteConfigValidationError,
8
+ type RemoteConfigVersion,
9
+ type RemoteConfigVersionDetail,
10
+ type RemoteConfigVersionsPage,
11
+ } from "@hot-updater/server/plugins/remote-config";
12
+
13
+ import { ConsoleFeatureUnavailableError } from "../console-features";
14
+
15
+ /** How a publish from the console ended; `invalid` lists what to fix. */
16
+ export type ConsolePublishResult =
17
+ | RemoteConfigPublishResult
18
+ | {
19
+ readonly status: "invalid";
20
+ readonly issues: readonly RemoteConfigTemplateIssue[];
21
+ };
22
+
23
+ /** The Remote Config the console pages use. */
24
+ export interface ConsoleRemoteConfig {
25
+ getActive(): Promise<RemoteConfigActive>;
26
+ listVersions(input: {
27
+ readonly limit?: number;
28
+ readonly cursor?: string;
29
+ }): Promise<RemoteConfigVersionsPage>;
30
+ getVersion(version: number): Promise<RemoteConfigVersionDetail | null>;
31
+ publish(input: {
32
+ readonly template: unknown;
33
+ readonly baseVersion: number;
34
+ readonly description?: string;
35
+ }): Promise<ConsolePublishResult>;
36
+ rollback(input: {
37
+ readonly version: number;
38
+ readonly baseVersion: number;
39
+ }): Promise<ConsolePublishResult | { readonly status: "not_found" }>;
40
+ }
41
+
42
+ const invalidOf = (error: unknown): ConsolePublishResult => {
43
+ if (error instanceof RemoteConfigValidationError) {
44
+ return { status: "invalid", issues: error.issues };
45
+ }
46
+ throw error;
47
+ };
48
+
49
+ /** Remote Config over the `remoteConfig()` API the console assembled on the database. */
50
+ export const createLocalRemoteConfig = (
51
+ api: RemoteConfigApi,
52
+ ): ConsoleRemoteConfig => ({
53
+ getActive: () => api.getActive(),
54
+ listVersions: (input) => api.listVersions(input),
55
+ getVersion: (version) => api.getVersion(version),
56
+ publish: (input) => api.publish(input).catch(invalidOf),
57
+ rollback: (input) => api.rollback(input),
58
+ });
59
+
60
+ const readBody = async (response: Response): Promise<unknown> =>
61
+ response.json().catch(() => null);
62
+
63
+ const field = (body: unknown, name: string): unknown =>
64
+ typeof body === "object" && body !== null
65
+ ? Reflect.get(body, name)
66
+ : undefined;
67
+
68
+ /** A published version's record, as a publish or a rollback answers it. */
69
+ const isPublishedVersion = (body: unknown): body is RemoteConfigVersion => {
70
+ const version = field(body, "version");
71
+ const updateType = field(body, "updateType");
72
+ return (
73
+ typeof version === "number" &&
74
+ Number.isSafeInteger(version) &&
75
+ version > 0 &&
76
+ (updateType === "PUBLISH" || updateType === "ROLLBACK") &&
77
+ typeof field(body, "createdAtMs") === "number"
78
+ );
79
+ };
80
+
81
+ const errorText = (body: unknown): string | undefined => {
82
+ const error = field(body, "error");
83
+ return typeof error === "string" ? error : undefined;
84
+ };
85
+
86
+ /**
87
+ * Remote Config over a self-hosted server's admin routes. A route the server
88
+ * does not mount answers core's 404: it runs without `remoteConfig()`.
89
+ */
90
+ export const createAdminRemoteConfig = (
91
+ fetchAdmin: RemoteDatabase["fetchAdmin"],
92
+ ): ConsoleRemoteConfig => {
93
+ const unavailable = () =>
94
+ new ConsoleFeatureUnavailableError("remoteConfig", { remote: true });
95
+ const request = async (
96
+ path: string,
97
+ init?: { readonly method: string; readonly body: unknown },
98
+ ): Promise<{ readonly status: number; readonly body: unknown }> => {
99
+ const response = await fetchAdmin(
100
+ path,
101
+ init === undefined
102
+ ? undefined
103
+ : {
104
+ method: init.method,
105
+ headers: { "content-type": "application/json" },
106
+ body: JSON.stringify(init.body),
107
+ },
108
+ );
109
+ const body = await readBody(response);
110
+ // Core's own 404 for a path no route matches.
111
+ if (response.status === 404 && errorText(body) === "Not found") {
112
+ throw unavailable();
113
+ }
114
+ return { status: response.status, body };
115
+ };
116
+ const expectOk = <T>(
117
+ path: string,
118
+ { status, body }: { status: number; body: unknown },
119
+ ): T => {
120
+ if (status >= 200 && status < 300) return body as T;
121
+ throw new Error(
122
+ errorText(body) ??
123
+ `The server answered ${path.split("?")[0]} with ${status}.`,
124
+ );
125
+ };
126
+ const published = (
127
+ path: string,
128
+ result: { status: number; body: unknown },
129
+ ): ConsolePublishResult => {
130
+ const currentVersion = field(result.body, "currentVersion");
131
+ if (
132
+ result.status === 409 &&
133
+ typeof currentVersion === "number" &&
134
+ Number.isSafeInteger(currentVersion)
135
+ ) {
136
+ return { status: "conflict", currentVersion };
137
+ }
138
+ const issues = field(result.body, "issues");
139
+ if (result.status === 400 && Array.isArray(issues)) {
140
+ return { status: "invalid", issues };
141
+ }
142
+ const version = expectOk<unknown>(path, result);
143
+ // Anything else, such as the active template a server that ignores the
144
+ // method answers, published nothing.
145
+ if (!isPublishedVersion(version)) {
146
+ throw new Error(
147
+ `The server answered ${path} without the version it published.`,
148
+ );
149
+ }
150
+ return { status: "published", version };
151
+ };
152
+
153
+ return {
154
+ getActive: async () => {
155
+ const path = "/remote-config/template";
156
+ return expectOk(path, await request(path));
157
+ },
158
+ listVersions: async ({ limit, cursor }) => {
159
+ const query = new URLSearchParams();
160
+ if (limit !== undefined) query.set("limit", String(limit));
161
+ if (cursor !== undefined) query.set("cursor", cursor);
162
+ const path = `/remote-config/versions${query.size === 0 ? "" : `?${query}`}`;
163
+ return expectOk(path, await request(path));
164
+ },
165
+ getVersion: async (version) => {
166
+ const path = `/remote-config/versions/${version}`;
167
+ const result = await request(path);
168
+ return result.status === 404 ? null : expectOk(path, result);
169
+ },
170
+ publish: async ({ template, baseVersion, description }) => {
171
+ const path = "/remote-config/template";
172
+ return published(
173
+ path,
174
+ await request(path, {
175
+ method: "PUT",
176
+ body: { template, baseVersion, description },
177
+ }),
178
+ );
179
+ },
180
+ rollback: async ({ version, baseVersion }) => {
181
+ const path = `/remote-config/versions/${version}/rollback`;
182
+ const result = await request(path, {
183
+ method: "POST",
184
+ body: { baseVersion },
185
+ });
186
+ return result.status === 404
187
+ ? { status: "not_found" }
188
+ : published(path, result);
189
+ },
190
+ };
191
+ };
@@ -1,36 +1,125 @@
1
- import type {
2
- ApiKeyModel,
3
- BundleRepository,
4
- DatabasePlugin,
1
+ import {
2
+ type ConfiguredDatabase,
3
+ isRemoteDatabase,
5
4
  } from "@hot-updater/plugin-core";
5
+
6
+ import {
7
+ type ConsoleFeature,
8
+ ConsoleFeatureUnavailableError,
9
+ type ConsoleFeatures,
10
+ consoleFeatures,
11
+ resolveConsoleFeatures,
12
+ } from "../console-features";
6
13
  import {
7
- createInsightsProvider,
8
- type InsightsProvider,
9
- } from "@hot-updater/server";
10
-
11
- export function createRuntimeHotUpdater(config: {
12
- readonly database: DatabasePlugin;
13
- }): InsightsProvider {
14
- return createInsightsProvider(config.database.models.insights);
14
+ type ConsoleFeatureApis,
15
+ consoleFeatureApis,
16
+ } from "./console-feature-apis";
17
+
18
+ export type { ConsoleFeatureApis } from "./console-feature-apis";
19
+
20
+ export interface ConsoleRuntime {
21
+ /** Whether the console reaches a self-hosted server through its admin API. */
22
+ readonly remote: boolean;
23
+ /** The features on here, from the plugins the console's config lists. */
24
+ features(): Promise<ConsoleFeatures>;
25
+ /** What serves the features the console can serve here; {@link requireFeature} hands it out. */
26
+ readonly apis: Partial<ConsoleFeatureApis>;
15
27
  }
16
28
 
17
- export function createApiKeyStore(config: {
18
- readonly database: BundleRepository;
19
- }): ApiKeyModel | null {
20
- const models: unknown = Reflect.get(config.database, "models");
21
- const apiKeys: unknown =
22
- typeof models === "object" && models !== null
23
- ? Reflect.get(models, "apiKeys")
24
- : undefined;
25
- if (
26
- typeof apiKeys !== "object" ||
27
- apiKeys === null ||
28
- typeof Reflect.get(apiKeys, "create") !== "function" ||
29
- typeof Reflect.get(apiKeys, "findByHash") !== "function" ||
30
- typeof Reflect.get(apiKeys, "list") !== "function" ||
31
- typeof Reflect.get(apiKeys, "revoke") !== "function"
32
- ) {
33
- return null;
29
+ const isRecord = (value: unknown): value is Record<string, unknown> =>
30
+ typeof value === "object" && value !== null;
31
+
32
+ /**
33
+ * Whether one of Hot Updater's own factories made `plugin`: the brand they
34
+ * set under a registered symbol, read here with a copy of the key so the
35
+ * console imports no package's internals. Assembly refuses any other plugin
36
+ * that takes one of their ids, so features key on it.
37
+ */
38
+ const isOfficialPlugin = (plugin: unknown): boolean =>
39
+ isRecord(plugin) &&
40
+ Reflect.get(plugin, Symbol.for("@hot-updater/server/official-plugin")) ===
41
+ true;
42
+
43
+ const featureIds = Object.keys(consoleFeatureApis) as ConsoleFeature[];
44
+
45
+ /**
46
+ * What serves each feature, built by its factory: over the plugins' APIs the
47
+ * console assembled, or over a self-hosted server's admin API. A feature
48
+ * without its plugin, or without a factory for the admin API, has none.
49
+ */
50
+ const featureApis = (
51
+ build: (feature: ConsoleFeature) => unknown,
52
+ ): Partial<ConsoleFeatureApis> =>
53
+ Object.fromEntries(
54
+ featureIds.flatMap((feature) => {
55
+ const api = build(feature);
56
+ return api === undefined ? [] : [[feature, api]];
57
+ }),
58
+ );
59
+
60
+ /**
61
+ * The console's features and what serves them, for the plugins its config
62
+ * lists, the plugins the server runs:
63
+ * - a self-hosted server (`standaloneRepository`) runs them itself; the
64
+ * console serves what that server's admin API serves of their features,
65
+ * and nothing that needs the database;
66
+ * - otherwise the console runs them over the database, as the server does,
67
+ * and serves their features through `api`, their APIs.
68
+ */
69
+ export const createConsoleRuntime = (config: {
70
+ readonly database: ConfiguredDatabase;
71
+ readonly plugins: readonly unknown[];
72
+ readonly api?: Readonly<Record<string, unknown>>;
73
+ }): ConsoleRuntime => {
74
+ const { database } = config;
75
+ // A feature's plugin is Hot Updater's own, which alone may take its id:
76
+ // the server's assembly refuses any other, and features key on the
77
+ // factory's mark, so another plugin under that id turns on nothing.
78
+ const official = new Set(
79
+ config.plugins
80
+ .filter(isOfficialPlugin)
81
+ .map((plugin) => (plugin as { id: string }).id),
82
+ );
83
+ if (isRemoteDatabase(database)) {
84
+ const { fetchAdmin } = database;
85
+ const features = resolveConsoleFeatures([...official], { remote: true });
86
+ return {
87
+ remote: true,
88
+ features: async () => features,
89
+ apis: featureApis((feature) =>
90
+ consoleFeatureApis[feature].remote?.(fetchAdmin),
91
+ ),
92
+ };
34
93
  }
35
- return apiKeys as ApiKeyModel;
36
- }
94
+ const api = config.api ?? {};
95
+ const features = resolveConsoleFeatures([...official], { remote: false });
96
+ return {
97
+ remote: false,
98
+ features: async () => features,
99
+ apis: featureApis((feature) => {
100
+ const { plugin } = consoleFeatures[feature];
101
+ const pluginApi = official.has(plugin) ? api[plugin] : undefined;
102
+ return pluginApi === undefined
103
+ ? undefined
104
+ : consoleFeatureApis[feature].local(pluginApi as never);
105
+ }),
106
+ };
107
+ };
108
+
109
+ /**
110
+ * What serves `feature`. Every feature's server function asks for it first,
111
+ * so a feature the console does not serve is refused with one error the
112
+ * client recognizes, before anything is read.
113
+ */
114
+ export const requireFeature = async <F extends ConsoleFeature>(
115
+ runtime: ConsoleRuntime,
116
+ feature: F,
117
+ ): Promise<ConsoleFeatureApis[F]> => {
118
+ const api = runtime.apis[feature];
119
+ if (api === undefined || !(await runtime.features())[feature]) {
120
+ throw new ConsoleFeatureUnavailableError(feature, {
121
+ remote: runtime.remote,
122
+ });
123
+ }
124
+ return api as ConsoleFeatureApis[F];
125
+ };
@@ -0,0 +1,65 @@
1
+ import {
2
+ checkFailureRate,
3
+ failureRate,
4
+ readUpdateFailuresInput,
5
+ type UpdateFailuresInput,
6
+ type UpdateFailuresReport,
7
+ } from "../insights-failures";
8
+ import { insightsPeriodEnd, recoveryWindows } from "../insights-recovery";
9
+ import type { ConsoleInsightsReads } from "./adminInsights";
10
+
11
+ /** A release's or a channel's update failures over a period that ends with the current hour. */
12
+ export async function getUpdateFailuresReport(
13
+ reads: Pick<ConsoleInsightsReads, "getUpdateFailures">,
14
+ input: UpdateFailuresInput,
15
+ now = Date.now(),
16
+ ): Promise<UpdateFailuresReport> {
17
+ readUpdateFailuresInput(input);
18
+ const end = insightsPeriodEnd(now);
19
+ const timeRange =
20
+ input.window === undefined
21
+ ? undefined
22
+ : {
23
+ start: Math.max(0, end - recoveryWindows[input.window].durationMs),
24
+ end,
25
+ };
26
+ const scope = {
27
+ platform: input.platform,
28
+ channel: input.channel,
29
+ ...(input.releaseId === undefined ? {} : { releaseId: input.releaseId }),
30
+ };
31
+ const previousRange =
32
+ timeRange && timeRange.start >= timeRange.end - timeRange.start
33
+ ? {
34
+ start: timeRange.start - (timeRange.end - timeRange.start),
35
+ end: timeRange.start,
36
+ }
37
+ : undefined;
38
+ const [failures, previous] = await Promise.all([
39
+ reads.getUpdateFailures({
40
+ ...scope,
41
+ ...(timeRange === undefined ? {} : { timeRange }),
42
+ }),
43
+ previousRange === undefined
44
+ ? undefined
45
+ : reads.getUpdateFailures({ ...scope, timeRange: previousRange }),
46
+ ]);
47
+ return {
48
+ ...failures,
49
+ startMs: timeRange?.start ?? null,
50
+ endMs: end,
51
+ ...(previous === undefined
52
+ ? {}
53
+ : {
54
+ previous: {
55
+ attemptRate: failureRate(previous),
56
+ checkRate: previous.checks
57
+ ? checkFailureRate(previous.checks)
58
+ : null,
59
+ complete:
60
+ previous.coverage.kind === "complete" &&
61
+ failures.coverage.kind === "complete",
62
+ },
63
+ }),
64
+ };
65
+ }