@hot-updater/console 1.0.0-rc.9 → 1.0.0

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 (341) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/ConsoleFeatureUnavailable-jB4Loglh.js +1 -0
  3. package/.output/public/assets/EventDetails-B6yUsyNA.js +1 -0
  4. package/.output/public/assets/HashValueDisplay-BFX_kY3-.js +1 -0
  5. package/.output/public/assets/InsightsInfo-BNO0WznZ.js +1 -0
  6. package/.output/public/assets/InsightsPageHeader-B892oVml.js +1 -0
  7. package/.output/public/assets/NotFoundPage-DIqCpWa3.js +1 -0
  8. package/.output/public/assets/alert-dialog-Bbk_4-WJ.js +1 -0
  9. package/.output/public/assets/api-DCM0wr6O.js +1 -0
  10. package/.output/public/assets/api-keys-CIO9L_wu.js +1 -0
  11. package/.output/public/assets/api-keys-DVTL_--f.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-BH5m_PtQ.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-6o51GkFe.js +11 -0
  22. package/.output/public/assets/input-group-ja_30qaF.js +1 -0
  23. package/.output/public/assets/insights-CmraDzQh.js +1 -0
  24. package/.output/public/assets/insights-CxZ2GCYV.js +2 -0
  25. package/.output/public/assets/insights-api-BigAU9sZ.js +1 -0
  26. package/.output/public/assets/insights-usage-rpc-DYEhsCKT.js +1 -0
  27. package/.output/public/assets/insights-vNRCrfnS.js +42 -0
  28. package/.output/public/assets/insights_.distribution-ChOerfJt.js +1 -0
  29. package/.output/public/assets/insights_.distribution-D-e4iga8.js +1 -0
  30. package/.output/public/assets/insights_.distribution-DZtkibX1.js +2 -0
  31. package/.output/public/assets/installations-BazSJXE0.js +2 -0
  32. package/.output/public/assets/installations-ClHTqdAO.js +1 -0
  33. package/.output/public/assets/installations-DpKsBuHi.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-BWwHOkCK.js +1 -0
  38. package/.output/public/assets/remote-config-Br56BODb.js +6 -0
  39. package/.output/public/assets/remote-config-CXOi0LRu.js +1 -0
  40. package/.output/public/assets/routes-B88Erkpk.js +2 -0
  41. package/.output/public/assets/routes-CfzsAleC.js +1 -0
  42. package/.output/public/assets/search-DaTDPW25.js +1 -0
  43. package/.output/public/assets/select-BZZBXMkr.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-DTGEAJFv.css +2 -0
  47. package/.output/public/assets/switch-DZ1TPwx-.js +1 -0
  48. package/.output/public/assets/table-Gm4qRWwE.js +1 -0
  49. package/.output/public/assets/tabs-C7FOEjFC.js +1 -0
  50. package/.output/public/assets/undo-2-CmrHjwFc.js +1 -0
  51. package/.output/public/assets/useCompositeListItem-9E4pj_CH.js +1 -0
  52. package/.output/public/assets/valueToPercent-CH8GCHaL.js +1 -0
  53. package/.output/server/__23tanstack-start-server-fn-resolver-BpRlos5Q.mjs +170 -0
  54. package/.output/server/_libs/@base-ui/react+[...].mjs +4389 -4207
  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 +221 -9
  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-x94o9rlU.mjs +13 -0
  67. package/.output/server/_ssr/ConsoleFeatureUnavailable-ldW8v8-q.mjs +58 -0
  68. package/.output/server/_ssr/EventDetails-Q8kfj9Nr.mjs +344 -0
  69. package/.output/server/_ssr/{HashValueDisplay-CyMmr0O9.mjs → HashValueDisplay-n1GholGS.mjs} +3 -3
  70. package/.output/server/_ssr/InsightsInfo-DuJFatQO.mjs +75 -0
  71. package/.output/server/_ssr/InsightsPageHeader-BSUX3Ye9.mjs +145 -0
  72. package/.output/server/_ssr/NotFoundPage-DZzOOzS3.mjs +37 -0
  73. package/.output/server/_ssr/{dialog-CKvQ4VIE.mjs → alert-dialog-C43Doa0I.mjs} +6 -83
  74. package/.output/server/_ssr/{api-keys-CzZ7Ont4.mjs → api-keys-B4sYm8dM.mjs} +48 -9
  75. package/.output/server/_ssr/api-keys-CEnM9fqG.mjs +5 -0
  76. package/.output/server/_ssr/api-keys-rpc-BIFuUgMt.mjs +52 -0
  77. package/.output/server/_ssr/api-oZjRn9tP.mjs +186 -0
  78. package/.output/server/_ssr/api-rpc-BNiFRRK8.mjs +240 -0
  79. package/.output/server/_ssr/{auth-BF8mbAgF.mjs → auth-UP0wXs4i.mjs} +1 -1
  80. package/.output/server/_ssr/{auth-rpc-DH6Bhwcz.mjs → auth-rpc-DnZqYxyq.mjs} +6 -6
  81. package/.output/server/_ssr/auth.server-DFpyxRDd.mjs +5374 -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-DskXCAyI.mjs +35 -0
  85. package/.output/server/_ssr/console-access-BJfAXknd.mjs +17 -0
  86. package/.output/server/_ssr/console-features-Oz0q20jJ.mjs +61 -0
  87. package/.output/server/_ssr/console-features-api-C9kiKvaV.mjs +43 -0
  88. package/.output/server/_ssr/console-features-rpc-P0GL7Z-R.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-CPphvbag.mjs +73 -0
  94. package/.output/server/_ssr/dialog-BbYDMjdC.mjs +84 -0
  95. package/.output/server/_ssr/{dist-CyzPaI5v.mjs → dist-CbQbBIw-.mjs} +140 -7
  96. package/.output/server/_ssr/{downloadBundle-CI7JUKvC.mjs → downloadBundle-CZysjmNW.mjs} +10 -10
  97. package/.output/server/_ssr/empty-CVLGfxuu.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-asw83X0v.mjs +189 -0
  101. package/.output/server/_ssr/getBundleChildren-Cv9iz1As.mjs +22 -0
  102. package/.output/server/_ssr/{input-group-BrGSjz4S.mjs → input-group-CuYJHosb.mjs} +2 -2
  103. package/.output/server/_ssr/insights-B55FG5fm.mjs +14 -0
  104. package/.output/server/_ssr/insights-CWnHIFUV.mjs +5 -0
  105. package/.output/server/_ssr/insights-CpLpedFZ.mjs +2315 -0
  106. package/.output/server/_ssr/insights-api-sjws23WF.mjs +122 -0
  107. package/.output/server/_ssr/insights-errors-BjnFUyZj.mjs +34 -0
  108. package/.output/server/_ssr/insights-failures-rpc-BvbWvqNf.mjs +36 -0
  109. package/.output/server/_ssr/insights-failures-xaa41Buf.mjs +46 -0
  110. package/.output/server/_ssr/insights-recovery-BWQhiZaF.mjs +28 -0
  111. package/.output/server/_ssr/insights-recovery-rpc-GiYXdkaY.mjs +32 -0
  112. package/.output/server/_ssr/{insights-rpc-DDKmIrpV.mjs → insights-rpc-DP_8Rmz4.mjs} +22 -14
  113. package/.output/server/_ssr/insights-usage-BpBM0U0c.mjs +29 -0
  114. package/.output/server/_ssr/insights-usage-rpc-BiolgB8e.mjs +29 -0
  115. package/.output/server/_ssr/insights-usage-rpc-KStL0zOx.mjs +41 -0
  116. package/.output/server/_ssr/insightsUsage-CzhVjsc8.mjs +56 -0
  117. package/.output/server/_ssr/insights_.distribution-CVrnbP3R.mjs +7 -0
  118. package/.output/server/_ssr/insights_.distribution-D2HaNTAA.mjs +16 -0
  119. package/.output/server/_ssr/{insights_.distribution-cIsU-kHG.mjs → insights_.distribution-DN9pR422.mjs} +50 -22
  120. package/.output/server/_ssr/installations-BHmbbnH8.mjs +5 -0
  121. package/.output/server/_ssr/{installations-C5anhyP0.mjs → installations-BSCmFq8Q.mjs} +279 -367
  122. package/.output/server/_ssr/installations-DD1g__Nc.mjs +69 -0
  123. package/.output/server/_ssr/listReleases-BfUBlCHo.mjs +79 -0
  124. package/.output/server/_ssr/plugins-BaD1slHx.mjs +2086 -0
  125. package/.output/server/_ssr/release-adoption-005tQRpo.mjs +55 -0
  126. package/.output/server/_ssr/release-adoption-rpc-BsstqxUp.mjs +41 -0
  127. package/.output/server/_ssr/releaseAdoption-CtOFkwli.mjs +114 -0
  128. package/.output/server/_ssr/releaseManagement-DFMUnDF5.mjs +11 -0
  129. package/.output/server/_ssr/remote-config-C2Ee8lXO.mjs +5 -0
  130. package/.output/server/_ssr/remote-config-Dv1ccMo-.mjs +17 -0
  131. package/.output/server/_ssr/remote-config-of0lGQH8.mjs +2648 -0
  132. package/.output/server/_ssr/remote-config-rpc-BzX31mUS.mjs +115 -0
  133. package/.output/server/_ssr/rolldown-runtime-rSIU-vHC.mjs +13 -0
  134. package/.output/server/_ssr/{router-BuanSsRI.mjs → router-D8NuoJ_I.mjs} +107 -134
  135. package/.output/server/_ssr/{routes-DY-vqU6R.mjs → routes-DgvnFqDy.mjs} +195 -429
  136. package/.output/server/_ssr/routes-V8sDybGf.mjs +409 -0
  137. package/.output/server/_ssr/runtime.server-De0QVWc9.mjs +104 -0
  138. package/.output/server/_ssr/schema-BiAVyEj2.mjs +361 -0
  139. package/.output/server/_ssr/{select-DD2szs3Y.mjs → select-t3iXRwc9.mjs} +3 -3
  140. package/.output/server/_ssr/server-h4VxnExB.mjs +8325 -0
  141. package/.output/server/_ssr/{createSsrRpc-CdlfejmI.mjs → sidebar-DJcErBMo.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-E-btwMGt.mjs +43 -0
  145. package/.output/server/_ssr/{table-CTj6n-4F.mjs → table-37Dd23TN.mjs} +2 -2
  146. package/.output/server/_ssr/tabs-DEFnTm95.mjs +45 -0
  147. package/.output/server/_ssr/updateFailures-H6pnnYK4.mjs +40 -0
  148. package/.output/server/_ssr/{uuidv7-D0QFkPL_.mjs → uuidv7-C88s9jWF.mjs} +2 -2
  149. package/.output/server/_tanstack-start-manifest_v-55AGsehh.mjs +139 -0
  150. package/.output/server/index.mjs +309 -211
  151. package/LICENSE +13 -14
  152. package/README.md +80 -71
  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 +69 -74
  158. package/src/components/ConsoleFeatureUnavailable.tsx +80 -0
  159. package/src/components/NotFoundPage.tsx +7 -7
  160. package/src/components/features/FeatureSlots.tsx +103 -0
  161. package/src/components/features/api-keys/apiKeysFeatureUi.tsx +15 -0
  162. package/src/components/features/bundles/BundleMetadata.tsx +8 -5
  163. package/src/components/features/bundles/RolloutCohortsDialog.tsx +1 -1
  164. package/src/components/features/channels/ChannelManagementDialog.tsx +8 -6
  165. package/src/components/features/insights/AppUsage.tsx +41 -14
  166. package/src/components/features/insights/BuiltinBundle.tsx +36 -0
  167. package/src/components/features/insights/ComparedBundlesChart.tsx +310 -0
  168. package/src/components/features/insights/EstimatedCount.tsx +18 -0
  169. package/src/components/features/insights/EventDetails.tsx +390 -26
  170. package/src/components/features/insights/EventHistoryCard.tsx +111 -25
  171. package/src/components/features/insights/EventHistoryList.tsx +1 -1
  172. package/src/components/features/insights/FailureReports.tsx +407 -0
  173. package/src/components/features/insights/InsightsControls.tsx +240 -142
  174. package/src/components/features/insights/InsightsErrorAlert.tsx +14 -1
  175. package/src/components/features/insights/InsightsOverview.tsx +601 -403
  176. package/src/components/features/insights/InsightsPageHeader.tsx +19 -14
  177. package/src/components/features/insights/InsightsPeriodSelector.tsx +67 -12
  178. package/src/components/features/insights/InstallationHistoryCard.tsx +65 -10
  179. package/src/components/features/insights/InstallationMatchesCard.tsx +1 -1
  180. package/src/components/features/insights/ReleaseActivity.tsx +308 -0
  181. package/src/components/features/insights/ReportingDevicesSummary.tsx +13 -13
  182. package/src/components/features/insights/RollbackButton.tsx +118 -0
  183. package/src/components/features/insights/UpdateFailures.tsx +382 -0
  184. package/src/components/features/insights/insightsFeatureUi.tsx +35 -0
  185. package/src/components/features/releases/ReleaseEditorSheet.tsx +27 -13
  186. package/src/components/features/remote-config/ConditionDialog.tsx +592 -0
  187. package/src/components/features/remote-config/ConditionsCard.tsx +247 -0
  188. package/src/components/features/remote-config/ParameterDialog.tsx +426 -0
  189. package/src/components/features/remote-config/ParametersCard.tsx +254 -0
  190. package/src/components/features/remote-config/PreviewCard.tsx +284 -0
  191. package/src/components/features/remote-config/PublishDialog.tsx +325 -0
  192. package/src/components/features/remote-config/RemoteConfigPage.tsx +353 -0
  193. package/src/components/features/remote-config/RuleSummary.tsx +21 -0
  194. package/src/components/features/remote-config/ValueInput.tsx +151 -0
  195. package/src/components/features/remote-config/VersionsCard.tsx +384 -0
  196. package/src/components/features/remote-config/remoteConfigFeatureUi.tsx +15 -0
  197. package/src/components/features/remote-config/useDateTimeText.ts +16 -0
  198. package/src/components/ui/sidebar.tsx +22 -29
  199. package/src/index.ts +26 -19
  200. package/src/lib/api-keys-api.ts +0 -28
  201. package/src/lib/api-keys-rpc.ts +27 -38
  202. package/src/lib/api-rpc.ts +125 -288
  203. package/src/lib/api.ts +15 -134
  204. package/src/lib/bundle-activity.ts +24 -4
  205. package/src/lib/console-access.ts +20 -0
  206. package/src/lib/console-features-api.ts +68 -0
  207. package/src/lib/console-features-rpc.ts +13 -0
  208. package/src/lib/console-features.ts +98 -0
  209. package/src/lib/insights-api.ts +56 -2
  210. package/src/lib/insights-errors.ts +64 -0
  211. package/src/lib/insights-failures-rpc.ts +36 -0
  212. package/src/lib/insights-failures.ts +130 -0
  213. package/src/lib/insights-recovery-rpc.ts +13 -17
  214. package/src/lib/insights-recovery.ts +12 -36
  215. package/src/lib/insights-retention.ts +21 -0
  216. package/src/lib/insights-rpc.ts +30 -12
  217. package/src/lib/insights-search.ts +50 -4
  218. package/src/lib/insights-usage-rpc.ts +15 -9
  219. package/src/lib/insights-usage.ts +41 -3
  220. package/src/lib/insights-view.ts +34 -5
  221. package/src/lib/release-adoption-api.ts +214 -0
  222. package/src/lib/release-adoption-rpc.ts +49 -0
  223. package/src/lib/release-adoption.ts +164 -0
  224. package/src/lib/remote-config-api.ts +119 -0
  225. package/src/lib/remote-config-draft.ts +666 -0
  226. package/src/lib/remote-config-rpc.ts +196 -0
  227. package/src/lib/scroll-restoration.ts +21 -0
  228. package/src/lib/server/auth.server.ts +13 -5
  229. package/src/lib/server/bundleActivity.ts +27 -28
  230. package/src/lib/server/config.server.ts +21 -76
  231. package/src/lib/server/console-feature-apis.ts +70 -0
  232. package/src/lib/server/console-runtime.server.ts +82 -38
  233. package/src/lib/server/downloadBundle.ts +14 -15
  234. package/src/lib/server/failureReports.ts +50 -0
  235. package/src/lib/server/getBundleChildren.ts +31 -122
  236. package/src/lib/server/insightsUsage.ts +52 -113
  237. package/src/lib/server/listReleases.ts +109 -44
  238. package/src/lib/server/releaseAdoption.ts +151 -0
  239. package/src/lib/server/releaseReachability.ts +8 -8
  240. package/src/lib/server/remoteConfig.ts +75 -0
  241. package/src/lib/server/runtime.server.ts +120 -31
  242. package/src/lib/server/updateFailures.ts +66 -0
  243. package/src/lib/use-dialog-target.ts +27 -0
  244. package/src/routeTree.gen.ts +44 -44
  245. package/src/router.tsx +3 -2
  246. package/src/routes/-installations-search.ts +32 -8
  247. package/src/routes/-releases-search.ts +76 -29
  248. package/src/routes/__root.tsx +7 -1
  249. package/src/routes/api/bundles/$bundleId/download.ts +2 -5
  250. package/src/routes/api-keys.tsx +5 -2
  251. package/src/routes/index.tsx +210 -152
  252. package/src/routes/insights.tsx +91 -23
  253. package/src/routes/insights_.distribution.tsx +50 -9
  254. package/src/routes/installations.tsx +97 -40
  255. package/src/routes/remote-config.tsx +42 -0
  256. package/src/styles.css +9 -0
  257. package/src/vite-internal.ts +15 -3
  258. package/src/vite.ts +2 -0
  259. package/.output/public/assets/HashValueDisplay-BpTc3Oyg.js +0 -1
  260. package/.output/public/assets/InsightsPageHeader-DC2eEst8.js +0 -1
  261. package/.output/public/assets/api-CdUyUqzR.js +0 -42
  262. package/.output/public/assets/api-keys-64PM9PwW.js +0 -1
  263. package/.output/public/assets/api-keys-api-BeaJnvY5.js +0 -1
  264. package/.output/public/assets/arrow-left-04Rh8Zgp.js +0 -1
  265. package/.output/public/assets/card-CYHs5FZv.js +0 -1
  266. package/.output/public/assets/chart-no-axes-combined-DwyMsVFW.js +0 -1
  267. package/.output/public/assets/createServerFn-DU71xucx.js +0 -3
  268. package/.output/public/assets/dialog-IVMkBRCq.js +0 -1
  269. package/.output/public/assets/dist-BR4GzeEu.js +0 -1
  270. package/.output/public/assets/field-B3CYDpjg.js +0 -1
  271. package/.output/public/assets/index-Cb_D1D6N.js +0 -10
  272. package/.output/public/assets/info-C3y9HNoU.js +0 -1
  273. package/.output/public/assets/input-group-nLZylhWF.js +0 -1
  274. package/.output/public/assets/insights-Bm9v8uO3.js +0 -2
  275. package/.output/public/assets/insights-DS3iO7Up.js +0 -1
  276. package/.output/public/assets/insights-search-Byj3g3Mp.js +0 -1
  277. package/.output/public/assets/insights-usage-rpc-BaGR3lgq.js +0 -1
  278. package/.output/public/assets/insights_.distribution-B1i6cjlQ.js +0 -1
  279. package/.output/public/assets/insights_.distribution-C1Wf27eB.js +0 -2
  280. package/.output/public/assets/installations-CkFW4fV9.js +0 -2
  281. package/.output/public/assets/installations-DxTaIcHD.js +0 -1
  282. package/.output/public/assets/lazyRouteComponent-BapzjYnb.js +0 -1
  283. package/.output/public/assets/list-Dqt53fWZ.js +0 -1
  284. package/.output/public/assets/loader-circle-CUKfUqow.js +0 -1
  285. package/.output/public/assets/preload-helper-ClBMtM1g.js +0 -1
  286. package/.output/public/assets/refresh-cw-CNE_2PnD.js +0 -1
  287. package/.output/public/assets/routes-Cgtvspjg.js +0 -2
  288. package/.output/public/assets/routes-O2r2UUN8.js +0 -1
  289. package/.output/public/assets/shield-check-BFcJ46kz.js +0 -1
  290. package/.output/public/assets/signing-BgspC-xP.js +0 -1
  291. package/.output/public/assets/styles-DnrWuBwd.css +0 -2
  292. package/.output/public/assets/table-BuWQgxJJ.js +0 -1
  293. package/.output/public/assets/useMutation-CSV9NCUl.js +0 -1
  294. package/.output/public/assets/useOpenInteractionType-D5kduCTD.js +0 -1
  295. package/.output/public/assets/valueToPercent-BAtcbSLk.js +0 -1
  296. package/.output/server/__23tanstack-start-server-fn-resolver-DBRojHaG.mjs +0 -150
  297. package/.output/server/_ssr/InsightsPageHeader-mtxVuZKK.mjs +0 -64
  298. package/.output/server/_ssr/api-BfCgYlj5.mjs +0 -332
  299. package/.output/server/_ssr/api-keys-api-C3oeq01R.mjs +0 -60
  300. package/.output/server/_ssr/api-keys-rpc-CdPFdGOx.mjs +0 -136
  301. package/.output/server/_ssr/api-rpc-B2E1ZIG5.mjs +0 -1399
  302. package/.output/server/_ssr/auth.server-CmicHh0J.mjs +0 -47
  303. package/.output/server/_ssr/bundle-signing-rpc-Di7-I6fE.mjs +0 -16
  304. package/.output/server/_ssr/bundle-signing.server-Uqt1OF49.mjs +0 -12
  305. package/.output/server/_ssr/bundleActivity-pBrPSG01.mjs +0 -28
  306. package/.output/server/_ssr/card-Bemj0dpY.mjs +0 -108
  307. package/.output/server/_ssr/config-Dv8qLDZ0.mjs +0 -13
  308. package/.output/server/_ssr/config.server-B8WsT3s0.mjs +0 -368
  309. package/.output/server/_ssr/databaseRows-5bCRTxm-.mjs +0 -126
  310. package/.output/server/_ssr/deleteBundle-8X-jq7Z7.mjs +0 -66
  311. package/.output/server/_ssr/field-BrQ2u0fT.mjs +0 -74
  312. package/.output/server/_ssr/getBundleChildren-DoL8l_AS.mjs +0 -50
  313. package/.output/server/_ssr/insights-BwQ-wF2a.mjs +0 -10
  314. package/.output/server/_ssr/insights-gr1I_dJU.mjs +0 -938
  315. package/.output/server/_ssr/insights-recovery-Cv6RSyeR.mjs +0 -21
  316. package/.output/server/_ssr/insights-recovery-rpc-DOzxfYfv.mjs +0 -37
  317. package/.output/server/_ssr/insights-search-1czJMZgk.mjs +0 -21
  318. package/.output/server/_ssr/insights-usage-rpc-4ImOq_0p.mjs +0 -184
  319. package/.output/server/_ssr/insights-usage-rpc-BUruODjE.mjs +0 -24
  320. package/.output/server/_ssr/insightsContract-B09K_Px1.mjs +0 -303
  321. package/.output/server/_ssr/insightsHistory-Ci4rYAgl.mjs +0 -41
  322. package/.output/server/_ssr/insightsRecovery-CaghWL2t.mjs +0 -113
  323. package/.output/server/_ssr/insightsUsage-DNIj6jxF.mjs +0 -71
  324. package/.output/server/_ssr/insights_.distribution-VmHuikRE.mjs +0 -10
  325. package/.output/server/_ssr/installations-HXMF_kKf.mjs +0 -29
  326. package/.output/server/_ssr/routes-Dru3IRj0.mjs +0 -37
  327. package/.output/server/_ssr/runtime.server-CsiKFp_Z.mjs +0 -436
  328. package/.output/server/_ssr/server-DJm72Kj-.mjs +0 -7468
  329. package/.output/server/_ssr/signing-H8Lg4lPe.mjs +0 -133
  330. package/.output/server/_ssr/start-DpViQGnt.mjs +0 -4
  331. package/.output/server/_tanstack-start-manifest_v-DsFcTLw_.mjs +0 -125
  332. package/src/components/features/bundles/BundleActivityChart.tsx +0 -97
  333. package/src/components/features/bundles/BundleInsightsSummary.tsx +0 -126
  334. package/src/components/features/signing/BundleSigningPage.tsx +0 -139
  335. package/src/lib/bundle-signing-api.ts +0 -14
  336. package/src/lib/bundle-signing-rpc.ts +0 -16
  337. package/src/lib/server/bundle-signing.server.ts +0 -23
  338. package/src/lib/server/deleteBundle.ts +0 -145
  339. package/src/lib/server/insightsHistory.ts +0 -52
  340. package/src/lib/server/insightsRecovery.ts +0 -187
  341. package/src/routes/signing.tsx +0 -37
@@ -0,0 +1,214 @@
1
+ import { useQueries, useQuery } from "@tanstack/react-query";
2
+
3
+ import { insightsPeriodEnd, recoveryWindows } from "./insights-recovery";
4
+ import type { InsightsWindow } from "./insights-rpc";
5
+ import type { HealthChart } from "./insights-search";
6
+ import {
7
+ DEFAULT_ADOPTION_RELEASES,
8
+ type AdoptionRelease,
9
+ type BundleEventType,
10
+ type BundleEventsSeries,
11
+ MAX_ADOPTION_RELEASES,
12
+ } from "./release-adoption";
13
+ import {
14
+ getAdoptionReleaseRpc,
15
+ getBundleEventsRpc,
16
+ listAdoptionReleasesRpc,
17
+ } from "./release-adoption-rpc";
18
+
19
+ /** What each Release health chart reads per bundle. */
20
+ const READS: Record<HealthChart, readonly BundleEventType[]> = {
21
+ adoption: ["DOWNLOADED", "LAUNCHED"],
22
+ crashes: ["LAUNCHED", "RECOVERED"],
23
+ };
24
+
25
+ /** One compared bundle and the counts read for it so far. */
26
+ export type ComparedBundle = {
27
+ readonly release: AdoptionRelease;
28
+ readonly downloaded?: BundleEventsSeries;
29
+ readonly launched?: BundleEventsSeries;
30
+ readonly recovered?: BundleEventsSeries;
31
+ readonly error: Error | null;
32
+ };
33
+
34
+ /** What Release health shows: the period, the bundles, their reports. */
35
+ export type ReleaseHealthState = {
36
+ readonly period: {
37
+ readonly startMs: number;
38
+ readonly endMs: number;
39
+ readonly durationMs: number;
40
+ readonly intervalMs: number;
41
+ };
42
+ /** The newest bundle deployments, newest first; undefined while loading. */
43
+ readonly candidates: readonly AdoptionRelease[] | undefined;
44
+ /** The compared bundles; undefined until they are known. */
45
+ readonly releases: readonly ComparedBundle[] | undefined;
46
+ /** True when the bundles are the default, not a choice. */
47
+ readonly isDefault: boolean;
48
+ readonly error: Error | null;
49
+ readonly isFetching: boolean;
50
+ readonly refresh: () => void;
51
+ };
52
+
53
+ /**
54
+ * Release health's reads, each keyed by what it depends on: the newest
55
+ * deployments by channel and platform, kept five minutes; a bundle the
56
+ * newest leave out, kept; and one count series per bundle and report type,
57
+ * so adding a bundle or opening the other chart reads only what is new.
58
+ */
59
+ export function useReleaseHealth({
60
+ platform,
61
+ channel,
62
+ window,
63
+ releaseIds,
64
+ focusReleaseId,
65
+ chart,
66
+ }: {
67
+ readonly platform: "ios" | "android";
68
+ readonly channel: string;
69
+ readonly window: InsightsWindow;
70
+ /** Chosen bundles; undefined, the focus and its previous, or the newest. */
71
+ readonly releaseIds: readonly string[] | undefined;
72
+ readonly focusReleaseId: string | undefined;
73
+ readonly chart: HealthChart;
74
+ }): ReleaseHealthState {
75
+ const { durationMs, intervalMs } = recoveryWindows[window];
76
+ // Every series of one chart reads the same hours: the period ends with the
77
+ // current hour, so a new hour reads them all again, together.
78
+ const endMs = insightsPeriodEnd(Date.now());
79
+ const scope = { platform, channel };
80
+ const candidatesQuery = useQuery({
81
+ queryKey: ["insights", "adoption-releases", scope],
82
+ queryFn: () => listAdoptionReleasesRpc({ data: scope }),
83
+ staleTime: 5 * 60_000,
84
+ });
85
+ const candidates = candidatesQuery.data;
86
+ const focusIndex =
87
+ focusReleaseId === undefined || candidates === undefined
88
+ ? -1
89
+ : candidates.findIndex((release) => release.releaseId === focusReleaseId);
90
+ // A focus among the newest has its previous bundle there too, unless it is
91
+ // the oldest of them.
92
+ const focusQuery = useQuery({
93
+ queryKey: ["insights", "adoption-release", scope, focusReleaseId, true],
94
+ queryFn: () =>
95
+ getAdoptionReleaseRpc({
96
+ data: { ...scope, releaseId: focusReleaseId!, withPrevious: true },
97
+ }),
98
+ enabled:
99
+ releaseIds === undefined &&
100
+ focusReleaseId !== undefined &&
101
+ candidates !== undefined &&
102
+ (focusIndex === -1 || focusIndex === candidates.length - 1),
103
+ staleTime: Number.POSITIVE_INFINITY,
104
+ });
105
+ const known = new Map<string, AdoptionRelease>();
106
+ for (const release of [
107
+ ...(focusQuery.data?.release ? [focusQuery.data.release] : []),
108
+ ...(focusQuery.data?.previous ? [focusQuery.data.previous] : []),
109
+ // The newest list last, so a rolled-back bundle shows as it is now.
110
+ ...(candidates ?? []),
111
+ ])
112
+ known.set(release.releaseId, release);
113
+ const newest = (list: readonly AdoptionRelease[]) =>
114
+ list
115
+ .slice(0, DEFAULT_ADOPTION_RELEASES)
116
+ .map((release) => release.releaseId);
117
+ const chosen: readonly string[] | undefined =
118
+ releaseIds !== undefined
119
+ ? releaseIds.slice(0, MAX_ADOPTION_RELEASES)
120
+ : candidates === undefined
121
+ ? undefined
122
+ : focusReleaseId === undefined
123
+ ? newest(candidates)
124
+ : focusIndex !== -1 && focusIndex < candidates.length - 1
125
+ ? [focusReleaseId, candidates[focusIndex + 1]!.releaseId]
126
+ : focusQuery.data === undefined
127
+ ? undefined
128
+ : focusQuery.data.release === null
129
+ ? // A focus that is no bundle of this scope: the newest.
130
+ newest(candidates)
131
+ : [
132
+ focusQuery.data.release.releaseId,
133
+ focusQuery.data.previous?.releaseId,
134
+ ].filter((id) => id !== undefined);
135
+ // A chosen bundle the newest leave out, as from a copied link.
136
+ const unknown = (chosen ?? []).filter(
137
+ (releaseId) => !known.has(releaseId) && releaseId !== focusReleaseId,
138
+ );
139
+ const labelQueries = useQueries({
140
+ queries: unknown.map((releaseId) => ({
141
+ queryKey: ["insights", "adoption-release", scope, releaseId, false],
142
+ queryFn: () => getAdoptionReleaseRpc({ data: { ...scope, releaseId } }),
143
+ staleTime: Number.POSITIVE_INFINITY,
144
+ })),
145
+ });
146
+ for (const query of labelQueries)
147
+ if (query.data?.release)
148
+ known.set(query.data.release.releaseId, query.data.release);
149
+ const releases =
150
+ chosen === undefined || labelQueries.some((query) => query.isPending)
151
+ ? undefined
152
+ : chosen.flatMap((releaseId) => {
153
+ const release = known.get(releaseId);
154
+ return release === undefined ? [] : [release];
155
+ });
156
+ const reads = (releases ?? []).flatMap((release) =>
157
+ READS[chart].map((type) => ({ release, type })),
158
+ );
159
+ const seriesQueries = useQueries({
160
+ queries: reads.map(({ release, type }) => {
161
+ const data = {
162
+ ...scope,
163
+ window,
164
+ endMs,
165
+ bundleId: release.bundleId,
166
+ type,
167
+ };
168
+ return {
169
+ queryKey: ["insights", "bundle-events", data],
170
+ queryFn: () => getBundleEventsRpc({ data }),
171
+ staleTime: 30_000,
172
+ // Keep a bundle's line while its next period loads.
173
+ placeholderData: (
174
+ previous: BundleEventsSeries | undefined,
175
+ ): BundleEventsSeries | undefined =>
176
+ previous?.bundleId === release.bundleId && previous.type === type
177
+ ? previous
178
+ : undefined,
179
+ };
180
+ }),
181
+ });
182
+ const seriesOf = (bundleId: string, type: BundleEventType) => {
183
+ const index = reads.findIndex(
184
+ (read) => read.release.bundleId === bundleId && read.type === type,
185
+ );
186
+ return index === -1 ? undefined : seriesQueries[index];
187
+ };
188
+ return {
189
+ period: { startMs: endMs - durationMs, endMs, durationMs, intervalMs },
190
+ candidates,
191
+ releases: releases?.map((release) => {
192
+ const downloaded = seriesOf(release.bundleId, "DOWNLOADED");
193
+ const launched = seriesOf(release.bundleId, "LAUNCHED");
194
+ const recovered = seriesOf(release.bundleId, "RECOVERED");
195
+ return {
196
+ release,
197
+ downloaded: downloaded?.data,
198
+ launched: launched?.data,
199
+ recovered: recovered?.data,
200
+ error: downloaded?.error ?? launched?.error ?? recovered?.error ?? null,
201
+ };
202
+ }),
203
+ isDefault: releaseIds === undefined,
204
+ error: candidatesQuery.error ?? focusQuery.error ?? null,
205
+ isFetching:
206
+ candidatesQuery.isFetching ||
207
+ focusQuery.isFetching ||
208
+ seriesQueries.some((query) => query.isFetching),
209
+ refresh: () => {
210
+ void candidatesQuery.refetch();
211
+ for (const query of seriesQueries) void query.refetch();
212
+ },
213
+ };
214
+ }
@@ -0,0 +1,49 @@
1
+ import { createServerFn } from "@tanstack/react-start";
2
+
3
+ import { consoleAccess } from "./console-access";
4
+ import {
5
+ readAdoptionReleaseInput,
6
+ readAdoptionReleasesInput,
7
+ readBundleEventsInput,
8
+ } from "./release-adoption";
9
+
10
+ export const listAdoptionReleasesRpc = createServerFn({ method: "GET" })
11
+ .middleware([consoleAccess])
12
+ .validator(readAdoptionReleasesInput)
13
+ .handler(async ({ data }) => {
14
+ const [{ prepareConfig }, { listAdoptionReleases }] = await Promise.all([
15
+ import("./server/config.server"),
16
+ import("./server/releaseAdoption"),
17
+ ]);
18
+ const { core } = await prepareConfig();
19
+ return listAdoptionReleases(core, data);
20
+ });
21
+
22
+ export const getAdoptionReleaseRpc = createServerFn({ method: "GET" })
23
+ .middleware([consoleAccess])
24
+ .validator(readAdoptionReleaseInput)
25
+ .handler(async ({ data }) => {
26
+ const [{ prepareConfig }, { getAdoptionRelease }] = await Promise.all([
27
+ import("./server/config.server"),
28
+ import("./server/releaseAdoption"),
29
+ ]);
30
+ const { core } = await prepareConfig();
31
+ return getAdoptionRelease(core, data);
32
+ });
33
+
34
+ export const getBundleEventsRpc = createServerFn({ method: "GET" })
35
+ .middleware([consoleAccess])
36
+ .validator(readBundleEventsInput)
37
+ .handler(async ({ data }) => {
38
+ const [{ prepareConfig }, { getBundleEvents }, { requireFeature }] =
39
+ await Promise.all([
40
+ import("./server/config.server"),
41
+ import("./server/releaseAdoption"),
42
+ import("./server/runtime.server"),
43
+ ]);
44
+ const { runtime } = await prepareConfig();
45
+ return getBundleEvents(
46
+ await requireFeature(runtime, "insightsAnalytics"),
47
+ data,
48
+ );
49
+ });
@@ -0,0 +1,164 @@
1
+ import { recoveryWindows } from "./insights-recovery";
2
+ import type { InsightsWindow } from "./insights-rpc";
3
+
4
+ /**
5
+ * Bundles Release health compares at most: the newest two and two more.
6
+ * Each is a read of its counts over the period, and four hues stay apart.
7
+ */
8
+ export const MAX_ADOPTION_RELEASES = 4;
9
+ /** Bundles Release health compares when none are chosen: the newest two. */
10
+ export const DEFAULT_ADOPTION_RELEASES = 2;
11
+ /** Bundle deployments Release health offers to add. */
12
+ export const ADOPTION_CANDIDATES = 10;
13
+ /** A crash rate at which Release health recommends rolling a bundle back. */
14
+ export const CRASH_RATE_THRESHOLD = 0.05;
15
+ /** Attempts below which a crash rate is too noisy to recommend anything. */
16
+ export const CRASH_MIN_ATTEMPTS = 20;
17
+
18
+ /** A deployed bundle, as Release health labels and rolls it back. */
19
+ export type AdoptionRelease = {
20
+ readonly releaseId: string;
21
+ readonly bundleId: string;
22
+ readonly deployedAtMs: number;
23
+ readonly targetAppVersion: string | null;
24
+ readonly enabled: boolean;
25
+ readonly revision: number;
26
+ };
27
+
28
+ /** A channel and platform's newest bundle deployments. */
29
+ export type AdoptionReleasesInput = {
30
+ readonly platform: "ios" | "android";
31
+ readonly channel: string;
32
+ };
33
+
34
+ /** One bundle deployment, and with `withPrevious` the one before it. */
35
+ export type AdoptionReleaseInput = AdoptionReleasesInput & {
36
+ readonly releaseId: string;
37
+ readonly withPrevious?: boolean;
38
+ };
39
+
40
+ export type AdoptionReleaseResult = {
41
+ readonly release: AdoptionRelease | null;
42
+ readonly previous?: AdoptionRelease | null;
43
+ };
44
+
45
+ /**
46
+ * What Release health charts per bundle: downloads (download reports, and
47
+ * the downloads a launch or crash implied when its download report never
48
+ * arrived), launches (apply reports, and the kept reports that moved an
49
+ * installation to the bundle with no apply report), and recoveries.
50
+ */
51
+ export type BundleEventType = "DOWNLOADED" | "LAUNCHED" | "RECOVERED";
52
+
53
+ /** One bundle's reports of one type over the period that ends at `endMs`. */
54
+ export type BundleEventsInput = AdoptionReleasesInput & {
55
+ readonly window: InsightsWindow;
56
+ /** The end of an hour; every bundle of one chart reads the same one. */
57
+ readonly endMs: number;
58
+ readonly bundleId: string;
59
+ readonly type: BundleEventType;
60
+ };
61
+
62
+ /**
63
+ * A bundle's counts of one type in each interval of the period, every
64
+ * interval present: downloads of it, launches of it, or recoveries from it.
65
+ * Each counts an installation once when it downloads the bundle, first
66
+ * reports running it, or crashes on it; one that comes back counts again.
67
+ */
68
+ export type BundleEventsSeries = {
69
+ readonly bundleId: string;
70
+ readonly type: BundleEventType;
71
+ readonly measuredAtMs: number;
72
+ readonly points: readonly {
73
+ readonly startMs: number;
74
+ readonly events: number;
75
+ }[];
76
+ readonly total: number;
77
+ };
78
+
79
+ const HOUR_MS = 3_600_000;
80
+
81
+ const isText = (value: unknown): value is string =>
82
+ typeof value === "string" && value.trim().length > 0 && value.length <= 1024;
83
+
84
+ const invalid = (): never => {
85
+ throw new Error("Choose a platform, channel, period, and bundle.");
86
+ };
87
+
88
+ export function readAdoptionReleasesInput(
89
+ input: AdoptionReleasesInput,
90
+ ): AdoptionReleasesInput {
91
+ if (
92
+ !input ||
93
+ (input.platform !== "ios" && input.platform !== "android") ||
94
+ !isText(input.channel)
95
+ )
96
+ invalid();
97
+ return { platform: input.platform, channel: input.channel };
98
+ }
99
+
100
+ export function readAdoptionReleaseInput(
101
+ input: AdoptionReleaseInput,
102
+ ): AdoptionReleaseInput {
103
+ const scope = readAdoptionReleasesInput(input);
104
+ if (
105
+ !isText(input.releaseId) ||
106
+ (input.withPrevious !== undefined &&
107
+ typeof input.withPrevious !== "boolean")
108
+ )
109
+ invalid();
110
+ return {
111
+ ...scope,
112
+ releaseId: input.releaseId,
113
+ ...(input.withPrevious ? { withPrevious: true } : {}),
114
+ };
115
+ }
116
+
117
+ export function readBundleEventsInput(
118
+ input: BundleEventsInput,
119
+ ): BundleEventsInput {
120
+ const scope = readAdoptionReleasesInput(input);
121
+ if (
122
+ !Object.hasOwn(recoveryWindows, input.window) ||
123
+ typeof input.endMs !== "number" ||
124
+ !Number.isSafeInteger(input.endMs) ||
125
+ input.endMs <= 0 ||
126
+ input.endMs % HOUR_MS !== 0 ||
127
+ !isText(input.bundleId) ||
128
+ (input.type !== "DOWNLOADED" &&
129
+ input.type !== "LAUNCHED" &&
130
+ input.type !== "RECOVERED")
131
+ )
132
+ invalid();
133
+ return {
134
+ ...scope,
135
+ window: input.window,
136
+ endMs: input.endMs,
137
+ bundleId: input.bundleId,
138
+ type: input.type,
139
+ };
140
+ }
141
+
142
+ /**
143
+ * Of the installations that tried a bundle, the share whose launch crashed
144
+ * and rolled back: one that crashes never reports a launch, so attempts
145
+ * count both.
146
+ */
147
+ export const crashRateOf = (launched: number, recovered: number) => {
148
+ const attempts = launched + recovered;
149
+ return { attempts, rate: attempts === 0 ? 0 : recovered / attempts };
150
+ };
151
+
152
+ /** Whether a bundle's crashes call for rolling it back. */
153
+ export const recommendsRollback = (
154
+ release: Pick<AdoptionRelease, "enabled">,
155
+ launched: number,
156
+ recovered: number,
157
+ ) => {
158
+ const { attempts, rate } = crashRateOf(launched, recovered);
159
+ return (
160
+ release.enabled &&
161
+ attempts >= CRASH_MIN_ATTEMPTS &&
162
+ rate >= CRASH_RATE_THRESHOLD
163
+ );
164
+ };
@@ -0,0 +1,119 @@
1
+ import {
2
+ keepPreviousData,
3
+ useInfiniteQuery,
4
+ useMutation,
5
+ useQuery,
6
+ useQueryClient,
7
+ } from "@tanstack/react-query";
8
+
9
+ import type { RemoteConfigTemplate } from "./remote-config-draft";
10
+ import {
11
+ getRemoteConfigRpc,
12
+ getRemoteConfigVersionRpc,
13
+ listRemoteConfigVersionsRpc,
14
+ previewRemoteConfigRpc,
15
+ publishRemoteConfigRpc,
16
+ rollbackRemoteConfigRpc,
17
+ } from "./remote-config-rpc";
18
+
19
+ export const remoteConfigQueryKeys = {
20
+ all: ["remote-config"] as const,
21
+ active: ["remote-config", "active"] as const,
22
+ versions: ["remote-config", "versions"] as const,
23
+ version: (version: number) => ["remote-config", "version", version] as const,
24
+ preview: (input: unknown) => ["remote-config", "preview", input] as const,
25
+ };
26
+
27
+ /** The published template, read once a visit unless a publish changes it. */
28
+ export const useRemoteConfigQuery = () =>
29
+ useQuery({
30
+ queryKey: remoteConfigQueryKeys.active,
31
+ queryFn: () => getRemoteConfigRpc(),
32
+ staleTime: 60_000,
33
+ });
34
+
35
+ /** Published versions, newest first, a page at a time. */
36
+ export const useRemoteConfigVersionsQuery = ({
37
+ enabled,
38
+ }: {
39
+ readonly enabled: boolean;
40
+ }) =>
41
+ useInfiniteQuery({
42
+ queryKey: remoteConfigQueryKeys.versions,
43
+ queryFn: ({ pageParam }) =>
44
+ listRemoteConfigVersionsRpc({
45
+ data: pageParam === undefined ? {} : { cursor: pageParam },
46
+ }),
47
+ initialPageParam: undefined as string | undefined,
48
+ getNextPageParam: (page) => page.next,
49
+ staleTime: 60_000,
50
+ enabled,
51
+ });
52
+
53
+ /** One version with its template; a version never changes once published. */
54
+ export const useRemoteConfigVersionQuery = (version: number | null) =>
55
+ useQuery({
56
+ queryKey: remoteConfigQueryKeys.version(version ?? 0),
57
+ queryFn: () => getRemoteConfigVersionRpc({ data: { version: version! } }),
58
+ enabled: version !== null,
59
+ staleTime: Infinity,
60
+ });
61
+
62
+ const useRefreshAfterPublish = () => {
63
+ const queryClient = useQueryClient();
64
+ return async () => {
65
+ await Promise.all([
66
+ queryClient.invalidateQueries({ queryKey: remoteConfigQueryKeys.active }),
67
+ queryClient.invalidateQueries({
68
+ queryKey: remoteConfigQueryKeys.versions,
69
+ }),
70
+ ]);
71
+ };
72
+ };
73
+
74
+ export const usePublishRemoteConfigMutation = () => {
75
+ const refresh = useRefreshAfterPublish();
76
+ return useMutation({
77
+ mutationFn: (input: {
78
+ readonly template: RemoteConfigTemplate;
79
+ readonly baseVersion: number;
80
+ readonly description?: string;
81
+ }) => publishRemoteConfigRpc({ data: input }),
82
+ onSuccess: async (result) => {
83
+ if (result.status === "published" || result.status === "conflict") {
84
+ await refresh();
85
+ }
86
+ },
87
+ });
88
+ };
89
+
90
+ export const useRollbackRemoteConfigMutation = () => {
91
+ const refresh = useRefreshAfterPublish();
92
+ return useMutation({
93
+ mutationFn: (input: {
94
+ readonly version: number;
95
+ readonly baseVersion: number;
96
+ }) => rollbackRemoteConfigRpc({ data: input }),
97
+ onSuccess: refresh,
98
+ });
99
+ };
100
+
101
+ /** A device's view of the draft, kept on screen while the next one loads. */
102
+ export const useRemoteConfigPreviewQuery = (input: {
103
+ readonly template: RemoteConfigTemplate;
104
+ /** The device's fields, and `now` to evaluate at a chosen time. */
105
+ readonly context: {
106
+ readonly platform?: string;
107
+ readonly channel?: string;
108
+ readonly appVersion?: string;
109
+ readonly cohort?: string;
110
+ readonly fingerprintHash?: string;
111
+ readonly now?: number;
112
+ };
113
+ }) =>
114
+ useQuery({
115
+ queryKey: remoteConfigQueryKeys.preview(input),
116
+ queryFn: () => previewRemoteConfigRpc({ data: input }),
117
+ placeholderData: keepPreviousData,
118
+ staleTime: Infinity,
119
+ });