@hot-updater/plugin-insights 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.
@@ -365,7 +365,7 @@ const lifetimeMetrics = async (db, release) => {
365
365
  })).rows;
366
366
  return {
367
367
  downloads: lifetime?.downloads ?? 0,
368
- launches: lifetime?.launches ?? 0,
368
+ applies: lifetime?.applies ?? 0,
369
369
  failedLaunches: lifetime?.failed_launches ?? 0
370
370
  };
371
371
  };
@@ -474,21 +474,18 @@ const isText = (value, maxLength) => typeof value === "string" && value.length >
474
474
  const failed = (...rows) => (0, _hot_updater_plugin_core.countDistinct)((0, _hot_updater_plugin_core.mergeDistinct)(rows.flat().map((row) => row.failed_users)));
475
475
  /** Entries of a count map, most first, then in key order. */
476
476
  const mostFirst = (counts) => [...counts].sort(([leftKey, left], [rightKey, right]) => right.events - left.events || (0, _hot_updater_plugin_core.compareUtf8)(leftKey, rightKey)).map(([, entry]) => entry);
477
- /** Failure rows as their breakdown, and recovery rows as exit reasons. */
477
+ /** The stages a failure row records: where an update failed. */
478
+ const FAILURE_STAGES = /* @__PURE__ */ new Set([
479
+ "check",
480
+ "download",
481
+ "install",
482
+ "unknown"
483
+ ]);
484
+ /** Failure rows as their breakdown by stage, reason, and detail. */
478
485
  const breakdownOf = (rows) => {
479
486
  const failures = /* @__PURE__ */ new Map();
480
- const exits = /* @__PURE__ */ new Map();
481
487
  for (const { stage, reason, detail, events } of rows) {
482
488
  if (events <= 0) continue;
483
- if (stage === "launch") {
484
- const exit = exits.get(reason) ?? {
485
- value: reason,
486
- events: 0
487
- };
488
- exit.events += events;
489
- exits.set(reason, exit);
490
- continue;
491
- }
492
489
  const key = JSON.stringify([stage, reason]);
493
490
  const entry = failures.get(key) ?? {
494
491
  value: {
@@ -507,37 +504,30 @@ const breakdownOf = (rows) => {
507
504
  entry.details.set(detail, known);
508
505
  failures.set(key, entry);
509
506
  }
510
- return {
511
- breakdown: mostFirst(failures).map(({ value, events }) => {
512
- const { details } = failures.get(JSON.stringify([value.stage, value.reason]));
513
- return {
514
- ...value,
515
- events,
516
- details: mostFirst(details).map(({ value: detail, events }) => {
517
- const [resource, httpStatus, originCode, transport] = JSON.parse(detail);
518
- return {
519
- resource,
520
- httpStatus,
521
- originCode,
522
- transport,
523
- events
524
- };
525
- })
526
- };
527
- }),
528
- byExitReason: mostFirst(exits).map(({ value, events }) => ({
529
- exitReason: value,
530
- events
531
- }))
532
- };
507
+ return mostFirst(failures).map(({ value, events }) => {
508
+ const { details } = failures.get(JSON.stringify([value.stage, value.reason]));
509
+ return {
510
+ ...value,
511
+ events,
512
+ details: mostFirst(details).map(({ value: detail, events }) => {
513
+ const [resource, httpStatus, originCode, transport] = JSON.parse(detail);
514
+ return {
515
+ resource,
516
+ httpStatus,
517
+ originCode,
518
+ transport,
519
+ events
520
+ };
521
+ })
522
+ };
523
+ });
533
524
  };
534
525
  /**
535
526
  * A release's or a channel's update failures. Since a release's first
536
527
  * report: its lifetime counters and failed installations. Over a time range
537
- * of at most 30 days: failures, failed checks, the breakdown by stage,
538
- * reason, and detail, and recoveries by exit reason, all summed from the
539
- * breakdown's hourly rows; downloads and failed launches from the counters;
540
- * failed installations from their sketches. A release reads hourly rows, a
528
+ * of at most 30 days: failures, failed checks, and the breakdown by stage,
529
+ * reason, and detail, all summed from the breakdown's hourly rows; downloads
530
+ * from the counters; failed installations from their sketches. A release reads hourly rows, a
541
531
  * channel daily rows past 48 hours like its activity; the breakdown's rows
542
532
  * are hourly, so its coverage is the raw period's.
543
533
  */
@@ -624,13 +614,12 @@ const getUpdateFailures = async (db, input, now, retention) => {
624
614
  }))
625
615
  ]);
