@hot-updater/console 0.36.7 → 1.0.0-rc.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 (266) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/AnalyticsCapabilityContext-BCke57-7.js +1 -0
  3. package/.output/public/assets/BundleActivityChart-CnsBe_1J.js +42 -0
  4. package/.output/public/assets/BundleIdDisplay-Dky75zLU.js +1 -0
  5. package/.output/public/assets/alert-C-1KvDW8.js +1 -0
  6. package/.output/public/assets/analytics-Bqz0c-BT.js +1 -0
  7. package/.output/public/assets/analytics-api-CJdrTLLO.js +1 -0
  8. package/.output/public/assets/api-B_Rh-uzC.js +1 -0
  9. package/.output/public/assets/api-keys-BurCFhzH.js +1 -0
  10. package/.output/public/assets/api-keys-api-BEDJdQg3.js +1 -0
  11. package/.output/public/assets/badge-Bf0fZ-Gx.js +1 -0
  12. package/.output/public/assets/chart-no-axes-combined-FaYMGmQS.js +1 -0
  13. package/.output/public/assets/createServerFn-gM2-LFBy.js +3 -0
  14. package/.output/public/assets/dialog-BS60BRr2.js +1 -0
  15. package/.output/public/assets/dist-DspNKfMj.js +1 -0
  16. package/.output/public/assets/download-O-yNBc9H.js +1 -0
  17. package/.output/public/assets/field-Buq148-A.js +1 -0
  18. package/.output/public/assets/index-kFhd2S78.js +10 -0
  19. package/.output/public/assets/installations-BrQ-od41.js +2 -0
  20. package/.output/public/assets/installations-CGlZ3ROz.js +1 -0
  21. package/.output/public/assets/lazyRouteComponent-rPzgA7S-.js +1 -0
  22. package/.output/public/assets/loader-circle-DCYF-wJl.js +1 -0
  23. package/.output/public/assets/preload-helper-TKOBYGYD.js +1 -0
  24. package/.output/public/assets/refresh-cw-n8XYbPPj.js +1 -0
  25. package/.output/public/assets/routes-CrdokJ80.js +1 -0
  26. package/.output/public/assets/routes-DcQnVajO.js +2 -0
  27. package/.output/public/assets/search-DLhbWw3W.js +1 -0
  28. package/.output/public/assets/shield-check-DE_hVuHu.js +1 -0
  29. package/.output/public/assets/signing-HRexfjsH.js +1 -0
  30. package/.output/public/assets/styles-eB4EtvFS.css +2 -0
  31. package/.output/public/assets/table-D8CLieHX.js +1 -0
  32. package/.output/public/assets/textarea-YJLTVAoq.js +1 -0
  33. package/.output/public/assets/useMutation-JsGVjQMY.js +1 -0
  34. package/.output/public/assets/useOpenInteractionType-4cRD5xO0.js +1 -0
  35. package/.output/server/__23tanstack-start-server-fn-resolver-DBP9TQ7K.mjs +146 -0
  36. package/.output/server/_libs/@base-ui/react+[...].mjs +16124 -0
  37. package/.output/server/_libs/@reduxjs/toolkit+[...].mjs +1554 -0
  38. package/.output/server/_libs/@tanstack/react-router+[...].mjs +41 -10
  39. package/.output/server/_libs/@tanstack/react-router-ssr-query+[...].mjs +132 -0
  40. package/.output/server/_libs/d3-array.mjs +231 -0
  41. package/.output/server/_libs/d3-color.mjs +330 -0
  42. package/.output/server/_libs/d3-format.mjs +252 -0
  43. package/.output/server/_libs/d3-interpolate.mjs +207 -0
  44. package/.output/server/_libs/d3-path.mjs +77 -0
  45. package/.output/server/_libs/d3-scale+[...].mjs +1907 -0
  46. package/.output/server/_libs/d3-shape.mjs +818 -0
  47. package/.output/server/_libs/decimal.js-light.mjs +884 -0
  48. package/.output/server/_libs/es-toolkit.mjs +1134 -0
  49. package/.output/server/_libs/eventemitter3.mjs +236 -0
  50. package/.output/server/_libs/immer.mjs +532 -0
  51. package/.output/server/_libs/lucide-react.mjs +141 -13
  52. package/.output/server/_libs/next-themes.mjs +2 -2
  53. package/.output/server/_libs/react-redux+[...].mjs +205 -0
  54. package/.output/server/_libs/recharts+[...].mjs +14117 -0
  55. package/.output/server/_libs/sonner.mjs +2 -2
  56. package/.output/server/_libs/tanstack__query-core.mjs +114 -1
  57. package/.output/server/_libs/tanstack__react-query.mjs +3 -4
  58. package/.output/server/_runtime.mjs +10 -1
  59. package/.output/server/_ssr/AnalyticsCapabilityContext-ui4DX8GO.mjs +15 -0
  60. package/.output/server/_ssr/BundleActivityChart-BiB9ZORH.mjs +288 -0
  61. package/.output/server/_ssr/BundleIdDisplay-Czk6iR6p.mjs +13 -0
  62. package/.output/server/_ssr/alert-Dim3MGY5.mjs +86 -0
  63. package/.output/server/_ssr/analytics-DtMTn8fn.mjs +858 -0
  64. package/.output/server/_ssr/analytics-api-Cg1M5v_H.mjs +71 -0
  65. package/.output/server/_ssr/analytics-input-fRRRn0uA.mjs +96 -0
  66. package/.output/server/_ssr/analytics-rpc-DY5uXvHT.mjs +155 -0
  67. package/.output/server/_ssr/api-BTSv1tZb.mjs +282 -0
  68. package/.output/server/_ssr/api-keys-BnM_o2nS.mjs +300 -0
  69. package/.output/server/_ssr/api-keys-api-CIywsSF5.mjs +60 -0
  70. package/.output/server/_ssr/api-keys-rpc-BSp1wkIV.mjs +136 -0
  71. package/.output/server/_ssr/api-rpc-cG5wd_tO.mjs +1468 -0
  72. package/.output/server/_ssr/auth-BF8mbAgF.mjs +21 -0
  73. package/.output/server/_ssr/auth-rpc-BrJ6obHJ.mjs +24 -0
  74. package/.output/server/_ssr/auth.server-DwKoZoQM.mjs +50 -0
  75. package/.output/server/_ssr/badge-DCxbEeof.mjs +26 -0
  76. package/.output/server/_ssr/bundle-signing-rpc-COoQhW8E.mjs +16 -0
  77. package/.output/server/_ssr/bundle-signing.server-D5u8KkMp.mjs +51 -0
  78. package/.output/server/_ssr/config-Dv8qLDZ0.mjs +13 -0
  79. package/.output/server/_ssr/config.server-DEiLhKmy.mjs +361 -0
  80. package/.output/server/_ssr/{createServerFn-DzU0k62V.mjs → createServerFn-B2hcXoPM.mjs} +1 -1
  81. package/.output/server/_ssr/createServerRpc-DB29C_5p.mjs +12 -0
  82. package/.output/server/_ssr/{sidebar-Cv_H0_Jv.mjs → createSsrRpc-9P0S0lJ3.mjs} +68 -50
  83. package/.output/server/_ssr/databaseRows-CwbNwYSl.mjs +166 -0
  84. package/.output/server/_ssr/deleteBundle-Cu_XOMt0.mjs +66 -0
  85. package/.output/server/_ssr/dialog-CQuRMNZ4.mjs +170 -0
  86. package/.output/server/_ssr/dist-BVSLTpoP.mjs +250 -0
  87. package/.output/server/_ssr/downloadBundle-CNnYxYO2.mjs +24 -0
  88. package/.output/server/_ssr/{empty-plugin-adapters-B4HCzgmM.mjs → empty-plugin-adapters-B5QvGpEl.mjs} +1 -1
  89. package/.output/server/_ssr/field-DRIXKjZG.mjs +74 -0
  90. package/.output/server/_ssr/{getBundleChildren-CVtSiH5t.mjs → getBundleChildren-BcRMFoDF.mjs} +10 -18
  91. package/.output/server/_ssr/installations-B-azgAvX.mjs +19 -0
  92. package/.output/server/_ssr/installations-Ctvzp2JH.mjs +535 -0
  93. package/.output/server/_ssr/router-C-KUBfOi.mjs +513 -0
  94. package/.output/server/_ssr/routes-BM4ht9jb.mjs +2286 -0
  95. package/.output/server/_ssr/routes-Z7TwC6nC.mjs +37 -0
  96. package/.output/server/_ssr/runtime.server-awTLiiXW.mjs +527 -0
  97. package/.output/server/_ssr/server-BjzmvXnO.mjs +7468 -0
  98. package/.output/server/_ssr/signing-k8GxyBDF.mjs +202 -0
  99. package/.output/server/_ssr/ssr.mjs +4 -5740
  100. package/.output/server/_ssr/start-DpViQGnt.mjs +4 -0
  101. package/.output/server/_ssr/table-v1yCvYo7.mjs +53 -0
  102. package/.output/server/_ssr/textarea-DYHnskhb.mjs +14 -0
  103. package/.output/server/_ssr/uuidv7-BbJ-9D_I.mjs +31 -0
  104. package/.output/server/_tanstack-start-manifest_v-CH4StzJu.mjs +108 -0
  105. package/.output/server/index.mjs +256 -53
  106. package/README.md +29 -8
  107. package/lib/index.d.mts +36 -0
  108. package/lib/index.mjs +4 -0
  109. package/lib/vite-internal.mjs +127 -0
  110. package/lib/vite.d.mts +10 -0
  111. package/lib/vite.mjs +5 -0
  112. package/package.json +51 -29
  113. package/public/apple-touch-icon.png +0 -0
  114. package/public/favicon-16x16.png +0 -0
  115. package/public/favicon-32x32.png +0 -0
  116. package/public/favicon.ico +0 -0
  117. package/public/logo.svg +1 -0
  118. package/public/manifest.json +30 -0
  119. package/public/robots.txt +3 -0
  120. package/src/components/AppSidebar.tsx +159 -0
  121. package/src/components/BundleIdDisplay.tsx +18 -0
  122. package/src/components/ChannelBadge.tsx +31 -0
  123. package/src/components/ConsoleAccessPage.tsx +122 -0
  124. package/src/components/EnabledStatusIcon.tsx +54 -0
  125. package/src/components/HashValueDisplay.tsx +85 -0
  126. package/src/components/HotUpdaterLogo.tsx +30 -0
  127. package/src/components/NotFoundPage.tsx +33 -0
  128. package/src/components/PlatformIcon.tsx +39 -0
  129. package/src/components/RolloutPercentageBadge.tsx +28 -0
  130. package/src/components/ThemeProvider.tsx +116 -0
  131. package/src/components/TimestampDisplay.tsx +41 -0
  132. package/src/components/features/analytics/ActivityChart.tsx +150 -0
  133. package/src/components/features/analytics/AnalyticsCapabilityContext.tsx +24 -0
  134. package/src/components/features/analytics/AnalyticsControls.tsx +115 -0
  135. package/src/components/features/analytics/AnalyticsErrorAlert.tsx +21 -0
  136. package/src/components/features/analytics/AnalyticsOverview.tsx +208 -0
  137. package/src/components/features/analytics/BundleDistribution.tsx +133 -0
  138. package/src/components/features/analytics/BundleSelector.tsx +114 -0
  139. package/src/components/features/analytics/InstallationHistoryCard.tsx +248 -0
  140. package/src/components/features/analytics/InstallationMatchesCard.tsx +132 -0
  141. package/src/components/features/analytics/InstallationPageHeader.tsx +138 -0
  142. package/src/components/features/analytics/InstallationPagination.tsx +48 -0
  143. package/src/components/features/analytics/InstallationSearch.tsx +230 -0
  144. package/src/components/features/analytics/UpdateOutcomes.tsx +147 -0
  145. package/src/components/features/api-keys/ApiKeysPage.tsx +377 -0
  146. package/src/components/features/bundles/BundleActivityChart.tsx +193 -0
  147. package/src/components/features/bundles/BundleAnalyticsSummary.tsx +123 -0
  148. package/src/components/features/bundles/BundleChildrenPanel.tsx +201 -0
  149. package/src/components/features/bundles/BundleMetadata.tsx +136 -0
  150. package/src/components/features/bundles/RolloutCohortsDialog.tsx +201 -0
  151. package/src/components/features/channels/ChannelManagementDialog.tsx +228 -0
  152. package/src/components/features/releases/ReleaseEditorSheet.tsx +879 -0
  153. package/src/components/features/releases/ReleaseStateBadge.tsx +20 -0
  154. package/src/components/features/signing/BundleSigningPage.tsx +247 -0
  155. package/src/components/ui/alert-dialog.tsx +185 -0
  156. package/src/components/ui/alert.tsx +76 -0
  157. package/src/components/ui/badge.tsx +49 -0
  158. package/src/components/ui/button.tsx +58 -0
  159. package/src/components/ui/card.tsx +89 -0
  160. package/src/components/ui/chart.tsx +371 -0
  161. package/src/components/ui/combobox.tsx +303 -0
  162. package/src/components/ui/dialog.tsx +156 -0
  163. package/src/components/ui/dropdown-menu.tsx +270 -0
  164. package/src/components/ui/field.tsx +235 -0
  165. package/src/components/ui/input-group.tsx +147 -0
  166. package/src/components/ui/input.tsx +19 -0
  167. package/src/components/ui/label.tsx +25 -0
  168. package/src/components/ui/progress.tsx +84 -0
  169. package/src/components/ui/select.tsx +205 -0
  170. package/src/components/ui/separator.tsx +23 -0
  171. package/src/components/ui/sheet.tsx +132 -0
  172. package/src/components/ui/sidebar.tsx +779 -0
  173. package/src/components/ui/skeleton.tsx +13 -0
  174. package/src/components/ui/slider.tsx +56 -0
  175. package/src/components/ui/sonner.tsx +45 -0
  176. package/src/components/ui/switch.tsx +25 -0
  177. package/src/components/ui/table.tsx +114 -0
  178. package/src/components/ui/textarea.tsx +18 -0
  179. package/src/components/ui/toggle-group.tsx +89 -0
  180. package/src/components/ui/toggle.tsx +43 -0
  181. package/src/components/ui/tooltip.tsx +66 -0
  182. package/src/hooks/use-mobile.ts +30 -0
  183. package/src/index.ts +48 -0
  184. package/src/lib/analytics-api.ts +145 -0
  185. package/src/lib/analytics-error.ts +19 -0
  186. package/src/lib/analytics-input.ts +187 -0
  187. package/src/lib/analytics-overview.ts +170 -0
  188. package/src/lib/analytics-rpc.ts +218 -0
  189. package/src/lib/api-keys-api.ts +69 -0
  190. package/src/lib/api-keys-rpc.ts +87 -0
  191. package/src/lib/api-rpc.ts +478 -0
  192. package/src/lib/api.ts +444 -0
  193. package/src/lib/auth-rpc.ts +16 -0
  194. package/src/lib/bundle-signing-api.ts +14 -0
  195. package/src/lib/bundle-signing-rpc.ts +16 -0
  196. package/src/lib/constants.ts +1 -0
  197. package/src/lib/git.ts +51 -0
  198. package/src/lib/server/auth.server.ts +43 -0
  199. package/src/lib/server/bundle-signing.server.ts +100 -0
  200. package/src/lib/server/config.server.ts +109 -0
  201. package/src/lib/server/console-runtime.server.ts +70 -0
  202. package/src/lib/server/deleteBundle.ts +145 -0
  203. package/src/lib/server/downloadBundle.ts +46 -0
  204. package/src/lib/server/getBundleChildren.ts +130 -0
  205. package/src/lib/server/listReleases.ts +61 -0
  206. package/src/lib/server/releaseActivity.ts +27 -0
  207. package/src/lib/server/releaseReachability.ts +95 -0
  208. package/src/lib/server/runtime.server.ts +172 -0
  209. package/src/lib/utils.ts +9 -0
  210. package/src/logo.svg +1 -0
  211. package/src/routeTree.gen.ts +199 -0
  212. package/src/router.tsx +21 -0
  213. package/src/routes/-installations-search.ts +19 -0
  214. package/src/routes/-releases-search.ts +74 -0
  215. package/src/routes/__root.tsx +174 -0
  216. package/src/routes/analytics.tsx +139 -0
  217. package/src/routes/api/auth/$.ts +19 -0
  218. package/src/routes/api/bundles/$bundleId/download.ts +17 -0
  219. package/src/routes/api-keys.tsx +37 -0
  220. package/src/routes/index.tsx +982 -0
  221. package/src/routes/installations.tsx +194 -0
  222. package/src/routes/signing.tsx +37 -0
  223. package/src/styles.css +152 -0
  224. package/src/virtual-modules.d.ts +21 -0
  225. package/src/vite-env.d.ts +1 -0
  226. package/src/vite-internal.ts +195 -0
  227. package/src/vite.ts +12 -0
  228. package/.output/public/assets/dist-DJX53faP.js +0 -3
  229. package/.output/public/assets/index-Iylis5kY.js +0 -10
  230. package/.output/public/assets/routes-HT21hs0C.js +0 -54
  231. package/.output/public/assets/styles-C0Re4quB.css +0 -2
  232. package/.output/server/__23tanstack-start-server-fn-resolver-CgwgZ0-i.mjs +0 -66
  233. package/.output/server/_libs/@floating-ui/core+[...].mjs +0 -698
  234. package/.output/server/_libs/@floating-ui/dom+[...].mjs +0 -644
  235. package/.output/server/_libs/@floating-ui/react-dom+[...].mjs +0 -839
  236. package/.output/server/_libs/@radix-ui/react-alert-dialog+[...].mjs +0 -2138
  237. package/.output/server/_libs/@radix-ui/react-popper+[...].mjs +0 -308
  238. package/.output/server/_libs/@radix-ui/react-select+[...].mjs +0 -1038
  239. package/.output/server/_libs/@tanstack/devtools-event-client+[...].mjs +0 -196
  240. package/.output/server/_libs/@tanstack/form-core+[...].mjs +0 -3228
  241. package/.output/server/_libs/@tanstack/react-form+[...].mjs +0 -430
  242. package/.output/server/_libs/@tanstack/react-table+[...].mjs +0 -2382
  243. package/.output/server/_libs/radix-ui__number.mjs +0 -6
  244. package/.output/server/_libs/radix-ui__primitive.mjs +0 -9
  245. package/.output/server/_libs/radix-ui__react-arrow.mjs +0 -23
  246. package/.output/server/_libs/radix-ui__react-collection.mjs +0 -78
  247. package/.output/server/_libs/radix-ui__react-direction.mjs +0 -13
  248. package/.output/server/_libs/radix-ui__react-label.mjs +0 -22
  249. package/.output/server/_libs/radix-ui__react-separator.mjs +0 -30
  250. package/.output/server/_libs/radix-ui__react-slider.mjs +0 -510
  251. package/.output/server/_libs/radix-ui__react-switch.mjs +0 -157
  252. package/.output/server/_libs/radix-ui__react-tooltip.mjs +0 -493
  253. package/.output/server/_ssr/api-rpc-BIbDUKSz.mjs +0 -292
  254. package/.output/server/_ssr/config.server-BSS366KT.mjs +0 -41
  255. package/.output/server/_ssr/deleteBundle-BgbZTtpW.mjs +0 -141
  256. package/.output/server/_ssr/dist-KboydRVk.mjs +0 -120
  257. package/.output/server/_ssr/router-C3hqEy3k.mjs +0 -307
  258. package/.output/server/_ssr/routes-B0lQvvlC.mjs +0 -3048
  259. package/.output/server/_ssr/start-02sLteYG.mjs +0 -4
  260. package/.output/server/_ssr/storageProfile-FVkjPoGb.mjs +0 -9
  261. package/.output/server/_tanstack-start-manifest_v-Bpx52WOU.mjs +0 -20
  262. package/.output/server/node_modules/tslib/modules/index.js +0 -70
  263. package/.output/server/node_modules/tslib/modules/package.json +0 -3
  264. package/.output/server/node_modules/tslib/package.json +0 -47
  265. package/.output/server/node_modules/tslib/tslib.js +0 -484
  266. package/.output/server/package.json +0 -9
