@hot-updater/console 1.0.0-rc.20 → 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.
Files changed (180) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/{ConsoleFeatureUnavailable-B9VYGA3N.js → ConsoleFeatureUnavailable-D0LPL1Gy.js} +1 -1
  3. package/.output/public/assets/{HashValueDisplay-D880zntR.js → HashValueDisplay-BmuB4FW-.js} +1 -1
  4. package/.output/public/assets/InsightsInfo--v4SC07c.js +1 -0
  5. package/.output/public/assets/InsightsPageHeader-dU83CAQY.js +1 -0
  6. package/.output/public/assets/{NotFoundPage-Dys8C0cW.js → NotFoundPage-B5mhGG2a.js} +1 -1
  7. package/.output/public/assets/api-D-vOVlQ4.js +1 -0
  8. package/.output/public/assets/api-keys-Csh8ARKU.js +1 -0
  9. package/.output/public/assets/api-keys-OMPUoA_A.js +1 -0
  10. package/.output/public/assets/arrow-left-Bwq9X1eS.js +1 -0
  11. package/.output/public/assets/arrow-up-right-BkOVn4if.js +1 -0
  12. package/.output/public/assets/{card-DzCPSRsh.js → card-C_7d6O9a.js} +1 -1
  13. package/.output/public/assets/console-features-api-C1jRZTGw.js +1 -0
  14. package/.output/public/assets/dialog-DE8wFN_P.js +1 -0
  15. package/.output/public/assets/{dist-Clb6cQ9l.js → dist-C7ENGzpS.js} +1 -1
  16. package/.output/public/assets/empty-CVd5NlQD.js +1 -0
  17. package/.output/public/assets/{field-Bou7shoO.js → field-C06q1iC9.js} +1 -1
  18. package/.output/public/assets/index-BeWlQtO6.js +11 -0
  19. package/.output/public/assets/{input-group-DVtWFUUF.js → input-group-Bi61biIA.js} +1 -1
  20. package/.output/public/assets/insights-BjnAtC0f.js +2 -0
  21. package/.output/public/assets/insights-D86m11yH.js +42 -0
  22. package/.output/public/assets/insights-DpYstMoY.js +1 -0
  23. package/.output/public/assets/{insights-recovery-rpc-wvdyM4LD.js → insights-recovery-rpc-BIm5_ALc.js} +1 -1
  24. package/.output/public/assets/{insights-usage-rpc-qUkNtrWr.js → insights-usage-rpc-VHj8vyBt.js} +1 -1
  25. package/.output/public/assets/insights_.distribution-ADcrX48c.js +2 -0
  26. package/.output/public/assets/insights_.distribution-CuuJUN6Y.js +1 -0
  27. package/.output/public/assets/{insights_.distribution-CItZw3-8.js → insights_.distribution-DJRjuXsO.js} +1 -1
  28. package/.output/public/assets/installations-BSBBzqWs.js +2 -0
  29. package/.output/public/assets/installations-DqIE6WcO.js +1 -0
  30. package/.output/public/assets/installations-UsELbDvw.js +1 -0
  31. package/.output/public/assets/{loader-circle-Ca7C9YY1.js → loader-circle-CnCBxzHp.js} +1 -1
  32. package/.output/public/assets/mutation-Csp7xUNh.js +1 -0
  33. package/.output/public/assets/preload-helper-B-cOmpLH.js +1 -0
  34. package/.output/public/assets/{refresh-cw-CVGqvy8Z.js → refresh-cw-FF1NtzlQ.js} +1 -1
  35. package/.output/public/assets/{routes-veKtDW39.js → routes-CoN9XHP_.js} +2 -2
  36. package/.output/public/assets/routes-DT1jB0Zi.js +1 -0
  37. package/.output/public/assets/sidebar-D8mJRF15.js +3 -0
  38. package/.output/public/assets/styles-Bwt7v6U5.css +2 -0
  39. package/.output/public/assets/table-WBgfvgXg.js +1 -0
  40. package/.output/public/assets/{useCompositeListItem-CF7vsfcA.js → useCompositeListItem-CloBmDwn.js} +1 -1
  41. package/.output/public/assets/useMutation-DO8cvsTx.js +1 -0
  42. package/.output/public/assets/{useOpenInteractionType-AnAMWYIY.js → useOpenInteractionType-ReGr9GzJ.js} +1 -1
  43. package/.output/public/assets/valueToPercent-BvPAi4bP.js +1 -0
  44. package/.output/server/{__23tanstack-start-server-fn-resolver-CUQLz7hs.mjs → __23tanstack-start-server-fn-resolver-DMRD4e4f.mjs} +34 -42
  45. package/.output/server/_libs/@tanstack/react-router+[...].mjs +3756 -4022
  46. package/.output/server/_libs/@tanstack/react-router-ssr-query+[...].mjs +138 -95
  47. package/.output/server/_libs/tanstack__history.mjs +68 -41
  48. package/.output/server/_libs/tanstack__query-core.mjs +1618 -309
  49. package/.output/server/_libs/tanstack__react-query.mjs +255 -20
  50. package/.output/server/_ssr/{ConsoleFeatureUnavailable-DoxET2AT.mjs → ConsoleFeatureUnavailable-Du7jNPNS.mjs} +7 -7
  51. package/.output/server/_ssr/{HashValueDisplay-BplboHEb.mjs → HashValueDisplay-GQvFkXoi.mjs} +2 -2
  52. package/.output/server/_ssr/{InsightsInfo-o2YPd5Qc.mjs → InsightsInfo-Do7QSaFR.mjs} +2 -2
  53. package/.output/server/_ssr/{InsightsPageHeader-NExCJL1I.mjs → InsightsPageHeader-DSUWUWRc.mjs} +6 -6
  54. package/.output/server/_ssr/{NotFoundPage-Cd-A8ock.mjs → NotFoundPage-DsF4Vrhg.mjs} +2 -2
  55. package/.output/server/_ssr/{api-B1Ro0uys.mjs → api-BEiVs0H7.mjs} +9 -8
  56. package/.output/server/_ssr/{api-keys-Dvvy1sem.mjs → api-keys-BrvBWGRf.mjs} +2 -2
  57. package/.output/server/_ssr/{api-keys-CT3HG7KT.mjs → api-keys-K5v32nf7.mjs} +8 -8
  58. package/.output/server/_ssr/{api-keys-rpc-b1qTTFfG.mjs → api-keys-rpc-B96bBllm.mjs} +5 -5
  59. package/.output/server/_ssr/{api-rpc-Cbns2MGx.mjs → api-rpc-DcUe280B.mjs} +13 -55
  60. package/.output/server/_ssr/{auth-rpc-DChS7m9y.mjs → auth-rpc-C68W56DP.mjs} +6 -6
  61. package/.output/server/_ssr/{api-C0G9MTMI.mjs → auth.server-DM1J6NwR.mjs} +2815 -1308
  62. package/.output/server/_ssr/{bundleActivity-D17Pb_h5.mjs → bundleActivity-CpuY9CTP.mjs} +1 -1
  63. package/.output/server/_ssr/{card-DMbcvlPz.mjs → card-CPAEKx0w.mjs} +1 -1
  64. package/.output/server/_ssr/config-nsIcsHh8.mjs +15 -0
  65. package/.output/server/_ssr/config.server-DDCZk3Sq.mjs +36 -0
  66. package/.output/server/_ssr/{console-access-KpPv5lin.mjs → console-access-Da-2Qd7q.mjs} +3 -3
  67. package/.output/server/_ssr/{console-features-CsbyHYfK.mjs → console-features-Dzu2iUGD.mjs} +6 -6
  68. package/.output/server/_ssr/{console-features-api-B-KVuo4p.mjs → console-features-api-xAsSgFa_.mjs} +6 -6
  69. package/.output/server/_ssr/{console-features-rpc-Djyo5740.mjs → console-features-rpc-qARGpB24.mjs} +5 -5
  70. package/.output/server/_ssr/{createMiddleware-B_I3Ll4M.mjs → createMiddleware-BdBf_ezN.mjs} +41 -56
  71. package/.output/server/_ssr/{createServerRpc-DuNm5mmo.mjs → createServerRpc-B_y2lrwA.mjs} +2 -2
  72. package/.output/server/_ssr/databaseJsonValue-Bhxciy7Z.mjs +68 -0
  73. package/.output/server/_ssr/databaseRows-DwMAjaF8.mjs +74 -0
  74. package/.output/server/_ssr/{dialog-cOmLTUV2.mjs → dialog-q__BJ7mQ.mjs} +1 -1
  75. package/.output/server/_ssr/{dist-B-r4u3Lg.mjs → dist-Cxo9ghYv.mjs} +114 -3
  76. package/.output/server/_ssr/{downloadBundle-CexOFoZ4.mjs → downloadBundle-BW9I-pMQ.mjs} +6 -5
  77. package/.output/server/_ssr/{empty-BHoY_5jt.mjs → empty-DcBTAooP.mjs} +2 -2
  78. package/.output/server/_ssr/{empty-plugin-adapters-B5QvGpEl.mjs → empty-plugin-adapters-CYdinE-i.mjs} +1 -1
  79. package/.output/server/_ssr/{field-mjhcNEXD.mjs → field-QCDBVpXA.mjs} +2 -2
  80. package/.output/server/_ssr/{getBundleChildren-DbDXe9wB.mjs → getBundleChildren-DIAGCF4a.mjs} +2 -2
  81. package/.output/server/_ssr/{input-group-96H30HWC.mjs → input-group-hCk2nUoU.mjs} +1 -1
  82. package/.output/server/_ssr/{insights-DgFv1NsO.mjs → insights-BfBO0RFr.mjs} +18 -18
  83. package/.output/server/_ssr/{insights-D359GJFL.mjs → insights-DsAU3-TB.mjs} +5 -5
  84. package/.output/server/_ssr/{insights-D25VDVaz.mjs → insights-RIfg447W.mjs} +2 -2
  85. package/.output/server/_ssr/{insights-api-ChAKrbhG.mjs → insights-api-CDn-QQHU.mjs} +5 -5
  86. package/.output/server/_ssr/{insights-failures-WQ-ttjOx.mjs → insights-failures-BBGMhuUr.mjs} +2 -2
  87. package/.output/server/_ssr/{insights-failures-rpc-BFsqlltN.mjs → insights-failures-rpc-rA2dctK2.mjs} +8 -8
  88. package/.output/server/_ssr/{insights-recovery-DkL6bxMW.mjs → insights-recovery-Dpk6Mfb9.mjs} +1 -1
  89. package/.output/server/_ssr/{insights-recovery-rpc-2P5m8F62.mjs → insights-recovery-rpc-BCaF-xSd.mjs} +11 -11
  90. package/.output/server/_ssr/{insights-recovery-rpc-Tj8vNC19.mjs → insights-recovery-rpc-CcgYyUHk.mjs} +5 -5
  91. package/.output/server/_ssr/{insights-rpc-DrkVmV9o.mjs → insights-rpc-keycmwzw.mjs} +5 -5
  92. package/.output/server/_ssr/{insights-search-BsalnNw-.mjs → insights-search-BRg5VsNe.mjs} +1 -1
  93. package/.output/server/_ssr/{insights-usage-1nVTQrLy.mjs → insights-usage-cbw9n2Fq.mjs} +1 -1
  94. package/.output/server/_ssr/{insights-usage-rpc-Ct_4xHhZ.mjs → insights-usage-rpc-BKgV1QDf.mjs} +8 -8
  95. package/.output/server/_ssr/{insights-usage-rpc-DaYIRtFK.mjs → insights-usage-rpc-D-JYachy.mjs} +5 -5
  96. package/.output/server/_ssr/{insightsRecovery-CpGucWN1.mjs → insightsRecovery-B7oMc8d8.mjs} +2 -2
  97. package/.output/server/_ssr/{insightsUsage-JYpKZAF7.mjs → insightsUsage-Oa7kk5ek.mjs} +3 -3
  98. package/.output/server/_ssr/{insights_.distribution-jQZEF07J.mjs → insights_.distribution-BIqOqRW0.mjs} +13 -13
  99. package/.output/server/_ssr/{insights_.distribution-Bcu6uYs_.mjs → insights_.distribution-CuUrDa7P.mjs} +2 -2
  100. package/.output/server/_ssr/{insights_.distribution-D3Csuyjw.mjs → insights_.distribution-RT_6x_ud.mjs} +5 -5
  101. package/.output/server/_ssr/{installations-Cxikllmx.mjs → installations-BO5CjX2z.mjs} +11 -11
  102. package/.output/server/_ssr/{installations-BI5kelHv.mjs → installations-BjaHiIfh.mjs} +2 -2
  103. package/.output/server/_ssr/{installations-B8Vh54lH.mjs → installations-Dt5luA-N.mjs} +4 -4
  104. package/.output/server/_ssr/{listReleases-DqU9t1l3.mjs → listReleases-BitHzOYB.mjs} +1 -1
  105. package/.output/server/_ssr/{router-CRkm-wHD.mjs → router-DfVAixxe.mjs} +42 -39
  106. package/.output/server/_ssr/{routes-CNAi0Mmv.mjs → routes-C6LstNp8.mjs} +14 -14
  107. package/.output/server/_ssr/{routes-B3IJo_jL.mjs → routes-Q3Uhmwwz.mjs} +11 -11
  108. package/.output/server/_ssr/{runtime.server-NucbYjc7.mjs → runtime.server-fL0o6I2b.mjs} +500 -413
  109. package/.output/server/_ssr/schema-BKY9SqaY.mjs +25 -0
  110. package/.output/server/_ssr/{select-BkPAJFL7.mjs → select-CUlInmjk.mjs} +1 -1
  111. package/.output/server/_ssr/server-C43wq9ft.mjs +8330 -0
  112. package/.output/server/_ssr/{sidebar-Cw1VuC7d.mjs → sidebar-DKbx2ach.mjs} +3 -3
  113. package/.output/server/_ssr/ssr.mjs +1 -1
  114. package/.output/server/_ssr/start-BXUOv_n8.mjs +4 -0
  115. package/.output/server/_ssr/{table-q0P9wz7x.mjs → table-NGWV8B-S.mjs} +1 -1
  116. package/.output/server/_ssr/{updateFailures-DMU9f0pK.mjs → updateFailures-Jwe2Baab.mjs} +3 -3
  117. package/.output/server/_ssr/{uuidv7-BzSO-fr-.mjs → uuidv7-C8bwiZr8.mjs} +2 -2
  118. package/.output/server/_tanstack-start-manifest_v-WQ5qGn8n.mjs +124 -0
  119. package/.output/server/index.mjs +218 -218
  120. package/LICENSE +13 -14
  121. package/README.md +16 -12
  122. package/lib/index.d.mts +18 -8
  123. package/lib/vite-internal.mjs +15 -8
  124. package/lib/vite.d.mts +2 -0
  125. package/package.json +14 -14
  126. package/src/components/AppSidebar.tsx +9 -0
  127. package/src/components/ConsoleFeatureUnavailable.tsx +1 -3
  128. package/src/components/features/bundles/BundleMetadata.tsx +1 -1
  129. package/src/components/features/bundles/RolloutCohortsDialog.tsx +1 -1
  130. package/src/components/features/releases/ReleaseEditorSheet.tsx +6 -6
  131. package/src/index.ts +27 -15
  132. package/src/lib/api-rpc.ts +1 -54
  133. package/src/lib/api.ts +8 -89
  134. package/src/lib/console-features.ts +5 -5
  135. package/src/lib/scroll-restoration.ts +1 -1
  136. package/src/lib/server/config.server.ts +15 -52
  137. package/src/lib/server/console-runtime.server.ts +84 -6
  138. package/src/lib/server/downloadBundle.ts +10 -6
  139. package/src/lib/server/releaseReachability.ts +4 -4
  140. package/src/lib/server/runtime.server.ts +34 -46
  141. package/src/routeTree.gen.ts +31 -31
  142. package/src/routes/api/bundles/$bundleId/download.ts +2 -2
  143. package/src/vite-internal.ts +17 -8
  144. package/src/vite.ts +2 -0
  145. package/.output/public/assets/InsightsInfo-By_sPjTE.js +0 -1
  146. package/.output/public/assets/InsightsPageHeader-9_kx_LL7.js +0 -1
  147. package/.output/public/assets/api-CtarlV0l.js +0 -1
  148. package/.output/public/assets/api-keys-DFF_QqtN.js +0 -1
  149. package/.output/public/assets/api-keys-evfsQ4sN.js +0 -1
  150. package/.output/public/assets/arrow-left-BP9D-ta_.js +0 -1
  151. package/.output/public/assets/arrow-up-right-CDP9Tevw.js +0 -1
  152. package/.output/public/assets/console-features-api-yv7rm3DX.js +0 -1
  153. package/.output/public/assets/dialog-BDvCy_QO.js +0 -1
  154. package/.output/public/assets/empty-C5x9mn_s.js +0 -1
  155. package/.output/public/assets/index-0eg3l8aC.js +0 -10
  156. package/.output/public/assets/insights-2MbdiSNv.js +0 -1
  157. package/.output/public/assets/insights-C6tmCZHk.js +0 -2
  158. package/.output/public/assets/insights-CadS_aIA.js +0 -42
  159. package/.output/public/assets/insights_.distribution-BhBs6PNA.js +0 -1
  160. package/.output/public/assets/insights_.distribution-CJUH_wAS.js +0 -2
  161. package/.output/public/assets/installations-CH2H7JQt.js +0 -2
  162. package/.output/public/assets/installations-DMeMuwsW.js +0 -1
  163. package/.output/public/assets/installations-DnvxI9Dt.js +0 -1
  164. package/.output/public/assets/mutation-x9L28_CD.js +0 -1
  165. package/.output/public/assets/preload-helper-BN-UkjOE.js +0 -1
  166. package/.output/public/assets/routes-BhK8DuZA.js +0 -1
  167. package/.output/public/assets/sidebar-B45aMqRm.js +0 -3
  168. package/.output/public/assets/styles-Dn0pK1EX.css +0 -2
  169. package/.output/public/assets/table-CwLnN6KX.js +0 -1
  170. package/.output/public/assets/useMutation-CN1ygYEr.js +0 -1
  171. package/.output/public/assets/valueToPercent-C-t8FT0y.js +0 -1
  172. package/.output/server/_ssr/auth.server-Ccqz2iVv.mjs +0 -37
  173. package/.output/server/_ssr/config-DMEJdhoW.mjs +0 -13
  174. package/.output/server/_ssr/config.server-DyypN6cQ.mjs +0 -60
  175. package/.output/server/_ssr/databaseRows-CXxf_VIB.mjs +0 -132
  176. package/.output/server/_ssr/deleteBundle-Dcc21Lbd.mjs +0 -56
  177. package/.output/server/_ssr/server-DKJYsaCP.mjs +0 -7442
  178. package/.output/server/_ssr/start-DpViQGnt.mjs +0 -4
  179. package/.output/server/_tanstack-start-manifest_v-Bb0gKDbb.mjs +0 -124
  180. package/src/lib/server/deleteBundle.ts +0 -136
