@hot-updater/cloudflare 1.0.0-rc.0 → 1.0.0-rc.2

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.
@@ -2,7 +2,7 @@ var __defProp = Object.defineProperty;
2
2
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
3
 
4
4
  // ../../packages/server/dist/package.mjs
5
- var version = "1.0.0-rc.0";
5
+ var version = "1.0.0-rc.1";
6
6
 
7
7
  // ../../packages/server/dist/version.mjs
8
8
  var HOT_UPDATER_SERVER_VERSION = version;
@@ -13,262 +13,6 @@ var createVersionRouteHandlers = /* @__PURE__ */ __name(() => ({ version: /* @__
13
13
  version: HOT_UPDATER_SERVER_VERSION
14
14
  }), "version") }), "createVersionRouteHandlers");
15
15
 
16
- // ../../packages/server/dist/analytics/errors.mjs
17
- var AnalyticsScanLimitExceededError = class extends Error {
18
- static {
19
- __name(this, "AnalyticsScanLimitExceededError");
20
- }
21
- constructor(limit) {
22
- super(`Analytics event scan exceeded ${limit} rows.`);
23
- this.limit = limit;
24
- this.name = "AnalyticsScanLimitExceededError";
25
- }
26
- };
27
- var AnalyticsBadRequestError = class extends Error {
28
- static {
29
- __name(this, "AnalyticsBadRequestError");
30
- }
31
- name = "AnalyticsBadRequestError";
32
- };
33
- var AnalyticsPayloadTooLargeError = class extends Error {
34
- static {
35
- __name(this, "AnalyticsPayloadTooLargeError");
36
- }
37
- name = "AnalyticsPayloadTooLargeError";
38
- constructor(maximumBytes) {
39
- super(`Event payload exceeds ${maximumBytes} bytes`);
40
- this.maximumBytes = maximumBytes;
41
- }
42
- };
43
-
44
- // ../../packages/server/dist/analytics/eventInput.mjs
45
- var MAX_EVENT_STRING_LENGTH = 1024;
46
- var EVENT_BODY_MAX_BYTES = 16 * 1024;
47
- var eventKeys = /* @__PURE__ */ new Set([
48
- "type",
49
- "installId",
50
- "toBundleId",
51
- "userId",
52
- "username",
53
- "platform",
54
- "appVersion",
55
- "channel",
56
- "cohort",
57
- "fingerprintHash",
58
- "fromBundleId",
59
- "fromReleaseId",
60
- "toReleaseId",
61
- "updateStrategy",
62
- "sdkVersion"
63
- ]);
64
- function isRecord(value) {
65
- return typeof value === "object" && value !== null && !Array.isArray(value);
66
- }
67
- __name(isRecord, "isRecord");
68
- function requireStringField(payload, key) {
69
- const value = payload[key];
70
- if (typeof value !== "string" || value.length === 0 || value.length > MAX_EVENT_STRING_LENGTH) throw new AnalyticsBadRequestError(`Invalid event field: ${key}`);
71
- return value;
72
- }
73
- __name(requireStringField, "requireStringField");
74
- function requireNullableStringField(payload, key) {
75
- if (payload[key] === null) return null;
76
- return requireStringField(payload, key);
77
- }
78
- __name(requireNullableStringField, "requireNullableStringField");
79
- async function readBoundedText(request) {
80
- const contentLength = request.headers.get("content-length");
81
- const declaredByteLength = Number(contentLength);
82
- if (contentLength !== null && Number.isSafeInteger(declaredByteLength) && declaredByteLength > 16384) throw new AnalyticsPayloadTooLargeError(EVENT_BODY_MAX_BYTES);
83
- if (request.body === null) return "";
84
- const reader = request.body.getReader();
85
- const decoder = new TextDecoder();
86
- let byteLength = 0;
87
- let text = "";
88
- while (true) {
89
- const result = await reader.read();
90
- if (result.done) break;
91
- byteLength += result.value.byteLength;
92
- if (byteLength > 16384) {
93
- await reader.cancel();
94
- throw new AnalyticsPayloadTooLargeError(EVENT_BODY_MAX_BYTES);
95
- }
96
- text += decoder.decode(result.value, { stream: true });
97
- }
98
- return text + decoder.decode();
99
- }
100
- __name(readBoundedText, "readBoundedText");
101
- async function parseJson(request) {
102
- const text = await readBoundedText(request);
103
- try {
104
- return JSON.parse(text);
105
- } catch (error) {
106
- if (error instanceof SyntaxError) throw new AnalyticsBadRequestError("Invalid event payload");
107
- throw error;
108
- }
109
- }
110
- __name(parseJson, "parseJson");
111
- function requireEvent(payload) {
112
- if (!isRecord(payload) || Object.keys(payload).some((key) => !eventKeys.has(key))) throw new AnalyticsBadRequestError("Invalid event payload");
113
- const platform = requireStringField(payload, "platform");
114
- if (platform !== "ios" && platform !== "android") throw new AnalyticsBadRequestError("Invalid event field: platform");
115
- const base = {
116
- installId: requireStringField(payload, "installId"),
117
- toBundleId: requireStringField(payload, "toBundleId"),
118
- ...payload.userId === void 0 ? {} : { userId: requireStringField(payload, "userId") },
119
- ...payload.username === void 0 ? {} : { username: requireStringField(payload, "username") },
120
- platform,
121
- appVersion: requireStringField(payload, "appVersion"),
122
- channel: requireStringField(payload, "channel"),
123
- cohort: requireStringField(payload, "cohort"),
124
- fingerprintHash: requireNullableStringField(payload, "fingerprintHash"),
125
- sdkVersion: payload.sdkVersion === void 0 ? null : requireNullableStringField(payload, "sdkVersion"),
126
- fromReleaseId: requireNullableStringField(payload, "fromReleaseId"),
127
- toReleaseId: requireNullableStringField(payload, "toReleaseId")
128
- };
129
- const type = requireStringField(payload, "type");
130
- switch (type) {
131
- case "UPDATE_APPLIED":
132
- case "RECOVERED":
133
- case "RELEASE_ADOPTED": {
134
- const updateStrategy = requireStringField(payload, "updateStrategy");
135
- if (updateStrategy !== "fingerprint" && updateStrategy !== "appVersion") throw new AnalyticsBadRequestError("Invalid event field: updateStrategy");
136
- return {
137
- ...base,
138
- type,
139
- fromBundleId: requireStringField(payload, "fromBundleId"),
140
- updateStrategy
141
- };
142
- }
143
- case "UNCHANGED":
144
- if (payload.fromBundleId !== null || payload.updateStrategy !== null) throw new AnalyticsBadRequestError("Invalid unchanged event shape");
145
- return {
146
- ...base,
147
- type,
148
- fromBundleId: null,
149
- updateStrategy: null
150
- };
151
- default:
152
- throw new AnalyticsBadRequestError("Invalid event field: type");
153
- }
154
- }
155
- __name(requireEvent, "requireEvent");
156
- async function parseBundleEventRequest(request) {
157
- return requireEvent(await parseJson(request));
158
- }
159
- __name(parseBundleEventRequest, "parseBundleEventRequest");
160
-
161
- // ../../packages/server/dist/analytics/queryInput.mjs
162
- var EVENT_LIST_BOUNDS = {
163
- defaultValue: 50,
164
- maximum: 100,
165
- minimum: 1
166
- };
167
- var EVENT_LIST_OFFSET_BOUNDS = {
168
- defaultValue: 0,
169
- minimum: 0
170
- };
171
- var MAX_USER_ID_LENGTH = 1024;
172
- function parseInteger(url, key, bounds) {
173
- const value = url.searchParams.get(key);
174
- if (value === null) return bounds.defaultValue;
175
- const parsed = Number(value);
176
- if (!Number.isSafeInteger(parsed) || parsed < bounds.minimum || bounds.maximum !== void 0 && parsed > bounds.maximum) throw new AnalyticsBadRequestError(`Invalid '${key}' query parameter.`);
177
- return parsed;
178
- }
179
- __name(parseInteger, "parseInteger");
180
- var parsePagination = /* @__PURE__ */ __name((request) => {
181
- const url = new URL(request.url);
182
- return {
183
- limit: parseInteger(url, "limit", EVENT_LIST_BOUNDS),
184
- offset: parseInteger(url, "offset", EVENT_LIST_OFFSET_BOUNDS)
185
- };
186
- }, "parsePagination");
187
- var parseAnalyticsQuery = /* @__PURE__ */ __name((request) => {
188
- const window = new URL(request.url).searchParams.get("window") ?? "24h";
189
- if (window !== "24h" && window !== "7d" && window !== "30d" && window !== "all") throw new AnalyticsBadRequestError("Invalid 'window' query parameter.");
190
- return {
191
- ...parsePagination(request),
192
- window
193
- };
194
- }, "parseAnalyticsQuery");
195
- var parseActiveInstallationInput = /* @__PURE__ */ __name((request) => {
196
- const url = new URL(request.url);
197
- const windows = url.searchParams.getAll("window");
198
- if (windows.length > 1) throw new AnalyticsBadRequestError("Duplicate 'window' query parameter.");
199
- const window = windows[0] ?? "30d";
200
- if (window !== "24h" && window !== "7d" && window !== "30d") throw new AnalyticsBadRequestError("Invalid 'window' query parameter.");
201
- const userIds = url.searchParams.getAll("userId");
202
- if (userIds.length > 1) throw new AnalyticsBadRequestError("Duplicate 'userId' query parameter.");
203
- const userId = userIds[0];
204
- if (userId !== void 0 && (userId.length === 0 || userId.length > MAX_USER_ID_LENGTH)) throw new AnalyticsBadRequestError("Invalid 'userId' query parameter.");
205
- return userId === void 0 ? { window } : {
206
- window,
207
- userId
208
- };
209
- }, "parseActiveInstallationInput");
210
- var parseSearchInput = /* @__PURE__ */ __name((request) => ({
211
- ...parsePagination(request),
212
- query: new URL(request.url).searchParams.get("query")?.trim() ?? ""
213
- }), "parseSearchInput");
214
-
215
- // ../../packages/server/dist/analytics/routes.mjs
216
- var json = /* @__PURE__ */ __name((body, status) => Response.json(body, {
217
- headers: { "cache-control": "private, no-store" },
218
- status
219
- }), "json");
220
- var requireParam = /* @__PURE__ */ __name((params, key) => {
221
- const value = params[key];
222
- if (value === void 0 || value.length === 0) throw new AnalyticsBadRequestError(`Missing route parameter: ${key}`);
223
- return value;
224
- }, "requireParam");
225
- var run = /* @__PURE__ */ __name(async (operation) => {
226
- try {
227
- return await operation();
228
- } catch (error) {
229
- if (error instanceof AnalyticsBadRequestError) return json({ error: error.message }, 400);
230
- if (error instanceof AnalyticsPayloadTooLargeError) return json({ error: error.message }, 413);
231
- if (error instanceof AnalyticsScanLimitExceededError) return json({ error: {
232
- code: "ANALYTICS_SCAN_LIMIT_EXCEEDED",
233
- limit: error.limit
234
- } }, 503);
235
- throw error;
236
- }
237
- }, "run");
238
- var query = /* @__PURE__ */ __name((operation) => run(async () => json(await operation(), 200)), "query");
239
- var createAnalyticsRouteHandlers = /* @__PURE__ */ __name((provider) => ({
240
- appendBundleEvent: /* @__PURE__ */ __name(async (_params, request) => run(async () => {
241
- await provider.appendBundleEvent(await parseBundleEventRequest(request));
242
- return new Response(null, { status: 204 });
243
- }), "appendBundleEvent"),
244
- getBundleEventSummary: /* @__PURE__ */ __name((params) => query(() => provider.getBundleEventSummary(requireParam(params, "id"))), "getBundleEventSummary"),
245
- getBundleEventAnalytics: /* @__PURE__ */ __name((params, request) => query(() => {
246
- const input = parseAnalyticsQuery(request);
247
- return provider.getBundleEventAnalytics(requireParam(params, "id"), input.window, input.limit, input.offset);
248
- }), "getBundleEventAnalytics"),
249
- getBundleEventOverview: /* @__PURE__ */ __name(() => query(() => provider.getBundleEventOverview()), "getBundleEventOverview"),
250
- getActiveInstallationOverview: /* @__PURE__ */ __name((_params, request) => query(() => provider.getActiveInstallationOverview(parseActiveInstallationInput(request))), "getActiveInstallationOverview"),
251
- searchInstallations: /* @__PURE__ */ __name((_params, request) => query(() => {
252
- const input = parseSearchInput(request);
253
- return provider.searchInstallations(input.query, input.limit, input.offset);
254
- }), "searchInstallations"),
255
- getInstallationHistory: /* @__PURE__ */ __name((params, request) => query(() => {
256
- const input = parsePagination(request);
257
- return provider.getInstallationHistory(requireParam(params, "installId"), input.limit, input.offset);
258
- }), "getInstallationHistory")
259
- }), "createAnalyticsRouteHandlers");
260
- var registerAnalyticsClientRoutes = /* @__PURE__ */ __name((add) => {
261
- add("POST", "/events", "appendBundleEvent");
262
- }, "registerAnalyticsClientRoutes");
263
- var registerAnalyticsAdminRoutes = /* @__PURE__ */ __name((add) => {
264
- add("GET", "/bundles/:id/events/summary", "getBundleEventSummary");
265
- add("GET", "/bundles/:id/events/analytics", "getBundleEventAnalytics");
266
- add("GET", "/installations/overview", "getBundleEventOverview");
267
- add("GET", "/installations/active", "getActiveInstallationOverview");
268
- add("GET", "/installations", "searchInstallations");
269
- add("GET", "/installations/:installId/events", "getInstallationHistory");
270
- }, "registerAnalyticsAdminRoutes");
271
-
272
16
  // ../../packages/server/dist/handlerErrors.mjs
273
17
  var HandlerBadRequestError = class extends Error {
274
18
  static {
@@ -986,7 +730,7 @@ var databaseFields = {
986
730
  };
987
731
 
988
732
  // ../plugin-core/dist/databasePluginCrudValidationFields.mjs
989
- var isRecord2 = /* @__PURE__ */ __name((value) => typeof value === "object" && value !== null && !Array.isArray(value), "isRecord");
733
+ var isRecord = /* @__PURE__ */ __name((value) => typeof value === "object" && value !== null && !Array.isArray(value), "isRecord");
990
734
  var isChannelText2 = /* @__PURE__ */ __name((value) => {
991
735
  if (typeof value !== "string" || value.length === 0) return false;
992
736
  let codePointCount = 0;
@@ -1272,7 +1016,7 @@ var hasValidReleaseInvariants = /* @__PURE__ */ __name((data) => {
1272
1016
  }, "hasValidReleaseInvariants");
1273
1017
  var hasValidBundleEventInvariants = /* @__PURE__ */ __name((data) => (data.type === "UPDATE_APPLIED" || data.type === "RECOVERED" || data.type === "RELEASE_ADOPTED") && typeof data.from_bundle_id === "string" && (data.update_strategy === "fingerprint" || data.update_strategy === "appVersion") || data.type === "UNCHANGED" && data.from_bundle_id === null && data.update_strategy === null, "hasValidBundleEventInvariants");
1274
1018
  var validateCreateData = /* @__PURE__ */ __name((model, data) => {
1275
- if (!isRecord2(data)) throw new DatabasePluginInputError("invalid-data");
1019
+ if (!isRecord(data)) throw new DatabasePluginInputError("invalid-data");
1276
1020
  validateFields(model, Object.keys(data));
1277
1021
  for (const field of databaseFields[model]) {
1278
1022
  const validator = modelValidators[model][field];
@@ -1289,7 +1033,7 @@ var selectRow = /* @__PURE__ */ __name((row, input) => {
1289
1033
  return Object.fromEntries(select.map((field) => [field, Reflect.get(row, field)]));
1290
1034
  }, "selectRow");
1291
1035
  var validateResult = /* @__PURE__ */ __name((model, row, select) => {
1292
- if (!isRecord2(row)) throw new DatabasePluginInputError("invalid-result");
1036
+ if (!isRecord(row)) throw new DatabasePluginInputError("invalid-result");
1293
1037
  const fields = select ?? databaseFields[model];
1294
1038
  for (const field of fields) {
1295
1039
  const validator = modelValidators[model][field];
@@ -1317,10 +1061,10 @@ var validateMutationWhere = /* @__PURE__ */ __name((where) => {
1317
1061
  var validateUpdateWhere = /* @__PURE__ */ __name((model, where) => {
1318
1062
  const selector = where[0];
1319
1063
  const primaryField = model === "release_catalogs" ? "scope_key" : "id";
1320
- if (where.length !== 1 || !isRecord2(selector) || selector.field !== primaryField || selector.operator !== void 0 && selector.operator !== "eq" || typeof selector.value !== "string" || selector.connector !== void 0 || selector.mode !== void 0) throw new DatabasePluginInputError("invalid-update-selector");
1064
+ if (where.length !== 1 || !isRecord(selector) || selector.field !== primaryField || selector.operator !== void 0 && selector.operator !== "eq" || typeof selector.value !== "string" || selector.connector !== void 0 || selector.mode !== void 0) throw new DatabasePluginInputError("invalid-update-selector");
1321
1065
  }, "validateUpdateWhere");
1322
1066
  var validateBundleUpdateData = /* @__PURE__ */ __name((update) => {
1323
- if (!isRecord2(update)) throw new DatabasePluginInputError("invalid-data");
1067
+ if (!isRecord(update)) throw new DatabasePluginInputError("invalid-data");
1324
1068
  for (const [field, value] of Object.entries(update)) {
1325
1069
  if (field === "id") throw new DatabasePluginInputError("invalid-data");
1326
1070
  validateField("bundles", field);
@@ -1329,7 +1073,7 @@ var validateBundleUpdateData = /* @__PURE__ */ __name((update) => {
1329
1073
  }
1330
1074
  }, "validateBundleUpdateData");
1331
1075
  var validateApiKeyUpdateData = /* @__PURE__ */ __name((update) => {
1332
- if (!isRecord2(update) || Reflect.ownKeys(update).length !== 1 || !Object.hasOwn(update, "revoked_at_ms") || !modelValidators.api_keys.revoked_at_ms(Reflect.get(update, "revoked_at_ms"))) throw new DatabasePluginInputError("invalid-data");
1076
+ if (!isRecord(update) || Reflect.ownKeys(update).length !== 1 || !Object.hasOwn(update, "revoked_at_ms") || !modelValidators.api_keys.revoked_at_ms(Reflect.get(update, "revoked_at_ms"))) throw new DatabasePluginInputError("invalid-data");
1333
1077
  }, "validateApiKeyUpdateData");
1334
1078
  var RELEASE_MUTABLE_FIELDS = /* @__PURE__ */ new Set([
1335
1079
  "revision",
@@ -1344,7 +1088,7 @@ var RELEASE_MUTABLE_FIELDS = /* @__PURE__ */ new Set([
1344
1088
  "updated_at_ms"
1345
1089
  ]);
1346
1090
  var validateReleaseUpdateData = /* @__PURE__ */ __name((update) => {
1347
- if (!isRecord2(update) || Reflect.ownKeys(update).length === 0) throw new DatabasePluginInputError("invalid-data");
1091
+ if (!isRecord(update) || Reflect.ownKeys(update).length === 0) throw new DatabasePluginInputError("invalid-data");
1348
1092
  for (const [field, value] of Object.entries(update)) {
1349
1093
  if (!RELEASE_MUTABLE_FIELDS.has(field)) throw new DatabasePluginInputError("invalid-data");
1350
1094
  const validator = modelValidators.releases[field];
@@ -1352,7 +1096,7 @@ var validateReleaseUpdateData = /* @__PURE__ */ __name((update) => {
1352
1096
  }
1353
1097
  }, "validateReleaseUpdateData");
1354
1098
  var validateReleaseCatalogUpdateData = /* @__PURE__ */ __name((update) => {
1355
- if (!isRecord2(update)) throw new DatabasePluginInputError("invalid-data");
1099
+ if (!isRecord(update)) throw new DatabasePluginInputError("invalid-data");
1356
1100
  const expectedFields = Object.keys(modelValidators.release_catalogs).filter((field) => field !== "scope_key");
1357
1101
  if (Reflect.ownKeys(update).length !== expectedFields.length || expectedFields.some((field) => !Object.hasOwn(update, field))) throw new DatabasePluginInputError("invalid-data");
1358
1102
  for (const [field, value] of Object.entries(update)) {
@@ -1433,7 +1177,7 @@ var validateWhere = /* @__PURE__ */ __name((model, where) => {
1433
1177
  if (where === void 0) return;
1434
1178
  if (!Array.isArray(where)) throw new DatabasePluginInputError("invalid-query");
1435
1179
  for (const item of where) {
1436
- if (!isRecord2(item)) throw new DatabasePluginInputError("invalid-query");
1180
+ if (!isRecord(item)) throw new DatabasePluginInputError("invalid-query");
1437
1181
  if (item.connector !== void 0 && item.connector !== "AND" && item.connector !== "OR") throw new DatabasePluginInputError("invalid-query");
1438
1182
  validateWhereValue(model, item);
1439
1183
  }
@@ -1450,7 +1194,7 @@ var validateOrderBy = /* @__PURE__ */ __name((model, orderBy) => {
1450
1194
  if (!Array.isArray(orderBy) || orderBy.length === 0) throw new DatabasePluginInputError("invalid-query");
1451
1195
  const fields = /* @__PURE__ */ new Set();
1452
1196
  return orderBy.map((clause) => {
1453
- if (!isRecord2(clause) || typeof clause.field !== "string") throw new DatabasePluginInputError("invalid-query");
1197
+ if (!isRecord(clause) || typeof clause.field !== "string") throw new DatabasePluginInputError("invalid-query");
1454
1198
  validateField(model, clause.field);
1455
1199
  if (!sortableFields[model].has(clause.field)) throw new DatabasePluginInputError("invalid-query");
1456
1200
  if (clause.direction !== "asc" && clause.direction !== "desc") throw new DatabasePluginInputError("invalid-query");
@@ -1462,7 +1206,7 @@ var validateOrderBy = /* @__PURE__ */ __name((model, orderBy) => {
1462
1206
  }, "validateOrderBy");
1463
1207
  var validateDistinctOn = /* @__PURE__ */ __name((model, distinctOn, orderBy) => {
1464
1208
  if (distinctOn === void 0) return;
1465
- if (!isRecord2(distinctOn)) throw new DatabasePluginInputError("invalid-distinct");
1209
+ if (!isRecord(distinctOn)) throw new DatabasePluginInputError("invalid-distinct");
1466
1210
  const fields = validateDistinctFields(model, distinctOn.fields);
1467
1211
  if (fields === void 0 || orderBy === void 0) throw new DatabasePluginInputError("invalid-distinct");
1468
1212
  for (const [index, field] of fields.entries()) if (orderBy[index]?.field !== field) throw new DatabasePluginInputError("invalid-distinct");
@@ -1476,7 +1220,7 @@ var validateBundlePagination = /* @__PURE__ */ __name((options) => {
1476
1220
  if (!Number.isSafeInteger(options.limit) || options.limit <= 0 || options.page !== void 0 && (!Number.isSafeInteger(options.page) || options.page <= 0) || options.page !== void 0 && options.cursor !== void 0) throw new DatabasePluginInputError("invalid-pagination");
1477
1221
  const cursor = options.cursor;
1478
1222
  if (cursor === void 0) return;
1479
- if (!isRecord2(cursor)) throw new DatabasePluginInputError("invalid-pagination");
1223
+ if (!isRecord(cursor)) throw new DatabasePluginInputError("invalid-pagination");
1480
1224
  const hasAfter = Object.hasOwn(cursor, "after");
1481
1225
  if (hasAfter === Object.hasOwn(cursor, "before")) throw new DatabasePluginInputError("invalid-pagination");
1482
1226
  const value = hasAfter ? cursor.after : cursor.before;
@@ -1832,7 +1576,7 @@ var applyChange = /* @__PURE__ */ __name(async (database, change, changeIndex) =
1832
1576
  }
1833
1577
  return;
1834
1578
  }
1835
- case "analytics":
1579
+ case "insights":
1836
1580
  await database.create({
1837
1581
  model: "bundle_events",
1838
1582
  data: change.row
@@ -1910,10 +1654,10 @@ var hasOnlyKeys = /* @__PURE__ */ __name((value, keys) => {
1910
1654
  return Reflect.ownKeys(value).length === keys.length && keys.every((key) => Object.hasOwn(value, key));
1911
1655
  }, "hasOnlyKeys");
1912
1656
  var validateWhere2 = /* @__PURE__ */ __name((where, field, validateValue = (value) => typeof value === "string") => {
1913
- if (!isRecord2(where) || !hasOnlyKeys(where, [field]) || !validateValue(Reflect.get(where, field))) throw new DatabasePluginInputError("invalid-data");
1657
+ if (!isRecord(where) || !hasOnlyKeys(where, [field]) || !validateValue(Reflect.get(where, field))) throw new DatabasePluginInputError("invalid-data");
1914
1658
  }, "validateWhere");
1915
1659
  var validateDatabaseChange = /* @__PURE__ */ __name((change) => {
1916
- if (!isRecord2(change)) throw new DatabasePluginInputError("invalid-data");
1660
+ if (!isRecord(change)) throw new DatabasePluginInputError("invalid-data");
1917
1661
  switch (change.model) {
1918
1662
  case "bundles":
1919
1663
  switch (change.operation) {
@@ -2028,7 +1772,7 @@ var validateDatabaseChange = /* @__PURE__ */ __name((change) => {
2028
1772
  default:
2029
1773
  throw new DatabasePluginInputError("invalid-operation");
2030
1774
  }
2031
- case "analytics":
1775
+ case "insights":
2032
1776
  if (change.operation !== "insert" || !hasOnlyKeys(change, [
2033
1777
  "model",
2034
1778
  "operation",
@@ -2055,7 +1799,7 @@ var validateDatabaseChange = /* @__PURE__ */ __name((change) => {
2055
1799
  "update"
2056
1800
  ])) throw new DatabasePluginInputError("invalid-data");
2057
1801
  validateWhere2(change.where, "id");
2058
- if (!isRecord2(change.update)) throw new DatabasePluginInputError("invalid-data");
1802
+ if (!isRecord(change.update)) throw new DatabasePluginInputError("invalid-data");
2059
1803
  if (!hasOnlyKeys(change.update, ["revokedAtMs"])) throw new DatabasePluginInputError("invalid-data");
2060
1804
  validateApiKeyUpdateData({ revoked_at_ms: change.update.revokedAtMs });
2061
1805
  return;
@@ -2067,7 +1811,7 @@ var validateDatabaseChange = /* @__PURE__ */ __name((change) => {
2067
1811
  }
2068
1812
  }, "validateDatabaseChange");
2069
1813
  var validateDatabaseCommitExpectation = /* @__PURE__ */ __name((expectation) => {
2070
- if (!isRecord2(expectation)) throw new DatabasePluginInputError("invalid-data");
1814
+ if (!isRecord(expectation)) throw new DatabasePluginInputError("invalid-data");
2071
1815
  if (expectation.model === "releases") {
2072
1816
  if (!hasOnlyKeys(expectation, [
2073
1817
  "model",
@@ -2087,7 +1831,7 @@ var validateDatabaseCommitExpectation = /* @__PURE__ */ __name((expectation) =>
2087
1831
  throw new DatabasePluginInputError("invalid-model");
2088
1832
  }, "validateDatabaseCommitExpectation");
2089
1833
  function validateDatabaseCommit(input) {
2090
- if (!isRecord2(input) || !Array.isArray(input.changes) || (Object.hasOwn(input, "expectations") ? !hasOnlyKeys(input, ["changes", "expectations"]) || !Array.isArray(input.expectations) : !hasOnlyKeys(input, ["changes"]))) throw new DatabasePluginInputError("invalid-data");
1834
+ if (!isRecord(input) || !Array.isArray(input.changes) || (Object.hasOwn(input, "expectations") ? !hasOnlyKeys(input, ["changes", "expectations"]) || !Array.isArray(input.expectations) : !hasOnlyKeys(input, ["changes"]))) throw new DatabasePluginInputError("invalid-data");
2091
1835
  input.changes.forEach(validateDatabaseChange);
2092
1836
  if (Array.isArray(input.expectations)) input.expectations.forEach(validateDatabaseCommitExpectation);
2093
1837
  }
@@ -2307,7 +2051,7 @@ var createDatabasePluginAdapter = /* @__PURE__ */ __name((name, implementation)
2307
2051
  return result;
2308
2052
  }
2309
2053
  },
2310
- analytics: {
2054
+ insights: {
2311
2055
  async append(row) {
2312
2056
  await crud.create({
2313
2057
  model: "bundle_events",
@@ -4012,7 +3756,7 @@ var createReleaseCatalogRouteHandlers = /* @__PURE__ */ __name((clientAccessHead
4012
3756
 
4013
3757
  // ../../packages/server/dist/handlerReleaseManagementRoutes.mjs
4014
3758
  var unavailable = /* @__PURE__ */ __name(() => Response.json({ error: "Not found" }, { status: 404 }), "unavailable");
4015
- var isRecord3 = /* @__PURE__ */ __name((value) => typeof value === "object" && value !== null && !Array.isArray(value), "isRecord");
3759
+ var isRecord2 = /* @__PURE__ */ __name((value) => typeof value === "object" && value !== null && !Array.isArray(value), "isRecord");
4016
3760
  var parseRevision = /* @__PURE__ */ __name((value) => {
4017
3761
  if (value === void 0 || value === null || value === "") return void 0;
4018
3762
  const revision = typeof value === "number" ? value : Number(value);
@@ -4021,7 +3765,7 @@ var parseRevision = /* @__PURE__ */ __name((value) => {
4021
3765
  }, "parseRevision");
4022
3766
  var parsePolicyInput = /* @__PURE__ */ __name(async (request) => {
4023
3767
  const body = await request.json();
4024
- if (!isRecord3(body) || !isRecord3(body.patch)) throw new HandlerBadRequestError("Invalid Release policy mutation");
3768
+ if (!isRecord2(body) || !isRecord2(body.patch)) throw new HandlerBadRequestError("Invalid Release policy mutation");
4025
3769
  return {
4026
3770
  expectedRevision: parseRevision(body.expectedRevision),
4027
3771
  patch: body.patch
@@ -4130,6 +3874,262 @@ var createReleaseManagementRouteHandlers = /* @__PURE__ */ __name(() => ({
4130
3874
  }, "commitDatabase")
4131
3875
  }), "createReleaseManagementRouteHandlers");
4132
3876
 
3877
+ // ../../packages/server/dist/insights/errors.mjs
3878
+ var InsightsScanLimitExceededError = class extends Error {
3879
+ static {
3880
+ __name(this, "InsightsScanLimitExceededError");
3881
+ }
3882
+ constructor(limit) {
3883
+ super(`Insights event scan exceeded ${limit} rows.`);
3884
+ this.limit = limit;
3885
+ this.name = "InsightsScanLimitExceededError";
3886
+ }
3887
+ };
3888
+ var InsightsBadRequestError = class extends Error {
3889
+ static {
3890
+ __name(this, "InsightsBadRequestError");
3891
+ }
3892
+ name = "InsightsBadRequestError";
3893
+ };
3894
+ var InsightsPayloadTooLargeError = class extends Error {
3895
+ static {
3896
+ __name(this, "InsightsPayloadTooLargeError");
3897
+ }
3898
+ name = "InsightsPayloadTooLargeError";
3899
+ constructor(maximumBytes) {
3900
+ super(`Event payload exceeds ${maximumBytes} bytes`);
3901
+ this.maximumBytes = maximumBytes;
3902
+ }
3903
+ };
3904
+
3905
+ // ../../packages/server/dist/insights/eventInput.mjs
3906
+ var MAX_EVENT_STRING_LENGTH = 1024;
3907
+ var EVENT_BODY_MAX_BYTES = 16 * 1024;
3908
+ var eventKeys = /* @__PURE__ */ new Set([
3909
+ "type",
3910
+ "installId",
3911
+ "toBundleId",
3912
+ "userId",
3913
+ "username",
3914
+ "platform",
3915
+ "appVersion",
3916
+ "channel",
3917
+ "cohort",
3918
+ "fingerprintHash",
3919
+ "fromBundleId",
3920
+ "fromReleaseId",
3921
+ "toReleaseId",
3922
+ "updateStrategy",
3923
+ "sdkVersion"
3924
+ ]);
3925
+ function isRecord3(value) {
3926
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3927
+ }
3928
+ __name(isRecord3, "isRecord");
3929
+ function requireStringField(payload, key) {
3930
+ const value = payload[key];
3931
+ if (typeof value !== "string" || value.length === 0 || value.length > MAX_EVENT_STRING_LENGTH) throw new InsightsBadRequestError(`Invalid event field: ${key}`);
3932
+ return value;
3933
+ }
3934
+ __name(requireStringField, "requireStringField");
3935
+ function requireNullableStringField(payload, key) {
3936
+ if (payload[key] === null) return null;
3937
+ return requireStringField(payload, key);
3938
+ }
3939
+ __name(requireNullableStringField, "requireNullableStringField");
3940
+ async function readBoundedText(request) {
3941
+ const contentLength = request.headers.get("content-length");
3942
+ const declaredByteLength = Number(contentLength);
3943
+ if (contentLength !== null && Number.isSafeInteger(declaredByteLength) && declaredByteLength > 16384) throw new InsightsPayloadTooLargeError(EVENT_BODY_MAX_BYTES);
3944
+ if (request.body === null) return "";
3945
+ const reader = request.body.getReader();
3946
+ const decoder = new TextDecoder();
3947
+ let byteLength = 0;
3948
+ let text = "";
3949
+ while (true) {
3950
+ const result = await reader.read();
3951
+ if (result.done) break;
3952
+ byteLength += result.value.byteLength;
3953
+ if (byteLength > 16384) {
3954
+ await reader.cancel();
3955
+ throw new InsightsPayloadTooLargeError(EVENT_BODY_MAX_BYTES);
3956
+ }
3957
+ text += decoder.decode(result.value, { stream: true });
3958
+ }
3959
+ return text + decoder.decode();
3960
+ }
3961
+ __name(readBoundedText, "readBoundedText");
3962
+ async function parseJson(request) {
3963
+ const text = await readBoundedText(request);
3964
+ try {
3965
+ return JSON.parse(text);
3966
+ } catch (error) {
3967
+ if (error instanceof SyntaxError) throw new InsightsBadRequestError("Invalid event payload");
3968
+ throw error;
3969
+ }
3970
+ }
3971
+ __name(parseJson, "parseJson");
3972
+ function requireEvent(payload) {
3973
+ if (!isRecord3(payload) || Object.keys(payload).some((key) => !eventKeys.has(key))) throw new InsightsBadRequestError("Invalid event payload");
3974
+ const platform = requireStringField(payload, "platform");
3975
+ if (platform !== "ios" && platform !== "android") throw new InsightsBadRequestError("Invalid event field: platform");
3976
+ const base = {
3977
+ installId: requireStringField(payload, "installId"),
3978
+ toBundleId: requireStringField(payload, "toBundleId"),
3979
+ ...payload.userId === void 0 ? {} : { userId: requireStringField(payload, "userId") },
3980
+ ...payload.username === void 0 ? {} : { username: requireStringField(payload, "username") },
3981
+ platform,
3982
+ appVersion: requireStringField(payload, "appVersion"),
3983
+ channel: requireStringField(payload, "channel"),
3984
+ cohort: requireStringField(payload, "cohort"),
3985
+ fingerprintHash: requireNullableStringField(payload, "fingerprintHash"),
3986
+ sdkVersion: payload.sdkVersion === void 0 ? null : requireNullableStringField(payload, "sdkVersion"),
3987
+ fromReleaseId: requireNullableStringField(payload, "fromReleaseId"),
3988
+ toReleaseId: requireNullableStringField(payload, "toReleaseId")
3989
+ };
3990
+ const type = requireStringField(payload, "type");
3991
+ switch (type) {
3992
+ case "UPDATE_APPLIED":
3993
+ case "RECOVERED":
3994
+ case "RELEASE_ADOPTED": {
3995
+ const updateStrategy = requireStringField(payload, "updateStrategy");
3996
+ if (updateStrategy !== "fingerprint" && updateStrategy !== "appVersion") throw new InsightsBadRequestError("Invalid event field: updateStrategy");
3997
+ return {
3998
+ ...base,
3999
+ type,
4000
+ fromBundleId: requireStringField(payload, "fromBundleId"),
4001
+ updateStrategy
4002
+ };
4003
+ }
4004
+ case "UNCHANGED":
4005
+ if (payload.fromBundleId !== null || payload.updateStrategy !== null) throw new InsightsBadRequestError("Invalid unchanged event shape");
4006
+ return {
4007
+ ...base,
4008
+ type,
4009
+ fromBundleId: null,
4010
+ updateStrategy: null
4011
+ };
4012
+ default:
4013
+ throw new InsightsBadRequestError("Invalid event field: type");
4014
+ }
4015
+ }
4016
+ __name(requireEvent, "requireEvent");
4017
+ async function parseBundleEventRequest(request) {
4018
+ return requireEvent(await parseJson(request));
4019
+ }
4020
+ __name(parseBundleEventRequest, "parseBundleEventRequest");
4021
+
4022
+ // ../../packages/server/dist/insights/queryInput.mjs
4023
+ var EVENT_LIST_BOUNDS = {
4024
+ defaultValue: 50,
4025
+ maximum: 100,
4026
+ minimum: 1
4027
+ };
4028
+ var EVENT_LIST_OFFSET_BOUNDS = {
4029
+ defaultValue: 0,
4030
+ minimum: 0
4031
+ };
4032
+ var MAX_USER_ID_LENGTH = 1024;
4033
+ function parseInteger(url, key, bounds) {
4034
+ const value = url.searchParams.get(key);
4035
+ if (value === null) return bounds.defaultValue;
4036
+ const parsed = Number(value);
4037
+ if (!Number.isSafeInteger(parsed) || parsed < bounds.minimum || bounds.maximum !== void 0 && parsed > bounds.maximum) throw new InsightsBadRequestError(`Invalid '${key}' query parameter.`);
4038
+ return parsed;
4039
+ }
4040
+ __name(parseInteger, "parseInteger");
4041
+ var parsePagination = /* @__PURE__ */ __name((request) => {
4042
+ const url = new URL(request.url);
4043
+ return {
4044
+ limit: parseInteger(url, "limit", EVENT_LIST_BOUNDS),
4045
+ offset: parseInteger(url, "offset", EVENT_LIST_OFFSET_BOUNDS)
4046
+ };
4047
+ }, "parsePagination");
4048
+ var parseInsightsQuery = /* @__PURE__ */ __name((request) => {
4049
+ const window = new URL(request.url).searchParams.get("window") ?? "24h";
4050
+ if (window !== "24h" && window !== "7d" && window !== "30d" && window !== "all") throw new InsightsBadRequestError("Invalid 'window' query parameter.");
4051
+ return {
4052
+ ...parsePagination(request),
4053
+ window
4054
+ };
4055
+ }, "parseInsightsQuery");
4056
+ var parseActiveInstallationInput = /* @__PURE__ */ __name((request) => {
4057
+ const url = new URL(request.url);
4058
+ const windows = url.searchParams.getAll("window");
4059
+ if (windows.length > 1) throw new InsightsBadRequestError("Duplicate 'window' query parameter.");
4060
+ const window = windows[0] ?? "30d";
4061
+ if (window !== "24h" && window !== "7d" && window !== "30d") throw new InsightsBadRequestError("Invalid 'window' query parameter.");
4062
+ const userIds = url.searchParams.getAll("userId");
4063
+ if (userIds.length > 1) throw new InsightsBadRequestError("Duplicate 'userId' query parameter.");
4064
+ const userId = userIds[0];
4065
+ if (userId !== void 0 && (userId.length === 0 || userId.length > MAX_USER_ID_LENGTH)) throw new InsightsBadRequestError("Invalid 'userId' query parameter.");
4066
+ return userId === void 0 ? { window } : {
4067
+ window,
4068
+ userId
4069
+ };
4070
+ }, "parseActiveInstallationInput");
4071
+ var parseSearchInput = /* @__PURE__ */ __name((request) => ({
4072
+ ...parsePagination(request),
4073
+ query: new URL(request.url).searchParams.get("query")?.trim() ?? ""
4074
+ }), "parseSearchInput");
4075
+
4076
+ // ../../packages/server/dist/insights/routes.mjs
4077
+ var json = /* @__PURE__ */ __name((body, status) => Response.json(body, {
4078
+ headers: { "cache-control": "private, no-store" },
4079
+ status
4080
+ }), "json");
4081
+ var requireParam = /* @__PURE__ */ __name((params, key) => {
4082
+ const value = params[key];
4083
+ if (value === void 0 || value.length === 0) throw new InsightsBadRequestError(`Missing route parameter: ${key}`);
4084
+ return value;
4085
+ }, "requireParam");
4086
+ var run = /* @__PURE__ */ __name(async (operation) => {
4087
+ try {
4088
+ return await operation();
4089
+ } catch (error) {
4090
+ if (error instanceof InsightsBadRequestError) return json({ error: error.message }, 400);
4091
+ if (error instanceof InsightsPayloadTooLargeError) return json({ error: error.message }, 413);
4092
+ if (error instanceof InsightsScanLimitExceededError) return json({ error: {
4093
+ code: "INSIGHTS_SCAN_LIMIT_EXCEEDED",
4094
+ limit: error.limit
4095
+ } }, 503);
4096
+ throw error;
4097
+ }
4098
+ }, "run");
4099
+ var query = /* @__PURE__ */ __name((operation) => run(async () => json(await operation(), 200)), "query");
4100
+ var createInsightsRouteHandlers = /* @__PURE__ */ __name((provider) => ({
4101
+ appendBundleEvent: /* @__PURE__ */ __name(async (_params, request) => run(async () => {
4102
+ await provider.appendBundleEvent(await parseBundleEventRequest(request));
4103
+ return new Response(null, { status: 204 });
4104
+ }), "appendBundleEvent"),
4105
+ getBundleEventSummary: /* @__PURE__ */ __name((params) => query(() => provider.getBundleEventSummary(requireParam(params, "id"))), "getBundleEventSummary"),
4106
+ getBundleEventInsights: /* @__PURE__ */ __name((params, request) => query(() => {
4107
+ const input = parseInsightsQuery(request);
4108
+ return provider.getBundleEventInsights(requireParam(params, "id"), input.window, input.limit, input.offset);
4109
+ }), "getBundleEventInsights"),
4110
+ getBundleEventOverview: /* @__PURE__ */ __name(() => query(() => provider.getBundleEventOverview()), "getBundleEventOverview"),
4111
+ getActiveInstallationOverview: /* @__PURE__ */ __name((_params, request) => query(() => provider.getActiveInstallationOverview(parseActiveInstallationInput(request))), "getActiveInstallationOverview"),
4112
+ searchInstallations: /* @__PURE__ */ __name((_params, request) => query(() => {
4113
+ const input = parseSearchInput(request);
4114
+ return provider.searchInstallations(input.query, input.limit, input.offset);
4115
+ }), "searchInstallations"),
4116
+ getInstallationHistory: /* @__PURE__ */ __name((params, request) => query(() => {
4117
+ const input = parsePagination(request);
4118
+ return provider.getInstallationHistory(requireParam(params, "installId"), input.limit, input.offset);
4119
+ }), "getInstallationHistory")
4120
+ }), "createInsightsRouteHandlers");
4121
+ var registerInsightsClientRoutes = /* @__PURE__ */ __name((add) => {
4122
+ add("POST", "/events", "appendBundleEvent");
4123
+ }, "registerInsightsClientRoutes");
4124
+ var registerInsightsAdminRoutes = /* @__PURE__ */ __name((add) => {
4125
+ add("GET", "/bundles/:id/events/summary", "getBundleEventSummary");
4126
+ add("GET", "/bundles/:id/events/insights", "getBundleEventInsights");
4127
+ add("GET", "/installations/overview", "getBundleEventOverview");
4128
+ add("GET", "/installations/active", "getActiveInstallationOverview");
4129
+ add("GET", "/installations", "searchInstallations");
4130
+ add("GET", "/installations/:installId/events", "getInstallationHistory");
4131
+ }, "registerInsightsAdminRoutes");
4132
+
4133
4133
  // ../../packages/server/dist/internalRouter.mjs
4134
4134
  var normalizePath = /* @__PURE__ */ __name((path) => {
4135
4135
  if (!path) return "/";
@@ -4239,13 +4239,13 @@ var createDownloadStorageRouteHandler = /* @__PURE__ */ __name((downloadStorageO
4239
4239
  statusText: response.statusText
4240
4240
  });
4241
4241
  }, "createDownloadStorageRouteHandler");
4242
- function createHotUpdaterHandlers(api, analytics, apiKeyAuth, downloadStorageObject) {
4242
+ function createHotUpdaterHandlers(api, insights, apiKeyAuth, downloadStorageObject) {
4243
4243
  const routeHandlers = {
4244
4244
  ...createVersionRouteHandlers(),
4245
4245
  ...createReleaseCatalogRouteHandlers(apiKeyAuth?.headerName),
4246
4246
  ...createReleaseManagementRouteHandlers(),
4247
4247
  ...createBundleRouteHandlers(),
4248
- ...analytics === void 0 ? {} : createAnalyticsRouteHandlers(analytics),
4248
+ ...insights === void 0 ? {} : createInsightsRouteHandlers(insights),
4249
4249
  ...downloadStorageObject === void 0 ? {} : { downloadStorageObject: createDownloadStorageRouteHandler(downloadStorageObject) }
4250
4250
  };
4251
4251
  const clientRouter = createRouter();
@@ -4255,7 +4255,7 @@ function createHotUpdaterHandlers(api, analytics, apiKeyAuth, downloadStorageObj
4255
4255
  addClientRoute("GET", "/release-catalogs/app-version/:platform/:channelKey/:appVersion", "appVersionReleaseCatalog");
4256
4256
  addClientRoute("GET", "/release-catalogs/fingerprint/:platform/:channelKey/:fingerprintHash", "fingerprintReleaseCatalog");
4257
4257
  addClientRoute("GET", "/artifacts/:targetBundleId/from/:currentBundleId", "artifact");
4258
- if (analytics !== void 0) registerAnalyticsClientRoutes(addClientRoute);
4258
+ if (insights !== void 0) registerInsightsClientRoutes(addClientRoute);
4259
4259
  const adminRouter = createRouter();
4260
4260
  const addAdminRoute = /* @__PURE__ */ __name((method, path, handler) => addRoute(adminRouter, method, path, handler), "addAdminRoute");
4261
4261
  addAdminRoute("GET", "/releases/:id", "getRelease");
@@ -4275,7 +4275,7 @@ function createHotUpdaterHandlers(api, analytics, apiKeyAuth, downloadStorageObj
4275
4275
  addAdminRoute("POST", "/bundles", "createBundles");
4276
4276
  addAdminRoute("PATCH", "/bundles/:id", "updateBundle");
4277
4277
  addAdminRoute("DELETE", "/bundles/:id", "deleteBundle");
4278
- if (analytics !== void 0) registerAnalyticsAdminRoutes(addAdminRoute);
4278
+ if (insights !== void 0) registerInsightsAdminRoutes(addAdminRoute);
4279
4279
  return Object.freeze({
4280
4280
  client: createRequestHandler({
4281
4281
  api,
@@ -4293,7 +4293,7 @@ function createHotUpdaterHandlers(api, analytics, apiKeyAuth, downloadStorageObj
4293
4293
  }
4294
4294
  __name(createHotUpdaterHandlers, "createHotUpdaterHandlers");
4295
4295
 
4296
- // ../../packages/server/dist/analytics/bounded/activeOverview.mjs
4296
+ // ../../packages/server/dist/insights/bounded/activeOverview.mjs
4297
4297
  var HOUR_MS = 3600 * 1e3;
4298
4298
  var DAY_MS = 24 * HOUR_MS;
4299
4299
  var ACTIVE_BUNDLE_EVENT_TYPES = [
@@ -4381,11 +4381,11 @@ function collectActiveInstallationOverview(request) {
4381
4381
  }
4382
4382
  __name(collectActiveInstallationOverview, "collectActiveInstallationOverview");
4383
4383
 
4384
- // ../../packages/server/dist/analytics/bounded/scan.mjs
4385
- var ANALYTICS_SCAN_MAX_ROWS = 5e4;
4386
- var ANALYTICS_MATERIALIZATION_LIMIT = ANALYTICS_SCAN_MAX_ROWS + 1;
4387
- var ANALYTICS_SCAN_PAGE_SIZE = 1e3;
4388
- var ANALYTICS_LOWER_BOUND_ID = "00000000-0000-0000-0000-000000000000";
4384
+ // ../../packages/server/dist/insights/bounded/scan.mjs
4385
+ var INSIGHTS_SCAN_MAX_ROWS = 5e4;
4386
+ var INSIGHTS_MATERIALIZATION_LIMIT = INSIGHTS_SCAN_MAX_ROWS + 1;
4387
+ var INSIGHTS_SCAN_PAGE_SIZE = 1e3;
4388
+ var INSIGHTS_LOWER_BOUND_ID = "00000000-0000-0000-0000-000000000000";
4389
4389
  var compareCodePoints2 = /* @__PURE__ */ __name((left, right) => {
4390
4390
  if (left < right) return -1;
4391
4391
  if (left > right) return 1;
@@ -4398,23 +4398,23 @@ var materializeEventRows = /* @__PURE__ */ __name(async (scope) => {
4398
4398
  const seenIds = /* @__PURE__ */ new Set();
4399
4399
  let after = scope.lowerBoundMs === void 0 ? void 0 : {
4400
4400
  receivedAtMs: scope.lowerBoundMs,
4401
- id: ANALYTICS_LOWER_BOUND_ID
4401
+ id: INSIGHTS_LOWER_BOUND_ID
4402
4402
  };
4403
- while (rows.length < ANALYTICS_MATERIALIZATION_LIMIT) {
4404
- const limit = Math.min(ANALYTICS_SCAN_PAGE_SIZE, ANALYTICS_MATERIALIZATION_LIMIT - rows.length);
4403
+ while (rows.length < INSIGHTS_MATERIALIZATION_LIMIT) {
4404
+ const limit = Math.min(INSIGHTS_SCAN_PAGE_SIZE, INSIGHTS_MATERIALIZATION_LIMIT - rows.length);
4405
4405
  const page = await scope.persistence.scan({
4406
4406
  beforeReceivedAtMs: scope.cutoffMs,
4407
4407
  ...after === void 0 ? {} : { after },
4408
4408
  limit
4409
4409
  });
4410
4410
  if (page.length === 0) break;
4411
- if (page.length > limit) throw new AnalyticsPersistenceOrderError();
4411
+ if (page.length > limit) throw new InsightsPersistenceOrderError();
4412
4412
  let previous = after === void 0 ? void 0 : {
4413
4413
  received_at_ms: after.receivedAtMs,
4414
4414
  id: after.id
4415
4415
  };
4416
4416
  for (const row of page) {
4417
- if (row.received_at_ms >= scope.cutoffMs || previous !== void 0 && compareEventOldest(previous, row) >= 0 || seenIds.has(row.id)) throw new AnalyticsPersistenceOrderError();
4417
+ if (row.received_at_ms >= scope.cutoffMs || previous !== void 0 && compareEventOldest(previous, row) >= 0 || seenIds.has(row.id)) throw new InsightsPersistenceOrderError();
4418
4418
  seenIds.add(row.id);
4419
4419
  rows.push(row);
4420
4420
  previous = row;
@@ -4424,17 +4424,17 @@ var materializeEventRows = /* @__PURE__ */ __name(async (scope) => {
4424
4424
  receivedAtMs: last.received_at_ms,
4425
4425
  id: last.id
4426
4426
  };
4427
- if (rows.length > 5e4) throw new AnalyticsScanLimitExceededError(ANALYTICS_SCAN_MAX_ROWS);
4427
+ if (rows.length > 5e4) throw new InsightsScanLimitExceededError(INSIGHTS_SCAN_MAX_ROWS);
4428
4428
  }
4429
4429
  return rows;
4430
4430
  }, "materializeEventRows");
4431
- var AnalyticsPersistenceOrderError = class extends Error {
4431
+ var InsightsPersistenceOrderError = class extends Error {
4432
4432
  static {
4433
- __name(this, "AnalyticsPersistenceOrderError");
4433
+ __name(this, "InsightsPersistenceOrderError");
4434
4434
  }
4435
- name = "AnalyticsPersistenceOrderError";
4435
+ name = "InsightsPersistenceOrderError";
4436
4436
  constructor() {
4437
- super("Analytics persistence did not advance its scan cursor.");
4437
+ super("Insights persistence did not advance its scan cursor.");
4438
4438
  }
4439
4439
  };
4440
4440
  var materializeActiveRows = /* @__PURE__ */ __name(async (scope, window) => {
@@ -4519,7 +4519,7 @@ var collectEventActivity = /* @__PURE__ */ __name((request) => {
4519
4519
  };
4520
4520
  }, "collectEventActivity");
4521
4521
 
4522
- // ../../packages/server/dist/analytics/bounded/installationSearch.mjs
4522
+ // ../../packages/server/dist/insights/bounded/installationSearch.mjs
4523
4523
  function toSearchRow(row) {
4524
4524
  return {
4525
4525
  installId: row.install_id,
@@ -4562,7 +4562,7 @@ function searchEventInstallations(request) {
4562
4562
  }
4563
4563
  __name(searchEventInstallations, "searchEventInstallations");
4564
4564
 
4565
- // ../../packages/server/dist/analytics/bounded/persistence.mjs
4565
+ // ../../packages/server/dist/insights/bounded/persistence.mjs
4566
4566
  function createBundleEventRow(input) {
4567
4567
  const base = {
4568
4568
  id: createUUIDv7(),
@@ -4601,7 +4601,7 @@ function createBundleEventRow(input) {
4601
4601
  }
4602
4602
  __name(createBundleEventRow, "createBundleEventRow");
4603
4603
 
4604
- // ../../packages/server/dist/analytics/bounded/provider.mjs
4604
+ // ../../packages/server/dist/insights/bounded/provider.mjs
4605
4605
  var isTransitionEventRow = /* @__PURE__ */ __name((row) => row.type === "UPDATE_APPLIED" || row.type === "RECOVERED", "isTransitionEventRow");
4606
4606
  var toHistoryRow = /* @__PURE__ */ __name((row) => ({
4607
4607
  id: row.id,
@@ -4619,7 +4619,7 @@ var toHistoryRow = /* @__PURE__ */ __name((row) => ({
4619
4619
  var isInstalledForBundle = /* @__PURE__ */ __name((row, bundleId) => row.type === "UPDATE_APPLIED" && row.to_bundle_id === bundleId, "isInstalledForBundle");
4620
4620
  var isRecoveredFromBundle = /* @__PURE__ */ __name((row, bundleId) => row.type === "RECOVERED" && row.from_bundle_id === bundleId, "isRecoveredFromBundle");
4621
4621
  var countDistinctInstallations = /* @__PURE__ */ __name((rows) => new Set(rows.map(({ install_id }) => install_id)).size, "countDistinctInstallations");
4622
- var getAnalyticsResult = /* @__PURE__ */ __name(async (persistence, bundleId, window, limit, offset) => {
4622
+ var getInsightsResult = /* @__PURE__ */ __name(async (persistence, bundleId, window, limit, offset) => {
4623
4623
  const scope = {
4624
4624
  persistence,
4625
4625
  cutoffMs: Date.now()
@@ -4660,8 +4660,8 @@ var getAnalyticsResult = /* @__PURE__ */ __name(async (persistence, bundleId, wi
4660
4660
  }
4661
4661
  }
4662
4662
  };
4663
- }, "getAnalyticsResult");
4664
- var getAnalyticsSummaries = /* @__PURE__ */ __name(async (persistence, bundleIds, window) => {
4663
+ }, "getInsightsResult");
4664
+ var getInsightsSummaries = /* @__PURE__ */ __name(async (persistence, bundleIds, window) => {
4665
4665
  const normalizedBundleIds = [...new Set(bundleIds)];
4666
4666
  if (normalizedBundleIds.length === 0) return [];
4667
4667
  const requestedBundleIds = new Set(normalizedBundleIds);
@@ -4684,10 +4684,10 @@ var getAnalyticsSummaries = /* @__PURE__ */ __name(async (persistence, bundleIds
4684
4684
  installed: installedByBundleId.get(bundleId)?.size ?? 0,
4685
4685
  recovered: recoveredByBundleId.get(bundleId)?.size ?? 0
4686
4686
  }));
4687
- }, "getAnalyticsSummaries");
4688
- var createAnalyticsProvider = /* @__PURE__ */ __name((persistence) => Object.freeze({
4687
+ }, "getInsightsSummaries");
4688
+ var createInsightsProvider = /* @__PURE__ */ __name((persistence) => Object.freeze({
4689
4689
  mode: "bounded",
4690
- maxMatchingRows: ANALYTICS_SCAN_MAX_ROWS,
4690
+ maxMatchingRows: INSIGHTS_SCAN_MAX_ROWS,
4691
4691
  async appendBundleEvent(input) {
4692
4692
  await persistence.append(createBundleEventRow(input));
4693
4693
  },
@@ -4702,10 +4702,10 @@ var createAnalyticsProvider = /* @__PURE__ */ __name((persistence) => Object.fre
4702
4702
  };
4703
4703
  },
4704
4704
  getBundleEventSummaries(bundleIds, window) {
4705
- return getAnalyticsSummaries(persistence, bundleIds, window);
4705
+ return getInsightsSummaries(persistence, bundleIds, window);
4706
4706
  },
4707
- getBundleEventAnalytics(bundleId, window, limit, offset) {
4708
- return getAnalyticsResult(persistence, bundleId, window, limit, offset);
4707
+ getBundleEventInsights(bundleId, window, limit, offset) {
4708
+ return getInsightsResult(persistence, bundleId, window, limit, offset);
4709
4709
  },
4710
4710
  async getBundleEventOverview() {
4711
4711
  const rows = await materializeEventRows({
@@ -4764,7 +4764,7 @@ var createAnalyticsProvider = /* @__PURE__ */ __name((persistence) => Object.fre
4764
4764
  }
4765
4765
  };
4766
4766
  }
4767
- }), "createAnalyticsProvider");
4767
+ }), "createInsightsProvider");
4768
4768
 
4769
4769
  // ../../packages/server/dist/apiKeys.mjs
4770
4770
  var API_KEY_HEADER_NAME = "x-api-key";
@@ -5297,7 +5297,7 @@ var sqlProviders = [
5297
5297
  var noSqlProviders = ["mongodb"];
5298
5298
  [...sqlProviders, ...noSqlProviders];
5299
5299
  function isDatabasePlugin(plugin) {
5300
- return typeof plugin === "object" && plugin !== null && "name" in plugin && typeof plugin.name === "string" && "models" in plugin && typeof plugin.models === "object" && plugin.models !== null && "bundles" in plugin.models && typeof plugin.models.bundles === "object" && plugin.models.bundles !== null && "findById" in plugin.models.bundles && typeof plugin.models.bundles.findById === "function" && "findMany" in plugin.models.bundles && typeof plugin.models.bundles.findMany === "function" && "count" in plugin.models.bundles && typeof plugin.models.bundles.count === "function" && "bundlePatches" in plugin.models && typeof plugin.models.bundlePatches === "object" && plugin.models.bundlePatches !== null && "findByBundleIds" in plugin.models.bundlePatches && typeof plugin.models.bundlePatches.findByBundleIds === "function" && "channels" in plugin.models && typeof plugin.models.channels === "object" && plugin.models.channels !== null && "insert" in plugin.models.channels && typeof plugin.models.channels.insert === "function" && "list" in plugin.models.channels && typeof plugin.models.channels.list === "function" && "delete" in plugin.models.channels && typeof plugin.models.channels.delete === "function" && "analytics" in plugin.models && typeof plugin.models.analytics === "object" && plugin.models.analytics !== null && "append" in plugin.models.analytics && typeof plugin.models.analytics.append === "function" && "scan" in plugin.models.analytics && typeof plugin.models.analytics.scan === "function" && "apiKeys" in plugin.models && typeof plugin.models.apiKeys === "object" && plugin.models.apiKeys !== null && "create" in plugin.models.apiKeys && typeof plugin.models.apiKeys.create === "function" && "findByHash" in plugin.models.apiKeys && typeof plugin.models.apiKeys.findByHash === "function" && "list" in plugin.models.apiKeys && typeof plugin.models.apiKeys.list === "function" && "revoke" in plugin.models.apiKeys && typeof plugin.models.apiKeys.revoke === "function" && "commit" in plugin && typeof plugin.commit === "function" && (!("dispose" in plugin) || plugin.dispose === void 0 || typeof plugin.dispose === "function");
5300
+ return typeof plugin === "object" && plugin !== null && "name" in plugin && typeof plugin.name === "string" && "models" in plugin && typeof plugin.models === "object" && plugin.models !== null && "bundles" in plugin.models && typeof plugin.models.bundles === "object" && plugin.models.bundles !== null && "findById" in plugin.models.bundles && typeof plugin.models.bundles.findById === "function" && "findMany" in plugin.models.bundles && typeof plugin.models.bundles.findMany === "function" && "count" in plugin.models.bundles && typeof plugin.models.bundles.count === "function" && "bundlePatches" in plugin.models && typeof plugin.models.bundlePatches === "object" && plugin.models.bundlePatches !== null && "findByBundleIds" in plugin.models.bundlePatches && typeof plugin.models.bundlePatches.findByBundleIds === "function" && "channels" in plugin.models && typeof plugin.models.channels === "object" && plugin.models.channels !== null && "insert" in plugin.models.channels && typeof plugin.models.channels.insert === "function" && "list" in plugin.models.channels && typeof plugin.models.channels.list === "function" && "delete" in plugin.models.channels && typeof plugin.models.channels.delete === "function" && "insights" in plugin.models && typeof plugin.models.insights === "object" && plugin.models.insights !== null && "append" in plugin.models.insights && typeof plugin.models.insights.append === "function" && "scan" in plugin.models.insights && typeof plugin.models.insights.scan === "function" && "apiKeys" in plugin.models && typeof plugin.models.apiKeys === "object" && plugin.models.apiKeys !== null && "create" in plugin.models.apiKeys && typeof plugin.models.apiKeys.create === "function" && "findByHash" in plugin.models.apiKeys && typeof plugin.models.apiKeys.findByHash === "function" && "list" in plugin.models.apiKeys && typeof plugin.models.apiKeys.list === "function" && "revoke" in plugin.models.apiKeys && typeof plugin.models.apiKeys.revoke === "function" && "commit" in plugin && typeof plugin.commit === "function" && (!("dispose" in plugin) || plugin.dispose === void 0 || typeof plugin.dispose === "function");
5301
5301
  }
5302
5302
  __name(isDatabasePlugin, "isDatabasePlugin");
5303
5303
 
@@ -5414,21 +5414,21 @@ function createHotUpdaterCore(options) {
5414
5414
  readStorageText
5415
5415
  });
5416
5416
  const clientAccess = normalizeClientAccess(options.clientAccess);
5417
- const analytics = createAnalyticsProvider({
5417
+ const insights = createInsightsProvider({
5418
5418
  async append(row) {
5419
5419
  await assertSchemaReady();
5420
- return plugin.models.analytics.append(row);
5420
+ return plugin.models.insights.append(row);
5421
5421
  },
5422
5422
  async scan(input) {
5423
5423
  await assertSchemaReady();
5424
- return plugin.models.analytics.scan(input);
5424
+ return plugin.models.insights.scan(input);
5425
5425
  }
5426
5426
  });
5427
5427
  const apiKeys = createApiKeyManagement({
5428
5428
  apiKeys: plugin.models.apiKeys,
5429
5429
  beforeOperation: assertSchemaReady
5430
5430
  });
5431
- const handlers = createHotUpdaterHandlers(core.api, analytics, clientAccess.type === "api-key" ? {
5431
+ const handlers = createHotUpdaterHandlers(core.api, insights, clientAccess.type === "api-key" ? {
5432
5432
  authenticate: /* @__PURE__ */ __name((request) => authenticateApiKey({
5433
5433
  apiKeys: plugin.models.apiKeys,
5434
5434
  beforeLookup: assertSchemaReady,
@@ -5439,7 +5439,7 @@ function createHotUpdaterCore(options) {
5439
5439
  } : void 0, downloadStorageObject);
5440
5440
  const api = Object.assign({
5441
5441
  adapterName: adapterCapabilities.adapterName ?? core.adapterName,
5442
- analytics,
5442
+ insights,
5443
5443
  apiKeys,
5444
5444
  handlers
5445
5445
  }, core.api);
@@ -8331,7 +8331,7 @@ var changeQuery = /* @__PURE__ */ __name((change, guard) => {
8331
8331
  ...guard.params
8332
8332
  ]
8333
8333
  };
8334
- case "analytics":
8334
+ case "insights":
8335
8335
  return insertQuery({ model: "bundle_events", data: change.row }, guard);
8336
8336
  case "apiKeys":
8337
8337
  return change.operation === "insert" ? insertQuery(