@@ -0,0 +1,37 @@
1
+ import { d as createFileRoute, u as lazyRouteComponent } from "../_libs/@tanstack/react-router+[...].mjs";
2
+ //#region node_modules/.nitro/vite/services/ssr/assets/routes-Z7TwC6nC.js
3
+ var optionalString = (value) => typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
4
+ function validateReleaseSearch(search) {
5
+ const parsedPage = typeof search.page === "number" ? search.page : typeof search.page === "string" ? Number(search.page) : void 0;
6
+ const beforeReleaseId = optionalString(search.beforeReleaseId);
7
+ const afterReleaseId = beforeReleaseId ? void 0 : optionalString(search.afterReleaseId);
8
+ const enabled = search.enabled === true || search.enabled === "true" ? true : search.enabled === false || search.enabled === "false" ? false : void 0;
9
+ return {
10
+ afterReleaseId,
11
+ beforeReleaseId,
12
+ bundleId: optionalString(search.bundleId),
13
+ channelId: optionalString(search.channelId),
14
+ enabled,
15
+ page: parsedPage !== void 0 && Number.isInteger(parsedPage) && parsedPage > 1 ? parsedPage : void 0,
16
+ platform: search.platform === "ios" || search.platform === "android" ? search.platform : void 0,
17
+ releaseId: optionalString(search.releaseId),
18
+ targetAppVersion: optionalString(search.targetAppVersion)
19
+ };
20
+ }
21
+ function updateReleaseFilters(current, filters) {
22
+ return {
23
+ ...current,
24
+ ...filters,
25
+ afterReleaseId: void 0,
26
+ beforeReleaseId: void 0,
27
+ page: void 0,
28
+ releaseId: void 0
29
+ };
30
+ }
31
+ var $$splitComponentImporter = () => import("./routes-BM4ht9jb.mjs");
32
+ var Route = createFileRoute("/")({
33
+ component: lazyRouteComponent($$splitComponentImporter, "component"),
34
+ validateSearch: validateReleaseSearch
35
+ });
36
+ //#endregion
37
+ export { updateReleaseFilters as n, Route as t };
@@ -0,0 +1,527 @@
1
+ import { a as parseSearchInstallationsInput, i as parseInstallationHistoryInput, n as parseBundleEventAnalyticsInput, r as parseBundleEventSummaryInput, t as parseActiveInstallationInput } from "./analytics-input-fRRRn0uA.mjs";
2
+ import { t as createUUIDv7 } from "./uuidv7-BbJ-9D_I.mjs";
3
+ //#region node_modules/.nitro/vite/services/ssr/assets/runtime.server-awTLiiXW.js
4
+ var AnalyticsScanLimitExceededError = class extends Error {
5
+ constructor(limit) {
6
+ super(`Analytics event scan exceeded ${limit} rows.`);
7
+ this.limit = limit;
8
+ this.name = "AnalyticsScanLimitExceededError";
9
+ }
10
+ };
11
+ var HOUR_MS = 3600 * 1e3;
12
+ var DAY_MS = 24 * HOUR_MS;
13
+ var ACTIVE_BUNDLE_EVENT_TYPES = [
14
+ "UPDATE_APPLIED",
15
+ "RECOVERED",
16
+ "RELEASE_ADOPTED",
17
+ "UNCHANGED"
18
+ ];
19
+ var activeWindowDefinitions = {
20
+ "24h": {
21
+ bucketCount: 24,
22
+ bucketSizeMs: HOUR_MS
23
+ },
24
+ "7d": {
25
+ bucketCount: 7,
26
+ bucketSizeMs: DAY_MS
27
+ },
28
+ "30d": {
29
+ bucketCount: 30,
30
+ bucketSizeMs: DAY_MS
31
+ }
32
+ };
33
+ function getActiveWindowDefinition(window) {
34
+ return activeWindowDefinitions[window];
35
+ }
36
+ function compareCodePoints$1(left, right) {
37
+ if (left < right) return -1;
38
+ if (left > right) return 1;
39
+ return 0;
40
+ }
41
+ function isNewer(candidate, current) {
42
+ return candidate.received_at_ms > current.received_at_ms || candidate.received_at_ms === current.received_at_ms && compareCodePoints$1(candidate.id, current.id) > 0;
43
+ }
44
+ function collectActiveInstallationOverview(request) {
45
+ const definition = getActiveWindowDefinition(request.window);
46
+ const windowStartMs = request.asOfMs - definition.bucketCount * definition.bucketSizeMs;
47
+ const rows = request.rows.filter((row) => row.received_at_ms >= windowStartMs && row.received_at_ms < request.asOfMs);
48
+ const latestByInstall = /* @__PURE__ */ new Map();
49
+ for (const row of rows) {
50
+ const current = latestByInstall.get(row.install_id);
51
+ if (current === void 0 || isNewer(row, current)) latestByInstall.set(row.install_id, row);
52
+ }
53
+ const selectedRows = [...latestByInstall.values()].filter((row) => request.userId === void 0 || row.user_id === request.userId);
54
+ const selectedInstallIds = new Set(selectedRows.map((row) => row.install_id));
55
+ const bundleCounts = /* @__PURE__ */ new Map();
56
+ for (const row of selectedRows) bundleCounts.set(row.to_bundle_id, (bundleCounts.get(row.to_bundle_id) ?? 0) + 1);
57
+ const latestByBucket = Array.from({ length: definition.bucketCount }, () => /* @__PURE__ */ new Map());
58
+ for (const row of rows) {
59
+ if (!selectedInstallIds.has(row.install_id)) continue;
60
+ const bucket = latestByBucket[Math.floor((row.received_at_ms - windowStartMs) / definition.bucketSizeMs)];
61
+ if (bucket === void 0) continue;
62
+ const current = bucket.get(row.install_id);
63
+ if (current === void 0 || isNewer(row, current)) bucket.set(row.install_id, row);
64
+ }
65
+ const bundleCountsByBucket = latestByBucket.map((bucket) => {
66
+ const counts = /* @__PURE__ */ new Map();
67
+ for (const row of bucket.values()) counts.set(row.to_bundle_id, (counts.get(row.to_bundle_id) ?? 0) + 1);
68
+ return counts;
69
+ });
70
+ const bundleObservationTotals = /* @__PURE__ */ new Map();
71
+ for (const counts of bundleCountsByBucket) for (const [bundleId, count] of counts) bundleObservationTotals.set(bundleId, (bundleObservationTotals.get(bundleId) ?? 0) + count);
72
+ return {
73
+ asOfMs: request.asOfMs,
74
+ window: request.window,
75
+ activeInstallations: selectedInstallIds.size,
76
+ series: latestByBucket.map((bucket, index) => ({
77
+ bucketStartMs: windowStartMs + index * definition.bucketSizeMs,
78
+ value: bucket.size
79
+ })),
80
+ bundleSeries: [...bundleObservationTotals].sort(([leftId, leftTotal], [rightId, rightTotal]) => rightTotal - leftTotal || compareCodePoints$1(leftId, rightId)).map(([bundleId]) => ({
81
+ bundleId,
82
+ series: bundleCountsByBucket.map((counts, index) => ({
83
+ bucketStartMs: windowStartMs + index * definition.bucketSizeMs,
84
+ value: counts.get(bundleId) ?? 0
85
+ }))
86
+ })),
87
+ bundles: [...bundleCounts].map(([bundleId, installations]) => ({
88
+ bundleId,
89
+ installations
90
+ })).sort((left, right) => right.installations - left.installations || compareCodePoints$1(left.bundleId, right.bundleId))
91
+ };
92
+ }
93
+ var ANALYTICS_SCAN_MAX_ROWS = 5e4;
94
+ var ANALYTICS_MATERIALIZATION_LIMIT = ANALYTICS_SCAN_MAX_ROWS + 1;
95
+ var ANALYTICS_SCAN_PAGE_SIZE = 1e3;
96
+ var ANALYTICS_LOWER_BOUND_ID = "00000000-0000-0000-0000-000000000000";
97
+ var compareCodePoints = (left, right) => {
98
+ if (left < right) return -1;
99
+ if (left > right) return 1;
100
+ return 0;
101
+ };
102
+ var compareEventNewest = (left, right) => right.received_at_ms - left.received_at_ms || compareCodePoints(right.id, left.id);
103
+ var compareEventOldest = (left, right) => left.received_at_ms - right.received_at_ms || compareCodePoints(left.id, right.id);
104
+ var materializeEventRows = async (scope) => {
105
+ const rows = [];
106
+ const seenIds = /* @__PURE__ */ new Set();
107
+ let after = scope.lowerBoundMs === void 0 ? void 0 : {
108
+ receivedAtMs: scope.lowerBoundMs,
109
+ id: ANALYTICS_LOWER_BOUND_ID
110
+ };
111
+ while (rows.length < ANALYTICS_MATERIALIZATION_LIMIT) {
112
+ const limit = Math.min(ANALYTICS_SCAN_PAGE_SIZE, ANALYTICS_MATERIALIZATION_LIMIT - rows.length);
113
+ const page = await scope.persistence.scan({
114
+ beforeReceivedAtMs: scope.cutoffMs,
115
+ ...after === void 0 ? {} : { after },
116
+ limit
117
+ });
118
+ if (page.length === 0) break;
119
+ if (page.length > limit) throw new AnalyticsPersistenceOrderError();
120
+ let previous = after === void 0 ? void 0 : {
121
+ received_at_ms: after.receivedAtMs,
122
+ id: after.id
123
+ };
124
+ for (const row of page) {
125
+ if (row.received_at_ms >= scope.cutoffMs || previous !== void 0 && compareEventOldest(previous, row) >= 0 || seenIds.has(row.id)) throw new AnalyticsPersistenceOrderError();
126
+ seenIds.add(row.id);
127
+ rows.push(row);
128
+ previous = row;
129
+ }
130
+ const last = page.at(-1);
131
+ if (last !== void 0) after = {
132
+ receivedAtMs: last.received_at_ms,
133
+ id: last.id
134
+ };
135
+ if (rows.length > 5e4) throw new AnalyticsScanLimitExceededError(ANALYTICS_SCAN_MAX_ROWS);
136
+ }
137
+ return rows;
138
+ };
139
+ var AnalyticsPersistenceOrderError = class extends Error {
140
+ name = "AnalyticsPersistenceOrderError";
141
+ constructor() {
142
+ super("Analytics persistence did not advance its scan cursor.");
143
+ }
144
+ };
145
+ var materializeActiveRows = async (scope, window) => {
146
+ const definition = getActiveWindowDefinition(window);
147
+ const durationMs = definition.bucketCount * definition.bucketSizeMs;
148
+ return (await materializeEventRows({
149
+ ...scope,
150
+ lowerBoundMs: scope.cutoffMs - durationMs
151
+ })).filter((row) => row.received_at_ms >= scope.cutoffMs - durationMs && ACTIVE_BUNDLE_EVENT_TYPES.includes(row.type));
152
+ };
153
+ var startOfUtcHour = (value) => {
154
+ const date = new Date(value);
155
+ return Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours());
156
+ };
157
+ var startOfUtcDay = (value) => {
158
+ const date = new Date(value);
159
+ return Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate());
160
+ };
161
+ var getWindowRange = (window, now) => {
162
+ if (window === "24h") return {
163
+ sizeMs: 3600 * 1e3,
164
+ rangeStart: startOfUtcHour(now) - 1380 * 60 * 1e3
165
+ };
166
+ const days = window === "7d" ? 7 : 30;
167
+ return {
168
+ sizeMs: 1440 * 60 * 1e3,
169
+ rangeStart: startOfUtcDay(now) - (days - 1) * 24 * 60 * 60 * 1e3
170
+ };
171
+ };
172
+ var materializeRowsForWindow = async (scope, window) => {
173
+ if (window === "all") return materializeEventRows(scope);
174
+ const range = getWindowRange(window, scope.cutoffMs);
175
+ return (await materializeEventRows({
176
+ ...scope,
177
+ lowerBoundMs: range.rangeStart
178
+ })).filter(({ received_at_ms }) => received_at_ms >= range.rangeStart);
179
+ };
180
+ var bucketStart = (receivedAtMs, sizeMs) => sizeMs === 3600 * 1e3 ? startOfUtcHour(receivedAtMs) : startOfUtcDay(receivedAtMs);
181
+ var createSeries = (request) => {
182
+ const range = request.window === "all" ? void 0 : getWindowRange(request.window, request.cutoffMs);
183
+ const sizeMs = range?.sizeMs ?? 1440 * 60 * 1e3;
184
+ const installIdsByBucket = /* @__PURE__ */ new Map();
185
+ let oldestMs = request.cutoffMs;
186
+ for (const row of request.rows) {
187
+ oldestMs = Math.min(oldestMs, row.received_at_ms);
188
+ const start = bucketStart(row.received_at_ms, sizeMs);
189
+ const installIds = installIdsByBucket.get(start) ?? /* @__PURE__ */ new Set();
190
+ installIds.add(row.install_id);
191
+ installIdsByBucket.set(start, installIds);
192
+ }
193
+ const first = range?.rangeStart ?? startOfUtcDay(oldestMs);
194
+ const last = bucketStart(request.cutoffMs, sizeMs);
195
+ return Array.from({ length: Math.floor((last - first) / sizeMs) + 1 }, (_, index) => {
196
+ const start = first + index * sizeMs;
197
+ return {
198
+ bucketStartMs: start,
199
+ value: installIdsByBucket.get(start)?.size ?? 0
200
+ };
201
+ });
202
+ };
203
+ var collectEventActivity = (request) => {
204
+ const range = request.window === "all" ? void 0 : getWindowRange(request.window, request.cutoffMs);
205
+ const rows = range === void 0 ? request.rows : request.rows.filter(({ received_at_ms }) => received_at_ms >= range.rangeStart && received_at_ms < request.cutoffMs);
206
+ const installs = /* @__PURE__ */ new Set();
207
+ const installsByCohort = /* @__PURE__ */ new Map();
208
+ for (const row of rows) {
209
+ installs.add(row.install_id);
210
+ const cohort = installsByCohort.get(row.cohort) ?? /* @__PURE__ */ new Set();
211
+ cohort.add(row.install_id);
212
+ installsByCohort.set(row.cohort, cohort);
213
+ }
214
+ return {
215
+ summary: installs.size,
216
+ cohorts: [...installsByCohort].sort(([left], [right]) => compareCodePoints(left, right)).map(([cohort, installIds]) => ({
217
+ cohort,
218
+ value: installIds.size
219
+ })),
220
+ series: createSeries({
221
+ ...request,
222
+ rows
223
+ })
224
+ };
225
+ };
226
+ function toSearchRow(row) {
227
+ return {
228
+ installId: row.install_id,
229
+ username: row.username,
230
+ userId: row.user_id,
231
+ lastKnownBundleId: row.to_bundle_id,
232
+ latestStatus: row.type,
233
+ platform: row.platform,
234
+ appVersion: row.app_version,
235
+ channel: row.channel,
236
+ cohort: row.cohort,
237
+ receivedAtMs: row.received_at_ms
238
+ };
239
+ }
240
+ function matchesIdentity(row, query) {
241
+ return row.install_id.toLowerCase().includes(query) || row.user_id?.toLowerCase().includes(query) === true || row.username?.toLowerCase().includes(query) === true;
242
+ }
243
+ function searchEventInstallations(request) {
244
+ const query = request.query.toLowerCase();
245
+ const matchingInstallIds = /* @__PURE__ */ new Set();
246
+ for (const row of request.rows) if (query.length === 0 || matchesIdentity(row, query)) matchingInstallIds.add(row.install_id);
247
+ const latestByInstall = /* @__PURE__ */ new Map();
248
+ for (const row of request.rows) {
249
+ if (!matchingInstallIds.has(row.install_id)) continue;
250
+ const current = latestByInstall.get(row.install_id);
251
+ if (current === void 0 || compareEventNewest(row, current) < 0) latestByInstall.set(row.install_id, row);
252
+ }
253
+ const matchingRows = [...latestByInstall.values()].sort((left, right) => compareCodePoints(left.install_id, right.install_id));
254
+ const pageSize = Math.min(Math.max(request.limit, 0), 100);
255
+ return {
256
+ data: matchingRows.slice(request.offset, request.offset + pageSize).map(toSearchRow),
257
+ pagination: {
258
+ total: matchingRows.length,
259
+ limit: request.limit,
260
+ offset: request.offset
261
+ }
262
+ };
263
+ }
264
+ function createBundleEventRow(input) {
265
+ const base = {
266
+ id: createUUIDv7(),
267
+ install_id: input.installId,
268
+ user_id: input.userId ?? null,
269
+ username: input.username ?? null,
270
+ from_release_id: input.fromReleaseId,
271
+ to_release_id: input.toReleaseId,
272
+ to_bundle_id: input.toBundleId,
273
+ platform: input.platform,
274
+ app_version: input.appVersion,
275
+ channel: input.channel,
276
+ cohort: input.cohort,
277
+ fingerprint_hash: input.fingerprintHash,
278
+ sdk_version: input.sdkVersion ?? null,
279
+ received_at_ms: Date.now()
280
+ };
281
+ switch (input.type) {
282
+ case "UPDATE_APPLIED":
283
+ case "RECOVERED":
284
+ case "RELEASE_ADOPTED": return {
285
+ ...base,
286
+ type: input.type,
287
+ from_bundle_id: input.fromBundleId,
288
+ update_strategy: input.updateStrategy
289
+ };
290
+ case "UNCHANGED": return {
291
+ ...base,
292
+ type: input.type,
293
+ from_bundle_id: null,
294
+ update_strategy: null
295
+ };
296
+ }
297
+ }
298
+ var isTransitionEventRow = (row) => row.type === "UPDATE_APPLIED" || row.type === "RECOVERED";
299
+ var toHistoryRow = (row) => ({
300
+ id: row.id,
301
+ type: row.type,
302
+ fromBundleId: row.from_bundle_id,
303
+ toBundleId: row.to_bundle_id,
304
+ username: row.username,
305
+ userId: row.user_id,
306
+ platform: row.platform,
307
+ appVersion: row.app_version,
308
+ channel: row.channel,
309
+ cohort: row.cohort,
310
+ receivedAtMs: row.received_at_ms
311
+ });
312
+ var isInstalledForBundle = (row, bundleId) => row.type === "UPDATE_APPLIED" && row.to_bundle_id === bundleId;
313
+ var isRecoveredFromBundle = (row, bundleId) => row.type === "RECOVERED" && row.from_bundle_id === bundleId;
314
+ var countDistinctInstallations = (rows) => new Set(rows.map(({ install_id }) => install_id)).size;
315
+ var getAnalyticsResult = async (persistence, bundleId, window, limit, offset) => {
316
+ const scope = {
317
+ persistence,
318
+ cutoffMs: Date.now()
319
+ };
320
+ const rows = (await materializeRowsForWindow(scope, window)).filter(isTransitionEventRow);
321
+ const installedRows = rows.filter((row) => isInstalledForBundle(row, bundleId));
322
+ const recoveredRows = rows.filter((row) => isRecoveredFromBundle(row, bundleId));
323
+ const installed = collectEventActivity({
324
+ rows: installedRows,
325
+ window,
326
+ cutoffMs: scope.cutoffMs
327
+ });
328
+ const recovered = collectEventActivity({
329
+ rows: recoveredRows,
330
+ window,
331
+ cutoffMs: scope.cutoffMs
332
+ });
333
+ const recentRows = [...installedRows, ...recoveredRows].sort(compareEventNewest);
334
+ return {
335
+ summary: {
336
+ installed: installed.summary,
337
+ recovered: recovered.summary
338
+ },
339
+ series: {
340
+ installed: installed.series,
341
+ recovered: recovered.series
342
+ },
343
+ cohorts: {
344
+ installed: installed.cohorts,
345
+ recovered: recovered.cohorts
346
+ },
347
+ recentEvents: {
348
+ data: recentRows.slice(offset, offset + limit).map(toHistoryRow),
349
+ pagination: {
350
+ total: recentRows.length,
351
+ limit,
352
+ offset
353
+ }
354
+ }
355
+ };
356
+ };
357
+ var getAnalyticsSummaries = async (persistence, bundleIds, window) => {
358
+ const normalizedBundleIds = [...new Set(bundleIds)];
359
+ if (normalizedBundleIds.length === 0) return [];
360
+ const requestedBundleIds = new Set(normalizedBundleIds);
361
+ const installedByBundleId = /* @__PURE__ */ new Map();
362
+ const recoveredByBundleId = /* @__PURE__ */ new Map();
363
+ const rows = (await materializeRowsForWindow({
364
+ persistence,
365
+ cutoffMs: Date.now()
366
+ }, window)).filter(isTransitionEventRow);
367
+ for (const row of rows) {
368
+ const bundleId = row.type === "UPDATE_APPLIED" ? row.to_bundle_id : row.from_bundle_id;
369
+ if (!requestedBundleIds.has(bundleId)) continue;
370
+ const counts = row.type === "UPDATE_APPLIED" ? installedByBundleId : recoveredByBundleId;
371
+ const installIds = counts.get(bundleId) ?? /* @__PURE__ */ new Set();
372
+ installIds.add(row.install_id);
373
+ counts.set(bundleId, installIds);
374
+ }
375
+ return normalizedBundleIds.map((bundleId) => ({
376
+ bundleId,
377
+ installed: installedByBundleId.get(bundleId)?.size ?? 0,
378
+ recovered: recoveredByBundleId.get(bundleId)?.size ?? 0
379
+ }));
380
+ };
381
+ var createAnalyticsProvider = (persistence) => Object.freeze({
382
+ mode: "bounded",
383
+ maxMatchingRows: ANALYTICS_SCAN_MAX_ROWS,
384
+ async appendBundleEvent(input) {
385
+ await persistence.append(createBundleEventRow(input));
386
+ },
387
+ async getBundleEventSummary(bundleId) {
388
+ const rows = (await materializeEventRows({
389
+ persistence,
390
+ cutoffMs: Date.now()
391
+ })).filter(isTransitionEventRow);
392
+ return {
393
+ installed: countDistinctInstallations(rows.filter((row) => isInstalledForBundle(row, bundleId))),
394
+ recovered: countDistinctInstallations(rows.filter((row) => isRecoveredFromBundle(row, bundleId)))
395
+ };
396
+ },
397
+ getBundleEventSummaries(bundleIds, window) {
398
+ return getAnalyticsSummaries(persistence, bundleIds, window);
399
+ },
400
+ getBundleEventAnalytics(bundleId, window, limit, offset) {
401
+ return getAnalyticsResult(persistence, bundleId, window, limit, offset);
402
+ },
403
+ async getBundleEventOverview() {
404
+ const rows = await materializeEventRows({
405
+ persistence,
406
+ cutoffMs: Date.now()
407
+ });
408
+ const latestByInstall = /* @__PURE__ */ new Map();
409
+ for (const row of rows) {
410
+ const current = latestByInstall.get(row.install_id);
411
+ if (current === void 0 || compareEventNewest(row, current) < 0) latestByInstall.set(row.install_id, row);
412
+ }
413
+ const counts = /* @__PURE__ */ new Map();
414
+ for (const row of latestByInstall.values()) counts.set(row.to_bundle_id, (counts.get(row.to_bundle_id) ?? 0) + 1);
415
+ return {
416
+ trackedInstallations: latestByInstall.size,
417
+ bundles: [...counts].map(([bundleId, installations]) => ({
418
+ bundleId,
419
+ installations
420
+ })).sort((left, right) => right.installations - left.installations || compareCodePoints(left.bundleId, right.bundleId))
421
+ };
422
+ },
423
+ async getActiveInstallationOverview(input) {
424
+ const asOfMs = Date.now();
425
+ return collectActiveInstallationOverview({
426
+ rows: await materializeActiveRows({
427
+ persistence,
428
+ cutoffMs: asOfMs
429
+ }, input.window),
430
+ asOfMs,
431
+ window: input.window,
432
+ ...input.userId === void 0 ? {} : { userId: input.userId }
433
+ });
434
+ },
435
+ async searchInstallations(query, limit, offset) {
436
+ return searchEventInstallations({
437
+ rows: await materializeEventRows({
438
+ persistence,
439
+ cutoffMs: Date.now()
440
+ }),
441
+ query,
442
+ limit,
443
+ offset
444
+ });
445
+ },
446
+ async getInstallationHistory(installId, limit, offset) {
447
+ const ordered = (await materializeEventRows({
448
+ persistence,
449
+ cutoffMs: Date.now()
450
+ })).filter((row) => row.install_id === installId && isTransitionEventRow(row)).toSorted(compareEventNewest);
451
+ return {
452
+ data: ordered.slice(offset, offset + limit).map(toHistoryRow),
453
+ pagination: {
454
+ total: ordered.length,
455
+ limit,
456
+ offset
457
+ }
458
+ };
459
+ }
460
+ });
461
+ function createRuntimeHotUpdater(config) {
462
+ const models = Reflect.get(config.database, "models");
463
+ const analytics = typeof models === "object" && models !== null ? Reflect.get(models, "analytics") : void 0;
464
+ if (typeof analytics !== "object" || analytics === null || typeof Reflect.get(analytics, "append") !== "function" || typeof Reflect.get(analytics, "scan") !== "function") return null;
465
+ return createAnalyticsProvider(analytics);
466
+ }
467
+ function createApiKeyStore(config) {
468
+ const models = Reflect.get(config.database, "models");
469
+ const apiKeys = typeof models === "object" && models !== null ? Reflect.get(models, "apiKeys") : void 0;
470
+ if (typeof apiKeys !== "object" || apiKeys === null || typeof Reflect.get(apiKeys, "create") !== "function" || typeof Reflect.get(apiKeys, "findByHash") !== "function" || typeof Reflect.get(apiKeys, "list") !== "function" || typeof Reflect.get(apiKeys, "revoke") !== "function") return null;
471
+ return apiKeys;
472
+ }
473
+ var providerMethods = [
474
+ "appendBundleEvent",
475
+ "getBundleEventSummary",
476
+ "getBundleEventSummaries",
477
+ "getBundleEventAnalytics",
478
+ "getBundleEventOverview",
479
+ "getActiveInstallationOverview",
480
+ "searchInstallations",
481
+ "getInstallationHistory"
482
+ ];
483
+ var parseAnalyticsProvider = (value) => {
484
+ if (typeof value !== "object" || value === null || Reflect.get(value, "mode") !== "bounded" || typeof Reflect.get(value, "maxMatchingRows") !== "number" || !providerMethods.every((method) => typeof Reflect.get(value, method) === "function")) return null;
485
+ return value;
486
+ };
487
+ var getAnalyticsCapability = async (provider) => {
488
+ const parsed = parseAnalyticsProvider(provider);
489
+ return parsed === null ? {
490
+ analytics: false,
491
+ analyticsQueries: false,
492
+ eventIngestion: false
493
+ } : {
494
+ analytics: true,
495
+ analyticsQueries: true,
496
+ eventIngestion: true,
497
+ maxMatchingRows: parsed.maxMatchingRows,
498
+ mode: "bounded"
499
+ };
500
+ };
501
+ var requireAnalyticsSupport = async (provider) => {
502
+ const parsedProvider = parseAnalyticsProvider(provider);
503
+ if (parsedProvider === null) throw new Error("Analytics are not supported by the configured database plugin.");
504
+ return parsedProvider;
505
+ };
506
+ async function getBundleEventSummary(provider, input) {
507
+ const { bundleId } = parseBundleEventSummaryInput(input);
508
+ return (await requireAnalyticsSupport(provider)).getBundleEventSummary(bundleId);
509
+ }
510
+ async function getActiveInstallationOverview(provider, input) {
511
+ const parsed = parseActiveInstallationInput(input);
512
+ return (await requireAnalyticsSupport(provider)).getActiveInstallationOverview(parsed);
513
+ }
514
+ async function getBundleEventAnalytics(provider, input) {
515
+ const parsed = parseBundleEventAnalyticsInput(input);
516
+ return (await requireAnalyticsSupport(provider)).getBundleEventAnalytics(parsed.bundleId, parsed.window, parsed.limit ?? 50, parsed.offset ?? 0);
517
+ }
518
+ async function searchInstallations(provider, input) {
519
+ const parsed = parseSearchInstallationsInput(input);
520
+ return (await requireAnalyticsSupport(provider)).searchInstallations(parsed.query, parsed.limit ?? 50, parsed.offset ?? 0);
521
+ }
522
+ async function getInstallationHistory(provider, input) {
523
+ const parsed = parseInstallationHistoryInput(input);
524
+ return (await requireAnalyticsSupport(provider)).getInstallationHistory(parsed.installId, parsed.limit ?? 50, parsed.offset ?? 0);
525
+ }
526
+ //#endregion
527
+ export { createApiKeyStore, createRuntimeHotUpdater, getActiveInstallationOverview, getAnalyticsCapability, getBundleEventAnalytics, getBundleEventSummary, getInstallationHistory, searchInstallations };