@@ -1,379 +1,9 @@
1
- import { i as resolveConsoleFeatures, n as consoleFeatures, t as ConsoleFeatureUnavailableError } from "./console-features-CsbyHYfK.mjs";
2
- import { C as isUUIDv7 } from "./dist-B-r4u3Lg.mjs";
3
- import { n as DatabasePluginInputError, o as isDatabaseJsonObject } from "./databaseRows-CXxf_VIB.mjs";
4
- import { t as createUUIDv7 } from "./uuidv7-BzSO-fr-.mjs";
5
- import { a as createCoreReads, c as engineAdapterOf, d as validateSchema, i as createCoreApi, l as pruneDuringWrites, n as aggregateBatchingModule, r as coreModule, s as createDatabaseEngine, t as OFF_ENGINE_DATABASE, u as resolveSchema } from "./api-C0G9MTMI.mjs";
6
- //#region node_modules/.nitro/vite/services/ssr/assets/runtime.server-NucbYjc7.js
7
- /** Whether a configured database is a self-hosted server's. */
8
- var isRemoteDatabase = (database) => "core" in database && "fetchAdmin" in database;
9
- /** The settings rows migrations write, last, and the fence reads. */
10
- var SETTINGS_TABLE = {
11
- name: "private_hot_updater_settings",
12
- columns: [
13
- {
14
- name: "key",
15
- type: "string",
16
- nullable: false,
17
- maxLength: 255
18
- },
19
- {
20
- name: "value",
21
- type: "string",
22
- nullable: false,
23
- maxLength: 255
24
- },
25
- {
26
- name: "_v",
27
- type: "integer",
28
- nullable: false,
29
- default: 0
30
- }
31
- ],
32
- key: ["key"],
33
- indexes: []
34
- };
35
- /** The engine's storage-layout generation; it changes only with a migration. */
36
- var ENGINE_SCHEMA_KEY = "schema.engine";
37
- var settingMessage = (adapterName, { key, expected, found }) => `Hot Updater schema setting "${key}" for ${adapterName} is ${found === null ? "missing" : `"${found}"`}; expected "${expected}". ${key === "schema.engine" ? "Create a new empty database and run `hot-updater db migrate`; databases from before the storage engine are not converted." : "Run `hot-updater db migrate`."}`;
38
- /** The database needs `hot-updater db migrate`, or a new database; the handler answers 503. */
39
- var HotUpdaterSchemaMigrationRequiredError = class extends Error {
40
- adapterName;
41
- currentVersion;
42
- setting;
43
- constructor(adapterName, currentVersion, setting, options) {
44
- super(setting !== void 0 ? settingMessage(adapterName, setting) : currentVersion === void 0 ? `Hot Updater database schema is not initialized for ${adapterName}. Run \`hot-updater db migrate\` before using this adapter.` : `Hot Updater v1 cannot migrate schema ${currentVersion} in place. Create a new empty database and run \`hot-updater db migrate\`.`, options);
45
- this.adapterName = adapterName;
46
- this.currentVersion = currentVersion;
47
- this.setting = setting;
48
- this.name = "HotUpdaterSchemaMigrationRequiredError";
49
- }
50
- };
51
- /** Driver codes for a table or column the database lacks. */
52
- var MISSING_SCHEMA_CODES = /* @__PURE__ */ new Set([
53
- "42P01",
54
- "42703",
55
- "ER_NO_SUCH_TABLE",
56
- "ER_BAD_FIELD_ERROR"
57
- ]);
58
- var MISSING_SCHEMA_MESSAGE = /no such (?:table|column)|(?:relation|column) .+ does not exist|table .+ doesn't exist|unknown column/iu;
59
- /**
60
- * Whether a read failed because a table or column is missing, so the
61
- * database needs migrations. ORMs may wrap the driver's error in `cause`.
62
- */
63
- var isMissingSchemaError = (error) => {
64
- for (let depth = 0; error instanceof Object && depth < 8; depth += 1) {
65
- const { code, message, cause } = error;
66
- if (MISSING_SCHEMA_CODES.has(String(code)) || typeof message === "string" && MISSING_SCHEMA_MESSAGE.test(message)) return true;
67
- error = cause;
68
- }
69
- return false;
70
- };
71
- var readSettings = (adapter, keys) => adapter.get(SETTINGS_TABLE, keys.map((key) => [key]));
72
- /** Throws unless every expected setting is stored with its value: one batch read. */
73
- var checkSchemaFence = async (adapter, adapterName, expected) => {
74
- const keys = Object.keys(expected);
75
- let rows;
76
- try {
77
- rows = await readSettings(adapter, keys);
78
- } catch (cause) {
79
- if (!isMissingSchemaError(cause)) throw cause;
80
- throw new HotUpdaterSchemaMigrationRequiredError(adapterName, void 0, {
81
- key: ENGINE_SCHEMA_KEY,
82
- expected: "1",
83
- found: null
84
- }, { cause });
85
- }
86
- keys.forEach((key, position) => {
87
- const found = rows[position]?.value;
88
- if (found !== expected[key]) throw new HotUpdaterSchemaMigrationRequiredError(adapterName, void 0, {
89
- key,
90
- expected: expected[key],
91
- found: typeof found === "string" ? found : null
92
- });
93
- });
94
- };
95
- /** The database name a fenced adapter carries, so a server can fence its plugins' rows too. */
96
- var FENCED = Symbol.for("@hot-updater/server/schema-fence");
97
- /** The database name an adapter behind the schema fence reports under. */
98
- var fencedName = (adapter) => adapter[FENCED];
99
- /**
100
- * The adapter behind the schema fence: a process's first read or write
101
- * checks the settings first. Success is kept; a failure is checked again on
102
- * the next call.
103
- */
104
- var withSchemaFence = (adapter, adapterName, expected) => {
105
- let ready;
106
- const fence = () => {
107
- ready ??= checkSchemaFence(adapter, adapterName, expected).catch((error) => {
108
- ready = void 0;
109
- throw error;
110
- });
111
- return ready;
112
- };
113
- return {
114
- ...adapter,
115
- [FENCED]: adapterName,
116
- get: (table, keys) => fence().then(() => adapter.get(table, keys)),
117
- query: (table, request) => fence().then(() => adapter.query(table, request)),
118
- write: (ops) => fence().then(() => adapter.write(ops)),
119
- ...adapter.prune && { prune: (table, before, limit) => fence().then(() => adapter.prune(table, before, limit)) }
120
- };
121
- };
122
- /** A misconfigured `createHotUpdater` call, reported at startup. */
123
- var HotUpdaterConfigError = class extends Error {
124
- name = "HotUpdaterConfigError";
125
- };
126
- var EXPORT_NAME = /^[A-Za-z_$][\w$]*$/u;
127
- /**
128
- * Names app code cannot import as they are: reserved words, and the names
129
- * the printed app code declares itself.
130
- */
131
- var UNAVAILABLE_NAMES = /* @__PURE__ */ new Set([
132
- "App",
133
- "HotUpdater",
134
- "arguments",
135
- "await",
136
- "break",
137
- "case",
138
- "catch",
139
- "class",
140
- "const",
141
- "continue",
142
- "debugger",
143
- "default",
144
- "delete",
145
- "do",
146
- "else",
147
- "enum",
148
- "eval",
149
- "export",
150
- "extends",
151
- "false",
152
- "finally",
153
- "for",
154
- "function",
155
- "if",
156
- "implements",
157
- "import",
158
- "in",
159
- "instanceof",
160
- "interface",
161
- "let",
162
- "new",
163
- "null",
164
- "package",
165
- "private",
166
- "protected",
167
- "public",
168
- "return",
169
- "static",
170
- "super",
171
- "switch",
172
- "this",
173
- "throw",
174
- "true",
175
- "try",
176
- "typeof",
177
- "var",
178
- "void",
179
- "while",
180
- "with",
181
- "yield"
182
- ]);
183
- var isRecord$5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
184
- /**
185
- * The client plugins an app adds for the plugins, each once, in plugin
186
- * order: what init prints in `HotUpdater.init`'s `plugins`. A malformed one,
187
- * or one name from two modules, throws `HotUpdaterConfigError`.
188
- */
189
- var clientPluginsOf = (plugins) => {
190
- if (!Array.isArray(plugins)) throw new HotUpdaterConfigError("plugins must be an array of plugins.");
191
- const found = [];
192
- for (const plugin of plugins) {
193
- if (!isRecord$5(plugin) || !isRecord$5(plugin.cli)) continue;
194
- const value = plugin.cli.clientPlugin;
195
- if (value === void 0) continue;
196
- const id = String(plugin.id);
197
- if (!isRecord$5(value) || typeof value.module !== "string" || value.module === "" || typeof value.name !== "string" || !EXPORT_NAME.test(value.name) || UNAVAILABLE_NAMES.has(value.name)) throw new HotUpdaterConfigError(`Plugin "${id}" cli.clientPlugin needs a module and an export name the app can import: an identifier other than a reserved word, App, or HotUpdater.`);
198
- const { module, name } = value;
199
- const named = found.find((entry) => entry.name === name);
200
- if (named === void 0) found.push({
201
- module,
202
- name
203
- });
204
- else if (named.module !== module) throw new HotUpdaterConfigError(`Plugin "${id}" names the client plugin "${name}" from ${module}, but another plugin names it from ${named.module}.`);
205
- }
206
- return found;
207
- };
208
- resolveSchema([coreModule]);
209
- /** Each plugin's `schema.<id>` settings row. */
210
- var pluginSettings = (plugins) => Object.fromEntries(plugins.map(({ id, schemaVersion }) => [`schema.${id}`, schemaVersion]));
211
- /** A plugin's schema as the engine resolves it: prefixed by its id unless it keeps its names. */
212
- var pluginModule = ({ id, schema, namespace }) => ({
213
- id,
214
- schema,
215
- ...namespace === false ? {} : { namespace: id }
216
- });
217
- var PLUGIN_KEYS = /* @__PURE__ */ new Set([
218
- "id",
219
- "provides",
220
- "schemaVersion",
221
- "schema",
222
- "namespace",
223
- "init",
224
- "cli"
225
- ]);
226
- var CLI_KEYS = /* @__PURE__ */ new Set([
227
- "commands",
228
- "clientCredential",
229
- "clientPlugin"
230
- ]);
231
- var INSTANCE_KEYS = /* @__PURE__ */ new Set([
232
- "api",
233
- "endpoints",
234
- "clientAuth"
235
- ]);
236
- var METHODS = /* @__PURE__ */ new Set([
237
- "GET",
238
- "POST",
239
- "PUT",
240
- "PATCH",
241
- "DELETE"
242
- ]);
243
- /** An id that prefixes table names is a SQL name; one that does not may use camelCase. */
244
- var PREFIX_ID = /^[a-z][a-z0-9_]*$/u;
245
- var UNPREFIXED_ID = /^[a-z][A-Za-z0-9_]*$/u;
246
- var PATH = /^(\/(:?[A-Za-z0-9_.~-]+))+$/u;
247
- var fail = (message) => {
248
- throw new HotUpdaterConfigError(message);
249
- };
250
- var isRecord$4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
251
- var checkPlugin = (value, at) => {
252
- if (!isRecord$4(value)) return fail(`${at} is not a plugin.`);
253
- if ("kind" in value) fail(`${at} has a kind; core is built in and is never passed as a plugin.`);
254
- const unknown = Object.keys(value).find((key) => !PLUGIN_KEYS.has(key));
255
- if (unknown !== void 0) fail(`${at} has an unknown key "${unknown}".`);
256
- const plugin = value;
257
- if (plugin.namespace !== void 0 && plugin.namespace !== false) fail(`${at} may only set namespace: false.`);
258
- const pattern = plugin.namespace === false ? UNPREFIXED_ID : PREFIX_ID;
259
- if (typeof plugin.id !== "string" || !pattern.test(plugin.id)) fail(`${at} needs an id matching ${pattern.source}.`);
260
- if (plugin.id === coreModule.id) fail(`${at} uses the id "core", which is core's own.`);
261
- if (typeof plugin.schemaVersion !== "string" || !isRecord$4(plugin.schema) || typeof plugin.init !== "function") fail(`Plugin "${plugin.id}" needs a schemaVersion, a schema, and init.`);
262
- const provides = plugin.provides;
263
- if (provides !== void 0 && (!isRecord$4(provides) || Object.entries(provides).some(([key, flag]) => key !== "clientAuth" || flag !== true))) fail(`Plugin "${plugin.id}" may only declare provides: { clientAuth: true }.`);
264
- const cli = value.cli;
265
- if (cli !== void 0) {
266
- if (!isRecord$4(cli) || Object.keys(cli).some((key) => !CLI_KEYS.has(key)) || cli.commands !== void 0 && !Array.isArray(cli.commands)) fail(`Plugin "${plugin.id}" cli may only hold commands (an array), clientCredential, and clientPlugin.`);
267
- if (cli.clientCredential !== void 0 && plugin.provides?.clientAuth !== true) fail(`Plugin "${plugin.id}" adds cli.clientCredential without declaring provides: { clientAuth: true }.`);
268
- }
269
- return plugin;
270
- };
271
- var checkEndpoint = (id, value) => {
272
- const endpoint = isRecord$4(value) ? value : {};
273
- if (!METHODS.has(endpoint.method) || typeof endpoint.path !== "string" || !PATH.test(endpoint.path) || endpoint.access !== "client" && endpoint.access !== "admin" || typeof endpoint.handler !== "function") fail(`Plugin "${id}" has an endpoint that needs a method, a path like /name/:param, access "client" or "admin", and a handler.`);
274
- return {
275
- ...endpoint,
276
- plugin: id
277
- };
278
- };
279
- var checkInstance = (plugin, instance) => {
280
- const { id } = plugin;
281
- if (!isRecord$4(instance)) return fail(`Plugin "${id}" init returned no instance.`);
282
- if (typeof instance.then === "function") fail(`Plugin "${id}" init returned a promise; init must be synchronous.`);
283
- const unknown = Object.keys(instance).find((key) => !INSTANCE_KEYS.has(key));
284
- if (unknown !== void 0 || !("api" in instance)) fail(`Plugin "${id}" init must return { api, endpoints?, clientAuth? }${unknown === void 0 ? "" : `, not "${unknown}"`}.`);
285
- const declared = plugin.provides?.clientAuth === true;
286
- const clientAuth = instance.clientAuth;
287
- if (declared !== (clientAuth !== void 0)) fail(declared ? `Plugin "${id}" declares provides: { clientAuth: true } but its instance has no clientAuth.` : `Plugin "${id}" returns clientAuth without declaring provides: { clientAuth: true }.`);
288
- if (clientAuth !== void 0 && (typeof clientAuth.authenticate !== "function" || !Array.isArray(clientAuth.varyHeaders) || !clientAuth.varyHeaders.every((header) => typeof header === "string"))) fail(`Plugin "${id}" clientAuth needs varyHeaders and authenticate.`);
289
- const endpoints = instance.endpoints ?? [];
290
- if (!Array.isArray(endpoints)) fail(`Plugin "${id}" endpoints must be an array.`);
291
- return {
292
- api: instance.api,
293
- clientAuth,
294
- endpoints: endpoints.map((endpoint) => checkEndpoint(id, endpoint))
295
- };
296
- };
297
- /**
298
- * Checks every plugin, runs each `init` once against one engine over the
299
- * database's storage adapter, and collects APIs, endpoints, and clientAuth.
300
- * Core's reads run on the same engine, so plugins read core through `ctx.core`.
301
- */
302
- var assemblePlugins = (value, adapter, { now = Date.now, storage = { resolveFileUrl: async () => null }, verify = false, onCachedRoutesChange, batching } = {}) => {
303
- if (!Array.isArray(value)) return fail("plugins must be an array of plugins.");
304
- const plugins = value.map((plugin, position) => checkPlugin(plugin, `plugins[${position}]`));
305
- const ids = /* @__PURE__ */ new Set();
306
- for (const { id } of plugins) {
307
- if (ids.has(id)) fail(`Plugin "${id}" is registered twice.`);
308
- ids.add(id);
309
- }
310
- clientPluginsOf(plugins);
311
- const modules = plugins.map(pluginModule);
312
- if (modules.length > 0) validateSchema([coreModule, ...modules]);
313
- const name = fencedName(adapter);
314
- const fenced = name === void 0 || plugins.length === 0 ? adapter : withSchemaFence(adapter, name, pluginSettings(plugins));
315
- const schema = resolveSchema([
316
- coreModule,
317
- ...modules,
318
- ...batching === void 0 ? [] : [aggregateBatchingModule]
319
- ]);
320
- const engine = createDatabaseEngine({
321
- adapter: verify ? fenced : pruneDuringWrites(fenced, schema.tables, {
322
- leaseTable: SETTINGS_TABLE,
323
- now
324
- }),
325
- schema,
326
- verify,
327
- ...batching === void 0 ? {} : {
328
- batching,
329
- now
330
- }
331
- });
332
- const coreDatabase = engine.database(coreModule);
333
- const core = createCoreApi(coreDatabase, storage, {
334
- now,
335
- ...onCachedRoutesChange === void 0 ? {} : { onCachedRoutesChange }
336
- });
337
- const reads = createCoreReads(coreDatabase, storage);
338
- const api = {};
339
- const endpoints = [];
340
- let clientAuth;
341
- plugins.forEach((plugin, position) => {
342
- const instance = checkInstance(plugin, plugin.init({
343
- db: engine.database(modules[position]),
344
- core: reads,
345
- now
346
- }));
347
- if (instance.clientAuth !== void 0) {
348
- if (clientAuth !== void 0) fail(`Plugins "${clientAuth.plugin}" and "${plugin.id}" both provide clientAuth; keep one.`);
349
- const provided = instance.clientAuth;
350
- clientAuth = {
351
- plugin: plugin.id,
352
- varyHeaders: provided.varyHeaders,
353
- authenticate: (headers) => provided.authenticate(headers)
354
- };
355
- }
356
- endpoints.push(...instance.endpoints);
357
- api[plugin.id] = instance.api;
358
- });
359
- return {
360
- core,
361
- api,
362
- endpoints,
363
- ...clientAuth === void 0 ? {} : { clientAuth },
364
- ...verify ? { measureReads: engine.measureReads } : {},
365
- flush: engine.flush
366
- };
367
- };
368
- function createDatabasePluginApis(database, plugins, options = {}) {
369
- const adapter = engineAdapterOf(database);
370
- if (adapter === void 0) throw new Error(OFF_ENGINE_DATABASE);
371
- const { aggregateBatching } = database;
372
- return assemblePlugins(plugins, adapter, {
373
- ...options,
374
- ...aggregateBatching === void 0 ? {} : { batching: aggregateBatching }
375
- }).api;
376
- }
1
+ import { i as resolveConsoleFeatures, n as consoleFeatures, t as ConsoleFeatureUnavailableError } from "./console-features-Dzu2iUGD.mjs";
2
+ import { C as isUUIDv7 } from "./dist-Cxo9ghYv.mjs";
3
+ import { i as isDatabaseJsonObject, t as DatabaseAdapterInputError } from "./databaseJsonValue-Bhxciy7Z.mjs";
4
+ import { t as createUUIDv7 } from "./uuidv7-C8bwiZr8.mjs";
5
+ import { n as defineTable, r as isRemoteDatabase, t as defineAggregate } from "./schema-BKY9SqaY.mjs";
6
+ //#region node_modules/.nitro/vite/services/ssr/assets/runtime.server-fL0o6I2b.js
377
7
  var isOptional = (value, key, valid) => !Object.hasOwn(value, key) || valid(value[key]);