626
616
  const total = (field) => counters.reduce((sum, row) => sum + row[field], 0);
627
- const scoped = releaseId === void 0 ? rows : rows.filter((row) => row.release_id === releaseId);
617
+ const scoped = rows.filter((row) => FAILURE_STAGES.has(row.stage) && (releaseId === void 0 || row.release_id === releaseId));
628
618
  const failuresAt = (counted) => scoped.reduce((sum, row) => sum + (counted(row.stage) ? Math.max(0, row.events) : 0), 0);
629
- const { breakdown, byExitReason } = breakdownOf(scoped);
630
619
  return {
631
620
  coverage: coverageOf(timeRange.start, kept.hour),
632
621
  measuredAtMs: at,
633
- failedUpdates: failuresAt((stage) => stage !== "check" && stage !== "launch"),
622
+ failedUpdates: failuresAt((stage) => stage !== "check"),
634
623
  failedInstallations: failed(failures),
635
624
  downloads: total("downloads"),
636
625
  patchDownloads: total("patch_downloads"),
@@ -640,11 +629,7 @@ const getUpdateFailures = async (db, input, now, retention) => {
640
629
  failedInstallations: failed(checks),
641
630
  activeInstallations: (0, _hot_updater_plugin_core.countDistinct)((0, _hot_updater_plugin_core.mergeDistinct)(active.map((row) => row.activity_users)))
642
631
  } } : {},
643
- breakdown,
644
- recoveries: {
645
- failedLaunches: total("failed_launches"),
646
- byExitReason
647
- }
632
+ breakdown: breakdownOf(scoped)
648
633
  };
649
634
  };
650
635
  //#endregion
@@ -44,17 +44,6 @@ interface InsightsUpdateFailures {
44
44
  };
45
45
  /** Over a time range: failures by stage and reason, most first. */
46
46
  readonly breakdown?: readonly InsightsFailureBreakdown[];
47
- /**
48
- * Over a time range: failed launches, and the recoveries whose crashed
49
- * process exited for a reason Android 11+ reported, by reason, most first.
50
- */
51
- readonly recoveries?: {
52
- readonly failedLaunches: number;
53
- readonly byExitReason: readonly {
54
- readonly exitReason: string;
55
- readonly events: number;
56
- }[];
57
- };
58
47
  }
59
48
  //#endregion
60
49
  export { InsightsFailureBreakdown, InsightsUpdateFailures, InsightsUpdateFailuresInput };
@@ -44,17 +44,6 @@ interface InsightsUpdateFailures {
44
44
  };
45
45
  /** Over a time range: failures by stage and reason, most first. */
46
46
  readonly breakdown?: readonly InsightsFailureBreakdown[];
47
- /**
48
- * Over a time range: failed launches, and the recoveries whose crashed
49
- * process exited for a reason Android 11+ reported, by reason, most first.
50
- */
51
- readonly recoveries?: {
52
- readonly failedLaunches: number;
53
- readonly byExitReason: readonly {
54
- readonly exitReason: string;
55
- readonly events: number;
56
- }[];
57
- };
58
47
  }
59
48
  //#endregion
60
49
  export { InsightsFailureBreakdown, InsightsUpdateFailures, InsightsUpdateFailuresInput };
@@ -365,7 +365,7 @@ const lifetimeMetrics = async (db, release) => {
365
365
  })).rows;
366
366
  return {
367
367
  downloads: lifetime?.downloads ?? 0,
368
- launches: lifetime?.launches ?? 0,
368
+ applies: lifetime?.applies ?? 0,
369
369
  failedLaunches: lifetime?.failed_launches ?? 0
370
370
  };
371
371
  };
@@ -474,21 +474,18 @@ const isText = (value, maxLength) => typeof value === "string" && value.length >
474
474
  const failed = (...rows) => countDistinct(mergeDistinct(rows.flat().map((row) => row.failed_users)));
475
475
  /** Entries of a count map, most first, then in key order. */
