@hot-updater/console 0.36.7 → 1.0.0-rc.1

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 (262) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/BundleIdDisplay-D4ErdU5_.js +1 -0
  3. package/.output/public/assets/InsightsCapabilityContext-B42VqvP6.js +1 -0
  4. package/.output/public/assets/api-keys-api-DGVTTvTJ.js +1 -0
  5. package/.output/public/assets/api-keys-z16OZIP4.js +1 -0
  6. package/.output/public/assets/badge-Br_KDO0J.js +1 -0
  7. package/.output/public/assets/card-DzuEXvyJ.js +1 -0
  8. package/.output/public/assets/chart-no-axes-combined-FaYMGmQS.js +1 -0
  9. package/.output/public/assets/createServerFn-gM2-LFBy.js +3 -0
  10. package/.output/public/assets/dialog-BeAENgez.js +1 -0
  11. package/.output/public/assets/dist-Bssh_cv0.js +1 -0
  12. package/.output/public/assets/field-Buq148-A.js +1 -0
  13. package/.output/public/assets/index-uOpQAIC4.js +10 -0
  14. package/.output/public/assets/input-group-B3p5UpyW.js +1 -0
  15. package/.output/public/assets/insights-DJTr7Ozn.js +1 -0
  16. package/.output/public/assets/insights-api-DVMOoRMS.js +1 -0
  17. package/.output/public/assets/installations-BY64iIAK.js +2 -0
  18. package/.output/public/assets/installations-D5v9i3AL.js +1 -0
  19. package/.output/public/assets/lazyRouteComponent-DKYu5cZJ.js +1 -0
  20. package/.output/public/assets/preload-helper-CQ36nxok.js +1 -0
  21. package/.output/public/assets/refresh-cw-BkbfZ62O.js +1 -0
  22. package/.output/public/assets/routes-BuoI5-Cr.js +2 -0
  23. package/.output/public/assets/routes-C8fvgETO.js +1 -0
  24. package/.output/public/assets/search-qsIDvS2I.js +1 -0
  25. package/.output/public/assets/shield-check-DE_hVuHu.js +1 -0
  26. package/.output/public/assets/signing-mJD88Y4C.js +1 -0
  27. package/.output/public/assets/styles-BZIG7t42.css +2 -0
  28. package/.output/public/assets/table-RamFEclP.js +1 -0
  29. package/.output/public/assets/useCompositeListItem-ChzDqdpc.js +42 -0
  30. package/.output/public/assets/useMutation-JsGVjQMY.js +1 -0
  31. package/.output/public/assets/useOpenInteractionType-Dwfy-SpA.js +1 -0
  32. package/.output/server/__23tanstack-start-server-fn-resolver-Dftxlm2E.mjs +146 -0
  33. package/.output/server/_libs/@base-ui/react+[...].mjs +16124 -0
  34. package/.output/server/_libs/@reduxjs/toolkit+[...].mjs +1554 -0
  35. package/.output/server/_libs/@tanstack/react-router+[...].mjs +41 -10
  36. package/.output/server/_libs/@tanstack/react-router-ssr-query+[...].mjs +132 -0
  37. package/.output/server/_libs/d3-array.mjs +231 -0
  38. package/.output/server/_libs/d3-color.mjs +330 -0
  39. package/.output/server/_libs/d3-format.mjs +252 -0
  40. package/.output/server/_libs/d3-interpolate.mjs +207 -0
  41. package/.output/server/_libs/d3-path.mjs +77 -0
  42. package/.output/server/_libs/d3-scale+[...].mjs +1907 -0
  43. package/.output/server/_libs/d3-shape.mjs +818 -0
  44. package/.output/server/_libs/decimal.js-light.mjs +884 -0
  45. package/.output/server/_libs/es-toolkit.mjs +1134 -0
  46. package/.output/server/_libs/eventemitter3.mjs +236 -0
  47. package/.output/server/_libs/immer.mjs +532 -0
  48. package/.output/server/_libs/lucide-react.mjs +141 -13
  49. package/.output/server/_libs/next-themes.mjs +2 -2
  50. package/.output/server/_libs/react-redux+[...].mjs +205 -0
  51. package/.output/server/_libs/recharts+[...].mjs +14117 -0
  52. package/.output/server/_libs/sonner.mjs +2 -2
  53. package/.output/server/_libs/tanstack__query-core.mjs +114 -1
  54. package/.output/server/_libs/tanstack__react-query.mjs +3 -4
  55. package/.output/server/_runtime.mjs +10 -1
  56. package/.output/server/_ssr/BundleActivityChart-CWcdPuUn.mjs +288 -0
  57. package/.output/server/_ssr/BundleIdDisplay-BEvB3MvA.mjs +13 -0
  58. package/.output/server/_ssr/InsightsCapabilityContext-Cx8fjm2s.mjs +15 -0
  59. package/.output/server/_ssr/api-keys-CpBqHpuC.mjs +300 -0
  60. package/.output/server/_ssr/api-keys-api-Czkp57UV.mjs +60 -0
  61. package/.output/server/_ssr/api-keys-rpc-BTtMDSoR.mjs +136 -0
  62. package/.output/server/_ssr/api-rpc-BEX8ISai.mjs +1468 -0
  63. package/.output/server/_ssr/auth-BF8mbAgF.mjs +21 -0
  64. package/.output/server/_ssr/auth-rpc-BFMEC-TT.mjs +24 -0
  65. package/.output/server/_ssr/auth.server-DW71WzsD.mjs +47 -0
  66. package/.output/server/_ssr/badge-CpWXQwyF.mjs +26 -0
  67. package/.output/server/_ssr/bundle-signing-rpc-BbCFUQSu.mjs +16 -0
  68. package/.output/server/_ssr/bundle-signing.server-C4NW5mWx.mjs +12 -0
  69. package/.output/server/_ssr/card-Bsc8mRis.mjs +86 -0
  70. package/.output/server/_ssr/config-Dv8qLDZ0.mjs +13 -0
  71. package/.output/server/_ssr/config.server-DQYEr_5X.mjs +361 -0
  72. package/.output/server/_ssr/{createServerFn-DzU0k62V.mjs → createServerFn-B2hcXoPM.mjs} +1 -1
  73. package/.output/server/_ssr/createServerRpc-DB29C_5p.mjs +12 -0
  74. package/.output/server/_ssr/{sidebar-Cv_H0_Jv.mjs → createSsrRpc-DhfrxEjg.mjs} +68 -50
  75. package/.output/server/_ssr/databaseRows-QlCYLi0H.mjs +166 -0
  76. package/.output/server/_ssr/deleteBundle-Cu_XOMt0.mjs +66 -0
  77. package/.output/server/_ssr/dialog-C0vKUp8R.mjs +170 -0
  78. package/.output/server/_ssr/dist-BVSLTpoP.mjs +250 -0
  79. package/.output/server/_ssr/downloadBundle-CNnYxYO2.mjs +24 -0
  80. package/.output/server/_ssr/{empty-plugin-adapters-B4HCzgmM.mjs → empty-plugin-adapters-B5QvGpEl.mjs} +1 -1
  81. package/.output/server/_ssr/field-DASCh-5d.mjs +74 -0
  82. package/.output/server/_ssr/{getBundleChildren-CVtSiH5t.mjs → getBundleChildren-BcRMFoDF.mjs} +10 -18
  83. package/.output/server/_ssr/input-group-etIsBUqv.mjs +288 -0
  84. package/.output/server/_ssr/insights-api-C1F7UreR.mjs +71 -0
  85. package/.output/server/_ssr/insights-input-9fEJF-ZG.mjs +96 -0
  86. package/.output/server/_ssr/insights-rpc-C-8R6kED.mjs +155 -0
  87. package/.output/server/_ssr/insights-ygT_aIfo.mjs +858 -0
  88. package/.output/server/_ssr/installations-DqRT1QTd.mjs +535 -0
  89. package/.output/server/_ssr/installations-K8TCYTYh.mjs +19 -0
  90. package/.output/server/_ssr/router-BxBEvtF1.mjs +513 -0
  91. package/.output/server/_ssr/routes-BC7rn-tf.mjs +37 -0
  92. package/.output/server/_ssr/routes-CM5KAwpB.mjs +2285 -0
  93. package/.output/server/_ssr/runtime.server-la7TRAH_.mjs +527 -0
  94. package/.output/server/_ssr/server-CpzW--lT.mjs +7468 -0
  95. package/.output/server/_ssr/signing-jk4HANnl.mjs +134 -0
  96. package/.output/server/_ssr/ssr.mjs +4 -5740
  97. package/.output/server/_ssr/start-DpViQGnt.mjs +4 -0
  98. package/.output/server/_ssr/table-CU58HeI7.mjs +53 -0
  99. package/.output/server/_ssr/uuidv7-CaKuKXrV.mjs +31 -0
  100. package/.output/server/_tanstack-start-manifest_v-Mz9MmSlD.mjs +103 -0
  101. package/.output/server/index.mjs +229 -47
  102. package/README.md +29 -8
  103. package/lib/index.d.mts +35 -0
  104. package/lib/index.mjs +4 -0
  105. package/lib/vite-internal.mjs +127 -0
  106. package/lib/vite.d.mts +10 -0
  107. package/lib/vite.mjs +5 -0
  108. package/package.json +51 -29
  109. package/public/apple-touch-icon.png +0 -0
  110. package/public/favicon-16x16.png +0 -0
  111. package/public/favicon-32x32.png +0 -0
  112. package/public/favicon.ico +0 -0
  113. package/public/logo.svg +1 -0
  114. package/public/manifest.json +30 -0
  115. package/public/robots.txt +3 -0
  116. package/src/components/AppSidebar.tsx +159 -0
  117. package/src/components/BundleIdDisplay.tsx +18 -0
  118. package/src/components/ChannelBadge.tsx +31 -0
  119. package/src/components/ConsoleAccessPage.tsx +122 -0
  120. package/src/components/EnabledStatusIcon.tsx +54 -0
  121. package/src/components/HashValueDisplay.tsx +85 -0
  122. package/src/components/HotUpdaterLogo.tsx +30 -0
  123. package/src/components/NotFoundPage.tsx +33 -0
  124. package/src/components/PlatformIcon.tsx +39 -0
  125. package/src/components/RolloutPercentageBadge.tsx +28 -0
  126. package/src/components/ThemeProvider.tsx +116 -0
  127. package/src/components/TimestampDisplay.tsx +41 -0
  128. package/src/components/features/api-keys/ApiKeysPage.tsx +377 -0
  129. package/src/components/features/bundles/BundleActivityChart.tsx +193 -0
  130. package/src/components/features/bundles/BundleChildrenPanel.tsx +201 -0
  131. package/src/components/features/bundles/BundleInsightsSummary.tsx +123 -0
  132. package/src/components/features/bundles/BundleMetadata.tsx +136 -0
  133. package/src/components/features/bundles/RolloutCohortsDialog.tsx +201 -0
  134. package/src/components/features/channels/ChannelManagementDialog.tsx +228 -0
  135. package/src/components/features/insights/ActivityChart.tsx +150 -0
  136. package/src/components/features/insights/BundleDistribution.tsx +133 -0
  137. package/src/components/features/insights/BundleSelector.tsx +114 -0
  138. package/src/components/features/insights/InsightsCapabilityContext.tsx +24 -0
  139. package/src/components/features/insights/InsightsControls.tsx +115 -0
  140. package/src/components/features/insights/InsightsErrorAlert.tsx +21 -0
  141. package/src/components/features/insights/InsightsOverview.tsx +208 -0
  142. package/src/components/features/insights/InstallationHistoryCard.tsx +248 -0
  143. package/src/components/features/insights/InstallationMatchesCard.tsx +132 -0
  144. package/src/components/features/insights/InstallationPageHeader.tsx +138 -0
  145. package/src/components/features/insights/InstallationPagination.tsx +48 -0
  146. package/src/components/features/insights/InstallationSearch.tsx +230 -0
  147. package/src/components/features/insights/UpdateOutcomes.tsx +147 -0
  148. package/src/components/features/releases/ReleaseEditorSheet.tsx +879 -0
  149. package/src/components/features/releases/ReleaseStateBadge.tsx +20 -0
  150. package/src/components/features/signing/BundleSigningPage.tsx +139 -0
  151. package/src/components/ui/alert-dialog.tsx +185 -0
  152. package/src/components/ui/alert.tsx +76 -0
  153. package/src/components/ui/badge.tsx +49 -0
  154. package/src/components/ui/button.tsx +58 -0
  155. package/src/components/ui/card.tsx +89 -0
  156. package/src/components/ui/chart.tsx +371 -0
  157. package/src/components/ui/combobox.tsx +303 -0
  158. package/src/components/ui/dialog.tsx +156 -0
  159. package/src/components/ui/dropdown-menu.tsx +270 -0
  160. package/src/components/ui/field.tsx +235 -0
  161. package/src/components/ui/input-group.tsx +147 -0
  162. package/src/components/ui/input.tsx +19 -0
  163. package/src/components/ui/label.tsx +25 -0
  164. package/src/components/ui/progress.tsx +84 -0
  165. package/src/components/ui/select.tsx +205 -0
  166. package/src/components/ui/separator.tsx +23 -0
  167. package/src/components/ui/sheet.tsx +132 -0
  168. package/src/components/ui/sidebar.tsx +779 -0
  169. package/src/components/ui/skeleton.tsx +13 -0
  170. package/src/components/ui/slider.tsx +56 -0
  171. package/src/components/ui/sonner.tsx +45 -0
  172. package/src/components/ui/switch.tsx +25 -0
  173. package/src/components/ui/table.tsx +114 -0
  174. package/src/components/ui/textarea.tsx +18 -0
  175. package/src/components/ui/toggle-group.tsx +89 -0
  176. package/src/components/ui/toggle.tsx +43 -0
  177. package/src/components/ui/tooltip.tsx +66 -0
  178. package/src/hooks/use-mobile.ts +30 -0
  179. package/src/index.ts +47 -0
  180. package/src/lib/api-keys-api.ts +69 -0
  181. package/src/lib/api-keys-rpc.ts +87 -0
  182. package/src/lib/api-rpc.ts +478 -0
  183. package/src/lib/api.ts +444 -0
  184. package/src/lib/auth-rpc.ts +16 -0
  185. package/src/lib/bundle-signing-api.ts +14 -0
  186. package/src/lib/bundle-signing-rpc.ts +16 -0
  187. package/src/lib/constants.ts +1 -0
  188. package/src/lib/git.ts +51 -0
  189. package/src/lib/insights-api.ts +144 -0
  190. package/src/lib/insights-error.ts +19 -0
  191. package/src/lib/insights-input.ts +187 -0
  192. package/src/lib/insights-overview.ts +170 -0
  193. package/src/lib/insights-rpc.ts +218 -0
  194. package/src/lib/server/auth.server.ts +43 -0
  195. package/src/lib/server/bundle-signing.server.ts +23 -0
  196. package/src/lib/server/config.server.ts +109 -0
  197. package/src/lib/server/console-runtime.server.ts +61 -0
  198. package/src/lib/server/deleteBundle.ts +145 -0
  199. package/src/lib/server/downloadBundle.ts +46 -0
  200. package/src/lib/server/getBundleChildren.ts +130 -0
  201. package/src/lib/server/listReleases.ts +61 -0
  202. package/src/lib/server/releaseActivity.ts +27 -0
  203. package/src/lib/server/releaseReachability.ts +95 -0
  204. package/src/lib/server/runtime.server.ts +172 -0
  205. package/src/lib/utils.ts +9 -0
  206. package/src/logo.svg +1 -0
  207. package/src/routeTree.gen.ts +199 -0
  208. package/src/router.tsx +21 -0
  209. package/src/routes/-installations-search.ts +19 -0
  210. package/src/routes/-releases-search.ts +74 -0
  211. package/src/routes/__root.tsx +174 -0
  212. package/src/routes/api/auth/$.ts +19 -0
  213. package/src/routes/api/bundles/$bundleId/download.ts +17 -0
  214. package/src/routes/api-keys.tsx +37 -0
  215. package/src/routes/index.tsx +982 -0
  216. package/src/routes/insights.tsx +139 -0
  217. package/src/routes/installations.tsx +194 -0
  218. package/src/routes/signing.tsx +37 -0
  219. package/src/styles.css +152 -0
  220. package/src/virtual-modules.d.ts +21 -0
  221. package/src/vite-env.d.ts +1 -0
  222. package/src/vite-internal.ts +195 -0
  223. package/src/vite.ts +12 -0
  224. package/.output/public/assets/dist-DJX53faP.js +0 -3
  225. package/.output/public/assets/index-Iylis5kY.js +0 -10
  226. package/.output/public/assets/routes-HT21hs0C.js +0 -54
  227. package/.output/public/assets/styles-C0Re4quB.css +0 -2
  228. package/.output/server/__23tanstack-start-server-fn-resolver-CgwgZ0-i.mjs +0 -66
  229. package/.output/server/_libs/@floating-ui/core+[...].mjs +0 -698
  230. package/.output/server/_libs/@floating-ui/dom+[...].mjs +0 -644
  231. package/.output/server/_libs/@floating-ui/react-dom+[...].mjs +0 -839
  232. package/.output/server/_libs/@radix-ui/react-alert-dialog+[...].mjs +0 -2138
  233. package/.output/server/_libs/@radix-ui/react-popper+[...].mjs +0 -308
  234. package/.output/server/_libs/@radix-ui/react-select+[...].mjs +0 -1038
  235. package/.output/server/_libs/@tanstack/devtools-event-client+[...].mjs +0 -196
  236. package/.output/server/_libs/@tanstack/form-core+[...].mjs +0 -3228
  237. package/.output/server/_libs/@tanstack/react-form+[...].mjs +0 -430
  238. package/.output/server/_libs/@tanstack/react-table+[...].mjs +0 -2382
  239. package/.output/server/_libs/radix-ui__number.mjs +0 -6
  240. package/.output/server/_libs/radix-ui__primitive.mjs +0 -9
  241. package/.output/server/_libs/radix-ui__react-arrow.mjs +0 -23
  242. package/.output/server/_libs/radix-ui__react-collection.mjs +0 -78
  243. package/.output/server/_libs/radix-ui__react-direction.mjs +0 -13
  244. package/.output/server/_libs/radix-ui__react-label.mjs +0 -22
  245. package/.output/server/_libs/radix-ui__react-separator.mjs +0 -30
  246. package/.output/server/_libs/radix-ui__react-slider.mjs +0 -510
  247. package/.output/server/_libs/radix-ui__react-switch.mjs +0 -157
  248. package/.output/server/_libs/radix-ui__react-tooltip.mjs +0 -493
  249. package/.output/server/_ssr/api-rpc-BIbDUKSz.mjs +0 -292
  250. package/.output/server/_ssr/config.server-BSS366KT.mjs +0 -41
  251. package/.output/server/_ssr/deleteBundle-BgbZTtpW.mjs +0 -141
  252. package/.output/server/_ssr/dist-KboydRVk.mjs +0 -120
  253. package/.output/server/_ssr/router-C3hqEy3k.mjs +0 -307
  254. package/.output/server/_ssr/routes-B0lQvvlC.mjs +0 -3048
  255. package/.output/server/_ssr/start-02sLteYG.mjs +0 -4
  256. package/.output/server/_ssr/storageProfile-FVkjPoGb.mjs +0 -9
  257. package/.output/server/_tanstack-start-manifest_v-Bpx52WOU.mjs +0 -20
  258. package/.output/server/node_modules/tslib/modules/index.js +0 -70
  259. package/.output/server/node_modules/tslib/modules/package.json +0 -3
  260. package/.output/server/node_modules/tslib/package.json +0 -47
  261. package/.output/server/node_modules/tslib/tslib.js +0 -484
  262. package/.output/server/package.json +0 -9
