@hot-updater/firebase 1.0.0-rc.32 → 1.0.0-rc.34

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.
@@ -4524,7 +4524,7 @@ const createKvAdapter = ({ store, tablePrefix = "" }) => {
4524
4524
  };
4525
4525
  //#endregion
4526
4526
  //#region ../../packages/server/dist/version.mjs
4527
- const HOT_UPDATER_SERVER_VERSION = "1.0.0-rc.32";
4527
+ const HOT_UPDATER_SERVER_VERSION = "1.0.0-rc.34";
4528
4528
  //#endregion
4529
4529
  //#region ../../packages/server/dist/handlerVersionRoutes.mjs
4530
4530
  /**
@@ -7686,8 +7686,8 @@ const isOptional = (value, key, valid) => !Object.hasOwn(value, key) || valid(va
7686
7686
  const isString = (value) => typeof value === "string";
7687
7687
  const isDatabaseHttpResponse = (value) => isDatabaseJsonObject(value) && (value.resource === "catalog" || value.resource === "artifact") && isString(value.path) && Number.isSafeInteger(value.status) && (value.body === null || isString(value.body)) && typeof value.body_truncated === "boolean" && Number.isSafeInteger(value.received_at_ms);
7688
7688
  /** An `UPDATE_FAILED` report's stored failure: a stage and a reason, and what else the client knew. */
7689
- const isDatabaseBundleEventFailure = (value) => isDatabaseJsonObject(value) && isString(value.stage) && isString(value.reason) && isOptional(value, "resource", isString) && isOptional(value, "http_status", (status) => Number.isSafeInteger(status)) && isOptional(value, "transport", isString) && isOptional(value, "origin_code", isString) && isOptional(value, "error_message", isString) && isOptional(value, "error_stack", isString) && isOptional(value, "previous_process_exit", isString);
7690
- const isDatabaseBundleEventMetadata = (value) => isDatabaseJsonObject(value) && typeof value.cohort === "string" && (value.update_strategy === null || value.update_strategy === "fingerprint" || value.update_strategy === "appVersion") && (value.fingerprint_hash === null || typeof value.fingerprint_hash === "string") && (value.sdk_version === null || typeof value.sdk_version === "string") && isOptional(value, "failure", isDatabaseBundleEventFailure) && isOptional(value, "http_response", isDatabaseHttpResponse) && isOptional(value, "delivery", isString) && isOptional(value, "patch_fallback", (flag) => typeof flag === "boolean") && isOptional(value, "previous_process_exit", isString);
7689
+ const isDatabaseBundleEventFailure = (value) => isDatabaseJsonObject(value) && isString(value.stage) && isString(value.reason) && isOptional(value, "resource", isString) && isOptional(value, "http_status", (status) => Number.isSafeInteger(status)) && isOptional(value, "transport", isString) && isOptional(value, "origin_code", isString) && isOptional(value, "error_message", isString) && isOptional(value, "error_stack", isString);
7690
+ const isDatabaseBundleEventMetadata = (value) => isDatabaseJsonObject(value) && typeof value.cohort === "string" && (value.update_strategy === null || value.update_strategy === "fingerprint" || value.update_strategy === "appVersion") && (value.fingerprint_hash === null || typeof value.fingerprint_hash === "string") && (value.sdk_version === null || typeof value.sdk_version === "string") && isOptional(value, "failure", isDatabaseBundleEventFailure) && isOptional(value, "http_response", isDatabaseHttpResponse) && isOptional(value, "delivery", isString) && isOptional(value, "patch_fallback", (flag) => typeof flag === "boolean");
7691
7691
  const isRecord$2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
7692
7692
  const isIdentity$1 = (value) => typeof value === "string" && value.length > 0 && value.length <= 255;
7693
7693
  const isTextOrNull = (value) => value === null || typeof value === "string";