476
476
  const mostFirst = (counts) => [...counts].sort(([leftKey, left], [rightKey, right]) => right.events - left.events || compareUtf8(leftKey, rightKey)).map(([, entry]) => entry);
477
- /** Failure rows as their breakdown, and recovery rows as exit reasons. */
477
+ /** The stages a failure row records: where an update failed. */
478
+ const FAILURE_STAGES = /* @__PURE__ */ new Set([
479
+ "check",
480
+ "download",
481
+ "install",
482
+ "unknown"
483
+ ]);
484
+ /** Failure rows as their breakdown by stage, reason, and detail. */
478
485
  const breakdownOf = (rows) => {
479
486
  const failures = /* @__PURE__ */ new Map();
480
- const exits = /* @__PURE__ */ new Map();
481
487
  for (const { stage, reason, detail, events } of rows) {
482
488
  if (events <= 0) continue;
483
- if (stage === "launch") {
484
- const exit = exits.get(reason) ?? {
485
- value: reason,
486
- events: 0
487
- };
488
- exit.events += events;
489
- exits.set(reason, exit);
490
- continue;
491
- }
492
489
  const key = JSON.stringify([stage, reason]);
493
490
  const entry = failures.get(key) ?? {
494
491
  value: {
@@ -507,37 +504,30 @@ const breakdownOf = (rows) => {
507
504
  entry.details.set(detail, known);
508
505
  failures.set(key, entry);
509
506
  }
510
- return {
511
- breakdown: mostFirst(failures).map(({ value, events }) => {
512
- const { details } = failures.get(JSON.stringify([value.stage, value.reason]));
513
- return {
514
- ...value,
515
- events,
516
- details: mostFirst(details).map(({ value: detail, events }) => {
517
- const [resource, httpStatus, originCode, transport] = JSON.parse(detail);
518
- return {
519
- resource,
520
- httpStatus,
521
- originCode,
522
- transport,
523
- events
524
- };
525
- })
526
- };
527
- }),
528
- byExitReason: mostFirst(exits).map(({ value, events }) => ({
529
- exitReason: value,
530
- events
531
- }))
532
- };
507
+ return mostFirst(failures).map(({ value, events }) => {
508
+ const { details } = failures.get(JSON.stringify([value.stage, value.reason]));
509
+ return {
510
+ ...value,
511
+ events,
512
+ details: mostFirst(details).map(({ value: detail, events }) => {
513
+ const [resource, httpStatus, originCode, transport] = JSON.parse(detail);
514
+ return {
515
+ resource,
516
+ httpStatus,
517
+ originCode,
518
+ transport,
519
+ events
520
+ };
521
+ })
522
+ };
523
+ });
533
524
  };
534
525
  /**
535
526
  * A release's or a channel's update failures. Since a release's first
536
527
  * report: its lifetime counters and failed installations. Over a time range
537
- * of at most 30 days: failures, failed checks, the breakdown by stage,
538
- * reason, and detail, and recoveries by exit reason, all summed from the
539
- * breakdown's hourly rows; downloads and failed launches from the counters;
540
- * failed installations from their sketches. A release reads hourly rows, a
528
+ * of at most 30 days: failures, failed checks, and the breakdown by stage,
529
+ * reason, and detail, all summed from the breakdown's hourly rows; downloads
530
+ * from the counters; failed installations from their sketches. A release reads hourly rows, a
541
531
  * channel daily rows past 48 hours like its activity; the breakdown's rows
542
532
  * are hourly, so its coverage is the raw period's.
543
533
  */
@@ -624,13 +614,12 @@ const getUpdateFailures = async (db, input, now, retention) => {
624
614
  }))
625
615
  ]);
626
616
  const total = (field) => counters.reduce((sum, row) => sum + row[field], 0);
627
- const scoped = releaseId === void 0 ? rows : rows.filter((row) => row.release_id === releaseId);
617
+ const scoped = rows.filter((row) => FAILURE_STAGES.has(row.stage) && (releaseId === void 0 || row.release_id === releaseId));
628
618
  const failuresAt = (counted) => scoped.reduce((sum, row) => sum + (counted(row.stage) ? Math.max(0, row.events) : 0), 0);