@@ -0,0 +1,87 @@
1
+ import type { ApiKeyRow } from "@hot-updater/plugin-core";
2
+ import { createApiKey } from "@hot-updater/server";
3
+ import { createServerFn } from "@tanstack/react-start";
4
+
5
+ export type ApiKeyView = Omit<ApiKeyRow, "hash">;
6
+
7
+ export const toApiKeyView = ({
8
+ hash: _hash,
9
+ ...record
10
+ }: ApiKeyRow): ApiKeyView => record;
11
+
12
+ const parseName = (input: unknown): { readonly name: string } => {
13
+ const name =
14
+ typeof input === "object" && input !== null
15
+ ? Reflect.get(input, "name")
16
+ : undefined;
17
+ if (typeof name !== "string") {
18
+ throw new TypeError("API key name must be a string.");
19
+ }
20
+ return { name };
21
+ };
22
+
23
+ const parseId = (input: unknown): { readonly id: string } => {
24
+ const id =
25
+ typeof input === "object" && input !== null
26
+ ? Reflect.get(input, "id")
27
+ : undefined;
28
+ if (typeof id !== "string" || !/^api-[A-Za-z0-9_-]{43}$/u.test(id)) {
29
+ throw new TypeError("Invalid API key id.");
30
+ }
31
+ return { id };
32
+ };
33
+
34
+ const requireStore = async () => {
35
+ const { prepareConfig } = await import("./server/config.server");
36
+ const { apiKeyStore } = await prepareConfig();
37
+ if (apiKeyStore === null) {
38
+ throw new Error(
39
+ "API keys are not supported by the configured database plugin.",
40
+ );
41
+ }
42
+ return apiKeyStore;
43
+ };
44
+
45
+ export const getApiKeyCapabilityRpc = createServerFn({
46
+ method: "GET",
47
+ }).handler(async () => {
48
+ const { prepareConfig } = await import("./server/config.server");
49
+ const { apiKeyStore } = await prepareConfig();
50
+ return { apiKeys: apiKeyStore !== null } as const;
51
+ });
52
+
53
+ export const listApiKeysRpc = createServerFn({
54
+ method: "GET",
55
+ }).handler(async () => {
56
+ const store = await requireStore();
57
+ const records = await store.list();
58
+ return [...records]
59
+ .sort((left, right) => right.created_at_ms - left.created_at_ms)
60
+ .map(toApiKeyView);
61
+ });
62
+
63
+ export const createApiKeyRpc = createServerFn({ method: "POST" })
64
+ .validator(parseName)
65
+ .handler(async ({ data }) => {
66
+ const store = await requireStore();
67
+ const created = await createApiKey({
68
+ apiKeys: store,
69
+ name: data.name,
70
+ });
71
+ return {
72
+ apiKey: created.apiKey,
73
+ record: created.record,
74
+ };
75
+ });
76
+
77
+ export const revokeApiKeyRpc = createServerFn({ method: "POST" })
78
+ .validator(parseId)
79
+ .handler(async ({ data }) => {
80
+ const store = await requireStore();
81
+ const revoked = await store.revoke({
82
+ id: data.id,
83
+ revokedAtMs: Date.now(),
84
+ });
85
+ if (revoked === null) throw new Error("API key not found.");
86
+ return toApiKeyView(revoked);
87
+ });
@@ -0,0 +1,478 @@
1
+ import {
2
+ DatabasePluginInputError,
3
+ type Bundle,
4
+ type ChannelDeleteInput,
5
+ type ChannelInsertInput,
6
+ type DatabaseBundleQueryOptions,
7
+ deleteRelease as deleteReleaseMutation,
8
+ preflightReleasePolicy,
9
+ promoteRelease as promoteReleaseMutation,
10
+ type ReleasePolicyPatch,
11
+ updateReleasePolicy,
12
+ } from "@hot-updater/plugin-core";
13
+ import { createServerFn } from "@tanstack/react-start";
14
+
15
+ import { DEFAULT_PAGE_LIMIT } from "./constants";
16
+ import {
17
+ parseBundleEventInsightsInput,
18
+ parseBundleEventSummaryInput,
19
+ parseInstallationHistoryInput,
20
+ parseSearchInstallationsInput,
21
+ } from "./insights-input";
22
+ import { listReleases } from "./server/listReleases";
23
+ import { getReleaseActivity30d } from "./server/releaseActivity";
24
+ import { addReleaseReachability } from "./server/releaseReachability";
25
+
26
+ type GetBundlesInput = {
27
+ platform?: "ios" | "android";
28
+ page?: number;
29
+ limit?: string;
30
+ after?: string;
31
+ before?: string;
32
+ };
33
+
34
+ type GetBundleInput = {
35
+ bundleId: string;
36
+ };
37
+
38
+ type GetBundleChildrenInput = {
39
+ baseBundleId: string;
40
+ };
41
+
42
+ type GetBundleChildCountsInput = {
43
+ bundleIds: string[];
44
+ };
45
+
46
+ type DeleteBundleInput = {
47
+ bundleId: string;
48
+ };
49
+
50
+ type DeleteBundlesInput = {
51
+ bundleIds: string[];
52
+ };
53
+
54
+ type GetReleasesInput = {
55
+ afterReleaseId?: string;
56
+ beforeReleaseId?: string;
57
+ bundleId?: string;
58
+ channelId?: string;
59
+ enabled?: boolean;
60
+ platform?: "ios" | "android";
61
+ limit?: number;
62
+ page?: number;
63
+ targetAppVersion?: string;
64
+ };
65
+
66
+ type ReleaseMutationInput = {
67
+ expectedRevision?: number;
68
+ patch: ReleasePolicyPatch;
69
+ releaseId: string;
70
+ };
71
+
72
+ type DeleteReleaseInput = {
73
+ expectedRevision?: number;
74
+ releaseId: string;
75
+ };
76
+
77
+ export const getReleases = createServerFn({ method: "GET" })
78
+ .inputValidator((input: GetReleasesInput | undefined) => input)
79
+ .handler(async ({ data }) => {
80
+ const { prepareConfig } = await import("./server/config.server");
81
+ const { config, hotUpdater } = await prepareConfig();
82
+ const result = await listReleases(config.database.models.releases, {
83
+ ...(data?.afterReleaseId === undefined
84
+ ? {}
85
+ : { afterReleaseId: data.afterReleaseId }),
86
+ ...(data?.beforeReleaseId === undefined
87
+ ? {}
88
+ : { beforeReleaseId: data.beforeReleaseId }),
89
+ ...(data?.bundleId === undefined ? {} : { bundleId: data.bundleId }),
90
+ ...(data?.channelId === undefined ? {} : { channelId: data.channelId }),
91
+ ...(data?.enabled === undefined ? {} : { enabled: data.enabled }),
92
+ ...(data?.page === undefined ? {} : { page: data.page }),
93
+ ...(data?.platform === undefined ? {} : { platform: data.platform }),
94
+ ...(data?.targetAppVersion === undefined
95
+ ? {}
96
+ : { targetAppVersion: data.targetAppVersion }),
97
+ limit: data?.limit ?? DEFAULT_PAGE_LIMIT,
98
+ });
99
+ const [releases, activityByBundleId] = await Promise.all([
100
+ addReleaseReachability(
101
+ config.database.models.releaseCatalogs,
102
+ result.data,
103
+ ),
104
+ getReleaseActivity30d(hotUpdater, result.data),
105
+ ]);
106
+ return {
107
+ ...result,
108
+ data: releases.map((release) => ({
109
+ ...release,
110
+ activity30d:
111
+ release.bundle_id === null
112
+ ? null
113
+ : (activityByBundleId.get(release.bundle_id) ?? null),
114
+ })),
115
+ };
116
+ });
117
+
118
+ export const getRelease = createServerFn({ method: "GET" })
119
+ .inputValidator((input: { releaseId: string }) => input)
120
+ .handler(async ({ data }) => {
121
+ const { prepareConfig } = await import("./server/config.server");
122
+ const { config } = await prepareConfig();
123
+ return config.database.models.releases.findById(data.releaseId);
124
+ });
125
+
126
+ export const updateRelease = createServerFn({ method: "POST" })
127
+ .inputValidator((input: ReleaseMutationInput) => input)
128
+ .handler(async ({ data }) => {
129
+ const { prepareConfig } = await import("./server/config.server");
130
+ const { config } = await prepareConfig();
131
+ return updateReleasePolicy({ database: config.database, ...data });
132
+ });
133
+
134
+ export const preflightRelease = createServerFn({ method: "POST" })
135
+ .inputValidator((input: ReleaseMutationInput) => input)
136
+ .handler(async ({ data }) => {
137
+ const { prepareConfig } = await import("./server/config.server");
138
+ const { config } = await prepareConfig();
139
+ return preflightReleasePolicy({ database: config.database, ...data });
140
+ });
141
+
142
+ export const deleteRelease = createServerFn({ method: "POST" })
143
+ .inputValidator((input: DeleteReleaseInput) => input)
144
+ .handler(async ({ data }) => {
145
+ const { prepareConfig } = await import("./server/config.server");
146
+ const { config } = await prepareConfig();
147
+ return deleteReleaseMutation({ database: config.database, ...data });
148
+ });
149
+
150
+ export const promoteRelease = createServerFn({ method: "POST" })
151
+ .inputValidator(
152
+ (input: {
153
+ action: "copy" | "move";
154
+ expectedRevision?: number;
155
+ releaseId: string;
156
+ targetChannel: string;
157
+ }) => input,
158
+ )
159
+ .handler(async ({ data }) => {
160
+ const { prepareConfig } = await import("./server/config.server");
161
+ const { config } = await prepareConfig();
162
+ return promoteReleaseMutation({ database: config.database, ...data });
163
+ });
164
+
165
+ export const getReleaseCatalogDiagnostics = createServerFn({ method: "GET" })
166
+ .inputValidator((input: { scopeKey: string }) => input)
167
+ .handler(async ({ data }) => {
168
+ const { prepareConfig } = await import("./server/config.server");
169
+ const { config } = await prepareConfig();
170
+ return config.database.models.releaseCatalogs.findByScopeKey(data.scopeKey);
171
+ });
172
+
173
+ // GET /api/config
174
+ export const getConfig = createServerFn().handler(async () => {
175
+ try {
176
+ const { prepareConfig } = await import("./server/config.server");
177
+ const { config } = await prepareConfig();
178
+ return { console: config.console };
179
+ } catch (error) {
180
+ console.error("Error during config retrieval:", error);
181
+ throw error;
182
+ }
183
+ });
184
+
185
+ // GET /api/channels
186
+ export const getChannels = createServerFn().handler(async () => {
187
+ try {
188
+ const { prepareConfig } = await import("./server/config.server");
189
+ const { databaseClient } = await prepareConfig();
190
+ const channels = await databaseClient.getChannels();
191
+ return channels ?? [];
192
+ } catch (error) {
193
+ console.error("Error during channel retrieval:", error);
194
+ throw error;
195
+ }
196
+ });
197
+
198
+ // POST /api/channels
199
+ export const createChannel = createServerFn({ method: "POST" })
200
+ .inputValidator((input: ChannelInsertInput) => input)
201
+ .handler(async ({ data }) => {
202
+ try {
203
+ const { prepareConfig } = await import("./server/config.server");
204
+ const { databaseClient } = await prepareConfig();
205
+ return { data: await databaseClient.insertChannel(data) };
206
+ } catch (error) {
207
+ console.error("Error during channel creation:", error);
208
+ throw error;
209
+ }
210
+ });
211
+
212
+ // DELETE /api/channels/:id
213
+ export const deleteChannel = createServerFn({ method: "POST" })
214
+ .inputValidator((input: ChannelDeleteInput) => input)
215
+ .handler(async ({ data }) => {
216
+ try {
217
+ const { prepareConfig } = await import("./server/config.server");
218
+ const { databaseClient } = await prepareConfig();
219
+ return { data: await databaseClient.deleteChannel(data) };
220
+ } catch (error) {
221
+ console.error("Error during channel deletion:", error);
222
+ throw error;
223
+ }
224
+ });
225
+
226
+ // GET /api/config-loaded
227
+ export const getConfigLoaded = createServerFn().handler(async () => {
228
+ try {
229
+ const [{ getRequest }, { requireConsoleAccess }] = await Promise.all([
230
+ import("@tanstack/react-start/server"),
231
+ import("./server/auth.server"),
232
+ ]);
233
+ await requireConsoleAccess(getRequest());
234
+ const { isConfigLoaded } = await import("./server/config.server");
235
+ const configLoaded = isConfigLoaded();
236
+ return { configLoaded };
237
+ } catch (error) {
238
+ console.error("Error during config loaded retrieval:", error);
239
+ throw error;
240
+ }
241
+ });
242
+
243
+ // GET /api/bundles
244
+ export const getBundles = createServerFn({ method: "GET" })
245
+ .inputValidator((input: GetBundlesInput | undefined) => input)
246
+ .handler(async ({ data }) => {
247
+ try {
248
+ const { prepareConfig } = await import("./server/config.server");
249
+ const query = {
250
+ platform: data?.platform ?? undefined,
251
+ page:
252
+ typeof data?.page === "number" &&
253
+ Number.isInteger(data.page) &&
254
+ data.page > 1
255
+ ? data.page
256
+ : undefined,
257
+ limit: data?.limit ? Number(data.limit) : DEFAULT_PAGE_LIMIT,
258
+ after: data?.after ?? undefined,
259
+ before: data?.before ?? undefined,
260
+ };
261
+ if (
262
+ (query.after !== undefined && query.before !== undefined) ||
263
+ (query.page !== undefined &&
264
+ (query.after !== undefined || query.before !== undefined))
265
+ ) {
266
+ throw new DatabasePluginInputError("invalid-pagination");
267
+ }
268
+ const pagination =
269
+ query.page !== undefined
270
+ ? { page: query.page }
271
+ : query.after !== undefined
272
+ ? { cursor: { after: query.after } }
273
+ : query.before !== undefined
274
+ ? { cursor: { before: query.before } }
275
+ : {};
276
+
277
+ const { databaseClient } = await prepareConfig();
278
+ const bundleQueryOptions: DatabaseBundleQueryOptions = {
279
+ where: {
280
+ platform: query.platform,
281
+ },
282
+ limit: query.limit,
283
+ ...pagination,
284
+ };
285
+ const bundles = await databaseClient.getBundles(bundleQueryOptions);
286
+
287
+ return (
288
+ bundles ?? {
289
+ data: [],
290
+ pagination: {
291
+ total: 0,
292
+ hasNextPage: false,
293
+ hasPreviousPage: false,
294
+ currentPage: 1,
295
+ totalPages: 0,
296
+ },
297
+ }
298
+ );
299
+ } catch (error) {
300
+ console.error("Error during bundle retrieval:", error);
301
+ throw error;
302
+ }
303
+ });
304
+
305
+ // GET /api/bundles/:bundleId
306
+ export const getBundle = createServerFn({ method: "GET" })
307
+ .inputValidator((input: GetBundleInput) => input)
308
+ .handler(async ({ data }) => {
309
+ try {
310
+ const { prepareConfig } = await import("./server/config.server");
311
+ const { databaseClient } = await prepareConfig();
312
+ const bundle = await databaseClient.getBundleById(data.bundleId);
313
+ return bundle ?? null;
314
+ } catch (error) {
315
+ console.error("Error during bundle retrieval:", error);
316
+ throw error;
317
+ }
318
+ });
319
+
320
+ export const getBundleEventSummary = createServerFn({ method: "GET" })
321
+ .inputValidator(parseBundleEventSummaryInput)
322
+ .handler(async ({ data }) => {
323
+ try {
324
+ const { prepareConfig } = await import("./server/config.server");
325
+ const { getBundleEventSummary: getBundleEventSummaryWithRuntime } =
326
+ await import("./server/runtime.server");
327
+ const { hotUpdater } = await prepareConfig();
328
+
329
+ return await getBundleEventSummaryWithRuntime(hotUpdater, data);
330
+ } catch (error) {
331
+ console.error("Error during bundle event summary retrieval:", error);
332
+ throw error;
333
+ }
334
+ });
335
+
336
+ export const getBundleEventInsights = createServerFn({ method: "GET" })
337
+ .inputValidator(parseBundleEventInsightsInput)
338
+ .handler(async ({ data }) => {
339
+ try {
340
+ const { prepareConfig } = await import("./server/config.server");
341
+ const { getBundleEventInsights: getBundleEventInsightsWithRuntime } =
342
+ await import("./server/runtime.server");
343
+ const { hotUpdater } = await prepareConfig();
344
+
345
+ return await getBundleEventInsightsWithRuntime(hotUpdater, data);
346
+ } catch (error) {
347
+ console.error("Error during bundle event insights retrieval:", error);
348
+ throw error;
349
+ }
350
+ });
351
+
352
+ export const searchInstallations = createServerFn({ method: "GET" })
353
+ .inputValidator(parseSearchInstallationsInput)
354
+ .handler(async ({ data }) => {
355
+ try {
356
+ const { prepareConfig } = await import("./server/config.server");
357
+ const { searchInstallations: searchInstallationsWithRuntime } =
358
+ await import("./server/runtime.server");
359
+ const { hotUpdater } = await prepareConfig();
360
+
361
+ return await searchInstallationsWithRuntime(hotUpdater, data);
362
+ } catch (error) {
363
+ console.error("Error during installation search:", error);
364
+ throw error;
365
+ }
366
+ });
367
+
368
+ export const getInstallationHistory = createServerFn({ method: "GET" })
369
+ .inputValidator(parseInstallationHistoryInput)
370
+ .handler(async ({ data }) => {
371
+ try {
372
+ const { prepareConfig } = await import("./server/config.server");
373
+ const { getInstallationHistory: getInstallationHistoryWithRuntime } =
374
+ await import("./server/runtime.server");
375
+ const { hotUpdater } = await prepareConfig();
376
+
377
+ return await getInstallationHistoryWithRuntime(hotUpdater, data);
378
+ } catch (error) {
379
+ console.error("Error during installation history retrieval:", error);
380
+ throw error;
381
+ }
382
+ });
383
+
384
+ export const getBundleChildren = createServerFn({ method: "GET" })
385
+ .inputValidator((input: GetBundleChildrenInput) => input)
386
+ .handler(async ({ data }) => {
387
+ try {
388
+ const { prepareConfig } = await import("./server/config.server");
389
+ const { getBundleChildren: getBundleChildrenWithConfig } =
390
+ await import("./server/getBundleChildren");
391
+ const { databaseClient } = await prepareConfig();
392
+
393
+ return await getBundleChildrenWithConfig(data, {
394
+ databaseClient,
395
+ });
396
+ } catch (error) {
397
+ console.error("Error during bundle children retrieval:", error);
398
+ throw error;
399
+ }
400
+ });
401
+
402
+ export const getBundleChildCounts = createServerFn({ method: "GET" })
403
+ .inputValidator((input: GetBundleChildCountsInput) => input)
404
+ .handler(async ({ data }) => {
405
+ try {
406
+ const { prepareConfig } = await import("./server/config.server");
407
+ const { getBundleChildCounts: getBundleChildCountsWithConfig } =
408
+ await import("./server/getBundleChildren");
409
+ const { databaseClient } = await prepareConfig();
410
+
411
+ return await getBundleChildCountsWithConfig(data.bundleIds, {
412
+ databaseClient,
413
+ });
414
+ } catch (error) {
415
+ console.error("Error during bundle child count retrieval:", error);
416
+ throw error;
417
+ }
418
+ });
419
+
420
+ // POST /api/bundles
421
+ export const createBundle = createServerFn({ method: "POST" })
422
+ .inputValidator((input: Bundle) => input)
423
+ .handler(async ({ data }) => {
424
+ try {
425
+ const { prepareConfig } = await import("./server/config.server");
426
+ const { databaseClient } = await prepareConfig();
427
+ await databaseClient.insertBundle(data);
428
+ return { success: true, bundleId: data.id };
429
+ } catch (error) {
430
+ console.error("Error during bundle creation:", error);
431
+ throw error;
432
+ }
433
+ });
434
+
435
+ // DELETE /api/bundles/:bundleId
436
+ export const deleteBundle = createServerFn({ method: "POST" })
437
+ .inputValidator((input: DeleteBundleInput) => input)
438
+ .handler(async ({ data }) => {
439
+ try {
440
+ const { prepareConfig } = await import("./server/config.server");
441
+ const { deleteBundle: deleteBundleWithStorage } =
442
+ await import("./server/deleteBundle");
443
+ const { databaseClient, storagePlugin } = await prepareConfig();
444
+
445
+ await deleteBundleWithStorage(data, {
446
+ databaseClient,
447
+ storagePlugin,
448
+ waitForStorageCleanup: false,
449
+ });
450
+
451
+ return { success: true };
452
+ } catch (error) {
453
+ console.error("Error during bundle deletion:", error);
454
+ throw error;
455
+ }
456
+ });
457
+
458
+ export const deleteBundles = createServerFn({ method: "POST" })
459
+ .inputValidator((input: DeleteBundlesInput) => input)
460
+ .handler(async ({ data }) => {
461
+ try {
462
+ const { prepareConfig } = await import("./server/config.server");
463
+ const { deleteBundles: deleteBundlesWithStorage } =
464
+ await import("./server/deleteBundle");
465
+ const { databaseClient, storagePlugin } = await prepareConfig();
466
+
467
+ const result = await deleteBundlesWithStorage(data, {
468
+ databaseClient,
469
+ storagePlugin,
470
+ waitForStorageCleanup: false,
471
+ });
472
+
473
+ return { success: true, ...result };
474
+ } catch (error) {
475
+ console.error("Error during bundle deletion:", error);
476
+ throw error;
477
+ }
478
+ });