@hot-updater/firebase 1.0.0-rc.0 → 1.0.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,7 +6,7 @@ let firebase_admin_firestore = require("firebase-admin/firestore");
6
6
  let firebase_admin_storage = require("firebase-admin/storage");
7
7
  //#endregion
8
8
  //#region ../../packages/server/dist/version.mjs
9
- const HOT_UPDATER_SERVER_VERSION = "1.0.0-rc.0";
9
+ const HOT_UPDATER_SERVER_VERSION = "1.0.0-rc.1";
10
10
  //#endregion
11
11
  //#region ../../packages/server/dist/handlerVersionRoutes.mjs
12
12
  const createVersionRouteHandlers = () => ({ version: async () => Response.json({
@@ -14,244 +14,6 @@ const createVersionRouteHandlers = () => ({ version: async () => Response.json({
14
14
  version: HOT_UPDATER_SERVER_VERSION
15
15
  }) });
16
16
  //#endregion
17
- //#region ../../packages/server/dist/analytics/errors.mjs
18
- var AnalyticsScanLimitExceededError = class extends Error {
19
- constructor(limit) {
20
- super(`Analytics event scan exceeded ${limit} rows.`);
21
- this.limit = limit;
22
- this.name = "AnalyticsScanLimitExceededError";
23
- }
24
- };
25
- var AnalyticsBadRequestError = class extends Error {
26
- name = "AnalyticsBadRequestError";
27
- };
28
- var AnalyticsPayloadTooLargeError = class extends Error {
29
- name = "AnalyticsPayloadTooLargeError";
30
- constructor(maximumBytes) {
31
- super(`Event payload exceeds ${maximumBytes} bytes`);
32
- this.maximumBytes = maximumBytes;
33
- }
34
- };
35
- //#endregion
36
- //#region ../../packages/server/dist/analytics/eventInput.mjs
37
- const MAX_EVENT_STRING_LENGTH = 1024;
38
- const EVENT_BODY_MAX_BYTES = 16 * 1024;
39
- const eventKeys = new Set([
40
- "type",
41
- "installId",
42
- "toBundleId",
43
- "userId",
44
- "username",
45
- "platform",
46
- "appVersion",
47
- "channel",
48
- "cohort",
49
- "fingerprintHash",
50
- "fromBundleId",
51
- "fromReleaseId",
52
- "toReleaseId",
53
- "updateStrategy",
54
- "sdkVersion"
55
- ]);
56
- function isRecord$2(value) {
57
- return typeof value === "object" && value !== null && !Array.isArray(value);
58
- }
59
- function requireStringField(payload, key) {
60
- const value = payload[key];
61
- if (typeof value !== "string" || value.length === 0 || value.length > MAX_EVENT_STRING_LENGTH) throw new AnalyticsBadRequestError(`Invalid event field: ${key}`);
62
- return value;
63
- }
64
- function requireNullableStringField(payload, key) {
65
- if (payload[key] === null) return null;
66
- return requireStringField(payload, key);
67
- }
68
- async function readBoundedText(request) {
69
- const contentLength = request.headers.get("content-length");
70
- const declaredByteLength = Number(contentLength);
71
- if (contentLength !== null && Number.isSafeInteger(declaredByteLength) && declaredByteLength > 16384) throw new AnalyticsPayloadTooLargeError(EVENT_BODY_MAX_BYTES);
72
- if (request.body === null) return "";
73
- const reader = request.body.getReader();
74
- const decoder = new TextDecoder();
75
- let byteLength = 0;
76
- let text = "";
77
- while (true) {
78
- const result = await reader.read();
79
- if (result.done) break;
80
- byteLength += result.value.byteLength;
81
- if (byteLength > 16384) {
82
- await reader.cancel();
83
- throw new AnalyticsPayloadTooLargeError(EVENT_BODY_MAX_BYTES);
84
- }
85
- text += decoder.decode(result.value, { stream: true });
86
- }
87
- return text + decoder.decode();
88
- }
89
- async function parseJson(request) {
90
- const text = await readBoundedText(request);
91
- try {
92
- return JSON.parse(text);
93
- } catch (error) {
94
- if (error instanceof SyntaxError) throw new AnalyticsBadRequestError("Invalid event payload");
95
- throw error;
96
- }
97
- }
98
- function requireEvent(payload) {
99
- if (!isRecord$2(payload) || Object.keys(payload).some((key) => !eventKeys.has(key))) throw new AnalyticsBadRequestError("Invalid event payload");
100
- const platform = requireStringField(payload, "platform");
101
- if (platform !== "ios" && platform !== "android") throw new AnalyticsBadRequestError("Invalid event field: platform");
102
- const base = {
103
- installId: requireStringField(payload, "installId"),
104
- toBundleId: requireStringField(payload, "toBundleId"),
105
- ...payload.userId === void 0 ? {} : { userId: requireStringField(payload, "userId") },
106
- ...payload.username === void 0 ? {} : { username: requireStringField(payload, "username") },
107
- platform,
108
- appVersion: requireStringField(payload, "appVersion"),
109
- channel: requireStringField(payload, "channel"),
110
- cohort: requireStringField(payload, "cohort"),
111
- fingerprintHash: requireNullableStringField(payload, "fingerprintHash"),
112
- sdkVersion: payload.sdkVersion === void 0 ? null : requireNullableStringField(payload, "sdkVersion"),
113
- fromReleaseId: requireNullableStringField(payload, "fromReleaseId"),
114
- toReleaseId: requireNullableStringField(payload, "toReleaseId")
115
- };
116
- const type = requireStringField(payload, "type");
117
- switch (type) {
118
- case "UPDATE_APPLIED":
119
- case "RECOVERED":
120
- case "RELEASE_ADOPTED": {
121
- const updateStrategy = requireStringField(payload, "updateStrategy");
122
- if (updateStrategy !== "fingerprint" && updateStrategy !== "appVersion") throw new AnalyticsBadRequestError("Invalid event field: updateStrategy");
123
- return {
124
- ...base,
125
- type,
126
- fromBundleId: requireStringField(payload, "fromBundleId"),
127
- updateStrategy
128
- };
129
- }
130
- case "UNCHANGED":
131
- if (payload.fromBundleId !== null || payload.updateStrategy !== null) throw new AnalyticsBadRequestError("Invalid unchanged event shape");
132
- return {
133
- ...base,
134
- type,
135
- fromBundleId: null,
136
- updateStrategy: null
137
- };
138
- default: throw new AnalyticsBadRequestError("Invalid event field: type");
139
- }
140
- }
141
- async function parseBundleEventRequest(request) {
142
- return requireEvent(await parseJson(request));
143
- }
144
- //#endregion
145
- //#region ../../packages/server/dist/analytics/queryInput.mjs
146
- const EVENT_LIST_BOUNDS = {
147
- defaultValue: 50,
148
- maximum: 100,
149
- minimum: 1
150
- };
151
- const EVENT_LIST_OFFSET_BOUNDS = {
152
- defaultValue: 0,
153
- minimum: 0
154
- };
155
- const MAX_USER_ID_LENGTH = 1024;
156
- function parseInteger(url, key, bounds) {
157
- const value = url.searchParams.get(key);
158
- if (value === null) return bounds.defaultValue;
159
- const parsed = Number(value);
160
- if (!Number.isSafeInteger(parsed) || parsed < bounds.minimum || bounds.maximum !== void 0 && parsed > bounds.maximum) throw new AnalyticsBadRequestError(`Invalid '${key}' query parameter.`);
161
- return parsed;
162
- }
163
- const parsePagination = (request) => {
164
- const url = new URL(request.url);
165
- return {
166
- limit: parseInteger(url, "limit", EVENT_LIST_BOUNDS),
167
- offset: parseInteger(url, "offset", EVENT_LIST_OFFSET_BOUNDS)
168
- };
169
- };
170
- const parseAnalyticsQuery = (request) => {
171
- const window = new URL(request.url).searchParams.get("window") ?? "24h";
172
- if (window !== "24h" && window !== "7d" && window !== "30d" && window !== "all") throw new AnalyticsBadRequestError("Invalid 'window' query parameter.");
173
- return {
174
- ...parsePagination(request),
175
- window
176
- };
177
- };
178
- const parseActiveInstallationInput = (request) => {
179
- const url = new URL(request.url);
180
- const windows = url.searchParams.getAll("window");
181
- if (windows.length > 1) throw new AnalyticsBadRequestError("Duplicate 'window' query parameter.");
182
- const window = windows[0] ?? "30d";
183
- if (window !== "24h" && window !== "7d" && window !== "30d") throw new AnalyticsBadRequestError("Invalid 'window' query parameter.");
184
- const userIds = url.searchParams.getAll("userId");
185
- if (userIds.length > 1) throw new AnalyticsBadRequestError("Duplicate 'userId' query parameter.");
186
- const userId = userIds[0];
187
- if (userId !== void 0 && (userId.length === 0 || userId.length > MAX_USER_ID_LENGTH)) throw new AnalyticsBadRequestError("Invalid 'userId' query parameter.");
188
- return userId === void 0 ? { window } : {
189
- window,
190
- userId
191
- };
192
- };
193
- const parseSearchInput = (request) => ({
194
- ...parsePagination(request),
195
- query: new URL(request.url).searchParams.get("query")?.trim() ?? ""
196
- });
197
- //#endregion
198
- //#region ../../packages/server/dist/analytics/routes.mjs
199
- const json = (body, status) => Response.json(body, {
200
- headers: { "cache-control": "private, no-store" },
201
- status
202
- });
203
- const requireParam = (params, key) => {
204
- const value = params[key];
205
- if (value === void 0 || value.length === 0) throw new AnalyticsBadRequestError(`Missing route parameter: ${key}`);
206
- return value;
207
- };
208
- const run = async (operation) => {
209
- try {
210
- return await operation();
211
- } catch (error) {
212
- if (error instanceof AnalyticsBadRequestError) return json({ error: error.message }, 400);
213
- if (error instanceof AnalyticsPayloadTooLargeError) return json({ error: error.message }, 413);
214
- if (error instanceof AnalyticsScanLimitExceededError) return json({ error: {
215
- code: "ANALYTICS_SCAN_LIMIT_EXCEEDED",
216
- limit: error.limit
217
- } }, 503);
218
- throw error;
219
- }
220
- };
221
- const query = (operation) => run(async () => json(await operation(), 200));
222
- const createAnalyticsRouteHandlers = (provider) => ({
223
- appendBundleEvent: async (_params, request) => run(async () => {
224
- await provider.appendBundleEvent(await parseBundleEventRequest(request));
225
- return new Response(null, { status: 204 });
226
- }),
227
- getBundleEventSummary: (params) => query(() => provider.getBundleEventSummary(requireParam(params, "id"))),
228
- getBundleEventAnalytics: (params, request) => query(() => {
229
- const input = parseAnalyticsQuery(request);
230
- return provider.getBundleEventAnalytics(requireParam(params, "id"), input.window, input.limit, input.offset);
231
- }),
232
- getBundleEventOverview: () => query(() => provider.getBundleEventOverview()),
233
- getActiveInstallationOverview: (_params, request) => query(() => provider.getActiveInstallationOverview(parseActiveInstallationInput(request))),
234
- searchInstallations: (_params, request) => query(() => {
235
- const input = parseSearchInput(request);
236
- return provider.searchInstallations(input.query, input.limit, input.offset);
237
- }),
238
- getInstallationHistory: (params, request) => query(() => {
239
- const input = parsePagination(request);
240
- return provider.getInstallationHistory(requireParam(params, "installId"), input.limit, input.offset);
241
- })
242
- });
243
- const registerAnalyticsClientRoutes = (add) => {
244
- add("POST", "/events", "appendBundleEvent");
245
- };
246
- const registerAnalyticsAdminRoutes = (add) => {
247
- add("GET", "/bundles/:id/events/summary", "getBundleEventSummary");
248
- add("GET", "/bundles/:id/events/analytics", "getBundleEventAnalytics");
249
- add("GET", "/installations/overview", "getBundleEventOverview");
250
- add("GET", "/installations/active", "getActiveInstallationOverview");
251
- add("GET", "/installations", "searchInstallations");
252
- add("GET", "/installations/:installId/events", "getInstallationHistory");
253
- };
254
- //#endregion
255
17
  //#region ../../packages/server/dist/handlerErrors.mjs
256
18
  var HandlerBadRequestError = class extends Error {
257
19
  name = "HandlerBadRequestError";
@@ -947,7 +709,7 @@ const databaseFields = {
947
709
  };
948
710
  //#endregion
949
711
  //#region ../plugin-core/dist/databasePluginCrudValidationFields.mjs
950
- const isRecord$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
712
+ const isRecord$2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
951
713
  const isChannelText = (value) => {
952
714
  if (typeof value !== "string" || value.length === 0) return false;
953
715
  let codePointCount = 0;
@@ -1233,7 +995,7 @@ const hasValidReleaseInvariants = (data) => {
1233
995
  };
1234
996
  const hasValidBundleEventInvariants = (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;
1235
997
  const validateCreateData = (model, data) => {
1236
- if (!isRecord$1(data)) throw new DatabasePluginInputError("invalid-data");
998
+ if (!isRecord$2(data)) throw new DatabasePluginInputError("invalid-data");
1237
999
  validateFields(model, Object.keys(data));
1238
1000
  for (const field of databaseFields[model]) {
1239
1001
  const validator = modelValidators[model][field];
@@ -1250,7 +1012,7 @@ const selectRow = (row, input) => {
1250
1012
  return Object.fromEntries(select.map((field) => [field, Reflect.get(row, field)]));
1251
1013
  };
1252
1014
  const validateResult = (model, row, select) => {
1253
- if (!isRecord$1(row)) throw new DatabasePluginInputError("invalid-result");
1015
+ if (!isRecord$2(row)) throw new DatabasePluginInputError("invalid-result");
1254
1016
  const fields = select ?? databaseFields[model];
1255
1017
  for (const field of fields) {
1256
1018
  const validator = modelValidators[model][field];
@@ -1278,10 +1040,10 @@ const validateMutationWhere = (where) => {
1278
1040
  const validateUpdateWhere = (model, where) => {
1279
1041
  const selector = where[0];
1280
1042
  const primaryField = model === "release_catalogs" ? "scope_key" : "id";
1281
- if (where.length !== 1 || !isRecord$1(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");
1043
+ if (where.length !== 1 || !isRecord$2(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");
1282
1044
  };
1283
1045
  const validateBundleUpdateData = (update) => {
1284
- if (!isRecord$1(update)) throw new DatabasePluginInputError("invalid-data");
1046
+ if (!isRecord$2(update)) throw new DatabasePluginInputError("invalid-data");
1285
1047
  for (const [field, value] of Object.entries(update)) {
1286
1048
  if (field === "id") throw new DatabasePluginInputError("invalid-data");
1287
1049
  validateField("bundles", field);
@@ -1290,7 +1052,7 @@ const validateBundleUpdateData = (update) => {
1290
1052
  }
1291
1053
  };
1292
1054
  const validateApiKeyUpdateData = (update) => {
1293
- if (!isRecord$1(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");
1055
+ if (!isRecord$2(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");
1294
1056
  };
1295
1057
  const RELEASE_MUTABLE_FIELDS = new Set([
1296
1058
  "revision",
@@ -1305,7 +1067,7 @@ const RELEASE_MUTABLE_FIELDS = new Set([
1305
1067
  "updated_at_ms"
1306
1068
  ]);
1307
1069
  const validateReleaseUpdateData = (update) => {
1308
- if (!isRecord$1(update) || Reflect.ownKeys(update).length === 0) throw new DatabasePluginInputError("invalid-data");
1070
+ if (!isRecord$2(update) || Reflect.ownKeys(update).length === 0) throw new DatabasePluginInputError("invalid-data");
1309
1071
  for (const [field, value] of Object.entries(update)) {
1310
1072
  if (!RELEASE_MUTABLE_FIELDS.has(field)) throw new DatabasePluginInputError("invalid-data");
1311
1073
  const validator = modelValidators.releases[field];
@@ -1313,7 +1075,7 @@ const validateReleaseUpdateData = (update) => {
1313
1075
  }
1314
1076
  };
1315
1077
  const validateReleaseCatalogUpdateData = (update) => {
1316
- if (!isRecord$1(update)) throw new DatabasePluginInputError("invalid-data");
1078
+ if (!isRecord$2(update)) throw new DatabasePluginInputError("invalid-data");
1317
1079
  const expectedFields = Object.keys(modelValidators.release_catalogs).filter((field) => field !== "scope_key");
1318
1080
  if (Reflect.ownKeys(update).length !== expectedFields.length || expectedFields.some((field) => !Object.hasOwn(update, field))) throw new DatabasePluginInputError("invalid-data");
1319
1081
  for (const [field, value] of Object.entries(update)) {
@@ -1393,7 +1155,7 @@ const validateWhere$1 = (model, where) => {
1393
1155
  if (where === void 0) return;
1394
1156
  if (!Array.isArray(where)) throw new DatabasePluginInputError("invalid-query");
1395
1157
  for (const item of where) {
1396
- if (!isRecord$1(item)) throw new DatabasePluginInputError("invalid-query");
1158
+ if (!isRecord$2(item)) throw new DatabasePluginInputError("invalid-query");
1397
1159
  if (item.connector !== void 0 && item.connector !== "AND" && item.connector !== "OR") throw new DatabasePluginInputError("invalid-query");
1398
1160
  validateWhereValue(model, item);
1399
1161
  }
@@ -1410,7 +1172,7 @@ const validateOrderBy = (model, orderBy) => {
1410
1172
  if (!Array.isArray(orderBy) || orderBy.length === 0) throw new DatabasePluginInputError("invalid-query");
1411
1173
  const fields = /* @__PURE__ */ new Set();
1412
1174
  return orderBy.map((clause) => {
1413
- if (!isRecord$1(clause) || typeof clause.field !== "string") throw new DatabasePluginInputError("invalid-query");
1175
+ if (!isRecord$2(clause) || typeof clause.field !== "string") throw new DatabasePluginInputError("invalid-query");
1414
1176
  validateField(model, clause.field);
1415
1177
  if (!sortableFields[model].has(clause.field)) throw new DatabasePluginInputError("invalid-query");
1416
1178
  if (clause.direction !== "asc" && clause.direction !== "desc") throw new DatabasePluginInputError("invalid-query");
@@ -1422,7 +1184,7 @@ const validateOrderBy = (model, orderBy) => {
1422
1184
  };
1423
1185
  const validateDistinctOn = (model, distinctOn, orderBy) => {
1424
1186
  if (distinctOn === void 0) return;
1425
- if (!isRecord$1(distinctOn)) throw new DatabasePluginInputError("invalid-distinct");
1187
+ if (!isRecord$2(distinctOn)) throw new DatabasePluginInputError("invalid-distinct");
1426
1188
  const fields = validateDistinctFields(model, distinctOn.fields);
1427
1189
  if (fields === void 0 || orderBy === void 0) throw new DatabasePluginInputError("invalid-distinct");
1428
1190
  for (const [index, field] of fields.entries()) if (orderBy[index]?.field !== field) throw new DatabasePluginInputError("invalid-distinct");
@@ -1436,7 +1198,7 @@ const validateBundlePagination = (options) => {
1436
1198
  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");
1437
1199
  const cursor = options.cursor;
1438
1200
  if (cursor === void 0) return;
1439
- if (!isRecord$1(cursor)) throw new DatabasePluginInputError("invalid-pagination");
1201
+ if (!isRecord$2(cursor)) throw new DatabasePluginInputError("invalid-pagination");
1440
1202
  const hasAfter = Object.hasOwn(cursor, "after");
1441
1203
  if (hasAfter === Object.hasOwn(cursor, "before")) throw new DatabasePluginInputError("invalid-pagination");
1442
1204
  const value = hasAfter ? cursor.after : cursor.before;
@@ -1779,7 +1541,7 @@ const applyChange = async (database, change, changeIndex) => {
1779
1541
  }
1780
1542
  return;
1781
1543
  }
1782
- case "analytics":
1544
+ case "insights":
1783
1545
  await database.create({
1784
1546
  model: "bundle_events",
1785
1547
  data: change.row
@@ -1856,10 +1618,10 @@ const hasOnlyKeys = (value, keys) => {
1856
1618
  return Reflect.ownKeys(value).length === keys.length && keys.every((key) => Object.hasOwn(value, key));
1857
1619
  };
1858
1620
  const validateWhere = (where, field, validateValue = (value) => typeof value === "string") => {
1859
- if (!isRecord$1(where) || !hasOnlyKeys(where, [field]) || !validateValue(Reflect.get(where, field))) throw new DatabasePluginInputError("invalid-data");
1621
+ if (!isRecord$2(where) || !hasOnlyKeys(where, [field]) || !validateValue(Reflect.get(where, field))) throw new DatabasePluginInputError("invalid-data");
1860
1622
  };
1861
1623
  const validateDatabaseChange = (change) => {
1862
- if (!isRecord$1(change)) throw new DatabasePluginInputError("invalid-data");
1624
+ if (!isRecord$2(change)) throw new DatabasePluginInputError("invalid-data");
1863
1625
  switch (change.model) {
1864
1626
  case "bundles": switch (change.operation) {
1865
1627
  case "insert":
@@ -1966,7 +1728,7 @@ const validateDatabaseChange = (change) => {
1966
1728
  return;
1967
1729
  default: throw new DatabasePluginInputError("invalid-operation");
1968
1730
  }
1969
- case "analytics":
1731
+ case "insights":
1970
1732
  if (change.operation !== "insert" || !hasOnlyKeys(change, [
1971
1733
  "model",
1972
1734
  "operation",
@@ -1992,7 +1754,7 @@ const validateDatabaseChange = (change) => {
1992
1754
  "update"
1993
1755
  ])) throw new DatabasePluginInputError("invalid-data");
1994
1756
  validateWhere(change.where, "id");
1995
- if (!isRecord$1(change.update)) throw new DatabasePluginInputError("invalid-data");
1757
+ if (!isRecord$2(change.update)) throw new DatabasePluginInputError("invalid-data");
1996
1758
  if (!hasOnlyKeys(change.update, ["revokedAtMs"])) throw new DatabasePluginInputError("invalid-data");
1997
1759
  validateApiKeyUpdateData({ revoked_at_ms: change.update.revokedAtMs });
1998
1760
  return;
@@ -2002,7 +1764,7 @@ const validateDatabaseChange = (change) => {
2002
1764
  }
2003
1765
  };
2004
1766
  const validateDatabaseCommitExpectation = (expectation) => {
2005
- if (!isRecord$1(expectation)) throw new DatabasePluginInputError("invalid-data");
1767
+ if (!isRecord$2(expectation)) throw new DatabasePluginInputError("invalid-data");
2006
1768
  if (expectation.model === "releases") {
2007
1769
  if (!hasOnlyKeys(expectation, [
2008
1770
  "model",
@@ -2022,7 +1784,7 @@ const validateDatabaseCommitExpectation = (expectation) => {
2022
1784
  throw new DatabasePluginInputError("invalid-model");
2023
1785
  };
2024
1786
  function validateDatabaseCommit(input) {
2025
- if (!isRecord$1(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");
1787
+ if (!isRecord$2(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");
2026
1788
  input.changes.forEach(validateDatabaseChange);
2027
1789
  if (Array.isArray(input.expectations)) input.expectations.forEach(validateDatabaseCommitExpectation);
2028
1790
  }
@@ -2241,7 +2003,7 @@ const createDatabasePluginAdapter = (name, implementation) => {
2241
2003
  return result;
2242
2004
  }
2243
2005
  },
2244
- analytics: {
2006
+ insights: {
2245
2007
  async append(row) {
2246
2008
  await crud.create({
2247
2009
  model: "bundle_events",
@@ -3848,7 +3610,7 @@ const createReleaseCatalogRouteHandlers = (clientAccessHeaderName = "x-api-key")
3848
3610
  //#endregion
3849
3611
  //#region ../../packages/server/dist/handlerReleaseManagementRoutes.mjs
3850
3612
  const unavailable = () => Response.json({ error: "Not found" }, { status: 404 });
3851
- const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3613
+ const isRecord$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3852
3614
  const parseRevision = (value) => {
3853
3615
  if (value === void 0 || value === null || value === "") return void 0;
3854
3616
  const revision = typeof value === "number" ? value : Number(value);
@@ -3857,7 +3619,7 @@ const parseRevision = (value) => {
3857
3619
  };
3858
3620
  const parsePolicyInput = async (request) => {
3859
3621
  const body = await request.json();
3860
- if (!isRecord(body) || !isRecord(body.patch)) throw new HandlerBadRequestError("Invalid Release policy mutation");
3622
+ if (!isRecord$1(body) || !isRecord$1(body.patch)) throw new HandlerBadRequestError("Invalid Release policy mutation");
3861
3623
  return {
3862
3624
  expectedRevision: parseRevision(body.expectedRevision),
3863
3625
  patch: body.patch
@@ -3966,6 +3728,244 @@ const createReleaseManagementRouteHandlers = () => ({
3966
3728
  }
3967
3729
  });
3968
3730
  //#endregion
3731
+ //#region ../../packages/server/dist/insights/errors.mjs
3732
+ var InsightsScanLimitExceededError = class extends Error {
3733
+ constructor(limit) {
3734
+ super(`Insights event scan exceeded ${limit} rows.`);
3735
+ this.limit = limit;
3736
+ this.name = "InsightsScanLimitExceededError";
3737
+ }
3738
+ };
3739
+ var InsightsBadRequestError = class extends Error {
3740
+ name = "InsightsBadRequestError";
3741
+ };
3742
+ var InsightsPayloadTooLargeError = class extends Error {
3743
+ name = "InsightsPayloadTooLargeError";
3744
+ constructor(maximumBytes) {
3745
+ super(`Event payload exceeds ${maximumBytes} bytes`);
3746
+ this.maximumBytes = maximumBytes;
3747
+ }
3748
+ };
3749
+ //#endregion
3750
+ //#region ../../packages/server/dist/insights/eventInput.mjs
3751
+ const MAX_EVENT_STRING_LENGTH = 1024;
3752
+ const EVENT_BODY_MAX_BYTES = 16 * 1024;
3753
+ const eventKeys = new Set([
3754
+ "type",
3755
+ "installId",
3756
+ "toBundleId",
3757
+ "userId",
3758
+ "username",
3759
+ "platform",
3760
+ "appVersion",
3761
+ "channel",
3762
+ "cohort",
3763
+ "fingerprintHash",
3764
+ "fromBundleId",
3765
+ "fromReleaseId",
3766
+ "toReleaseId",
3767
+ "updateStrategy",
3768
+ "sdkVersion"
3769
+ ]);
3770
+ function isRecord(value) {
3771
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3772
+ }
3773
+ function requireStringField(payload, key) {
3774
+ const value = payload[key];
3775
+ if (typeof value !== "string" || value.length === 0 || value.length > MAX_EVENT_STRING_LENGTH) throw new InsightsBadRequestError(`Invalid event field: ${key}`);
3776
+ return value;
3777
+ }
3778
+ function requireNullableStringField(payload, key) {
3779
+ if (payload[key] === null) return null;
3780
+ return requireStringField(payload, key);
3781
+ }
3782
+ async function readBoundedText(request) {
3783
+ const contentLength = request.headers.get("content-length");
3784
+ const declaredByteLength = Number(contentLength);
3785
+ if (contentLength !== null && Number.isSafeInteger(declaredByteLength) && declaredByteLength > 16384) throw new InsightsPayloadTooLargeError(EVENT_BODY_MAX_BYTES);
3786
+ if (request.body === null) return "";
3787
+ const reader = request.body.getReader();
3788
+ const decoder = new TextDecoder();
3789
+ let byteLength = 0;
3790
+ let text = "";
3791
+ while (true) {
3792
+ const result = await reader.read();
3793
+ if (result.done) break;
3794
+ byteLength += result.value.byteLength;
3795
+ if (byteLength > 16384) {
3796
+ await reader.cancel();
3797
+ throw new InsightsPayloadTooLargeError(EVENT_BODY_MAX_BYTES);
3798
+ }
3799
+ text += decoder.decode(result.value, { stream: true });
3800
+ }
3801
+ return text + decoder.decode();
3802
+ }
3803
+ async function parseJson(request) {
3804
+ const text = await readBoundedText(request);
3805
+ try {
3806
+ return JSON.parse(text);
3807
+ } catch (error) {
3808
+ if (error instanceof SyntaxError) throw new InsightsBadRequestError("Invalid event payload");
3809
+ throw error;
3810
+ }
3811
+ }
3812
+ function requireEvent(payload) {
3813
+ if (!isRecord(payload) || Object.keys(payload).some((key) => !eventKeys.has(key))) throw new InsightsBadRequestError("Invalid event payload");
3814
+ const platform = requireStringField(payload, "platform");
3815
+ if (platform !== "ios" && platform !== "android") throw new InsightsBadRequestError("Invalid event field: platform");
3816
+ const base = {
3817
+ installId: requireStringField(payload, "installId"),
3818
+ toBundleId: requireStringField(payload, "toBundleId"),
3819
+ ...payload.userId === void 0 ? {} : { userId: requireStringField(payload, "userId") },
3820
+ ...payload.username === void 0 ? {} : { username: requireStringField(payload, "username") },
3821
+ platform,
3822
+ appVersion: requireStringField(payload, "appVersion"),
3823
+ channel: requireStringField(payload, "channel"),
3824
+ cohort: requireStringField(payload, "cohort"),
3825
+ fingerprintHash: requireNullableStringField(payload, "fingerprintHash"),
3826
+ sdkVersion: payload.sdkVersion === void 0 ? null : requireNullableStringField(payload, "sdkVersion"),
3827
+ fromReleaseId: requireNullableStringField(payload, "fromReleaseId"),
3828
+ toReleaseId: requireNullableStringField(payload, "toReleaseId")
3829
+ };
3830
+ const type = requireStringField(payload, "type");
3831
+ switch (type) {
3832
+ case "UPDATE_APPLIED":
3833
+ case "RECOVERED":
3834
+ case "RELEASE_ADOPTED": {
3835
+ const updateStrategy = requireStringField(payload, "updateStrategy");
3836
+ if (updateStrategy !== "fingerprint" && updateStrategy !== "appVersion") throw new InsightsBadRequestError("Invalid event field: updateStrategy");
3837
+ return {
3838
+ ...base,
3839
+ type,
3840
+ fromBundleId: requireStringField(payload, "fromBundleId"),
3841
+ updateStrategy
3842
+ };
3843
+ }
3844
+ case "UNCHANGED":
3845
+ if (payload.fromBundleId !== null || payload.updateStrategy !== null) throw new InsightsBadRequestError("Invalid unchanged event shape");
3846
+ return {
3847
+ ...base,
3848
+ type,
3849
+ fromBundleId: null,
3850
+ updateStrategy: null
3851
+ };
3852
+ default: throw new InsightsBadRequestError("Invalid event field: type");
3853
+ }
3854
+ }
3855
+ async function parseBundleEventRequest(request) {
3856
+ return requireEvent(await parseJson(request));
3857
+ }
3858
+ //#endregion
3859
+ //#region ../../packages/server/dist/insights/queryInput.mjs
3860
+ const EVENT_LIST_BOUNDS = {
3861
+ defaultValue: 50,
3862
+ maximum: 100,
3863
+ minimum: 1
3864
+ };
3865
+ const EVENT_LIST_OFFSET_BOUNDS = {
3866
+ defaultValue: 0,
3867
+ minimum: 0
3868
+ };
3869
+ const MAX_USER_ID_LENGTH = 1024;
3870
+ function parseInteger(url, key, bounds) {
3871
+ const value = url.searchParams.get(key);
3872
+ if (value === null) return bounds.defaultValue;
3873
+ const parsed = Number(value);
3874
+ if (!Number.isSafeInteger(parsed) || parsed < bounds.minimum || bounds.maximum !== void 0 && parsed > bounds.maximum) throw new InsightsBadRequestError(`Invalid '${key}' query parameter.`);
3875
+ return parsed;
3876
+ }
3877
+ const parsePagination = (request) => {
3878
+ const url = new URL(request.url);
3879
+ return {
3880
+ limit: parseInteger(url, "limit", EVENT_LIST_BOUNDS),
3881
+ offset: parseInteger(url, "offset", EVENT_LIST_OFFSET_BOUNDS)
3882
+ };
3883
+ };
3884
+ const parseInsightsQuery = (request) => {
3885
+ const window = new URL(request.url).searchParams.get("window") ?? "24h";
3886
+ if (window !== "24h" && window !== "7d" && window !== "30d" && window !== "all") throw new InsightsBadRequestError("Invalid 'window' query parameter.");
3887
+ return {
3888
+ ...parsePagination(request),
3889
+ window
3890
+ };
3891
+ };
3892
+ const parseActiveInstallationInput = (request) => {
3893
+ const url = new URL(request.url);
3894
+ const windows = url.searchParams.getAll("window");
3895
+ if (windows.length > 1) throw new InsightsBadRequestError("Duplicate 'window' query parameter.");
3896
+ const window = windows[0] ?? "30d";
3897
+ if (window !== "24h" && window !== "7d" && window !== "30d") throw new InsightsBadRequestError("Invalid 'window' query parameter.");
3898
+ const userIds = url.searchParams.getAll("userId");
3899
+ if (userIds.length > 1) throw new InsightsBadRequestError("Duplicate 'userId' query parameter.");
3900
+ const userId = userIds[0];
3901
+ if (userId !== void 0 && (userId.length === 0 || userId.length > MAX_USER_ID_LENGTH)) throw new InsightsBadRequestError("Invalid 'userId' query parameter.");
3902
+ return userId === void 0 ? { window } : {
3903
+ window,
3904
+ userId
3905
+ };
3906
+ };
3907
+ const parseSearchInput = (request) => ({
3908
+ ...parsePagination(request),
3909
+ query: new URL(request.url).searchParams.get("query")?.trim() ?? ""
3910
+ });
3911
+ //#endregion
3912
+ //#region ../../packages/server/dist/insights/routes.mjs
3913
+ const json = (body, status) => Response.json(body, {
3914
+ headers: { "cache-control": "private, no-store" },
3915
+ status
3916
+ });
3917
+ const requireParam = (params, key) => {
3918
+ const value = params[key];
3919
+ if (value === void 0 || value.length === 0) throw new InsightsBadRequestError(`Missing route parameter: ${key}`);
3920
+ return value;
3921
+ };
3922
+ const run = async (operation) => {
3923
+ try {
3924
+ return await operation();
3925
+ } catch (error) {
3926
+ if (error instanceof InsightsBadRequestError) return json({ error: error.message }, 400);
3927
+ if (error instanceof InsightsPayloadTooLargeError) return json({ error: error.message }, 413);
3928
+ if (error instanceof InsightsScanLimitExceededError) return json({ error: {
3929
+ code: "INSIGHTS_SCAN_LIMIT_EXCEEDED",
3930
+ limit: error.limit
3931
+ } }, 503);
3932
+ throw error;
3933
+ }
3934
+ };
3935
+ const query = (operation) => run(async () => json(await operation(), 200));
3936
+ const createInsightsRouteHandlers = (provider) => ({
3937
+ appendBundleEvent: async (_params, request) => run(async () => {
3938
+ await provider.appendBundleEvent(await parseBundleEventRequest(request));
3939
+ return new Response(null, { status: 204 });
3940
+ }),
3941
+ getBundleEventSummary: (params) => query(() => provider.getBundleEventSummary(requireParam(params, "id"))),
3942
+ getBundleEventInsights: (params, request) => query(() => {
3943
+ const input = parseInsightsQuery(request);
3944
+ return provider.getBundleEventInsights(requireParam(params, "id"), input.window, input.limit, input.offset);
3945
+ }),
3946
+ getBundleEventOverview: () => query(() => provider.getBundleEventOverview()),
3947
+ getActiveInstallationOverview: (_params, request) => query(() => provider.getActiveInstallationOverview(parseActiveInstallationInput(request))),
3948
+ searchInstallations: (_params, request) => query(() => {
3949
+ const input = parseSearchInput(request);
3950
+ return provider.searchInstallations(input.query, input.limit, input.offset);
3951
+ }),
3952
+ getInstallationHistory: (params, request) => query(() => {
3953
+ const input = parsePagination(request);
3954
+ return provider.getInstallationHistory(requireParam(params, "installId"), input.limit, input.offset);
3955
+ })
3956
+ });
3957
+ const registerInsightsClientRoutes = (add) => {
3958
+ add("POST", "/events", "appendBundleEvent");
3959
+ };
3960
+ const registerInsightsAdminRoutes = (add) => {
3961
+ add("GET", "/bundles/:id/events/summary", "getBundleEventSummary");
3962
+ add("GET", "/bundles/:id/events/insights", "getBundleEventInsights");
3963
+ add("GET", "/installations/overview", "getBundleEventOverview");
3964
+ add("GET", "/installations/active", "getActiveInstallationOverview");
3965
+ add("GET", "/installations", "searchInstallations");
3966
+ add("GET", "/installations/:installId/events", "getInstallationHistory");
3967
+ };
3968
+ //#endregion
3969
3969
  //#region ../../packages/server/dist/internalRouter.mjs
3970
3970
  const normalizePath = (path) => {
3971
3971
  if (!path) return "/";
@@ -4072,13 +4072,13 @@ const createDownloadStorageRouteHandler = (downloadStorageObject) => async (para
4072
4072
  statusText: response.statusText
4073
4073
  });
4074
4074
  };
4075
- function createHotUpdaterHandlers(api, analytics, apiKeyAuth, downloadStorageObject) {
4075
+ function createHotUpdaterHandlers(api, insights, apiKeyAuth, downloadStorageObject) {
4076
4076
  const routeHandlers = {
4077
4077
  ...createVersionRouteHandlers(),
4078
4078
  ...createReleaseCatalogRouteHandlers(apiKeyAuth?.headerName),
4079
4079
  ...createReleaseManagementRouteHandlers(),
4080
4080
  ...createBundleRouteHandlers(),
4081
- ...analytics === void 0 ? {} : createAnalyticsRouteHandlers(analytics),
4081
+ ...insights === void 0 ? {} : createInsightsRouteHandlers(insights),
4082
4082
  ...downloadStorageObject === void 0 ? {} : { downloadStorageObject: createDownloadStorageRouteHandler(downloadStorageObject) }
4083
4083
  };
4084
4084
  const clientRouter = createRouter();
@@ -4088,7 +4088,7 @@ function createHotUpdaterHandlers(api, analytics, apiKeyAuth, downloadStorageObj
4088
4088
  addClientRoute("GET", "/release-catalogs/app-version/:platform/:channelKey/:appVersion", "appVersionReleaseCatalog");
4089
4089
  addClientRoute("GET", "/release-catalogs/fingerprint/:platform/:channelKey/:fingerprintHash", "fingerprintReleaseCatalog");
4090
4090
  addClientRoute("GET", "/artifacts/:targetBundleId/from/:currentBundleId", "artifact");
4091
- if (analytics !== void 0) registerAnalyticsClientRoutes(addClientRoute);
4091
+ if (insights !== void 0) registerInsightsClientRoutes(addClientRoute);
4092
4092
  const adminRouter = createRouter();
4093
4093
  const addAdminRoute = (method, path, handler) => addRoute(adminRouter, method, path, handler);
4094
4094
  addAdminRoute("GET", "/releases/:id", "getRelease");
@@ -4108,7 +4108,7 @@ function createHotUpdaterHandlers(api, analytics, apiKeyAuth, downloadStorageObj
4108
4108
  addAdminRoute("POST", "/bundles", "createBundles");
4109
4109
  addAdminRoute("PATCH", "/bundles/:id", "updateBundle");
4110
4110
  addAdminRoute("DELETE", "/bundles/:id", "deleteBundle");
4111
- if (analytics !== void 0) registerAnalyticsAdminRoutes(addAdminRoute);
4111
+ if (insights !== void 0) registerInsightsAdminRoutes(addAdminRoute);
4112
4112
  return Object.freeze({
4113
4113
  client: createRequestHandler({
4114
4114
  api,
@@ -4125,7 +4125,7 @@ function createHotUpdaterHandlers(api, analytics, apiKeyAuth, downloadStorageObj
4125
4125
  });
4126
4126
  }
4127
4127
  //#endregion
4128
- //#region ../../packages/server/dist/analytics/bounded/activeOverview.mjs
4128
+ //#region ../../packages/server/dist/insights/bounded/activeOverview.mjs
4129
4129
  const HOUR_MS = 3600 * 1e3;
4130
4130
  const DAY_MS = 24 * HOUR_MS;
4131
4131
  const ACTIVE_BUNDLE_EVENT_TYPES = [
@@ -4209,11 +4209,11 @@ function collectActiveInstallationOverview(request) {
4209
4209
  };
4210
4210
  }
4211
4211
  //#endregion
4212
- //#region ../../packages/server/dist/analytics/bounded/scan.mjs
4213
- const ANALYTICS_SCAN_MAX_ROWS = 5e4;
4214
- const ANALYTICS_MATERIALIZATION_LIMIT = ANALYTICS_SCAN_MAX_ROWS + 1;
4215
- const ANALYTICS_SCAN_PAGE_SIZE = 1e3;
4216
- const ANALYTICS_LOWER_BOUND_ID = "00000000-0000-0000-0000-000000000000";
4212
+ //#region ../../packages/server/dist/insights/bounded/scan.mjs
4213
+ const INSIGHTS_SCAN_MAX_ROWS = 5e4;
4214
+ const INSIGHTS_MATERIALIZATION_LIMIT = INSIGHTS_SCAN_MAX_ROWS + 1;
4215
+ const INSIGHTS_SCAN_PAGE_SIZE = 1e3;
4216
+ const INSIGHTS_LOWER_BOUND_ID = "00000000-0000-0000-0000-000000000000";
4217
4217
  const compareCodePoints = (left, right) => {
4218
4218
  if (left < right) return -1;
4219
4219
  if (left > right) return 1;
@@ -4226,23 +4226,23 @@ const materializeEventRows = async (scope) => {
4226
4226
  const seenIds = /* @__PURE__ */ new Set();
4227
4227
  let after = scope.lowerBoundMs === void 0 ? void 0 : {
4228
4228
  receivedAtMs: scope.lowerBoundMs,
4229
- id: ANALYTICS_LOWER_BOUND_ID
4229
+ id: INSIGHTS_LOWER_BOUND_ID
4230
4230
  };
4231
- while (rows.length < ANALYTICS_MATERIALIZATION_LIMIT) {
4232
- const limit = Math.min(ANALYTICS_SCAN_PAGE_SIZE, ANALYTICS_MATERIALIZATION_LIMIT - rows.length);
4231
+ while (rows.length < INSIGHTS_MATERIALIZATION_LIMIT) {
4232
+ const limit = Math.min(INSIGHTS_SCAN_PAGE_SIZE, INSIGHTS_MATERIALIZATION_LIMIT - rows.length);
4233
4233
  const page = await scope.persistence.scan({
4234
4234
  beforeReceivedAtMs: scope.cutoffMs,
4235
4235
  ...after === void 0 ? {} : { after },
4236
4236
  limit
4237
4237
  });
4238
4238
  if (page.length === 0) break;
4239
- if (page.length > limit) throw new AnalyticsPersistenceOrderError();
4239
+ if (page.length > limit) throw new InsightsPersistenceOrderError();
4240
4240
  let previous = after === void 0 ? void 0 : {
4241
4241
  received_at_ms: after.receivedAtMs,
4242
4242
  id: after.id
4243
4243
  };
4244
4244
  for (const row of page) {
4245
- if (row.received_at_ms >= scope.cutoffMs || previous !== void 0 && compareEventOldest(previous, row) >= 0 || seenIds.has(row.id)) throw new AnalyticsPersistenceOrderError();
4245
+ if (row.received_at_ms >= scope.cutoffMs || previous !== void 0 && compareEventOldest(previous, row) >= 0 || seenIds.has(row.id)) throw new InsightsPersistenceOrderError();
4246
4246
  seenIds.add(row.id);
4247
4247
  rows.push(row);
4248
4248
  previous = row;
@@ -4252,14 +4252,14 @@ const materializeEventRows = async (scope) => {
4252
4252
  receivedAtMs: last.received_at_ms,
4253
4253
  id: last.id
4254
4254
  };
4255
- if (rows.length > 5e4) throw new AnalyticsScanLimitExceededError(ANALYTICS_SCAN_MAX_ROWS);
4255
+ if (rows.length > 5e4) throw new InsightsScanLimitExceededError(INSIGHTS_SCAN_MAX_ROWS);
4256
4256
  }
4257
4257
  return rows;
4258
4258
  };
4259
- var AnalyticsPersistenceOrderError = class extends Error {
4260
- name = "AnalyticsPersistenceOrderError";
4259
+ var InsightsPersistenceOrderError = class extends Error {
4260
+ name = "InsightsPersistenceOrderError";
4261
4261
  constructor() {
4262
- super("Analytics persistence did not advance its scan cursor.");
4262
+ super("Insights persistence did not advance its scan cursor.");
4263
4263
  }
4264
4264
  };
4265
4265
  const materializeActiveRows = async (scope, window) => {
@@ -4344,7 +4344,7 @@ const collectEventActivity = (request) => {
4344
4344
  };
4345
4345
  };
4346
4346
  //#endregion
4347
- //#region ../../packages/server/dist/analytics/bounded/installationSearch.mjs
4347
+ //#region ../../packages/server/dist/insights/bounded/installationSearch.mjs
4348
4348
  function toSearchRow(row) {
4349
4349
  return {
4350
4350
  installId: row.install_id,
@@ -4384,7 +4384,7 @@ function searchEventInstallations(request) {
4384
4384
  };
4385
4385
  }
4386
4386
  //#endregion
4387
- //#region ../../packages/server/dist/analytics/bounded/persistence.mjs
4387
+ //#region ../../packages/server/dist/insights/bounded/persistence.mjs
4388
4388
  function createBundleEventRow(input) {
4389
4389
  const base = {
4390
4390
  id: createUUIDv7(),
@@ -4420,7 +4420,7 @@ function createBundleEventRow(input) {
4420
4420
  }
4421
4421
  }
4422
4422
  //#endregion
4423
- //#region ../../packages/server/dist/analytics/bounded/provider.mjs
4423
+ //#region ../../packages/server/dist/insights/bounded/provider.mjs
4424
4424
  const isTransitionEventRow = (row) => row.type === "UPDATE_APPLIED" || row.type === "RECOVERED";
4425
4425
  const toHistoryRow = (row) => ({
4426
4426
  id: row.id,
@@ -4438,7 +4438,7 @@ const toHistoryRow = (row) => ({
4438
4438
  const isInstalledForBundle = (row, bundleId) => row.type === "UPDATE_APPLIED" && row.to_bundle_id === bundleId;
4439
4439
  const isRecoveredFromBundle = (row, bundleId) => row.type === "RECOVERED" && row.from_bundle_id === bundleId;
4440
4440
  const countDistinctInstallations = (rows) => new Set(rows.map(({ install_id }) => install_id)).size;
4441
- const getAnalyticsResult = async (persistence, bundleId, window, limit, offset) => {
4441
+ const getInsightsResult = async (persistence, bundleId, window, limit, offset) => {
4442
4442
  const scope = {
4443
4443
  persistence,
4444
4444
  cutoffMs: Date.now()
@@ -4480,7 +4480,7 @@ const getAnalyticsResult = async (persistence, bundleId, window, limit, offset)
4480
4480
  }
4481
4481
  };
4482
4482
  };
4483
- const getAnalyticsSummaries = async (persistence, bundleIds, window) => {
4483
+ const getInsightsSummaries = async (persistence, bundleIds, window) => {
4484
4484
  const normalizedBundleIds = [...new Set(bundleIds)];
4485
4485
  if (normalizedBundleIds.length === 0) return [];
4486
4486
  const requestedBundleIds = new Set(normalizedBundleIds);
@@ -4504,9 +4504,9 @@ const getAnalyticsSummaries = async (persistence, bundleIds, window) => {
4504
4504
  recovered: recoveredByBundleId.get(bundleId)?.size ?? 0
4505
4505
  }));
4506
4506
  };
4507
- const createAnalyticsProvider = (persistence) => Object.freeze({
4507
+ const createInsightsProvider = (persistence) => Object.freeze({
4508
4508
  mode: "bounded",
4509
- maxMatchingRows: ANALYTICS_SCAN_MAX_ROWS,
4509
+ maxMatchingRows: INSIGHTS_SCAN_MAX_ROWS,
4510
4510
  async appendBundleEvent(input) {
4511
4511
  await persistence.append(createBundleEventRow(input));
4512
4512
  },
@@ -4521,10 +4521,10 @@ const createAnalyticsProvider = (persistence) => Object.freeze({
4521
4521
  };
4522
4522
  },
4523
4523
  getBundleEventSummaries(bundleIds, window) {
4524
- return getAnalyticsSummaries(persistence, bundleIds, window);
4524
+ return getInsightsSummaries(persistence, bundleIds, window);
4525
4525
  },
4526
- getBundleEventAnalytics(bundleId, window, limit, offset) {
4527
- return getAnalyticsResult(persistence, bundleId, window, limit, offset);
4526
+ getBundleEventInsights(bundleId, window, limit, offset) {
4527
+ return getInsightsResult(persistence, bundleId, window, limit, offset);
4528
4528
  },
4529
4529
  async getBundleEventOverview() {
4530
4530
  const rows = await materializeEventRows({
@@ -5106,7 +5106,7 @@ const sqlProviders = [
5106
5106
  const noSqlProviders = ["mongodb"];
5107
5107
  [...sqlProviders, ...noSqlProviders];
5108
5108
  function isDatabasePlugin(plugin) {
5109
- 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");
5109
+ 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");
5110
5110
  }
5111
5111
  //#endregion
5112
5112
  //#region ../../packages/server/dist/storageAccess.mjs
@@ -5222,21 +5222,21 @@ function createHotUpdaterCore(options) {
5222
5222
  readStorageText
5223
5223
  });
5224
5224
  const clientAccess = normalizeClientAccess(options.clientAccess);
5225
- const analytics = createAnalyticsProvider({
5225
+ const insights = createInsightsProvider({
5226
5226
  async append(row) {
5227
5227
  await assertSchemaReady();
5228
- return plugin.models.analytics.append(row);
5228
+ return plugin.models.insights.append(row);
5229
5229
  },
5230
5230
  async scan(input) {
5231
5231
  await assertSchemaReady();
5232
- return plugin.models.analytics.scan(input);
5232
+ return plugin.models.insights.scan(input);
5233
5233
  }
5234
5234
  });
5235
5235
  const apiKeys = createApiKeyManagement({
5236
5236
  apiKeys: plugin.models.apiKeys,
5237
5237
  beforeOperation: assertSchemaReady
5238
5238
  });
5239
- const handlers = createHotUpdaterHandlers(core.api, analytics, clientAccess.type === "api-key" ? {
5239
+ const handlers = createHotUpdaterHandlers(core.api, insights, clientAccess.type === "api-key" ? {
5240
5240
  authenticate: (request) => authenticateApiKey({
5241
5241
  apiKeys: plugin.models.apiKeys,
5242
5242
  beforeLookup: assertSchemaReady,
@@ -5247,7 +5247,7 @@ function createHotUpdaterCore(options) {
5247
5247
  } : void 0, downloadStorageObject);
5248
5248
  const api = Object.assign({
5249
5249
  adapterName: adapterCapabilities.adapterName ?? core.adapterName,
5250
- analytics,
5250
+ insights,
5251
5251
  apiKeys,
5252
5252
  handlers
5253
5253
  }, core.api);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hot-updater/firebase",
3
3
  "type": "module",
4
- "version": "1.0.0-rc.0",
4
+ "version": "1.0.0-rc.1",
5
5
  "description": "React Native OTA solution for self-hosted",
6
6
  "main": "dist/index.cjs",
7
7
  "types": "dist/index.d.cts",
@@ -43,10 +43,10 @@
43
43
  ],
44
44
  "dependencies": {
45
45
  "hono": "4.12.34",
46
- "@hot-updater/cli-tools": "1.0.0-rc.0",
46
+ "@hot-updater/cli-tools": "1.0.0-rc.1",
47
47
  "@hot-updater/core": "1.0.0-rc.0",
48
- "@hot-updater/plugin-core": "1.0.0-rc.0",
49
- "@hot-updater/server": "1.0.0-rc.0"
48
+ "@hot-updater/server": "1.0.0-rc.1",
49
+ "@hot-updater/plugin-core": "1.0.0-rc.1"
50
50
  },
51
51
  "publishConfig": {
52
52
  "access": "public"
@@ -61,8 +61,8 @@
61
61
  "firebase-tools": "^13.32.0",
62
62
  "fkill": "^9.0.0",
63
63
  "mime": "^4.0.4",
64
- "@hot-updater/mock": "1.0.0-rc.0",
65
- "@hot-updater/test-utils": "1.0.0-rc.0"
64
+ "@hot-updater/mock": "1.0.0-rc.1",
65
+ "@hot-updater/test-utils": "1.0.0-rc.1"
66
66
  },
67
67
  "peerDependencies": {
68
68
  "firebase-admin": "*",