629
- const { breakdown, byExitReason } = breakdownOf(scoped);
630
619
  return {
631
620
  coverage: coverageOf(timeRange.start, kept.hour),
632
621
  measuredAtMs: at,
633
- failedUpdates: failuresAt((stage) => stage !== "check" && stage !== "launch"),
622
+ failedUpdates: failuresAt((stage) => stage !== "check"),
634
623
  failedInstallations: failed(failures),
635
624
  downloads: total("downloads"),
636
625
  patchDownloads: total("patch_downloads"),
@@ -640,11 +629,7 @@ const getUpdateFailures = async (db, input, now, retention) => {
640
629
  failedInstallations: failed(checks),
641
630
  activeInstallations: countDistinct(mergeDistinct(active.map((row) => row.activity_users)))
642
631
  } } : {},
643
- breakdown,
644
- recoveries: {
645
- failedLaunches: total("failed_launches"),
646
- byExitReason
647
- }
632
+ breakdown: breakdownOf(scoped)
648
633
  };
649
634
  };
650
635
  //#endregion
@@ -112,7 +112,7 @@ const countEvent = (tx, event) => {
112
112
  const counters = {
113
113
  ...Object.fromEntries([
114
114
  ["downloads", delta.downloads],
115
- ["launches", delta.launches],
115
+ ["applies", delta.applies],
116
116
  ["failed_launches", delta.failedLaunches]
117
117
  ].filter(([, value]) => value !== 0)),
118
118
  ...delta.downloads === 0 ? {} : patchCounters(event)
@@ -235,23 +235,6 @@ const countFailure = (tx, event) => {
235
235
  detail: failureDetail(failure)
236
236
  }, { events: 1 }, { shardBy });
237
237
  };
238
- /**
239
- * A recovery's exit reason, which Android 11+ reports, in the breakdown of
240
- * the release whose launch failed: stage `launch`, the reason as its reason.
241
- */
242
- const countExit = (tx, event) => {
243
- const exit = event.metadata.previous_process_exit;
244
- if (event.type !== "RECOVERED" || exit === void 0) return;
245
- tx.aggregate("insights_failures", {
246
- platform: event.platform,
247
- channel: event.channel,
248
- bucket_start_ms: hourOf(event.received_at_ms),
249
- release_id: event.from_release_id ?? "",
250
- stage: "launch",
251
- reason: exit,
252
- detail: ""
253
- }, { events: 1 }, { shardBy: event.install_id });
254
- };
255
238
  const isNewer = (event, head) => event.received_at_ms !== head.received_at_ms ? event.received_at_ms > head.received_at_ms : (0, _hot_updater_plugin_core.compareUtf8)(event.id, head.id) > 0;
256
239
  /** What an UNCHANGED report must share with its installation's head to repeat it. */