@@ -7892,7 +7892,7 @@ const createValidatedInsightsModel = (model) => ({
7892
7892
  async getReleaseActivity(input) {
7893
7893
  if (!isRecord$2(input) || !hasOnlyKeys(input, ["releases"]) || !Array.isArray(input.releases) || input.releases.length < 1 || input.releases.length > 100 || !input.releases.every(isReleaseReference)) invalidQuery();
7894
7894
  const result = await model.getReleaseActivity(input);
7895
- if (!isRecord$2(result) || !isTimestamp(result.measuredAtMs) || !Array.isArray(result.data) || result.data.some((item) => !isRecord$2(item) || !isReleaseReference(item.release) || !isRecord$2(item.metrics) || !isTimestamp(item.metrics.downloads) || !isTimestamp(item.metrics.launches) || !isTimestamp(item.metrics.failedLaunches))) invalidResult();
7895
+ if (!isRecord$2(result) || !isTimestamp(result.measuredAtMs) || !Array.isArray(result.data) || result.data.some((item) => !isRecord$2(item) || !isReleaseReference(item.release) || !isRecord$2(item.metrics) || !isTimestamp(item.metrics.downloads) || !isTimestamp(item.metrics.applies) || !isTimestamp(item.metrics.failedLaunches))) invalidResult();
7896
7896
  return result;
7897
7897
  },
7898
7898
  async getAppUsage(input) {
@@ -8027,7 +8027,7 @@ const DELIVERIES = [
8027
8027
  "manifest",
8028
8028
  "archive"
8029
8029
  ];
8030
- /** A storage error code or an Android exit reason: letters, digits, and `._-`. */
8030
+ /** A storage error code: letters, digits, and `._-`. */
8031
8031
  const CODE = /^[A-Za-z0-9._-]{1,64}$/;
8032
8032
  const readCode = (value) => typeof value === "string" && CODE.test(value) ? value : void 0;
8033
8033
  const readMetadata = (payload) => isRecord$1(payload.metadata) ? payload.metadata : {};
@@ -8043,7 +8043,6 @@ function readFailure(value) {
8043
8043
  const originCode = readCode(failure.originCode);
8044
8044
  const errorMessage = typeof failure.errorMessage === "string" && failure.errorMessage.length <= 2048 ? failure.errorMessage : void 0;
8045
8045
  const errorStack = typeof failure.errorStack === "string" && failure.errorStack.length <= 4096 ? failure.errorStack : void 0;
8046
- const previousProcessExit = readCode(failure.previousProcessExit);
8047
8046
  return {
8048
8047
  stage: oneOf(STAGES, failure.stage),
8049
8048
  reason: oneOf(REASONS, failure.reason),
@@ -8052,8 +8051,7 @@ function readFailure(value) {
8052
8051
  ...failure.transport == null ? {} : { transport: oneOf(TRANSPORTS, failure.transport) },
8053
8052
  ...originCode === void 0 ? {} : { originCode },
8054
8053
  ...errorMessage === void 0 ? {} : { errorMessage },
8055
- ...errorStack === void 0 ? {} : { errorStack },
8056
- ...previousProcessExit === void 0 ? {} : { previousProcessExit }
8054
+ ...errorStack === void 0 ? {} : { errorStack }
8057
8055
  };
8058
8056
  }
8059
8057
  function readHttpResponse(value) {
@@ -8143,21 +8141,11 @@ function requireEvent(payload) {
8143
8141
  ...Object.keys(read).length === 0 ? {} : { metadata: read }
8144
8142
  };
8145
8143
  }
8146
- case "UPDATE_APPLIED": return {
8144
+ case "UPDATE_APPLIED":
8145
+ case "RECOVERED": return {
8147
8146
  ...movement(),
8148
8147
  type
8149
8148
  };
8150
- case "RECOVERED": {
8151
- const previousProcessExit = readCode(metadata.previousProcessExit);
8152
- return {
8153
- ...movement(),
8154
- type,
8155
- ...previousProcessExit === void 0 ? {} : { metadata: {
8156
- ...httpMetadata,
8157
- previousProcessExit
8158
- } }
8159
- };
8160
- }
8161
8149
  case "UPDATE_FAILED": return {
8162
8150
  ...movement(),
8163
8151
  type,
@@ -8223,24 +8211,13 @@ function createBundleEventRow(input) {
8223
8211
  }
8224
8212
  };
8225
8213
  }
8226
- case "UPDATE_APPLIED": return {
8214
+ case "UPDATE_APPLIED":
8215
+ case "RECOVERED": return {
8227
8216
  ...base,
8228
8217
  from_bundle_id: input.fromBundleId,
8229
8218
  type: input.type,
8230
8219
  metadata
8231
8220
  };
8232
- case "RECOVERED": {
8233
- const exit = input.metadata?.previousProcessExit;
8234
- return {
8235
- ...base,
8236
- from_bundle_id: input.fromBundleId,
8237
- type: input.type,
8238
- metadata: {
8239
- ...metadata,
8240
- ...exit === void 0 ? {} : { previous_process_exit: exit }
8241
- }
8242
- };
8243
- }
8244
8221
  case "UPDATE_FAILED": {
8245
8222
  const { failure } = input.metadata;
8246
8223
  return {
@@ -8257,8 +8234,7 @@ function createBundleEventRow(input) {
8257
8234
  ...failure.resource === void 0 ? {} : { resource: failure.resource },
8258
8235
  ...failure.httpStatus === void 0 ? {} : { http_status: failure.httpStatus },
8259
8236
  ...failure.transport === void 0 ? {} : { transport: failure.transport },
8260
- ...failure.originCode === void 0 ? {} : { origin_code: failure.originCode },
8261
- ...failure.previousProcessExit === void 0 ? {} : { previous_process_exit: failure.previousProcessExit }
8237
+ ...failure.originCode === void 0 ? {} : { origin_code: failure.originCode }
8262
8238
  }
8263
8239
  }
8264
8240
  };
@@ -8422,10 +8398,9 @@ const toFailure = (failure) => ({
8422
8398
  ...failure.resource === void 0 ? {} : { resource: failure.resource },
8423
8399
  ...failure.http_status === void 0 ? {} : { httpStatus: failure.http_status },
8424
8400
  ...failure.transport === void 0 ? {} : { transport: failure.transport },
8425
- ...failure.origin_code === void 0 ? {} : { originCode: failure.origin_code },
8426
- ...failure.previous_process_exit === void 0 ? {} : { previousProcessExit: failure.previous_process_exit }
8401
+ ...failure.origin_code === void 0 ? {} : { originCode: failure.origin_code }
8427
8402
  });
8428
- /** What a row's metadata adds for its type: a failure, a delivery, an exit reason. */
8403
+ /** What a row's metadata adds for its type: a failure or a delivery. */
8429
8404
  const eventDetails = ({ type, metadata }) => ({
8430
8405
  ...metadata.http_response !== void 0 ? { httpResponse: {
8431
8406
  resource: metadata.http_response.resource,
@@ -8437,8 +8412,7 @@ const eventDetails = ({ type, metadata }) => ({
8437
8412
  } } : {},
8438
8413
  ...type === "UPDATE_FAILED" && metadata.failure !== void 0 ? { failure: toFailure(metadata.failure) } : {},
8439
8414
  ...type === "UPDATE_DOWNLOADED" && metadata.delivery !== void 0 ? { delivery: metadata.delivery } : {},
8440
- ...type === "UPDATE_DOWNLOADED" && metadata.patch_fallback === true ? { patchFallback: true } : {},
8441
- ...type === "RECOVERED" && metadata.previous_process_exit !== void 0 ? { previousProcessExit: metadata.previous_process_exit } : {}
8415
+ ...type === "UPDATE_DOWNLOADED" && metadata.patch_fallback === true ? { patchFallback: true } : {}
8442
8416
  });
8443
8417
  const toEventHistoryRow = (row) => ({
8444
8418
  ...row.to_release_id == null ? {} : { toReleaseId: row.to_release_id },
@@ -8861,29 +8835,42 @@ const window = {
8861
8835
  eq: ["identity"],
8862
8836
  sort: ["bucket_start_ms"]
8863
8837
  };
8838
+ /** What a release or channel row counts in every period kind. */
8839
+ const WINDOW_COUNTERS = [
8840
+ "downloads",
8841
+ "failed_launches",
8842
+ "patch_downloads",
8843
+ "patch_fallbacks"
8844
+ ];
8864
8845
  /**
8865
- * Counters of one period kind: hourly, daily, or lifetime at bucket 0. A
8866
- * release or channel row counts downloads and failed launches, and downloads
8867
- * a patch delivered and those that fell back from one. Only a release's
8868
- * lifetime row counts launches, which the bundle list reads and no windowed
8869
- * read sums. It also counts the release's failed updates (`failed_updates`),
8870
- * since no breakdown row outlives the raw period; windowed reads sum
8871
- * `insights_failures` instead.
8846
+ * Hourly or daily counters: a release or channel row counts downloads,
8847
+ * launches that crashed and recovered (`failed_launches`), and downloads a
8848
+ * patch delivered and those that fell back from one.
8872
8849
  */
8873
8850
  const counters = (retention) => defineAggregate(identityFields, {
8851
+ key: ["identity", "bucket_start_ms"],
8852
+ counters: WINDOW_COUNTERS,
8853
+ shards: COUNTER_SHARDS,
8854
+ batched: true,
8855
+ indexes: { window },
8856
+ retention
8857
+ });
8858
+ /**
8859
+ * A release's counters since its first report, at bucket 0: the windowed
8860
+ * counters, its applies, which the bundle list reads, and its failed updates
8861
+ * (`failed_updates`), since no breakdown row outlives the raw period; windowed
8862
+ * reads sum `insights_failures` instead.
8863
+ */
8864
+ const lifetimeCounters = () => defineAggregate(identityFields, {
8874
8865
  key: ["identity", "bucket_start_ms"],
8875
8866
  counters: [
8876
- "downloads",
8877
- "launches",
8878
- "failed_launches",
8879
- "failed_updates",
8880
- "patch_downloads",
8881
- "patch_fallbacks"
8867
+ ...WINDOW_COUNTERS,
8868
+ "applies",
8869
+ "failed_updates"
8882
8870
  ],
8883
8871
  shards: COUNTER_SHARDS,
8884
8872
  batched: true,
8885
- indexes: { window },
8886
- ...retention === void 0 ? {} : { retention }
8873
+ indexes: { window }
8887
8874
  });
8888
8875
  /**
8889
8876
  * Unique-installation sketches of one period kind: a usage row's active
@@ -8907,14 +8894,12 @@ const lifetimeSketches = () => defineAggregate(identityFields, {
8907
8894
  indexes: { window }
8908
8895
  });
8909
8896
  /**
8910
- * Update failures by what failed, and recoveries by why the crashed process
8911
- * exited, a row an hour. `release_id` is the failed update's target release,
8912
- * or empty for a failed check or an unknown target, and the recovered
8913
- * release for a recovery. A failure's `stage` and `reason` say where and why
8914
- * it failed, and `detail` what else the client knew, as the JSON array
8915
- * `[resource, httpStatus, originCode, transport]`. A recovery's stage is
8916
- * `launch` and its reason the exit reason, with an empty detail. Every index
8917
- * is in key order, so its counters stay blind increments.
8897
+ * Update failures by what failed, a row an hour. `release_id` is the failed
8898
+ * update's target release, or empty for a failed check or an unknown target.
8899
+ * `stage` and `reason` say where and why it failed, and `detail` what else
8900
+ * the client knew, as the JSON array `[resource, httpStatus, originCode,
8901
+ * transport]`. Every index is in key order, so its counters stay blind
8902
+ * increments.
8918
8903
  */
8919
8904
  const insightsFailures = (retention) => defineAggregate({
8920
8905
  platform: {
@@ -9114,7 +9099,7 @@ const createInsightsSchema = ({ rawDays, dailyDays } = DEFAULT_INSIGHTS_RETENTIO
9114
9099
  insights_overview_daily: counters(daily),
9115
9100
  insights_sketches_daily: sketches(daily),
9116
9101
  /** A release's counters since its first event, and its failed installations: kept. */
9117
- insights_overview_lifetime: counters(),
9102
+ insights_overview_lifetime: lifetimeCounters(),
9118
9103
  insights_sketches_lifetime: lifetimeSketches(),
9119
9104
  insights_distribution: insightsDistribution(daily),
9120
9105
  insights_latest_by_bundle: insightsLatestByBundle(daily),
@@ -9157,7 +9142,7 @@ const insightsOverviewDeltas = (event) => {
9157
9142
  counters.set(key, {
9158
9143
  identity,
9159
9144
  downloads: (previous?.downloads ?? 0) + values.downloads,
9160
- launches: (previous?.launches ?? 0) + values.launches,
9145
+ applies: (previous?.applies ?? 0) + values.applies,
9161
9146
  failedLaunches: (previous?.failedLaunches ?? 0) + values.failedLaunches,
9162
9147
  activityIdentity: previous?.activityIdentity ?? values.activityIdentity
9163
9148
  });
@@ -9165,7 +9150,7 @@ const insightsOverviewDeltas = (event) => {
9165
9150
  const metric = (releaseId, values) => {
9166
9151
  const windowed = {
9167
9152
  ...values,
9168
- launches: 0
9153
+ applies: 0
9169
9154
  };
9170
9155
  if (releaseId !== null) for (const [periodKind, start] of [["lifetime", 0], ["hour", bucketStartMs]]) append({
9171
9156
  scopeKind: "release",
@@ -9192,21 +9177,19 @@ const insightsOverviewDeltas = (event) => {
9192
9177
  };
9193
9178
  if (event.type === "UPDATE_DOWNLOADED") metric(event.to_release_id, {
9194
9179
  downloads: 1,
9195
- launches: 0,
9180
+ applies: 0,
9196
9181
  failedLaunches: 0
9197
9182
  });
9198
- else if (event.type !== "UPDATE_FAILED") {
9199
- metric(event.to_release_id, {
9200
- downloads: 0,
9201
- launches: 1,
9202
- failedLaunches: 0
9203
- });
9204
- if (event.type === "RECOVERED") metric(event.from_release_id, {
9205
- downloads: 0,
9206
- launches: 0,
9207
- failedLaunches: 1
9208
- });
9209
- }
9183
+ else if (event.type === "UPDATE_APPLIED") metric(event.to_release_id, {
9184
+ downloads: 0,
9185
+ applies: 1,
9186
+ failedLaunches: 0
9187
+ });
9188
+ else if (event.type === "RECOVERED") metric(event.from_release_id, {
9189
+ downloads: 0,
9190
+ applies: 0,
9191
+ failedLaunches: 1
9192
+ });
9210
9193
  for (const platform of [event.platform, "all"]) for (const appVersionKind of ["specific", "all"]) append({
9211
9194
  scopeKind: "usage",
9212
9195
  releaseKind: "all",
@@ -9219,7 +9202,7 @@ const insightsOverviewDeltas = (event) => {
9219
9202
  bucketStartMs
9220
9203
  }, {
9221
9204
  downloads: 0,
9222
- launches: 0,
9205
+ applies: 0,
9223
9206
  failedLaunches: 0,
9224
9207
  activityIdentity: event.install_id
9225
9208
  });
@@ -9336,7 +9319,7 @@ const countEvent = (tx, event) => {
9336
9319
  const counters = {
9337
9320
  ...Object.fromEntries([
9338
9321
  ["downloads", delta.downloads],
9339
- ["launches", delta.launches],
9322
+ ["applies", delta.applies],
9340
9323
  ["failed_launches", delta.failedLaunches]
9341
9324
  ].filter(([, value]) => value !== 0)),
9342
9325
  ...delta.downloads === 0 ? {} : patchCounters(event)
@@ -9459,23 +9442,6 @@ const countFailure = (tx, event) => {
9459
9442
  detail: failureDetail(failure)
9460
9443
  }, { events: 1 }, { shardBy });
9461
9444
  };
9462
- /**
9463
- * A recovery's exit reason, which Android 11+ reports, in the breakdown of
9464
- * the release whose launch failed: stage `launch`, the reason as its reason.
9465
- */
9466
- const countExit = (tx, event) => {
9467
- const exit = event.metadata.previous_process_exit;
9468
- if (event.type !== "RECOVERED" || exit === void 0) return;
9469
- tx.aggregate("insights_failures", {
9470
- platform: event.platform,
9471
- channel: event.channel,
9472
- bucket_start_ms: hourOf(event.received_at_ms),
9473
- release_id: event.from_release_id ?? "",
9474
- stage: "launch",
9475
- reason: exit,
9476
- detail: ""
9477
- }, { events: 1 }, { shardBy: event.install_id });
9478
- };
9479
9445
  const isNewer = (event, head) => event.received_at_ms !== head.received_at_ms ? event.received_at_ms > head.received_at_ms : compareUtf8(event.id, head.id) > 0;
9480
9446
  /** What an UNCHANGED report must share with its installation's head to repeat it. */
9481
9447
  const REPEATED_FIELDS = [
@@ -9520,7 +9486,6 @@ const recordEvent = (db, event) => {
9520
9486
  return;
9521
9487
  }
9522
9488
  countEvent(tx, event);
9523
- countExit(tx, event);
9524
9489
  if (previous !== null && !isNewer(event, previous)) return;
9525
9490
  if (previous === null) tx.create("bundle_event_heads", event);
9526
9491
  else {
@@ -9895,7 +9860,7 @@ const lifetimeMetrics = async (db, release) => {
9895
9860
  })).rows;
9896
9861
  return {
9897
9862
  downloads: lifetime?.downloads ?? 0,
9898
- launches: lifetime?.launches ?? 0,
9863
+ applies: lifetime?.applies ?? 0,
9899
9864
  failedLaunches: lifetime?.failed_launches ?? 0
9900
9865
  };
9901
9866
  };
@@ -10004,21 +9969,18 @@ const isText = (value, maxLength) => typeof value === "string" && value.length >
10004
9969
  const failed = (...rows) => countDistinct(mergeDistinct(rows.flat().map((row) => row.failed_users)));
10005
9970
  /** Entries of a count map, most first, then in key order. */
10006
9971
  const mostFirst = (counts) => [...counts].sort(([leftKey, left], [rightKey, right]) => right.events - left.events || compareUtf8(leftKey, rightKey)).map(([, entry]) => entry);
10007
- /** Failure rows as their breakdown, and recovery rows as exit reasons. */
9972
+ /** The stages a failure row records: where an update failed. */
9973
+ const FAILURE_STAGES = /* @__PURE__ */ new Set([
9974
+ "check",
9975
+ "download",
9976
+ "install",
9977
+ "unknown"
9978
+ ]);
9979
+ /** Failure rows as their breakdown by stage, reason, and detail. */
10008
9980
  const breakdownOf = (rows) => {
10009
9981
  const failures = /* @__PURE__ */ new Map();
10010
- const exits = /* @__PURE__ */ new Map();
10011
9982
  for (const { stage, reason, detail, events } of rows) {
10012
9983
  if (events <= 0) continue;
10013
- if (stage === "launch") {
10014
- const exit = exits.get(reason) ?? {
10015
- value: reason,
10016
- events: 0
10017
- };
10018
- exit.events += events;
10019
- exits.set(reason, exit);
10020
- continue;
10021
- }
10022
9984
  const key = JSON.stringify([stage, reason]);
10023
9985
  const entry = failures.get(key) ?? {
10024
9986
  value: {
@@ -10037,37 +9999,30 @@ const breakdownOf = (rows) => {
10037
9999
  entry.details.set(detail, known);
10038
10000
  failures.set(key, entry);
10039
10001
  }
10040
- return {
10041
- breakdown: mostFirst(failures).map(({ value, events }) => {
10042
- const { details } = failures.get(JSON.stringify([value.stage, value.reason]));
10043
- return {
10044
- ...value,
10045
- events,
10046
- details: mostFirst(details).map(({ value: detail, events }) => {
10047
- const [resource, httpStatus, originCode, transport] = JSON.parse(detail);
10048
- return {
10049
- resource,
10050
- httpStatus,
10051
- originCode,
10052
- transport,
10053
- events
10054
- };
10055
- })
10056
- };
10057
- }),
10058
- byExitReason: mostFirst(exits).map(({ value, events }) => ({
10059
- exitReason: value,
10060
- events
10061
- }))
10062
- };
10002
+ return mostFirst(failures).map(({ value, events }) => {
10003
+ const { details } = failures.get(JSON.stringify([value.stage, value.reason]));
10004
+ return {
10005
+ ...value,
10006
+ events,
10007
+ details: mostFirst(details).map(({ value: detail, events }) => {
10008
+ const [resource, httpStatus, originCode, transport] = JSON.parse(detail);
10009
+ return {
10010
+ resource,
10011
+ httpStatus,
10012
+ originCode,
10013
+ transport,
10014
+ events
10015
+ };
10016
+ })
10017
+ };
10018
+ });
10063
10019
  };
10064
10020
  /**
10065
10021
  * A release's or a channel's update failures. Since a release's first
10066
10022
  * report: its lifetime counters and failed installations. Over a time range
10067
- * of at most 30 days: failures, failed checks, the breakdown by stage,
10068
- * reason, and detail, and recoveries by exit reason, all summed from the
10069
- * breakdown's hourly rows; downloads and failed launches from the counters;
10070
- * failed installations from their sketches. A release reads hourly rows, a
10023
+ * of at most 30 days: failures, failed checks, and the breakdown by stage,
10024
+ * reason, and detail, all summed from the breakdown's hourly rows; downloads
10025
+ * from the counters; failed installations from their sketches. A release reads hourly rows, a
10071
10026
  * channel daily rows past 48 hours like its activity; the breakdown's rows
10072
10027
  * are hourly, so its coverage is the raw period's.
10073
10028
  */
@@ -10154,13 +10109,12 @@ const getUpdateFailures = async (db, input, now, retention) => {
10154
10109
  }))
10155
10110
  ]);
10156
10111
  const total = (field) => counters.reduce((sum, row) => sum + row[field], 0);
10157
- const scoped = releaseId === void 0 ? rows : rows.filter((row) => row.release_id === releaseId);
10112
+ const scoped = rows.filter((row) => FAILURE_STAGES.has(row.stage) && (releaseId === void 0 || row.release_id === releaseId));
10158
10113
  const failuresAt = (counted) => scoped.reduce((sum, row) => sum + (counted(row.stage) ? Math.max(0, row.events) : 0), 0);
10159
- const { breakdown, byExitReason } = breakdownOf(scoped);
10160
10114
  return {
10161
10115
  coverage: coverageOf(timeRange.start, kept.hour),
10162
10116
  measuredAtMs: at,
10163
- failedUpdates: failuresAt((stage) => stage !== "check" && stage !== "launch"),
10117
+ failedUpdates: failuresAt((stage) => stage !== "check"),
10164
10118
  failedInstallations: failed(failures),
10165
10119
  downloads: total("downloads"),
10166
10120
  patchDownloads: total("patch_downloads"),
@@ -10170,11 +10124,7 @@ const getUpdateFailures = async (db, input, now, retention) => {
10170
10124
  failedInstallations: failed(checks),
10171
10125
  activeInstallations: countDistinct(mergeDistinct(active.map((row) => row.activity_users)))
10172
10126
  } } : {},
10173
- breakdown,
10174
- recoveries: {
10175
- failedLaunches: total("failed_launches"),
10176
- byExitReason
10177
- }
10127
+ breakdown: breakdownOf(scoped)
10178
10128
  };
10179
10129
  };
10180
10130
  //#endregion
@@ -10437,7 +10387,7 @@ const insights = (options = {}) => {
10437
10387
  const plugin = definePlugin({
10438
10388
  id: "insights",
10439
10389
  namespace: false,
10440
- schemaVersion: "1.2.0",
10390
+ schemaVersion: "1.0.0",
10441
10391
  schema: createInsightsSchema(retention),
10442
10392
  init: ({ db, now }) => {
10443
10393
  const api = createInsightsApi(db, now, retention);
package/dist/index.d.cts CHANGED
@@ -53,8 +53,6 @@ type BundleEventFailure = DatabaseJsonObject & {
53
53
  readonly origin_code?: string;
54
54
  readonly error_message?: string;
55
55
  readonly error_stack?: string;
56
- /** Android's `ApplicationExitInfo` reason for the process before this one. */
57
- readonly previous_process_exit?: string;
58
56
  };
59
57
  type DatabaseHttpResponse = DatabaseJsonObject & {
60
58
  readonly resource: "catalog" | "artifact";
@@ -77,8 +75,6 @@ type DatabaseBundleEventMetadata = DatabaseJsonObject & {
77
75
  readonly delivery?: "patch" | "manifest" | "archive" | "unknown";
78
76
  /** `UPDATE_DOWNLOADED`: a patch failed and the full files came instead. */
79
77
  readonly patch_fallback?: boolean;
80
- /** `RECOVERED`: Android's `ApplicationExitInfo` reason for the crashed process. */
81
- readonly previous_process_exit?: string;
82
78
  };
83
79
  type BundleEventRowBase = {
84
80
  readonly id: string;
@@ -203,8 +199,11 @@ type InsightsCoverage = {
203
199
  * which are kept.
204
200
  */
205
201
  interface ReleaseActivityMetrics {
202
+ /** Download reports of the release. */
206
203
  readonly downloads: number;
207
- readonly launches: number;
204
+ /** Apply reports of the release: installations that started running it. */
205
+ readonly applies: number;
206
+ /** Launches that crashed on the release and recovered to the one before. */
208
207
  readonly failedLaunches: number;
209
208
  }
210
209
  interface InsightsGetReleaseActivityInput {
@@ -304,17 +303,6 @@ interface InsightsUpdateFailures {
304
303
  };
305
304
  /** Over a time range: failures by stage and reason, most first. */
306
305
  readonly breakdown?: readonly InsightsFailureBreakdown[];
307
- /**
308
- * Over a time range: failed launches, and the recoveries whose crashed
309
- * process exited for a reason Android 11+ reported, by reason, most first.
310
- */
311
- readonly recoveries?: {
312
- readonly failedLaunches: number;
313
- readonly byExitReason: readonly {
314
- readonly exitReason: string;
315
- readonly events: number;
316
- }[];
317
- };
318
306
  }
319
307
  //#endregion
320
308
  //#region ../api-keys/dist/server/model.d.mts
@@ -482,7 +470,7 @@ declare const plugins: readonly [import("@hot-updater/plugin-core").HotUpdaterPl
482
470
  readonly bucket_start_ms: {
483
471
  readonly type: "integer";
484
472
  };
485
- }, "downloads" | "launches" | "failed_launches" | "failed_updates" | "patch_downloads" | "patch_fallbacks", {
473
+ }, "downloads" | "failed_launches" | "patch_downloads" | "patch_fallbacks", {
486
474
  readonly window: {
487
475
  readonly eq: readonly ["identity"];
488
476
  readonly sort: readonly ["bucket_start_ms"];
@@ -510,7 +498,7 @@ declare const plugins: readonly [import("@hot-updater/plugin-core").HotUpdaterPl
510
498
  readonly bucket_start_ms: {
511
499
  readonly type: "integer";
512
500
  };
513
- }, "downloads" | "launches" | "failed_launches" | "failed_updates" | "patch_downloads" | "patch_fallbacks", {
501
+ }, "downloads" | "failed_launches" | "patch_downloads" | "patch_fallbacks", {
514
502
  readonly window: {
515
503
  readonly eq: readonly ["identity"];
516
504
  readonly sort: readonly ["bucket_start_ms"];
@@ -538,7 +526,7 @@ declare const plugins: readonly [import("@hot-updater/plugin-core").HotUpdaterPl
538
526
  readonly bucket_start_ms: {
539
527
  readonly type: "integer";
540
528
  };
541
- }, "downloads" | "launches" | "failed_launches" | "failed_updates" | "patch_downloads" | "patch_fallbacks", {
529
+ }, "downloads" | "failed_launches" | "patch_downloads" | "patch_fallbacks" | "applies" | "failed_updates", {
542
530
  readonly window: {
543
531
  readonly eq: readonly ["identity"];
544
532
  readonly sort: readonly ["bucket_start_ms"];
package/dist/index.d.mts CHANGED
@@ -53,8 +53,6 @@ type BundleEventFailure = DatabaseJsonObject & {
53
53
  readonly origin_code?: string;
54
54
  readonly error_message?: string;
55
55
  readonly error_stack?: string;
56
- /** Android's `ApplicationExitInfo` reason for the process before this one. */
57
- readonly previous_process_exit?: string;
58
56
  };
59
57
  type DatabaseHttpResponse = DatabaseJsonObject & {
60
58
  readonly resource: "catalog" | "artifact";
@@ -77,8 +75,6 @@ type DatabaseBundleEventMetadata = DatabaseJsonObject & {
77
75
  readonly delivery?: "patch" | "manifest" | "archive" | "unknown";
78
76
  /** `UPDATE_DOWNLOADED`: a patch failed and the full files came instead. */
79
77
  readonly patch_fallback?: boolean;
80
- /** `RECOVERED`: Android's `ApplicationExitInfo` reason for the crashed process. */
81
- readonly previous_process_exit?: string;
82
78
  };
83
79
  type BundleEventRowBase = {
84
80
  readonly id: string;
@@ -203,8 +199,11 @@ type InsightsCoverage = {
203
199
  * which are kept.
204
200
  */
205
201
  interface ReleaseActivityMetrics {
202
+ /** Download reports of the release. */
206
203
  readonly downloads: number;
207
- readonly launches: number;
204
+ /** Apply reports of the release: installations that started running it. */
205
+ readonly applies: number;
206
+ /** Launches that crashed on the release and recovered to the one before. */
208
207
  readonly failedLaunches: number;
209
208
  }
210
209
  interface InsightsGetReleaseActivityInput {
@@ -304,17 +303,6 @@ interface InsightsUpdateFailures {
304
303
  };
305
304
  /** Over a time range: failures by stage and reason, most first. */
306
305
  readonly breakdown?: readonly InsightsFailureBreakdown[];
307
- /**
308
- * Over a time range: failed launches, and the recoveries whose crashed
309
- * process exited for a reason Android 11+ reported, by reason, most first.
310
- */
311
- readonly recoveries?: {
312
- readonly failedLaunches: number;
313
- readonly byExitReason: readonly {
314
- readonly exitReason: string;
315
- readonly events: number;
316
- }[];
317
- };
318
306
  }
319
307
  //#endregion
320
308
  //#region ../api-keys/dist/server/model.d.mts
@@ -482,7 +470,7 @@ declare const plugins: readonly [import("@hot-updater/plugin-core").HotUpdaterPl
482
470
  readonly bucket_start_ms: {
483
471
  readonly type: "integer";
484
472
  };
485
- }, "downloads" | "launches" | "failed_launches" | "failed_updates" | "patch_downloads" | "patch_fallbacks", {
473
+ }, "downloads" | "failed_launches" | "patch_downloads" | "patch_fallbacks", {
486
474
  readonly window: {
487
475
  readonly eq: readonly ["identity"];
488
476
  readonly sort: readonly ["bucket_start_ms"];
@@ -510,7 +498,7 @@ declare const plugins: readonly [import("@hot-updater/plugin-core").HotUpdaterPl
510
498
  readonly bucket_start_ms: {
511
499
  readonly type: "integer";
512
500
  };
513
- }, "downloads" | "launches" | "failed_launches" | "failed_updates" | "patch_downloads" | "patch_fallbacks", {
501
+ }, "downloads" | "failed_launches" | "patch_downloads" | "patch_fallbacks", {
514
502
  readonly window: {
515
503
  readonly eq: readonly ["identity"];
516
504
  readonly sort: readonly ["bucket_start_ms"];
@@ -538,7 +526,7 @@ declare const plugins: readonly [import("@hot-updater/plugin-core").HotUpdaterPl
538
526
  readonly bucket_start_ms: {
539
527
  readonly type: "integer";
540
528
  };
541
- }, "downloads" | "launches" | "failed_launches" | "failed_updates" | "patch_downloads" | "patch_fallbacks", {
529
+ }, "downloads" | "failed_launches" | "patch_downloads" | "patch_fallbacks" | "applies" | "failed_updates", {
542
530
  readonly window: {
543
531
  readonly eq: readonly ["identity"];
544
532
  readonly sort: readonly ["bucket_start_ms"];
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.32",
4
+ "version": "1.0.0-rc.34",
5
5
  "description": "React Native OTA solution for self-hosted",
6
6
  "main": "dist/index.cjs",
7
7
  "types": "dist/index.d.cts",
@@ -37,10 +37,10 @@
37
37
  ],
38
38
  "dependencies": {
39
39
  "hono": "4.12.34",
40
- "@hot-updater/cli-tools": "1.0.0-rc.32",
41
- "@hot-updater/plugin-core": "1.0.0-rc.29",
42
- "@hot-updater/protocol": "1.0.0-rc.29",
43
- "@hot-updater/server": "1.0.0-rc.32"
40
+ "@hot-updater/cli-tools": "1.0.0-rc.34",
41
+ "@hot-updater/protocol": "1.0.0-rc.30",
42
+ "@hot-updater/plugin-core": "1.0.0-rc.30",
43
+ "@hot-updater/server": "1.0.0-rc.34"
44
44
  },
45
45
  "publishConfig": {
46
46
  "access": "public"
@@ -55,8 +55,8 @@
55
55
  "firebase-tools": "15.29.0",
56
56
  "fkill": "^9.0.0",
57
57
  "mime": "^4.0.4",
58
- "@hot-updater/mock": "1.0.0-rc.29",
59
- "@hot-updater/test-utils": "1.0.0-rc.30"
58
+ "@hot-updater/mock": "1.0.0-rc.30",
59
+ "@hot-updater/test-utils": "1.0.0-rc.32"
60
60
  },
61
61
  "peerDependencies": {
62
62
  "firebase-admin": "*",