@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.
- package/.output/nitro.json +1 -1
- package/.output/public/assets/ConsoleFeatureUnavailable-D0LPL1Gy.js +1 -0
- package/.output/public/assets/HashValueDisplay-BmuB4FW-.js +1 -0
- package/.output/public/assets/InsightsInfo--v4SC07c.js +1 -0
- package/.output/public/assets/InsightsPageHeader-dU83CAQY.js +1 -0
- package/.output/public/assets/NotFoundPage-B5mhGG2a.js +1 -0
- package/.output/public/assets/api-D-vOVlQ4.js +1 -0
- package/.output/public/assets/api-keys-Csh8ARKU.js +1 -0
- package/.output/public/assets/api-keys-OMPUoA_A.js +1 -0
- package/.output/public/assets/arrow-left-Bwq9X1eS.js +1 -0
- package/.output/public/assets/arrow-up-right-BkOVn4if.js +1 -0
- package/.output/public/assets/card-C_7d6O9a.js +1 -0
- package/.output/public/assets/console-features-api-C1jRZTGw.js +1 -0
- package/.output/public/assets/dialog-DE8wFN_P.js +1 -0
- package/.output/public/assets/dist-C7ENGzpS.js +1 -0
- package/.output/public/assets/empty-CVd5NlQD.js +1 -0
- package/.output/public/assets/field-C06q1iC9.js +1 -0
- package/.output/public/assets/index-BeWlQtO6.js +11 -0
- package/.output/public/assets/input-group-Bi61biIA.js +1 -0
- package/.output/public/assets/insights-BjnAtC0f.js +2 -0
- package/.output/public/assets/insights-D86m11yH.js +42 -0
- package/.output/public/assets/insights-DpYstMoY.js +1 -0
- package/.output/public/assets/insights-recovery-rpc-BIm5_ALc.js +1 -0
- package/.output/public/assets/insights-search-KIF7mRtB.js +1 -0
- package/.output/public/assets/insights-usage-rpc-VHj8vyBt.js +1 -0
- package/.output/public/assets/insights_.distribution-ADcrX48c.js +2 -0
- package/.output/public/assets/insights_.distribution-CuuJUN6Y.js +1 -0
- package/.output/public/assets/insights_.distribution-DJRjuXsO.js +1 -0
- package/.output/public/assets/installations-BSBBzqWs.js +2 -0
- package/.output/public/assets/installations-DqIE6WcO.js +1 -0
- package/.output/public/assets/installations-UsELbDvw.js +1 -0
- package/.output/public/assets/loader-circle-CnCBxzHp.js +1 -0
- package/.output/public/assets/mutation-Csp7xUNh.js +1 -0
- package/.output/public/assets/preload-helper-B-cOmpLH.js +1 -0
- package/.output/public/assets/refresh-cw-FF1NtzlQ.js +1 -0
- package/.output/public/assets/routes-CoN9XHP_.js +2 -0
- package/.output/public/assets/routes-DT1jB0Zi.js +1 -0
- package/.output/public/assets/sidebar-D8mJRF15.js +3 -0
- package/.output/public/assets/styles-Bwt7v6U5.css +2 -0
- package/.output/public/assets/table-WBgfvgXg.js +1 -0
- package/.output/public/assets/useCompositeListItem-CloBmDwn.js +1 -0
- package/.output/public/assets/useMutation-DO8cvsTx.js +1 -0
- package/.output/public/assets/useOpenInteractionType-ReGr9GzJ.js +1 -0
- package/.output/public/assets/valueToPercent-BvPAi4bP.js +1 -0
- package/.output/server/{__23tanstack-start-server-fn-resolver-B7ioJhtE.mjs → __23tanstack-start-server-fn-resolver-DMRD4e4f.mjs} +51 -47
- package/.output/server/_libs/@base-ui/react+[...].mjs +7044 -3622
- package/.output/server/_libs/@reduxjs/toolkit+[...].mjs +1554 -0
- package/.output/server/_libs/@tanstack/react-router+[...].mjs +3803 -4023
- package/.output/server/_libs/@tanstack/react-router-ssr-query+[...].mjs +141 -98
- package/.output/server/_libs/d3-array.mjs +231 -0
- package/.output/server/_libs/d3-color.mjs +330 -0
- package/.output/server/_libs/d3-format.mjs +252 -0
- package/.output/server/_libs/d3-interpolate.mjs +207 -0
- package/.output/server/_libs/d3-path.mjs +77 -0
- package/.output/server/_libs/d3-scale+[...].mjs +1907 -0
- package/.output/server/_libs/d3-shape.mjs +818 -0
- package/.output/server/_libs/decimal.js-light.mjs +884 -0
- package/.output/server/_libs/es-toolkit.mjs +1159 -0
- package/.output/server/_libs/eventemitter3.mjs +236 -0
- package/.output/server/_libs/immer.mjs +532 -0
- package/.output/server/_libs/lucide-react.mjs +46 -32
- package/.output/server/_libs/next-themes.mjs +2 -2
- package/.output/server/_libs/react-redux+[...].mjs +205 -0
- package/.output/server/_libs/recharts+[...].mjs +14350 -0
- package/.output/server/_libs/sonner.mjs +2 -2
- package/.output/server/_libs/tanstack__history.mjs +68 -41
- package/.output/server/_libs/tanstack__query-core.mjs +1618 -309
- package/.output/server/_libs/tanstack__react-query.mjs +262 -22
- package/.output/server/_libs/verkit.mjs +99 -1
- package/.output/server/_runtime.mjs +10 -1
- package/.output/server/_ssr/ConsoleFeatureUnavailable-Du7jNPNS.mjs +58 -0
- package/.output/server/_ssr/{HashValueDisplay-DwpGLfrl.mjs → HashValueDisplay-GQvFkXoi.mjs} +3 -3
- package/.output/server/_ssr/InsightsInfo-Do7QSaFR.mjs +37 -0
- package/.output/server/_ssr/InsightsPageHeader-DSUWUWRc.mjs +154 -0
- package/.output/server/_ssr/NotFoundPage-DsF4Vrhg.mjs +37 -0
- package/.output/server/_ssr/api-BEiVs0H7.mjs +203 -0
- package/.output/server/_ssr/api-keys-BrvBWGRf.mjs +5 -0
- package/.output/server/_ssr/{api-keys-CZeTPQys.mjs → api-keys-K5v32nf7.mjs} +49 -9
- package/.output/server/_ssr/api-keys-rpc-B96bBllm.mjs +52 -0
- package/.output/server/_ssr/api-rpc-DcUe280B.mjs +296 -0
- package/.output/server/_ssr/{auth-rpc-DUaeOWKK.mjs → auth-rpc-C68W56DP.mjs} +6 -6
- package/.output/server/_ssr/auth.server-DM1J6NwR.mjs +5712 -0
- package/.output/server/_ssr/bundleActivity-CpuY9CTP.mjs +19 -0
- package/.output/server/_ssr/card-CPAEKx0w.mjs +49 -0
- package/.output/server/_ssr/config-nsIcsHh8.mjs +15 -0
- package/.output/server/_ssr/config.server-DDCZk3Sq.mjs +36 -0
- package/.output/server/_ssr/console-access-Da-2Qd7q.mjs +17 -0
- package/.output/server/_ssr/console-features-Dzu2iUGD.mjs +55 -0
- package/.output/server/_ssr/console-features-api-xAsSgFa_.mjs +43 -0
- package/.output/server/_ssr/console-features-rpc-qARGpB24.mjs +19 -0
- package/.output/server/_ssr/{createServerFn-B2hcXoPM.mjs → createMiddleware-BdBf_ezN.mjs} +67 -56
- package/.output/server/_ssr/{createServerRpc-DB29C_5p.mjs → createServerRpc-B_y2lrwA.mjs} +2 -2
- package/.output/server/_ssr/{databaseJsonValue-d6I3nAS4.mjs → databaseJsonValue-Bhxciy7Z.mjs} +28 -5
- package/.output/server/_ssr/databaseRows-DwMAjaF8.mjs +74 -0
- package/.output/server/_ssr/{dialog-DS-nw-pA.mjs → dialog-q__BJ7mQ.mjs} +2 -2
- package/.output/server/_ssr/{dist-CyzPaI5v.mjs → dist-Cxo9ghYv.mjs} +140 -6
- package/.output/server/_ssr/{downloadBundle-CNnYxYO2.mjs → downloadBundle-BW9I-pMQ.mjs} +10 -10
- package/.output/server/_ssr/empty-DcBTAooP.mjs +49 -0
- package/.output/server/_ssr/{empty-plugin-adapters-B5QvGpEl.mjs → empty-plugin-adapters-CYdinE-i.mjs} +1 -1
- package/.output/server/_ssr/{table-CnZmon_Z.mjs → field-QCDBVpXA.mjs} +63 -51
- package/.output/server/_ssr/getBundleChildren-DIAGCF4a.mjs +22 -0
- package/.output/server/_ssr/{input-group-BpsnmvZw.mjs → input-group-hCk2nUoU.mjs} +2 -2
- package/.output/server/_ssr/insights-BfBO0RFr.mjs +1238 -0
- package/.output/server/_ssr/insights-DsAU3-TB.mjs +14 -0
- package/.output/server/_ssr/insights-RIfg447W.mjs +5 -0
- package/.output/server/_ssr/insights-api-CDn-QQHU.mjs +105 -0
- package/.output/server/_ssr/insights-failures-BBGMhuUr.mjs +44 -0
- package/.output/server/_ssr/insights-failures-rpc-rA2dctK2.mjs +21 -0
- package/.output/server/_ssr/insights-recovery-Dpk6Mfb9.mjs +28 -0
- package/.output/server/_ssr/insights-recovery-rpc-BCaF-xSd.mjs +46 -0
- package/.output/server/_ssr/insights-recovery-rpc-CcgYyUHk.mjs +20 -0
- package/.output/server/_ssr/{insights-rpc-38DujK0G.mjs → insights-rpc-keycmwzw.mjs} +22 -14
- package/.output/server/_ssr/insights-search-BRg5VsNe.mjs +25 -0
- package/.output/server/_ssr/insights-usage-cbw9n2Fq.mjs +29 -0
- package/.output/server/_ssr/insights-usage-rpc-BKgV1QDf.mjs +29 -0
- package/.output/server/_ssr/insights-usage-rpc-D-JYachy.mjs +41 -0
- package/.output/server/_ssr/insightsRecovery-B7oMc8d8.mjs +45 -0
- package/.output/server/_ssr/insightsUsage-Oa7kk5ek.mjs +56 -0
- package/.output/server/_ssr/insights_.distribution-BIqOqRW0.mjs +335 -0
- package/.output/server/_ssr/insights_.distribution-CuUrDa7P.mjs +5 -0
- package/.output/server/_ssr/insights_.distribution-RT_6x_ud.mjs +14 -0
- package/.output/server/_ssr/{installations-DiVKEF5T.mjs → installations-BO5CjX2z.mjs} +517 -54
- package/.output/server/_ssr/installations-BjaHiIfh.mjs +5 -0
- package/.output/server/_ssr/installations-Dt5luA-N.mjs +69 -0
- package/.output/server/_ssr/listReleases-BitHzOYB.mjs +79 -0
- package/.output/server/_ssr/releaseManagement-B3e7I8J9.mjs +11 -0
- package/.output/server/_ssr/{router-B9wG_9f8.mjs → router-DfVAixxe.mjs} +135 -139
- package/.output/server/_ssr/{routes-D7Yh_jfj.mjs → routes-C6LstNp8.mjs} +176 -455
- package/.output/server/_ssr/routes-Q3Uhmwwz.mjs +292 -0
- package/.output/server/_ssr/runtime.server-fL0o6I2b.mjs +1459 -0
- package/.output/server/_ssr/schema-BKY9SqaY.mjs +25 -0
- package/.output/server/_ssr/select-CUlInmjk.mjs +107 -0
- package/.output/server/_ssr/server-C43wq9ft.mjs +8330 -0
- package/.output/server/_ssr/{createSsrRpc-DjWNJ0uc.mjs → sidebar-DKbx2ach.mjs} +117 -63
- package/.output/server/_ssr/ssr.mjs +1 -1
- package/.output/server/_ssr/start-BXUOv_n8.mjs +4 -0
- package/.output/server/_ssr/table-NGWV8B-S.mjs +53 -0
- package/.output/server/_ssr/updateFailures-Jwe2Baab.mjs +24 -0
- package/.output/server/_ssr/{uuidv7-D0QFkPL_.mjs → uuidv7-C8bwiZr8.mjs} +2 -2
- package/.output/server/_tanstack-start-manifest_v-WQ5qGn8n.mjs +124 -0
- package/.output/server/index.mjs +267 -162
- package/LICENSE +13 -14
- package/README.md +82 -72
- package/lib/index.d.mts +23 -11
- package/lib/vite-internal.mjs +13 -3
- package/lib/vite.d.mts +2 -0
- package/package.json +14 -14
- package/src/components/AppSidebar.tsx +100 -71
- package/src/components/ConsoleAccessPage.tsx +5 -1
- package/src/components/ConsoleFeatureUnavailable.tsx +80 -0
- package/src/components/NotFoundPage.tsx +7 -7
- package/src/components/features/FeatureSlots.tsx +99 -0
- package/src/components/features/api-keys/apiKeysFeatureUi.tsx +15 -0
- package/src/components/features/bundles/BundleMetadata.tsx +8 -5
- package/src/components/features/bundles/RolloutCohortsDialog.tsx +1 -1
- package/src/components/features/channels/ChannelManagementDialog.tsx +2 -5
- package/src/components/features/insights/AppUsage.tsx +370 -0
- package/src/components/features/insights/EstimatedCount.tsx +18 -0
- package/src/components/features/insights/EventDetails.tsx +117 -37
- package/src/components/features/insights/EventHistoryCard.tsx +112 -26
- package/src/components/features/insights/EventHistoryList.tsx +2 -2
- package/src/components/features/insights/InsightsControls.tsx +265 -102
- package/src/components/features/insights/InsightsErrorAlert.tsx +14 -1
- package/src/components/features/insights/InsightsInfo.tsx +48 -0
- package/src/components/features/insights/InsightsOverview.tsx +176 -173
- package/src/components/features/insights/InsightsPageHeader.tsx +35 -20
- package/src/components/features/insights/InsightsPeriodSelector.tsx +89 -0
- package/src/components/features/insights/InstallationHistoryCard.tsx +52 -10
- package/src/components/features/insights/InstallationMatchesCard.tsx +1 -1
- package/src/components/features/insights/ReleaseActivity.tsx +188 -0
- package/src/components/features/insights/ReportingDevicesSummary.tsx +65 -0
- package/src/components/features/insights/UpdateFailures.tsx +319 -0
- package/src/components/features/insights/insightsFeatureUi.tsx +35 -0
- package/src/components/features/releases/ReleaseEditorSheet.tsx +30 -7
- package/src/components/ui/card.tsx +1 -1
- package/src/components/ui/empty.tsx +101 -0
- package/src/components/ui/scroll-area.tsx +52 -0
- package/src/components/ui/sidebar.tsx +22 -29
- package/src/components/ui/tabs.tsx +81 -0
- package/src/index.ts +26 -19
- package/src/lib/api-keys-api.ts +0 -28
- package/src/lib/api-keys-rpc.ts +27 -38
- package/src/lib/api-rpc.ts +189 -266
- package/src/lib/api.ts +19 -102
- package/src/lib/bundle-activity.ts +31 -0
- package/src/lib/console-access.ts +20 -0
- package/src/lib/console-features-api.ts +68 -0
- package/src/lib/console-features-rpc.ts +13 -0
- package/src/lib/console-features.ts +91 -0
- package/src/lib/insights-api.ts +37 -1
- package/src/lib/insights-failures-rpc.ts +21 -0
- package/src/lib/insights-failures.ts +129 -0
- package/src/lib/insights-recovery-rpc.ts +51 -0
- package/src/lib/insights-recovery.ts +68 -0
- package/src/lib/insights-retention.ts +21 -0
- package/src/lib/insights-rpc.ts +30 -12
- package/src/lib/insights-search.ts +58 -0
- package/src/lib/insights-usage-rpc.ts +29 -0
- package/src/lib/insights-usage.ts +74 -0
- package/src/lib/insights-view.ts +38 -2
- package/src/lib/scroll-restoration.ts +21 -0
- package/src/lib/server/adminInsights.ts +124 -0
- package/src/lib/server/auth.server.ts +13 -5
- package/src/lib/server/bundleActivity.ts +37 -0
- package/src/lib/server/config.server.ts +21 -74
- package/src/lib/server/console-feature-apis.ts +59 -0
- package/src/lib/server/console-runtime.server.ts +82 -38
- package/src/lib/server/downloadBundle.ts +16 -15
- package/src/lib/server/getBundleChildren.ts +31 -122
- package/src/lib/server/insightsRecovery.ts +55 -0
- package/src/lib/server/insightsUsage.ts +63 -0
- package/src/lib/server/listReleases.ts +109 -44
- package/src/lib/server/releaseReachability.ts +8 -7
- package/src/lib/server/runtime.server.ts +120 -31
- package/src/lib/server/updateFailures.ts +31 -0
- package/src/routeTree.gen.ts +44 -44
- package/src/router.tsx +3 -2
- package/src/routes/-installations-search.ts +32 -8
- package/src/routes/-releases-search.ts +76 -29
- package/src/routes/__root.tsx +10 -4
- package/src/routes/api/bundles/$bundleId/download.ts +2 -5
- package/src/routes/api-keys.tsx +5 -2
- package/src/routes/index.tsx +167 -104
- package/src/routes/insights.tsx +100 -59
- package/src/routes/insights_.distribution.tsx +398 -0
- package/src/routes/installations.tsx +98 -41
- package/src/vite-internal.ts +15 -3
- package/src/vite.ts +2 -0
- package/.output/public/assets/HashValueDisplay-CB0Eskit.js +0 -1
- package/.output/public/assets/InsightsPageHeader-Car5LNiZ.js +0 -1
- package/.output/public/assets/api-keys-BzKocqRy.js +0 -1
- package/.output/public/assets/api-keys-api-BHcODB7q.js +0 -1
- package/.output/public/assets/card-Dhr_eFYj.js +0 -1
- package/.output/public/assets/chart-no-axes-combined-Dy4WJv2U.js +0 -1
- package/.output/public/assets/composite-Bp98A0Ld.js +0 -1
- package/.output/public/assets/createServerFn-CA8fs7QD.js +0 -3
- package/.output/public/assets/dialog-CUTrcPlB.js +0 -1
- package/.output/public/assets/dist-C_NydL0e.js +0 -1
- package/.output/public/assets/index-DdAz-CXv.js +0 -10
- package/.output/public/assets/input-group-cSOG4wh_.js +0 -1
- package/.output/public/assets/insights-C0PBSZ-n.js +0 -1
- package/.output/public/assets/installations-CE33wgbY.js +0 -2
- package/.output/public/assets/installations-CStZja8x.js +0 -1
- package/.output/public/assets/lazyRouteComponent-Ccp3bwBY.js +0 -1
- package/.output/public/assets/link-CI8idAUi.js +0 -1
- package/.output/public/assets/loader-circle-BoxLrsOu.js +0 -1
- package/.output/public/assets/preload-helper-CQ36nxok.js +0 -1
- package/.output/public/assets/refresh-cw-Bqc72UZG.js +0 -1
- package/.output/public/assets/routes-DGVXdzpw.js +0 -2
- package/.output/public/assets/routes-DTlwOr8I.js +0 -1
- package/.output/public/assets/shield-check-uK3MHWJZ.js +0 -1
- package/.output/public/assets/signing-CMf4aqDO.js +0 -1
- package/.output/public/assets/styles-BCvbtKsm.css +0 -2
- package/.output/public/assets/table-2vcV89QB.js +0 -1
- package/.output/public/assets/useCompositeListItem-CHLITE_4.js +0 -1
- package/.output/public/assets/x-DCd0j8c6.js +0 -1
- package/.output/server/_ssr/InsightsPageHeader-Df4Brj8u.mjs +0 -484
- package/.output/server/_ssr/api-keys-api-BUPQVSpk.mjs +0 -60
- package/.output/server/_ssr/api-keys-rpc-Cs7tS5_j.mjs +0 -136
- package/.output/server/_ssr/api-rpc-tAupfGQm.mjs +0 -1399
- package/.output/server/_ssr/auth.server-DW71WzsD.mjs +0 -47
- package/.output/server/_ssr/bundle-signing-rpc-BhnBw0-d.mjs +0 -16
- package/.output/server/_ssr/bundle-signing.server-C4NW5mWx.mjs +0 -12
- package/.output/server/_ssr/card-BRiPmi0C.mjs +0 -108
- package/.output/server/_ssr/config-Dv8qLDZ0.mjs +0 -13
- package/.output/server/_ssr/config.server-afDuAwaG.mjs +0 -368
- package/.output/server/_ssr/databaseRows-5bCRTxm-.mjs +0 -126
- package/.output/server/_ssr/deleteBundle-BTGwBgzx.mjs +0 -66
- package/.output/server/_ssr/getBundleChildren-C0vh4ZvE.mjs +0 -50
- package/.output/server/_ssr/insights-BdS364Xu.mjs +0 -460
- package/.output/server/_ssr/insightsContract-fjJDBeEE.mjs +0 -303
- package/.output/server/_ssr/installations-CtA59MQG.mjs +0 -29
- package/.output/server/_ssr/routes-CKrrS2q2.mjs +0 -37
- package/.output/server/_ssr/runtime.server-DacU1TBV.mjs +0 -438
- package/.output/server/_ssr/server-D_C2rMzf.mjs +0 -7468
- package/.output/server/_ssr/signing-Dz1WkZOf.mjs +0 -133
- package/.output/server/_ssr/start-DpViQGnt.mjs +0 -4
- package/.output/server/_tanstack-start-manifest_v-2MjTyxil.mjs +0 -97
- package/src/components/features/insights/InsightsBundleReports.tsx +0 -51
- package/src/components/features/signing/BundleSigningPage.tsx +0 -139
- package/src/lib/bundle-signing-api.ts +0 -14
- package/src/lib/bundle-signing-rpc.ts +0 -16
- package/src/lib/server/bundle-signing.server.ts +0 -23
- package/src/lib/server/deleteBundle.ts +0 -145
- package/src/routes/signing.tsx +0 -37
|
@@ -1,61 +1,126 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
HotUpdaterCoreApi,
|
|
3
|
+
ReleaseFilter,
|
|
4
|
+
ReleaseRow,
|
|
5
|
+
} from "@hot-updater/plugin-core";
|
|
2
6
|
|
|
3
7
|
export interface ReleaseListInput {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
platform?: "ios" | "android";
|
|
12
|
-
targetAppVersion?: string;
|
|
8
|
+
/** One of the filter sets the release indexes serve. */
|
|
9
|
+
readonly filter: ReleaseFilter;
|
|
10
|
+
/** Releases older than this id: the next page. */
|
|
11
|
+
readonly beforeReleaseId?: string;
|
|
12
|
+
/** Releases newer than this id: the previous page. */
|
|
13
|
+
readonly afterReleaseId?: string;
|
|
14
|
+
readonly limit: number;
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
export interface ReleaseListResult {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
/** Newest first. */
|
|
19
|
+
readonly data: readonly ReleaseRow[];
|
|
20
|
+
/** `beforeReleaseId` of the next page, when there may be older releases. */
|
|
21
|
+
readonly next?: string;
|
|
22
|
+
/** `afterReleaseId` of the previous page, when there may be newer releases. */
|
|
23
|
+
readonly previous?: string;
|
|
22
24
|
}
|
|
23
25
|
|
|
26
|
+
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
27
|
+
typeof value === "object" && value !== null;
|
|
28
|
+
|
|
29
|
+
const text = (value: unknown, name: string): string => {
|
|
30
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
31
|
+
throw new Error(`Release filter ${name} is required.`);
|
|
32
|
+
}
|
|
33
|
+
return value;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const enabledOf = (value: unknown) => {
|
|
37
|
+
if (value === undefined) return {};
|
|
38
|
+
if (typeof value !== "boolean") {
|
|
39
|
+
throw new Error("Release filter enabled must be true or false.");
|
|
40
|
+
}
|
|
41
|
+
return { enabled: value };
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* A release list's filter: none, a channel with its platform (and enabled),
|
|
46
|
+
* a bundle, or a scope (and enabled). Those are the sets the release indexes
|
|
47
|
+
* serve, so any other combination is refused.
|
|
48
|
+
*/
|
|
49
|
+
export const readReleaseFilter = (value: unknown): ReleaseFilter => {
|
|
50
|
+
if (value === undefined) return { kind: "all" };
|
|
51
|
+
if (!isRecord(value)) throw new Error("Invalid release filter.");
|
|
52
|
+
switch (value.kind) {
|
|
53
|
+
case "all":
|
|
54
|
+
return { kind: "all" };
|
|
55
|
+
case "channelPlatform": {
|
|
56
|
+
if (value.platform !== "ios" && value.platform !== "android") {
|
|
57
|
+
throw new Error("Release filter platform must be ios or android.");
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
kind: "channelPlatform",
|
|
61
|
+
channelId: text(value.channelId, "channelId"),
|
|
62
|
+
platform: value.platform,
|
|
63
|
+
...enabledOf(value.enabled),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
case "bundle":
|
|
67
|
+
return { kind: "bundle", bundleId: text(value.bundleId, "bundleId") };
|
|
68
|
+
case "scope":
|
|
69
|
+
return {
|
|
70
|
+
kind: "scope",
|
|
71
|
+
scopeKey: text(value.scopeKey, "scopeKey"),
|
|
72
|
+
...enabledOf(value.enabled),
|
|
73
|
+
};
|
|
74
|
+
default:
|
|
75
|
+
throw new Error("Invalid release filter.");
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* One page of releases, newest first, through the index the filter names:
|
|
81
|
+
* each page reads only its own rows. `next` is set for a full page, so the
|
|
82
|
+
* last page may lead to an empty one.
|
|
83
|
+
*/
|
|
24
84
|
export async function listReleases(
|
|
25
|
-
|
|
85
|
+
core: Pick<HotUpdaterCoreApi, "listReleases">,
|
|
26
86
|
input: ReleaseListInput,
|
|
27
87
|
): Promise<ReleaseListResult> {
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
88
|
+
const { filter, limit } = input;
|
|
89
|
+
if (input.afterReleaseId !== undefined) {
|
|
90
|
+
// Newer releases, read oldest first from the cursor, then turned around.
|
|
91
|
+
const data = (
|
|
92
|
+
await core.listReleases({
|
|
93
|
+
filter,
|
|
94
|
+
order: "asc",
|
|
95
|
+
after: input.afterReleaseId,
|
|
96
|
+
limit,
|
|
97
|
+
})
|
|
98
|
+
).reverse();
|
|
99
|
+
const first = data[0]?.id;
|
|
100
|
+
const last = data.at(-1)?.id;
|
|
101
|
+
return {
|
|
102
|
+
data,
|
|
103
|
+
...(last === undefined ? {} : { next: last }),
|
|
104
|
+
...(first !== undefined && data.length === limit
|
|
105
|
+
? { previous: first }
|
|
106
|
+
: {}),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
const data = await core.listReleases({
|
|
110
|
+
filter,
|
|
111
|
+
order: "desc",
|
|
112
|
+
limit,
|
|
33
113
|
...(input.beforeReleaseId === undefined
|
|
34
114
|
? {}
|
|
35
|
-
: {
|
|
36
|
-
...(input.bundleId === undefined ? {} : { bundleId: input.bundleId }),
|
|
37
|
-
...(input.channelId === undefined ? {} : { channelId: input.channelId }),
|
|
38
|
-
...(input.enabled === undefined ? {} : { enabled: input.enabled }),
|
|
39
|
-
...(input.platform === undefined ? {} : { platform: input.platform }),
|
|
40
|
-
...(input.targetAppVersion === undefined
|
|
41
|
-
? {}
|
|
42
|
-
: { targetAppVersion: input.targetAppVersion }),
|
|
43
|
-
limit: input.limit + 1,
|
|
115
|
+
: { after: input.beforeReleaseId }),
|
|
44
116
|
});
|
|
45
|
-
const
|
|
46
|
-
const
|
|
47
|
-
const data = isPreviousRequest
|
|
48
|
-
? hasExtraRow
|
|
49
|
-
? rows.slice(1)
|
|
50
|
-
: rows
|
|
51
|
-
: rows.slice(0, input.limit);
|
|
52
|
-
|
|
117
|
+
const first = data[0]?.id;
|
|
118
|
+
const last = data.at(-1)?.id;
|
|
53
119
|
return {
|
|
54
120
|
data,
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
},
|
|
121
|
+
...(last !== undefined && data.length === limit ? { next: last } : {}),
|
|
122
|
+
...(first !== undefined && input.beforeReleaseId !== undefined
|
|
123
|
+
? { previous: first }
|
|
124
|
+
: {}),
|
|
60
125
|
};
|
|
61
126
|
}
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
75
|
+
const row = await core.getReleaseCatalogRow(scopeKey);
|
|
75
76
|
const reachableReleaseIds =
|
|
76
77
|
row === null
|
|
77
78
|
? new Set<string>()
|
|
@@ -1,36 +1,125 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
} from "
|
|
10
|
-
|
|
11
|
-
export
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
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,31 @@
|
|
|
1
|
+
import {
|
|
2
|
+
readUpdateFailuresInput,
|
|
3
|
+
type UpdateFailuresInput,
|
|
4
|
+
type UpdateFailuresReport,
|
|
5
|
+
} from "../insights-failures";
|
|
6
|
+
import { insightsPeriodEnd, recoveryWindows } from "../insights-recovery";
|
|
7
|
+
import type { ConsoleInsightsReads } from "./adminInsights";
|
|
8
|
+
|
|
9
|
+
/** A release's or a channel's update failures over a period that ends with the current hour. */
|
|
10
|
+
export async function getUpdateFailuresReport(
|
|
11
|
+
reads: Pick<ConsoleInsightsReads, "getUpdateFailures">,
|
|
12
|
+
input: UpdateFailuresInput,
|
|
13
|
+
now = Date.now(),
|
|
14
|
+
): Promise<UpdateFailuresReport> {
|
|
15
|
+
readUpdateFailuresInput(input);
|
|
16
|
+
const end = insightsPeriodEnd(now);
|
|
17
|
+
const timeRange =
|
|
18
|
+
input.window === undefined
|
|
19
|
+
? undefined
|
|
20
|
+
: {
|
|
21
|
+
start: Math.max(0, end - recoveryWindows[input.window].durationMs),
|
|
22
|
+
end,
|
|
23
|
+
};
|
|
24
|
+
const failures = await reads.getUpdateFailures({
|
|
25
|
+
platform: input.platform,
|
|
26
|
+
channel: input.channel,
|
|
27
|
+
...(input.releaseId === undefined ? {} : { releaseId: input.releaseId }),
|
|
28
|
+
...(timeRange === undefined ? {} : { timeRange }),
|
|
29
|
+
});
|
|
30
|
+
return { ...failures, startMs: timeRange?.start ?? null, endMs: end };
|
|
31
|
+
}
|
package/src/routeTree.gen.ts
CHANGED
|
@@ -9,22 +9,22 @@
|
|
|
9
9
|
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
|
10
10
|
|
|
11
11
|
import { Route as rootRouteImport } from './routes/__root'
|
|
12
|
-
import { Route as SigningRouteImport } from './routes/signing'
|
|
13
|
-
import { Route as InstallationsRouteImport } from './routes/installations'
|
|
14
|
-
import { Route as InsightsRouteImport } from './routes/insights'
|
|
15
|
-
import { Route as ApiKeysRouteImport } from './routes/api-keys'
|
|
16
12
|
import { Route as IndexRouteImport } from './routes/index'
|
|
13
|
+
import { Route as ApiKeysRouteImport } from './routes/api-keys'
|
|
14
|
+
import { Route as InsightsRouteImport } from './routes/insights'
|
|
15
|
+
import { Route as InstallationsRouteImport } from './routes/installations'
|
|
16
|
+
import { Route as InsightsDistributionRouteImport } from './routes/insights_.distribution'
|
|
17
17
|
import { Route as ApiAuthSplatRouteImport } from './routes/api/auth/$'
|
|
18
18
|
import { Route as ApiBundlesBundleIdDownloadRouteImport } from './routes/api/bundles/$bundleId/download'
|
|
19
19
|
|
|
20
|
-
const
|
|
21
|
-
id: '/
|
|
22
|
-
path: '/
|
|
20
|
+
const IndexRoute = IndexRouteImport.update({
|
|
21
|
+
id: '/',
|
|
22
|
+
path: '/',
|
|
23
23
|
getParentRoute: () => rootRouteImport,
|
|
24
24
|
} as any)
|
|
25
|
-
const
|
|
26
|
-
id: '/
|
|
27
|
-
path: '/
|
|
25
|
+
const ApiKeysRoute = ApiKeysRouteImport.update({
|
|
26
|
+
id: '/api-keys',
|
|
27
|
+
path: '/api-keys',
|
|
28
28
|
getParentRoute: () => rootRouteImport,
|
|
29
29
|
} as any)
|
|
30
30
|
const InsightsRoute = InsightsRouteImport.update({
|
|
@@ -32,14 +32,14 @@ const InsightsRoute = InsightsRouteImport.update({
|
|
|
32
32
|
path: '/insights',
|
|
33
33
|
getParentRoute: () => rootRouteImport,
|
|
34
34
|
} as any)
|
|
35
|
-
const
|
|
36
|
-
id: '/
|
|
37
|
-
path: '/
|
|
35
|
+
const InstallationsRoute = InstallationsRouteImport.update({
|
|
36
|
+
id: '/installations',
|
|
37
|
+
path: '/installations',
|
|
38
38
|
getParentRoute: () => rootRouteImport,
|
|
39
39
|
} as any)
|
|
40
|
-
const
|
|
41
|
-
id: '/',
|
|
42
|
-
path: '/',
|
|
40
|
+
const InsightsDistributionRoute = InsightsDistributionRouteImport.update({
|
|
41
|
+
id: '/insights_/distribution',
|
|
42
|
+
path: '/insights/distribution',
|
|
43
43
|
getParentRoute: () => rootRouteImport,
|
|
44
44
|
} as any)
|
|
45
45
|
const ApiAuthSplatRoute = ApiAuthSplatRouteImport.update({
|
|
@@ -59,7 +59,7 @@ export interface FileRoutesByFullPath {
|
|
|
59
59
|
'/api-keys': typeof ApiKeysRoute
|
|
60
60
|
'/insights': typeof InsightsRoute
|
|
61
61
|
'/installations': typeof InstallationsRoute
|
|
62
|
-
'/
|
|
62
|
+
'/insights/distribution': typeof InsightsDistributionRoute
|
|
63
63
|
'/api/auth/$': typeof ApiAuthSplatRoute
|
|
64
64
|
'/api/bundles/$bundleId/download': typeof ApiBundlesBundleIdDownloadRoute
|
|
65
65
|
}
|
|
@@ -68,7 +68,7 @@ export interface FileRoutesByTo {
|
|
|
68
68
|
'/api-keys': typeof ApiKeysRoute
|
|
69
69
|
'/insights': typeof InsightsRoute
|
|
70
70
|
'/installations': typeof InstallationsRoute
|
|
71
|
-
'/
|
|
71
|
+
'/insights/distribution': typeof InsightsDistributionRoute
|
|
72
72
|
'/api/auth/$': typeof ApiAuthSplatRoute
|
|
73
73
|
'/api/bundles/$bundleId/download': typeof ApiBundlesBundleIdDownloadRoute
|
|
74
74
|
}
|
|
@@ -78,7 +78,7 @@ export interface FileRoutesById {
|
|
|
78
78
|
'/api-keys': typeof ApiKeysRoute
|
|
79
79
|
'/insights': typeof InsightsRoute
|
|
80
80
|
'/installations': typeof InstallationsRoute
|
|
81
|
-
'/
|
|
81
|
+
'/insights_/distribution': typeof InsightsDistributionRoute
|
|
82
82
|
'/api/auth/$': typeof ApiAuthSplatRoute
|
|
83
83
|
'/api/bundles/$bundleId/download': typeof ApiBundlesBundleIdDownloadRoute
|
|
84
84
|
}
|
|
@@ -89,7 +89,7 @@ export interface FileRouteTypes {
|
|
|
89
89
|
| '/api-keys'
|
|
90
90
|
| '/insights'
|
|
91
91
|
| '/installations'
|
|
92
|
-
| '/
|
|
92
|
+
| '/insights/distribution'
|
|
93
93
|
| '/api/auth/$'
|
|
94
94
|
| '/api/bundles/$bundleId/download'
|
|
95
95
|
fileRoutesByTo: FileRoutesByTo
|
|
@@ -98,7 +98,7 @@ export interface FileRouteTypes {
|
|
|
98
98
|
| '/api-keys'
|
|
99
99
|
| '/insights'
|
|
100
100
|
| '/installations'
|
|
101
|
-
| '/
|
|
101
|
+
| '/insights/distribution'
|
|
102
102
|
| '/api/auth/$'
|
|
103
103
|
| '/api/bundles/$bundleId/download'
|
|
104
104
|
id:
|
|
@@ -107,7 +107,7 @@ export interface FileRouteTypes {
|
|
|
107
107
|
| '/api-keys'
|
|
108
108
|
| '/insights'
|
|
109
109
|
| '/installations'
|
|
110
|
-
| '/
|
|
110
|
+
| '/insights_/distribution'
|
|
111
111
|
| '/api/auth/$'
|
|
112
112
|
| '/api/bundles/$bundleId/download'
|
|
113
113
|
fileRoutesById: FileRoutesById
|
|
@@ -117,25 +117,25 @@ export interface RootRouteChildren {
|
|
|
117
117
|
ApiKeysRoute: typeof ApiKeysRoute
|
|
118
118
|
InsightsRoute: typeof InsightsRoute
|
|
119
119
|
InstallationsRoute: typeof InstallationsRoute
|
|
120
|
-
|
|
120
|
+
InsightsDistributionRoute: typeof InsightsDistributionRoute
|
|
121
121
|
ApiAuthSplatRoute: typeof ApiAuthSplatRoute
|
|
122
122
|
ApiBundlesBundleIdDownloadRoute: typeof ApiBundlesBundleIdDownloadRoute
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
declare module '@tanstack/react-router' {
|
|
126
126
|
interface FileRoutesByPath {
|
|
127
|
-
'/
|
|
128
|
-
id: '/
|
|
129
|
-
path: '/
|
|
130
|
-
fullPath: '/
|
|
131
|
-
preLoaderRoute: typeof
|
|
127
|
+
'/': {
|
|
128
|
+
id: '/'
|
|
129
|
+
path: '/'
|
|
130
|
+
fullPath: '/'
|
|
131
|
+
preLoaderRoute: typeof IndexRouteImport
|
|
132
132
|
parentRoute: typeof rootRouteImport
|
|
133
133
|
}
|
|
134
|
-
'/
|
|
135
|
-
id: '/
|
|
136
|
-
path: '/
|
|
137
|
-
fullPath: '/
|
|
138
|
-
preLoaderRoute: typeof
|
|
134
|
+
'/api-keys': {
|
|
135
|
+
id: '/api-keys'
|
|
136
|
+
path: '/api-keys'
|
|
137
|
+
fullPath: '/api-keys'
|
|
138
|
+
preLoaderRoute: typeof ApiKeysRouteImport
|
|
139
139
|
parentRoute: typeof rootRouteImport
|
|
140
140
|
}
|
|
141
141
|
'/insights': {
|
|
@@ -145,18 +145,18 @@ declare module '@tanstack/react-router' {
|
|
|
145
145
|
preLoaderRoute: typeof InsightsRouteImport
|
|
146
146
|
parentRoute: typeof rootRouteImport
|
|
147
147
|
}
|
|
148
|
-
'/
|
|
149
|
-
id: '/
|
|
150
|
-
path: '/
|
|
151
|
-
fullPath: '/
|
|
152
|
-
preLoaderRoute: typeof
|
|
148
|
+
'/installations': {
|
|
149
|
+
id: '/installations'
|
|
150
|
+
path: '/installations'
|
|
151
|
+
fullPath: '/installations'
|
|
152
|
+
preLoaderRoute: typeof InstallationsRouteImport
|
|
153
153
|
parentRoute: typeof rootRouteImport
|
|
154
154
|
}
|
|
155
|
-
'/': {
|
|
156
|
-
id: '/'
|
|
157
|
-
path: '/'
|
|
158
|
-
fullPath: '/'
|
|
159
|
-
preLoaderRoute: typeof
|
|
155
|
+
'/insights_/distribution': {
|
|
156
|
+
id: '/insights_/distribution'
|
|
157
|
+
path: '/insights/distribution'
|
|
158
|
+
fullPath: '/insights/distribution'
|
|
159
|
+
preLoaderRoute: typeof InsightsDistributionRouteImport
|
|
160
160
|
parentRoute: typeof rootRouteImport
|
|
161
161
|
}
|
|
162
162
|
'/api/auth/$': {
|
|
@@ -181,7 +181,7 @@ const rootRouteChildren: RootRouteChildren = {
|
|
|
181
181
|
ApiKeysRoute: ApiKeysRoute,
|
|
182
182
|
InsightsRoute: InsightsRoute,
|
|
183
183
|
InstallationsRoute: InstallationsRoute,
|
|
184
|
-
|
|
184
|
+
InsightsDistributionRoute: InsightsDistributionRoute,
|
|
185
185
|
ApiAuthSplatRoute: ApiAuthSplatRoute,
|
|
186
186
|
ApiBundlesBundleIdDownloadRoute: ApiBundlesBundleIdDownloadRoute,
|
|
187
187
|
}
|
package/src/router.tsx
CHANGED
|
@@ -4,7 +4,7 @@ import { setupRouterSsrQueryIntegration } from "@tanstack/react-router-ssr-query
|
|
|
4
4
|
// Import the route tree selected by the local or hosted Vite integration.
|
|
5
5
|
import { routeTree } from "virtual:hot-updater-console/route-tree";
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { routeScrollRestorationKey } from "./lib/scroll-restoration";
|
|
8
8
|
|
|
9
9
|
// Create a new router instance
|
|
10
10
|
export const getRouter = () => {
|
|
@@ -14,7 +14,8 @@ export const getRouter = () => {
|
|
|
14
14
|
context: { queryClient },
|
|
15
15
|
|
|
16
16
|
scrollRestoration: true,
|
|
17
|
-
getScrollRestorationKey:
|
|
17
|
+
getScrollRestorationKey: (location): string =>
|
|
18
|
+
routeScrollRestorationKey(router, location),
|
|
18
19
|
defaultPreloadStaleTime: 0,
|
|
19
20
|
});
|
|
20
21
|
|
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
import type { ParsedLocation } from "@tanstack/react-router";
|
|
2
2
|
|
|
3
|
+
import {
|
|
4
|
+
DEFAULT_EVENT_RANGE,
|
|
5
|
+
EVENT_RANGES,
|
|
6
|
+
type EventRange,
|
|
7
|
+
} from "../lib/insights-view";
|
|
8
|
+
|
|
9
|
+
export type InsightsPaginationState = {
|
|
10
|
+
readonly eventsBack?: readonly string[];
|
|
11
|
+
readonly searchBack?: readonly string[];
|
|
12
|
+
readonly historyBack?: readonly string[];
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
declare module "@tanstack/react-router" {
|
|
16
|
+
interface HistoryState {
|
|
17
|
+
insightsPagination?: InsightsPaginationState;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
3
21
|
const readCursor = (value: unknown): string | undefined =>
|
|
4
22
|
typeof value === "string" && value.length > 0 ? value : undefined;
|
|
5
23
|
|
|
@@ -8,14 +26,19 @@ const readTimestamp = (value: unknown): number | undefined =>
|
|
|
8
26
|
? value
|
|
9
27
|
: undefined;
|
|
10
28
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
29
|
+
const readEventRange = (value: unknown): EventRange | undefined =>
|
|
30
|
+
EVENT_RANGES.find((range) => range === value);
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The installations route's scroll-restoration key: every visit to one page
|
|
34
|
+
* of the event list shares its scroll position, and a lookup keeps its own
|
|
35
|
+
* for each history entry.
|
|
36
|
+
*/
|
|
37
|
+
export function installationsScrollRestorationKey(location: ParsedLocation) {
|
|
38
|
+
const search = validateInstallationsSearch(location.search);
|
|
39
|
+
return search.query === undefined && search.installId === undefined
|
|
40
|
+
? `/installations?eventsBefore=${search.eventsBefore ?? "new"}&eventsRange=${search.eventsRange ?? DEFAULT_EVENT_RANGE}&eventsCursor=${search.eventsCursor ?? "first"}`
|
|
41
|
+
: location.state.__TSR_key!;
|
|
19
42
|
}
|
|
20
43
|
|
|
21
44
|
export function validateInstallationsSearch(search: Record<string, unknown>) {
|
|
@@ -25,6 +48,7 @@ export function validateInstallationsSearch(search: Record<string, unknown>) {
|
|
|
25
48
|
typeof search.installId === "string" ? search.installId : undefined,
|
|
26
49
|
eventsCursor: readCursor(search.eventsCursor),
|
|
27
50
|
eventsBefore: readTimestamp(search.eventsBefore),
|
|
51
|
+
eventsRange: readEventRange(search.eventsRange),
|
|
28
52
|
searchCursor: readCursor(search.searchCursor),
|
|
29
53
|
historyCursor: readCursor(search.historyCursor),
|
|
30
54
|
historyBefore: readTimestamp(search.historyBefore),
|