257
240
  const REPEATED_FIELDS = [
@@ -296,7 +279,6 @@ const recordEvent = (db, event) => {
296
279
  return;
297
280
  }
298
281
  countEvent(tx, event);
299
- countExit(tx, event);
300
282
  if (previous !== null && !isNewer(event, previous)) return;
301
283
  if (previous === null) tx.create("bundle_event_heads", event);
302
284
  else {
@@ -112,7 +112,7 @@ const countEvent = (tx, event) => {
112
112
  const counters = {
113
113
  ...Object.fromEntries([
114
114
  ["downloads", delta.downloads],
115
- ["launches", delta.launches],
115
+ ["applies", delta.applies],
116
116
  ["failed_launches", delta.failedLaunches]
117
117
  ].filter(([, value]) => value !== 0)),
118
118
  ...delta.downloads === 0 ? {} : patchCounters(event)
@@ -235,23 +235,6 @@ const countFailure = (tx, event) => {
235
235
  detail: failureDetail(failure)
236
236
  }, { events: 1 }, { shardBy });
237
237
  };
238
- /**
239
- * A recovery's exit reason, which Android 11+ reports, in the breakdown of
240
- * the release whose launch failed: stage `launch`, the reason as its reason.
241
- */
242
- const countExit = (tx, event) => {
243
- const exit = event.metadata.previous_process_exit;
244
- if (event.type !== "RECOVERED" || exit === void 0) return;
245
- tx.aggregate("insights_failures", {
246
- platform: event.platform,
247
- channel: event.channel,
248
- bucket_start_ms: hourOf(event.received_at_ms),
249
- release_id: event.from_release_id ?? "",
250
- stage: "launch",
251
- reason: exit,
252
- detail: ""
253
- }, { events: 1 }, { shardBy: event.install_id });
254
- };
255
238
  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;
256
239
  /** What an UNCHANGED report must share with its installation's head to repeat it. */
257
240
  const REPEATED_FIELDS = [
@@ -296,7 +279,6 @@ const recordEvent = (db, event) => {
296
279
  return;
297
280
  }
298
281
  countEvent(tx, event);
299
- countExit(tx, event);
300
282
  if (previous !== null && !isNewer(event, previous)) return;
301
283
  if (previous === null) tx.create("bundle_event_heads", event);
302
284
  else {
@@ -206,29 +206,42 @@ const window = {
206
206
  eq: ["identity"],
207
207
  sort: ["bucket_start_ms"]
208
208
  };
209
+ /** What a release or channel row counts in every period kind. */
210
+ const WINDOW_COUNTERS = [
211
+ "downloads",
212
+ "failed_launches",
213
+ "patch_downloads",
214
+ "patch_fallbacks"
215
+ ];
209
216
  /**
210
- * Counters of one period kind: hourly, daily, or lifetime at bucket 0. A
211
- * release or channel row counts downloads and failed launches, and downloads
212
- * a patch delivered and those that fell back from one. Only a release's
213
- * lifetime row counts launches, which the bundle list reads and no windowed
214
- * read sums. It also counts the release's failed updates (`failed_updates`),
215
- * since no breakdown row outlives the raw period; windowed reads sum
216
- * `insights_failures` instead.
217
+ * Hourly or daily counters: a release or channel row counts downloads,
218
+ * launches that crashed and recovered (`failed_launches`), and downloads a
219
+ * patch delivered and those that fell back from one.
217
220
  */
218
221
  const counters = (retention) => (0, _hot_updater_plugin_core.defineAggregate)(identityFields, {
222
+ key: ["identity", "bucket_start_ms"],
223
+ counters: WINDOW_COUNTERS,
224
+ shards: COUNTER_SHARDS,
225
+ batched: true,
226
+ indexes: { window },
227
+ retention
228
+ });
229
+ /**
230
+ * A release's counters since its first report, at bucket 0: the windowed
231
+ * counters, its applies, which the bundle list reads, and its failed updates
232
+ * (`failed_updates`), since no breakdown row outlives the raw period; windowed
233
+ * reads sum `insights_failures` instead.
234
+ */
235
+ const lifetimeCounters = () => (0, _hot_updater_plugin_core.defineAggregate)(identityFields, {
219
236
  key: ["identity", "bucket_start_ms"],
220
237
  counters: [
221
- "downloads",
222
- "launches",
223
- "failed_launches",
224
- "failed_updates",
225
- "patch_downloads",
226
- "patch_fallbacks"
238
+ ...WINDOW_COUNTERS,
239
+ "applies",
240
+ "failed_updates"
227
241
  ],
228
242
  shards: COUNTER_SHARDS,
229
243
  batched: true,
230
- indexes: { window },
231
- ...retention === void 0 ? {} : { retention }
244
+ indexes: { window }
232
245
  });
233
246
  /**
234
247
  * Unique-installation sketches of one period kind: a usage row's active
@@ -252,14 +265,12 @@ const lifetimeSketches = () => (0, _hot_updater_plugin_core.defineAggregate)(ide
252
265
  indexes: { window }
253
266
  });
254
267
  /**
255
- * Update failures by what failed, and recoveries by why the crashed process
256
- * exited, a row an hour. `release_id` is the failed update's target release,
257
- * or empty for a failed check or an unknown target, and the recovered
258
- * release for a recovery. A failure's `stage` and `reason` say where and why
259
- * it failed, and `detail` what else the client knew, as the JSON array
260
- * `[resource, httpStatus, originCode, transport]`. A recovery's stage is
261
- * `launch` and its reason the exit reason, with an empty detail. Every index
262
- * is in key order, so its counters stay blind increments.
268
+ * Update failures by what failed, a row an hour. `release_id` is the failed
269
+ * update's target release, or empty for a failed check or an unknown target.
270
+ * `stage` and `reason` say where and why it failed, and `detail` what else
271
+ * the client knew, as the JSON array `[resource, httpStatus, originCode,
272
+ * transport]`. Every index is in key order, so its counters stay blind
273
+ * increments.
263
274
  */
264
275
  const insightsFailures = (retention) => (0, _hot_updater_plugin_core.defineAggregate)({
265
276
  platform: {
@@ -459,7 +470,7 @@ const createInsightsSchema = ({ rawDays, dailyDays } = DEFAULT_INSIGHTS_RETENTIO
459
470
  insights_overview_daily: counters(daily),
460
471
  insights_sketches_daily: sketches(daily),
461
472
  /** A release's counters since its first event, and its failed installations: kept. */
462
- insights_overview_lifetime: counters(),
473
+ insights_overview_lifetime: lifetimeCounters(),
463
474
  insights_sketches_lifetime: lifetimeSketches(),
464
475
  insights_distribution: insightsDistribution(daily),
465
476
  insights_latest_by_bundle: insightsLatestByBundle(daily),
@@ -153,7 +153,7 @@ declare const createInsightsSchema: ({ rawDays, dailyDays }?: InsightsRetention)
153
153
  readonly bucket_start_ms: {
154
154
  readonly type: "integer";
155
155
  };
156
- }, "downloads" | "launches" | "failed_launches" | "failed_updates" | "patch_downloads" | "patch_fallbacks", {
156
+ }, "downloads" | "failed_launches" | "patch_downloads" | "patch_fallbacks", {
157
157
  readonly window: {
158
158
  readonly eq: readonly ["identity"];
159
159
  readonly sort: readonly ["bucket_start_ms"];
@@ -182,7 +182,7 @@ declare const createInsightsSchema: ({ rawDays, dailyDays }?: InsightsRetention)
182
182
  readonly bucket_start_ms: {
183
183
  readonly type: "integer";
184
184
  };
185
- }, "downloads" | "launches" | "failed_launches" | "failed_updates" | "patch_downloads" | "patch_fallbacks", {
185
+ }, "downloads" | "failed_launches" | "patch_downloads" | "patch_fallbacks", {
186
186
  readonly window: {
187
187
  readonly eq: readonly ["identity"];
188
188
  readonly sort: readonly ["bucket_start_ms"];
@@ -211,7 +211,7 @@ declare const createInsightsSchema: ({ rawDays, dailyDays }?: InsightsRetention)
211
211
  readonly bucket_start_ms: {
212
212
  readonly type: "integer";
213
213
  };
214
- }, "downloads" | "launches" | "failed_launches" | "failed_updates" | "patch_downloads" | "patch_fallbacks", {
214
+ }, "downloads" | "failed_launches" | "patch_downloads" | "patch_fallbacks" | "applies" | "failed_updates", {
215
215
  readonly window: {
216
216
  readonly eq: readonly ["identity"];
217
217
  readonly sort: readonly ["bucket_start_ms"];
@@ -488,7 +488,7 @@ declare const insightsSchema: {
488
488
  readonly bucket_start_ms: {
489
489
  readonly type: "integer";
490
490
  };
491
- }, "downloads" | "launches" | "failed_launches" | "failed_updates" | "patch_downloads" | "patch_fallbacks", {
491
+ }, "downloads" | "failed_launches" | "patch_downloads" | "patch_fallbacks", {
492
492
  readonly window: {
493
493
  readonly eq: readonly ["identity"];
494
494
  readonly sort: readonly ["bucket_start_ms"];
@@ -517,7 +517,7 @@ declare const insightsSchema: {
517
517
  readonly bucket_start_ms: {
518
518
  readonly type: "integer";
519
519
  };
520
- }, "downloads" | "launches" | "failed_launches" | "failed_updates" | "patch_downloads" | "patch_fallbacks", {
520
+ }, "downloads" | "failed_launches" | "patch_downloads" | "patch_fallbacks", {
521
521
  readonly window: {
522
522
  readonly eq: readonly ["identity"];
523
523
  readonly sort: readonly ["bucket_start_ms"];
@@ -546,7 +546,7 @@ declare const insightsSchema: {
546
546
  readonly bucket_start_ms: {
547
547
  readonly type: "integer";
548
548
  };
549
- }, "downloads" | "launches" | "failed_launches" | "failed_updates" | "patch_downloads" | "patch_fallbacks", {
549
+ }, "downloads" | "failed_launches" | "patch_downloads" | "patch_fallbacks" | "applies" | "failed_updates", {
550
550
  readonly window: {
551
551
  readonly eq: readonly ["identity"];
552
552
  readonly sort: readonly ["bucket_start_ms"];
@@ -153,7 +153,7 @@ declare const createInsightsSchema: ({ rawDays, dailyDays }?: InsightsRetention)
153
153
  readonly bucket_start_ms: {
154
154
  readonly type: "integer";
155
155
  };
156
- }, "downloads" | "launches" | "failed_launches" | "failed_updates" | "patch_downloads" | "patch_fallbacks", {
156
+ }, "downloads" | "failed_launches" | "patch_downloads" | "patch_fallbacks", {
157
157
  readonly window: {
158
158
  readonly eq: readonly ["identity"];
159
159
  readonly sort: readonly ["bucket_start_ms"];
@@ -182,7 +182,7 @@ declare const createInsightsSchema: ({ rawDays, dailyDays }?: InsightsRetention)
182
182
  readonly bucket_start_ms: {
183
183
  readonly type: "integer";
184
184
  };
185
- }, "downloads" | "launches" | "failed_launches" | "failed_updates" | "patch_downloads" | "patch_fallbacks", {
185
+ }, "downloads" | "failed_launches" | "patch_downloads" | "patch_fallbacks", {
186
186
  readonly window: {
187
187
  readonly eq: readonly ["identity"];
188
188
  readonly sort: readonly ["bucket_start_ms"];
@@ -211,7 +211,7 @@ declare const createInsightsSchema: ({ rawDays, dailyDays }?: InsightsRetention)
211
211
  readonly bucket_start_ms: {
212
212
  readonly type: "integer";
213
213
  };
214
- }, "downloads" | "launches" | "failed_launches" | "failed_updates" | "patch_downloads" | "patch_fallbacks", {
214
+ }, "downloads" | "failed_launches" | "patch_downloads" | "patch_fallbacks" | "applies" | "failed_updates", {
215
215
  readonly window: {
216
216
  readonly eq: readonly ["identity"];
217
217
  readonly sort: readonly ["bucket_start_ms"];
@@ -488,7 +488,7 @@ declare const insightsSchema: {
488
488
  readonly bucket_start_ms: {
489
489
  readonly type: "integer";
490
490
  };
491
- }, "downloads" | "launches" | "failed_launches" | "failed_updates" | "patch_downloads" | "patch_fallbacks", {
491
+ }, "downloads" | "failed_launches" | "patch_downloads" | "patch_fallbacks", {
492
492
  readonly window: {
493
493
  readonly eq: readonly ["identity"];
494
494
  readonly sort: readonly ["bucket_start_ms"];
@@ -517,7 +517,7 @@ declare const insightsSchema: {
517
517
  readonly bucket_start_ms: {
518
518
  readonly type: "integer";
519
519
  };
520
- }, "downloads" | "launches" | "failed_launches" | "failed_updates" | "patch_downloads" | "patch_fallbacks", {
520
+ }, "downloads" | "failed_launches" | "patch_downloads" | "patch_fallbacks", {
521
521
  readonly window: {
522
522
  readonly eq: readonly ["identity"];
523
523
  readonly sort: readonly ["bucket_start_ms"];
@@ -546,7 +546,7 @@ declare const insightsSchema: {
546
546
  readonly bucket_start_ms: {
547
547
  readonly type: "integer";
548
548
  };
549
- }, "downloads" | "launches" | "failed_launches" | "failed_updates" | "patch_downloads" | "patch_fallbacks", {
549
+ }, "downloads" | "failed_launches" | "patch_downloads" | "patch_fallbacks" | "applies" | "failed_updates", {
550
550
  readonly window: {
551
551
  readonly eq: readonly ["identity"];
552
552
  readonly sort: readonly ["bucket_start_ms"];
@@ -206,29 +206,42 @@ const window = {
206
206
  eq: ["identity"],
207
207
  sort: ["bucket_start_ms"]
208
208
  };
209
+ /** What a release or channel row counts in every period kind. */
210
+ const WINDOW_COUNTERS = [
211
+ "downloads",
212
+ "failed_launches",
213
+ "patch_downloads",
214
+ "patch_fallbacks"
215
+ ];
209
216
  /**
210
- * Counters of one period kind: hourly, daily, or lifetime at bucket 0. A
211
- * release or channel row counts downloads and failed launches, and downloads
212
- * a patch delivered and those that fell back from one. Only a release's
213
- * lifetime row counts launches, which the bundle list reads and no windowed
214
- * read sums. It also counts the release's failed updates (`failed_updates`),
215
- * since no breakdown row outlives the raw period; windowed reads sum
216
- * `insights_failures` instead.
217
+ * Hourly or daily counters: a release or channel row counts downloads,
218
+ * launches that crashed and recovered (`failed_launches`), and downloads a
219
+ * patch delivered and those that fell back from one.
217
220
  */
218
221
  const counters = (retention) => defineAggregate(identityFields, {
222
+ key: ["identity", "bucket_start_ms"],
223
+ counters: WINDOW_COUNTERS,
224
+ shards: COUNTER_SHARDS,
225
+ batched: true,
226
+ indexes: { window },
227
+ retention
228
+ });
229
+ /**
230
+ * A release's counters since its first report, at bucket 0: the windowed
231
+ * counters, its applies, which the bundle list reads, and its failed updates
232
+ * (`failed_updates`), since no breakdown row outlives the raw period; windowed
233
+ * reads sum `insights_failures` instead.
234
+ */
235
+ const lifetimeCounters = () => defineAggregate(identityFields, {
219
236
  key: ["identity", "bucket_start_ms"],
220
237
  counters: [
221
- "downloads",
222
- "launches",
223
- "failed_launches",
224
- "failed_updates",
225
- "patch_downloads",
226
- "patch_fallbacks"
238
+ ...WINDOW_COUNTERS,
239
+ "applies",
240
+ "failed_updates"
227
241
  ],
228
242
  shards: COUNTER_SHARDS,
229
243
  batched: true,
230
- indexes: { window },
231
- ...retention === void 0 ? {} : { retention }
244
+ indexes: { window }
232
245
  });
233
246
  /**
234
247
  * Unique-installation sketches of one period kind: a usage row's active
@@ -252,14 +265,12 @@ const lifetimeSketches = () => defineAggregate(identityFields, {
252
265
  indexes: { window }
253
266
  });
254
267
  /**
255
- * Update failures by what failed, and recoveries by why the crashed process
256
- * exited, a row an hour. `release_id` is the failed update's target release,
257
- * or empty for a failed check or an unknown target, and the recovered
258
- * release for a recovery. A failure's `stage` and `reason` say where and why
259
- * it failed, and `detail` what else the client knew, as the JSON array
260
- * `[resource, httpStatus, originCode, transport]`. A recovery's stage is
261
- * `launch` and its reason the exit reason, with an empty detail. Every index
262
- * is in key order, so its counters stay blind increments.
268
+ * Update failures by what failed, a row an hour. `release_id` is the failed
269
+ * update's target release, or empty for a failed check or an unknown target.
270
+ * `stage` and `reason` say where and why it failed, and `detail` what else
271
+ * the client knew, as the JSON array `[resource, httpStatus, originCode,
272
+ * transport]`. Every index is in key order, so its counters stay blind
273
+ * increments.
263
274
  */
264
275
  const insightsFailures = (retention) => defineAggregate({
265
276
  platform: {
@@ -459,7 +470,7 @@ const createInsightsSchema = ({ rawDays, dailyDays } = DEFAULT_INSIGHTS_RETENTIO
459
470
  insights_overview_daily: counters(daily),
460
471
  insights_sketches_daily: sketches(daily),
461
472
  /** A release's counters since its first event, and its failed installations: kept. */
462
- insights_overview_lifetime: counters(),
473
+ insights_overview_lifetime: lifetimeCounters(),
463
474
  insights_sketches_lifetime: lifetimeSketches(),
464
475
  insights_distribution: insightsDistribution(daily),
465
476
  insights_latest_by_bundle: insightsLatestByBundle(daily),