378
8
  var isString = (value) => typeof value === "string";
379
9
  /** An `UPDATE_FAILED` report's stored failure: a stage and a reason, and what else the client knew. */
@@ -411,10 +41,10 @@ var hasEventInvariants = (row) => (row.type === "UPDATE_DOWNLOADED" || row.type
411
41
  * `invalid-data` for anything else.
412
42
  */
413
43
  var validateBundleEventFields = (row) => {
414
- if (!isRecord$3(row)) throw new DatabasePluginInputError("invalid-data");
415
- for (const field of Object.keys(row)) if (!Object.hasOwn(BUNDLE_EVENT_FIELDS, field)) throw new DatabasePluginInputError("invalid-field");
416
- for (const [field, valid] of Object.entries(BUNDLE_EVENT_FIELDS)) if (!Object.hasOwn(row, field) || !valid(row[field])) throw new DatabasePluginInputError("invalid-data");
417
- if (!hasEventInvariants(row)) throw new DatabasePluginInputError("invalid-data");
44
+ if (!isRecord$3(row)) throw new DatabaseAdapterInputError("invalid-data");
45
+ for (const field of Object.keys(row)) if (!Object.hasOwn(BUNDLE_EVENT_FIELDS, field)) throw new DatabaseAdapterInputError("invalid-field");
46
+ for (const [field, valid] of Object.entries(BUNDLE_EVENT_FIELDS)) if (!Object.hasOwn(row, field) || !valid(row[field])) throw new DatabaseAdapterInputError("invalid-data");
47
+ if (!hasEventInvariants(row)) throw new DatabaseAdapterInputError("invalid-data");
418
48
  };
419
49
  var encoder = new TextEncoder();
420
50
  /** Exact UTF-8 byte ordering, without case folding or Unicode normalization. */
@@ -456,9 +86,9 @@ var isEventFilter = (value) => {
456
86
  var validateRow = (model, row, result = false) => {
457
87
  try {
458
88
  validateBundleEventFields(row);
459
- if (!isRecord$3(row) || typeof row.id !== "string" || !isUUIDv7(row.id) || [...Object.values(row), ...isRecord$3(row.metadata) ? Object.values(row.metadata) : []].some((value) => typeof value === "string" && !isWellFormedText(value))) throw new DatabasePluginInputError("invalid-data");
89
+ if (!isRecord$3(row) || typeof row.id !== "string" || !isUUIDv7(row.id) || [...Object.values(row), ...isRecord$3(row.metadata) ? Object.values(row.metadata) : []].some((value) => typeof value === "string" && !isWellFormedText(value))) throw new DatabaseAdapterInputError("invalid-data");
460
90
  } catch (error) {
461
- if (result) throw new DatabasePluginInputError("invalid-result");
91
+ if (result) throw new DatabaseAdapterInputError("invalid-result");
462
92
  throw error;
463
93
  }
464
94
  };
@@ -470,10 +100,10 @@ var matchesInsightsEventFilter = (event, filter) => {
470
100
  return event.type === filter.type && event.platform === filter.platform && event.channel === filter.channel && (filter.type === "RECOVERED" ? event.from_bundle_id === filter.fromBundleId : event.to_bundle_id === filter.toBundleId && event.metadata.failure?.stage !== "check");
471
101
  };
472
102
  var invalidQuery = () => {
473
- throw new DatabasePluginInputError("invalid-query");
103
+ throw new DatabaseAdapterInputError("invalid-query");
474
104
  };
475
105
  var invalidResult = () => {
476
- throw new DatabasePluginInputError("invalid-result");
106
+ throw new DatabaseAdapterInputError("invalid-result");
477
107
  };
478
108
  var validateCount = (count) => isTimestamp(count) ? count : invalidResult();
479
109
  var isTimeRange = (value) => isRecord$3(value) && hasOnlyKeys(value, ["start", "end"]) && isTimestamp(value.start) && isTimestamp(value.end) && value.start < value.end;
@@ -491,7 +121,7 @@ var validateAggregateResult = (value) => {
491
121
  /** Validate custom and bundled providers at the same public boundary. */
492
122
  var createValidatedInsightsModel = (model) => ({
493
123
  async recordEvent(input) {
494
- if (!isRecord$3(input) || !hasOnlyKeys(input, ["event"])) throw new DatabasePluginInputError("invalid-data");
124
+ if (!isRecord$3(input) || !hasOnlyKeys(input, ["event"])) throw new DatabaseAdapterInputError("invalid-data");
495
125
  validateRow("bundle_events", input.event);
496
126
  await model.recordEvent(input);
497
127
  },
@@ -611,6 +241,10 @@ var createInsightsModel = (api) => createValidatedInsightsModel({
611
241
  getReleaseActivity: api.getReleaseActivity,
612
242
  getAppUsage: api.getAppUsage
613
243
  });
244
+ /**
245
+ * What Insights' API throws for an input it refuses, such as an unknown
246
+ * event field or a malformed cursor; its routes answer 400 for it.
247
+ */
614
248
  var InsightsBadRequestError = class extends Error {
615
249
  name = "InsightsBadRequestError";
616
250
  };
@@ -863,7 +497,7 @@ var MAX_EVENT_ID_LENGTH = 1024;
863
497
  var MAX_IDENTITY_LENGTH = 255;
864
498
  var MAX_CURSOR_LENGTH = 8192;
865
499
  var HOUR_MS = 36e5;
866
- var DAY_MS = 24 * HOUR_MS;
500
+ var DAY_MS$1 = 24 * HOUR_MS;
867
501
  var WINDOW_MS = {
868
502
  "24h": 864e5,
869
503
  "7d": 6048e5,
@@ -1151,7 +785,7 @@ var createInsightsProvider = (model) => Object.freeze({
1151
785
  const now = Date.now();
1152
786
  const beforeReceivedAtMs = now - now % HOUR_MS + (now % HOUR_MS === 0 ? 0 : HOUR_MS);
1153
787
  const reach = Math.max(0, beforeReceivedAtMs - WINDOW_MS[window]);
1154
- const sinceMs = reach - reach % DAY_MS;
788
+ const sinceMs = reach - reach % DAY_MS$1;
1155
789
  const measure = async (count) => {
1156
790
  const value = await count;
1157
791
  if (!Number.isSafeInteger(value) || value < 0) throw new Error("Insights database returned an invalid count.");
@@ -1222,6 +856,464 @@ var createInsightsProvider = (model) => Object.freeze({
1222
856
  };
1223
857
  }
1224
858
  });
859
+ var DAY_MS = 864e5;
860
+ var DEFAULT_INSIGHTS_RETENTION = {
861
+ rawDays: 90,
862
+ dailyDays: 400
863
+ };
864
+ var bucketRetention = (days) => ({
865
+ field: "bucket_start_ms",
866
+ days
867
+ });
868
+ var MOVEMENTS = /* @__PURE__ */ new Set([
869
+ "UPDATE_DOWNLOADED",
870
+ "UPDATE_APPLIED",
871
+ "RECOVERED",
872
+ "UPDATE_FAILED"
873
+ ]);
874
+ /** A failed update check: it names no target bundle, so no bundle list shows it. */
875
+ var isFailedCheck = (row) => row.type === "UPDATE_FAILED" && row.metadata?.failure?.stage === "check";
876
+ /**
877
+ * Downloads, applies, recoveries, and update failures, whole. An UNCHANGED
878
+ * report counts as a launch and moves its installation's head, but no list
879
+ * shows it, so no row keeps it.
880
+ */
881
+ var bundleEvents = (days) => defineTable({
882
+ id: {
883
+ type: "string",
884
+ maxLength: 36
885
+ },
886
+ type: {
887
+ type: "string",
888
+ maxLength: 32
889
+ },
890
+ install_id: {
891
+ type: "string",
892
+ maxLength: 255
893
+ },
894
+ user_id: {
895
+ type: "string",
896
+ maxLength: 255,
897
+ required: false
898
+ },
899
+ from_release_id: {
900
+ type: "string",
901
+ maxLength: 36,
902
+ required: false
903
+ },
904
+ from_bundle_id: {
905
+ type: "string",
906
+ maxLength: 36,
907
+ required: false
908
+ },
909
+ to_release_id: {
910
+ type: "string",
911
+ maxLength: 36,
912
+ required: false
913
+ },
914
+ to_bundle_id: {
915
+ type: "string",
916
+ maxLength: 36
917
+ },
918
+ platform: {
919
+ type: "string",
920
+ maxLength: 16
921
+ },
922
+ app_version: { type: "string" },
923
+ channel: { type: "string" },
924
+ metadata: { type: "json" },
925
+ received_at_ms: { type: "integer" }
926
+ }, {
927
+ key: ["id"],
928
+ derived: {
929
+ day: {
930
+ type: "integer",
931
+ compute: (row) => row.received_at_ms - row.received_at_ms % DAY_MS
932
+ },
933
+ movement_install_id: {
934
+ type: "string",
935
+ compute: (row) => MOVEMENTS.has(row.type) ? row.install_id : null
936
+ },
937
+ bundle_ref: {
938
+ type: "string",
939
+ multi: true,
940
+ compute: (row) => isFailedCheck(row) ? [] : [row.type === "RECOVERED" && row.from_bundle_id !== null ? `from:${row.from_bundle_id}` : `to:${row.to_bundle_id}`]
941
+ }
942
+ },
943
+ indexes: {
944
+ recent: {
945
+ eq: [
946
+ "channel",
947
+ "platform",
948
+ "day"
949
+ ],
950
+ sort: ["received_at_ms"]
951
+ },
952
+ movementsByInstall: {
953
+ eq: ["movement_install_id"],
954
+ sort: ["received_at_ms"]
955
+ },
956
+ byBundle: {
957
+ eq: [
958
+ "platform",
959
+ "channel",
960
+ "type",
961
+ "bundle_ref",
962
+ "day"
963
+ ],
964
+ sort: ["received_at_ms"]
965
+ },
966
+ byDay: {
967
+ eq: ["day"],
968
+ sort: ["received_at_ms"]
969
+ }
970
+ },
971
+ retention: {
972
+ field: "day",
973
+ days
974
+ }
975
+ });
976
+ /**
977
+ * Each installation's latest event, whole, so reading it is one point read.
978
+ * Its gauges count it in the UTC day of that event. An update failure moves
979
+ * nothing, so no head holds one.
980
+ */
981
+ var bundleEventHeads = (days) => defineTable({
982
+ install_id: {
983
+ type: "string",
984
+ maxLength: 255
985
+ },
986
+ id: {
987
+ type: "string",
988
+ maxLength: 36
989
+ },
990
+ type: {
991
+ type: "string",
992
+ maxLength: 32
993
+ },
994
+ user_id: {
995
+ type: "string",
996
+ maxLength: 255,
997
+ required: false
998
+ },
999
+ from_release_id: {
1000
+ type: "string",
1001
+ maxLength: 36,
1002
+ required: false
1003
+ },
1004
+ from_bundle_id: {
1005
+ type: "string",
1006
+ maxLength: 36,
1007
+ required: false
1008
+ },
1009
+ to_release_id: {
1010
+ type: "string",
1011
+ maxLength: 36,
1012
+ required: false
1013
+ },
1014
+ to_bundle_id: {
1015
+ type: "string",
1016
+ maxLength: 36
1017
+ },
1018
+ platform: {
1019
+ type: "string",
1020
+ maxLength: 16
1021
+ },
1022
+ app_version: { type: "string" },
1023
+ channel: { type: "string" },
1024
+ metadata: { type: "json" },
1025
+ received_at_ms: { type: "integer" }
1026
+ }, {
1027
+ key: ["install_id"],
1028
+ indexes: { byUser: {
1029
+ eq: ["user_id"],
1030
+ sort: ["install_id"]
1031
+ } },
1032
+ retention: {
1033
+ field: "received_at_ms",
1034
+ days
1035
+ }
1036
+ });
1037
+ /**
1038
+ * Shards are keyed by install id. Counters are blind increments and never
1039
+ * conflict, so they keep 8. Gauges and sketches are read, merged, and written
1040
+ * back. B3's rollout gate on PostgreSQL retried 2.4% of transactions at 8
1041
+ * shards and 1.3% at 16. DynamoDB's transactions take longer, so D8's gate
1042
+ * on DynamoDB Local retried 2–24% at 16, almost all on gauge rows, and 1–5%
1043
+ * with gauges at 32. Sketches stay at 16: each shard row carries 2 KB of
1044
+ * registers that every read merges.
1045
+ *
1046
+ * Every Insights aggregate is `batched`: on DynamoDB and Firestore, which
1047
+ * bill each write, an event's changes apply after it commits, merged with
1048
+ * other events' into one write per row, on one shard per writer.
1049
+ */
1050
+ var COUNTER_SHARDS = 8;
1051
+ var GAUGE_SHARDS = 32;
1052
+ var SKETCH_SHARDS = 16;
1053
+ /** Rows keyed by a hashed identity (scope and period) and a bucket. */
1054
+ var identityFields = {
1055
+ identity: {
1056
+ type: "string",
1057
+ maxLength: 32
1058
+ },
1059
+ bucket_start_ms: { type: "integer" }
1060
+ };
1061
+ var window = {
1062
+ eq: ["identity"],
1063
+ sort: ["bucket_start_ms"]
1064
+ };
1065
+ /**
1066
+ * Counters of one period kind: hourly, daily, or lifetime at bucket 0. A
1067
+ * release or channel row counts downloads, launches, and failed launches,
1068
+ * and downloads a patch delivered and those that fell back from one. A
1069
+ * release's lifetime row counts its failed updates (`failed_updates`), since
1070
+ * no breakdown row outlives the raw period; windowed reads sum
1071
+ * `insights_failures` instead.
1072
+ */
1073
+ var counters = (retention) => defineAggregate(identityFields, {
1074
+ key: ["identity", "bucket_start_ms"],
1075
+ counters: [
1076
+ "downloads",
1077
+ "launches",
1078
+ "failed_launches",
1079
+ "failed_updates",
1080
+ "patch_downloads",
1081
+ "patch_fallbacks"
1082
+ ],
1083
+ shards: COUNTER_SHARDS,
1084
+ batched: true,
1085
+ indexes: { window },
1086
+ ...retention === void 0 ? {} : { retention }
1087
+ });
1088
+ /**
1089
+ * Unique-installation sketches of one period kind. Rows of a failure
1090
+ * identity (`failure` and `check` scopes) keep only `failed_users`, so a
1091
+ * release's launch rows never grow by a failure's registers.
1092
+ */
1093
+ var sketches = (retention) => defineAggregate(identityFields, {
1094
+ key: ["identity", "bucket_start_ms"],
1095
+ distinct: [
1096
+ "launch_users",
1097
+ "activity_users",
1098
+ "failed_users"
1099
+ ],
1100
+ shards: SKETCH_SHARDS,
1101
+ batched: true,
1102
+ indexes: { window },
1103
+ retention
1104
+ });
1105
+ /** Installations whose update to a release failed, since its first failure: kept, at bucket 0. */
1106
+ var lifetimeSketches = () => defineAggregate(identityFields, {
1107
+ key: ["identity", "bucket_start_ms"],
1108
+ distinct: ["failed_users"],
1109
+ shards: SKETCH_SHARDS,
1110
+ batched: true,
1111
+ indexes: { window }
1112
+ });
1113
+ /**
1114
+ * Update failures by what failed, and recoveries by why the crashed process
1115
+ * exited, a row an hour. `release_id` is the failed update's target release,
1116
+ * or empty for a failed check or an unknown target, and the recovered
1117
+ * release for a recovery. A failure's `stage` and `reason` say where and why
1118
+ * it failed, and `detail` what else the client knew, as the JSON array
1119
+ * `[resource, httpStatus, originCode, transport]`. A recovery's stage is
1120
+ * `launch` and its reason the exit reason, with an empty detail. Every index
1121
+ * is in key order, so its counters stay blind increments.
1122
+ */
1123
+ var insightsFailures = (retention) => defineAggregate({
1124
+ platform: {
1125
+ type: "string",
1126
+ maxLength: 16
1127
+ },
1128
+ channel: { type: "string" },
1129
+ bucket_start_ms: { type: "integer" },
1130
+ release_id: {
1131
+ type: "string",
1132
+ maxLength: 36
1133
+ },
1134
+ stage: {
1135
+ type: "string",
1136
+ maxLength: 16
1137
+ },
1138
+ reason: {
1139
+ type: "string",
1140
+ maxLength: 64
1141
+ },
1142
+ detail: {
1143
+ type: "string",
1144
+ maxLength: 160
1145
+ }
1146
+ }, {
1147
+ key: [
1148
+ "platform",
1149
+ "channel",
1150
+ "bucket_start_ms",
1151
+ "release_id",
1152
+ "stage",
1153
+ "reason",
1154
+ "detail"
1155
+ ],
1156
+ counters: ["events"],
1157
+ shards: COUNTER_SHARDS,
1158
+ batched: true,
1159
+ indexes: { byScope: {
1160
+ eq: ["platform", "channel"],
1161
+ sort: ["bucket_start_ms"]
1162
+ } },
1163
+ retention
1164
+ });
1165
+ /** Installations counted in the UTC day of their latest event, so reads sum whole UTC days. */
1166
+ var insightsDistribution = (retention) => defineAggregate({
1167
+ channel: { type: "string" },
1168
+ platform: {
1169
+ type: "string",
1170
+ maxLength: 16
1171
+ },
1172
+ app_version: { type: "string" },
1173
+ release_id: {
1174
+ type: "string",
1175
+ maxLength: 36
1176
+ },
1177
+ bucket_start_ms: { type: "integer" }
1178
+ }, {
1179
+ key: [
1180
+ "channel",
1181
+ "platform",
1182
+ "app_version",
1183
+ "release_id",
1184
+ "bucket_start_ms"
1185
+ ],
1186
+ gauges: ["latest_installations"],
1187
+ shards: GAUGE_SHARDS,
1188
+ batched: true,
1189
+ indexes: {
1190
+ byScope: {
1191
+ eq: ["channel", "platform"],
1192
+ sort: ["bucket_start_ms"]
1193
+ },
1194
+ byVersion: {
1195
+ eq: [
1196
+ "channel",
1197
+ "platform",
1198
+ "app_version"
1199
+ ],
1200
+ sort: ["bucket_start_ms"]
1201
+ }
1202
+ },
1203
+ retention
1204
+ });
1205
+ /** Latest events by the bundle they came from or went to, bucketed like the distribution. */
1206
+ var insightsLatestByBundle = (retention) => defineAggregate({
1207
+ platform: {
1208
+ type: "string",
1209
+ maxLength: 16
1210
+ },
1211
+ channel: { type: "string" },
1212
+ bundle_field: {
1213
+ type: "string",
1214
+ maxLength: 16
1215
+ },
1216
+ bundle_id: {
1217
+ type: "string",
1218
+ maxLength: 36
1219
+ },
1220
+ type: {
1221
+ type: "string",
1222
+ maxLength: 32
1223
+ },
1224
+ bucket_start_ms: { type: "integer" }
1225
+ }, {
1226
+ key: [
1227
+ "platform",
1228
+ "channel",
1229
+ "bundle_field",
1230
+ "bundle_id",
1231
+ "type",
1232
+ "bucket_start_ms"
1233
+ ],
1234
+ gauges: ["installations"],
1235
+ shards: GAUGE_SHARDS,
1236
+ batched: true,
1237
+ indexes: { byBundle: {
1238
+ eq: [
1239
+ "platform",
1240
+ "channel",
1241
+ "bundle_field",
1242
+ "bundle_id",
1243
+ "type"
1244
+ ],
1245
+ sort: ["bucket_start_ms"]
1246
+ } },
1247
+ retention
1248
+ });
1249
+ /**
1250
+ * Stored events by outcome and the bundle a bundle filter matches, a row an
1251
+ * hour, which a failed check has none of; and every stored event, a row a UTC
1252
+ * day (`DAILY_EVENTS`). Bundle counts sum the hourly rows, and event lists
1253
+ * read either to skip days without events.
1254
+ */
1255
+ var insightsOutcomes = (retention) => defineAggregate({
1256
+ platform: {
1257
+ type: "string",
1258
+ maxLength: 16
1259
+ },
1260
+ channel: { type: "string" },
1261
+ type: {
1262
+ type: "string",
1263
+ maxLength: 32
1264
+ },
1265
+ bundle_ref: {
1266
+ type: "string",
1267
+ maxLength: 41
1268
+ },
1269
+ bucket_start_ms: { type: "integer" }
1270
+ }, {
1271
+ key: [
1272
+ "platform",
1273
+ "channel",
1274
+ "type",
1275
+ "bundle_ref",
1276
+ "bucket_start_ms"
1277
+ ],
1278
+ counters: ["events"],
1279
+ shards: COUNTER_SHARDS,
1280
+ batched: true,
1281
+ indexes: { byRef: {
1282
+ eq: [
1283
+ "platform",
1284
+ "channel",
1285
+ "type",
1286
+ "bundle_ref"
1287
+ ],
1288
+ sort: ["bucket_start_ms"]
1289
+ } },
1290
+ retention
1291
+ });
1292
+ /**
1293
+ * Insights' tables, each expiring after its period. The periods are values
1294
+ * the engine applies at runtime; the tables and indexes are the same for
1295
+ * any periods.
1296
+ */
1297
+ var createInsightsSchema = ({ rawDays, dailyDays } = DEFAULT_INSIGHTS_RETENTION) => {
1298
+ const hourly = bucketRetention(rawDays);
1299
+ const daily = bucketRetention(dailyDays);
1300
+ return {
1301
+ bundle_events: bundleEvents(rawDays),
1302
+ bundle_event_heads: bundleEventHeads(dailyDays),
1303
+ insights_overview: counters(hourly),
1304
+ insights_sketches: sketches(hourly),
1305
+ insights_overview_daily: counters(daily),
1306
+ insights_sketches_daily: sketches(daily),
1307
+ insights_overview_lifetime: counters(),
1308
+ insights_sketches_lifetime: lifetimeSketches(),
1309
+ insights_distribution: insightsDistribution(daily),
1310
+ insights_latest_by_bundle: insightsLatestByBundle(daily),
1311
+ insights_outcomes: insightsOutcomes(hourly),
1312
+ insights_failures: insightsFailures(hourly)
1313
+ };
1314
+ };
1315
+ createInsightsSchema();
1316
+ 30 * DAY_MS;
1225
1317
  /** The `error` a failed admin route answered with, or a message naming its status. */
1226
1318
  var errorOf = async (response, path) => {
1227
1319
  const body = await response.json().catch(() => null);
@@ -1303,22 +1395,13 @@ var consoleFeatureApis = {
1303
1395
  apiKeys: featureApi({ local: (api) => api })
1304
1396
  };
1305
1397
  var isRecord = (value) => typeof value === "object" && value !== null;
1306
- /** The ids of the plugins a self-hosted server lists on its admin `/version`. */
1307
- var readServerPlugins = async (fetchAdmin) => {
1308
- const response = await fetchAdmin("/version");
1309
- if (!response.ok) throw new Error(`The server answered /version with ${response.status}.`);
1310
- const body = await response.json();
1311
- const plugins = isRecord(body) ? body.plugins : void 0;
1312
- return Array.isArray(plugins) ? plugins.filter((id) => typeof id === "string") : [];
1313
- };
1314
- /** Runs `load` once and keeps its result; a failure is not kept, so the next call retries. */
1315
- var once = (load) => {
1316
- let pending;
1317
- return () => pending ??= load().catch((error) => {
1318
- pending = void 0;
1319
- throw error;
1320
- });
1321
- };
1398
+ /**
1399
+ * Whether one of Hot Updater's own factories made `plugin`: the brand they
1400
+ * set under a registered symbol, read here with a copy of the key so the
1401
+ * console imports no package's internals. Assembly refuses any other plugin
1402
+ * that takes one of their ids, so features key on it.
1403
+ */
1404
+ var isOfficialPlugin = (plugin) => isRecord(plugin) && Reflect.get(plugin, Symbol.for("@hot-updater/server/official-plugin")) === true;
1322
1405
  var featureIds = Object.keys(consoleFeatureApis);
1323
1406
  /**
1324
1407
  * What serves each feature, built by its factory: over the plugins' APIs the
@@ -1330,30 +1413,34 @@ var featureApis = (build) => Object.fromEntries(featureIds.flatMap((feature) =>
1330
1413
  return api === void 0 ? [] : [[feature, api]];
1331
1414
  }));
1332
1415
  /**
1333
- * The console's features and what serves them, for a console config:
1334
- * - a self-hosted server (`standaloneRepository`) runs its plugins and lists
1335
- * them on its admin `/version`; the console serves what that server's admin
1336
- * API serves, and nothing that needs the database;
1337
- * - otherwise the console assembles `plugins` over the database, as the
1338
- * server does, and serves the features of the plugins it assembled.
1416
+ * The console's features and what serves them, for the plugins its config
1417
+ * lists, the plugins the server runs:
1418
+ * - a self-hosted server (`standaloneRepository`) runs them itself; the
1419
+ * console serves what that server's admin API serves of their features,
1420
+ * and nothing that needs the database;
1421
+ * - otherwise the console runs them over the database, as the server does,
1422
+ * and serves their features through `api`, their APIs.
1339
1423
  */
1340
1424
  var createConsoleRuntime = (config) => {
1341
1425
  const { database } = config;
1426
+ const official = new Set(config.plugins.filter(isOfficialPlugin).map((plugin) => plugin.id));
1342
1427
  if (isRemoteDatabase(database)) {
1343
1428
  const { fetchAdmin } = database;
1429
+ const features = resolveConsoleFeatures([...official], { remote: true });
1344
1430
  return {
1345
1431
  remote: true,
1346
- features: once(async () => resolveConsoleFeatures(await readServerPlugins(fetchAdmin), { remote: true })),
1432
+ features: async () => features,
1347
1433
  apis: featureApis((feature) => consoleFeatureApis[feature].remote?.(fetchAdmin))
1348
1434
  };
1349
1435
  }
1350
- const api = createDatabasePluginApis(database, config.plugins ?? []);
1351
- const features = resolveConsoleFeatures(Object.keys(api), { remote: false });
1436
+ const api = config.api ?? {};
1437
+ const features = resolveConsoleFeatures([...official], { remote: false });
1352
1438
  return {
1353
1439
  remote: false,
1354
1440
  features: async () => features,
1355
1441
  apis: featureApis((feature) => {
1356
- const pluginApi = api[consoleFeatures[feature].plugin];
1442
+ const { plugin } = consoleFeatures[feature];
1443
+ const pluginApi = official.has(plugin) ? api[plugin] : void 0;
1357
1444
  return pluginApi === void 0 ? void 0 : consoleFeatureApis[feature].local(pluginApi);
1358
1445
  })
1359
1446
  };