@hot-updater/plugin-insights 1.0.0-rc.29 → 1.0.0-rc.30

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.
@@ -345,19 +345,30 @@ const lifetimeMetrics = async (db, release) => {
345
345
  failedLaunches: lifetime?.failed_launches ?? 0
346
346
  };
347
347
  };
348
- /** Counters, unique users, and a per-UTC-day series over a window. */
349
- const rangedMetrics = async (db, parts, users, range, days, hour) => {
348
+ /**
349
+ * Counters, unique users, and a series over a window: a point per UTC day
350
+ * with rows, or, with `intervalMs`, every point of that span from the
351
+ * window's start.
352
+ */
353
+ const rangedMetrics = async (db, parts, users, range, days, hour, intervalMs) => {
350
354
  const [counters, sketches] = await Promise.all([counterRows(db, parts, range, days, hour), sketchRows(db, users.parts, range, days, hour)]);
351
355
  const series = /* @__PURE__ */ new Map();
356
+ if (intervalMs !== void 0) for (let start = range.start; start < range.end; start += intervalMs) series.set(start, {
357
+ downloads: 0,
358
+ launches: 0,
359
+ failedLaunches: 0
360
+ });
352
361
  for (const row of counters) {
353
- const day = dayFloor(row.bucket_start_ms);
354
- const point = series.get(day) ?? {
362
+ const start = intervalMs === void 0 ? dayFloor(row.bucket_start_ms) : row.bucket_start_ms - (row.bucket_start_ms - range.start) % intervalMs;
363
+ const point = series.get(start) ?? {
364
+ downloads: 0,
355
365
  launches: 0,
356
366
  failedLaunches: 0
357
367
  };
368
+ point.downloads += row.downloads;
358
369
  point.launches += row.launches;
359
370
  point.failedLaunches += row.failed_launches;
360
- series.set(day, point);
371
+ series.set(start, point);
361
372
  }
362
373
  const total = (field) => counters.reduce((sum, row) => sum + row[field], 0);
363
374
  return {
@@ -403,7 +414,7 @@ const getReleaseActivity = async (db, input, now, retention) => {
403
414
  metrics: input.timeRange === void 0 ? await lifetimeMetrics(db, release) : await rangedMetrics(db, releaseParts(release), {
404
415
  parts: releaseParts(release),
405
416
  field: "launch_users"
406
- }, input.timeRange, false, kept.hour)
417
+ }, input.timeRange, false, kept.hour, input.intervalMs)
407
418
  })));
408
419
  const oldest = kept[input.scope === void 0 ? "hour" : "day"];
409
420
  return {
@@ -669,12 +680,50 @@ const getUpdateFailures = async (db, input, now, retention) => {
669
680
  }
670
681
  };
671
682
  };
683
+ /** Reads daily gauges, never raw events or mutable latest-state distribution. */
684
+ const getDistributionHistory = async (db, input, now, retention) => {
685
+ const at = now();
686
+ const { start, end } = input.timeRange;
687
+ const points = /* @__PURE__ */ new Map();
688
+ for (let day = start; day < end; day += require_schema.DAY_MS) points.set(day, []);
689
+ const rows = await drain((page) => db.findAggregates("insights_distribution_history", {
690
+ index: "byScope",
691
+ where: {
692
+ channel: input.channel,
693
+ platform: input.platform
694
+ },
695
+ range: {
696
+ gte: start,
697
+ lt: end
698
+ },
699
+ limit: PAGE,
700
+ ...page
701
+ }));
702
+ for (const row of rows) {
703
+ if (row.installations <= 0) continue;
704
+ points.get(row.bucket_start_ms).push({
705
+ appVersion: row.app_version,
706
+ releaseId: row.release_id || null,
707
+ bundleKind: row.bundle_kind,
708
+ installations: row.installations
709
+ });
710
+ }
711
+ return {
712
+ coverage: coverageOf(start, retained(at, retention).day),
713
+ measuredAtMs: at,
714
+ points: [...points].map(([startMs, bundles]) => ({
715
+ startMs,
716
+ bundles
717
+ }))
718
+ };
719
+ };
672
720
  //#endregion
673
721
  exports.UPDATE_FAILURES_RANGE_MS = UPDATE_FAILURES_RANGE_MS;
674
722
  exports.countEvents = countEvents;
675
723
  exports.countLatestEvents = countLatestEvents;
676
724
  exports.findLatestEvents = findLatestEvents;
677
725
  exports.getAppUsage = getAppUsage;
726
+ exports.getDistributionHistory = getDistributionHistory;
678
727
  exports.getReleaseActivity = getReleaseActivity;
679
728
  exports.getUpdateFailures = getUpdateFailures;
680
729
  exports.listEvents = listEvents;
@@ -345,19 +345,30 @@ const lifetimeMetrics = async (db, release) => {
345
345
  failedLaunches: lifetime?.failed_launches ?? 0
346
346
  };
347
347
  };
348
- /** Counters, unique users, and a per-UTC-day series over a window. */
349
- const rangedMetrics = async (db, parts, users, range, days, hour) => {
348
+ /**
349
+ * Counters, unique users, and a series over a window: a point per UTC day
350
+ * with rows, or, with `intervalMs`, every point of that span from the
351
+ * window's start.
352
+ */
353
+ const rangedMetrics = async (db, parts, users, range, days, hour, intervalMs) => {
350
354
  const [counters, sketches] = await Promise.all([counterRows(db, parts, range, days, hour), sketchRows(db, users.parts, range, days, hour)]);
351
355
  const series = /* @__PURE__ */ new Map();
356
+ if (intervalMs !== void 0) for (let start = range.start; start < range.end; start += intervalMs) series.set(start, {
357
+ downloads: 0,
358
+ launches: 0,
359
+ failedLaunches: 0
360
+ });
352
361
  for (const row of counters) {
353
- const day = dayFloor(row.bucket_start_ms);
354
- const point = series.get(day) ?? {
362
+ const start = intervalMs === void 0 ? dayFloor(row.bucket_start_ms) : row.bucket_start_ms - (row.bucket_start_ms - range.start) % intervalMs;
363
+ const point = series.get(start) ?? {
364
+ downloads: 0,
355
365
  launches: 0,
356
366
  failedLaunches: 0
357
367
  };
368
+ point.downloads += row.downloads;
358
369
  point.launches += row.launches;
359
370
  point.failedLaunches += row.failed_launches;
360
- series.set(day, point);
371
+ series.set(start, point);
361
372
  }
362
373
  const total = (field) => counters.reduce((sum, row) => sum + row[field], 0);
363
374
  return {
@@ -403,7 +414,7 @@ const getReleaseActivity = async (db, input, now, retention) => {
403
414
  metrics: input.timeRange === void 0 ? await lifetimeMetrics(db, release) : await rangedMetrics(db, releaseParts(release), {
404
415
  parts: releaseParts(release),
405
416
  field: "launch_users"
406
- }, input.timeRange, false, kept.hour)
417
+ }, input.timeRange, false, kept.hour, input.intervalMs)
407
418
  })));
408
419
  const oldest = kept[input.scope === void 0 ? "hour" : "day"];
409
420
  return {
@@ -669,5 +680,42 @@ const getUpdateFailures = async (db, input, now, retention) => {
669
680
  }
670
681
  };
671
682
  };
683
+ /** Reads daily gauges, never raw events or mutable latest-state distribution. */
684
+ const getDistributionHistory = async (db, input, now, retention) => {
685
+ const at = now();
686
+ const { start, end } = input.timeRange;
687
+ const points = /* @__PURE__ */ new Map();
688
+ for (let day = start; day < end; day += DAY_MS) points.set(day, []);
689
+ const rows = await drain((page) => db.findAggregates("insights_distribution_history", {
690
+ index: "byScope",
691
+ where: {
692
+ channel: input.channel,
693
+ platform: input.platform
694
+ },
695
+ range: {
696
+ gte: start,
697
+ lt: end
698
+ },
699
+ limit: PAGE,
700
+ ...page
701
+ }));
702
+ for (const row of rows) {
703
+ if (row.installations <= 0) continue;
704
+ points.get(row.bucket_start_ms).push({
705
+ appVersion: row.app_version,
706
+ releaseId: row.release_id || null,
707
+ bundleKind: row.bundle_kind,
708
+ installations: row.installations
709
+ });
710
+ }
711
+ return {
712
+ coverage: coverageOf(start, retained(at, retention).day),
713
+ measuredAtMs: at,
714
+ points: [...points].map(([startMs, bundles]) => ({
715
+ startMs,
716
+ bundles
717
+ }))
718
+ };
719
+ };
672
720
  //#endregion
673
- export { UPDATE_FAILURES_RANGE_MS, countEvents, countLatestEvents, findLatestEvents, getAppUsage, getReleaseActivity, getUpdateFailures, listEvents };
721
+ export { UPDATE_FAILURES_RANGE_MS, countEvents, countLatestEvents, findLatestEvents, getAppUsage, getDistributionHistory, getReleaseActivity, getUpdateFailures, listEvents };
@@ -2,6 +2,7 @@ const require_contract = require("./contract.cjs");
2
2
  const require_overview = require("./overview.cjs");
3
3
  const require_schema = require("./schema.cjs");
4
4
  let _hot_updater_plugin_core = require("@hot-updater/plugin-core");
5
+ let _hot_updater_protocol = require("@hot-updater/protocol");
5
6
  //#region src/server/recordEvent.ts
6
7
  const hourOf = (ms) => ms - ms % require_schema.HOUR_MS;
7
8
  const dayOf = (ms) => ms - ms % require_schema.DAY_MS;
@@ -276,21 +277,66 @@ const REPEATED_FIELDS = [
276
277
  const repeatsHead = (event, head) => event.type === "UNCHANGED" && head.type !== "UPDATE_DOWNLOADED" && dayOf(event.received_at_ms) === dayOf(head.received_at_ms) && REPEATED_FIELDS.every((field) => event[field] === head[field]);
277
278
  /**
278
279
  * Records one event in one transaction: one batch read of the event and its
279
- * installation's head, one of the gauge and sketch rows it changes, then one
280
+ * installation's latest and daily heads, then the gauge and sketch rows it
281
+ * changes and one
280
282
  * write. On a database that batches aggregates (DynamoDB, Firestore), the
281
283
  * write holds the event's rows and one log row instead, and a compaction
282
284
  * reads and writes the aggregate rows. A stored event's id changes nothing
283
285
  * when repeated, whichever installation sends it, and neither does the id
284
286
  * of the installation's head. An UNCHANGED report that repeats its head on
285
- * the same UTC day writes nothing at all. An older event still counts in its
287
+ * the same UTC day only advances its daily observation. An older event counts in its
286
288
  * own hour but never replaces the head. An update failure is stored and
287
289
  * counted, but changes what no installation runs, so it moves no head.
288
290
  */
289
291
  const recordEvent = (db, event) => {
290
292
  require_contract.assertBundleEventRow(event);
291
293
  return db.transaction(async (tx) => {
292
- const [existing, previous] = await Promise.all([tx.findOne("bundle_events", { id: event.id }), tx.findOne("bundle_event_heads", { install_id: event.install_id })]);
293
- if (existing !== null || previous?.id === event.id) return;
294
+ const dailyKey = {
295
+ install_id: event.install_id,
296
+ bucket_start_ms: dayOf(event.received_at_ms)
297
+ };
298
+ const [existing, previous, daily] = await Promise.all([
299
+ tx.findOne("bundle_events", { id: event.id }),
300
+ tx.findOne("bundle_event_heads", { install_id: event.install_id }),
301
+ event.type === "UPDATE_FAILED" ? null : tx.findOne("bundle_daily_heads", dailyKey)
302
+ ]);
303
+ if (existing !== null || previous?.id === event.id || daily?.id === event.id) return;
304
+ if (event.type !== "UPDATE_FAILED" && (daily === null || isNewer(event, daily))) {
305
+ const downloaded = event.type === "UPDATE_DOWNLOADED";
306
+ const releaseId = downloaded ? event.from_release_id : event.to_release_id;
307
+ const bundleId = downloaded ? event.from_bundle_id : event.to_bundle_id;
308
+ const next = {
309
+ ...dailyKey,
310
+ id: event.id,
311
+ received_at_ms: event.received_at_ms,
312
+ channel: event.channel,
313
+ platform: event.platform,
314
+ app_version: event.app_version,
315
+ release_id: releaseId ?? "",
316
+ bundle_kind: bundleId === _hot_updater_protocol.NIL_UUID ? "builtin" : releaseId === null ? "unknown" : "release"
317
+ };
318
+ const count = (head, delta) => {
319
+ tx.aggregate("insights_distribution_history", {
320
+ channel: head.channel,
321
+ platform: head.platform,
322
+ app_version: head.app_version,
323
+ release_id: head.release_id,
324
+ bundle_kind: head.bundle_kind,
325
+ bucket_start_ms: head.bucket_start_ms
326
+ }, { installations: delta }, { shardBy: head.install_id });
327
+ };
328
+ if (daily !== null) {
329
+ if (daily.channel !== next.channel || daily.platform !== next.platform || daily.app_version !== next.app_version || daily.release_id !== next.release_id || daily.bundle_kind !== next.bundle_kind) {
330
+ count(daily, -1);
331
+ count(next, 1);
332
+ }
333
+ const { install_id: _, bucket_start_ms: __, ...fields } = next;
334
+ tx.update("bundle_daily_heads", daily, fields);
335
+ } else {
336
+ tx.create("bundle_daily_heads", next);
337
+ count(next, 1);
338
+ }
339
+ }
294
340
  if (previous !== null && repeatsHead(event, previous)) return;
295
341
  if (event.type !== "UNCHANGED") {
296
342
  tx.create("bundle_events", event);
@@ -2,6 +2,7 @@ import { assertBundleEventRow } from "./contract.mjs";
2
2
  import { insightsKey, insightsOverviewDeltas, insightsOverviewId } from "./overview.mjs";
3
3
  import { DAILY_EVENTS, DAY_MS, HOUR_MS, isFailedCheck } from "./schema.mjs";
4
4
  import { addDistinct, compareUtf8 } from "@hot-updater/plugin-core";
5
+ import { NIL_UUID } from "@hot-updater/protocol";
5
6
  //#region src/server/recordEvent.ts
6
7
  const hourOf = (ms) => ms - ms % HOUR_MS;
7
8
  const dayOf = (ms) => ms - ms % DAY_MS;
@@ -276,21 +277,66 @@ const REPEATED_FIELDS = [
276
277
  const repeatsHead = (event, head) => event.type === "UNCHANGED" && head.type !== "UPDATE_DOWNLOADED" && dayOf(event.received_at_ms) === dayOf(head.received_at_ms) && REPEATED_FIELDS.every((field) => event[field] === head[field]);
277
278
  /**
278
279
  * Records one event in one transaction: one batch read of the event and its
279
- * installation's head, one of the gauge and sketch rows it changes, then one
280
+ * installation's latest and daily heads, then the gauge and sketch rows it
281
+ * changes and one
280
282
  * write. On a database that batches aggregates (DynamoDB, Firestore), the
281
283
  * write holds the event's rows and one log row instead, and a compaction
282
284
  * reads and writes the aggregate rows. A stored event's id changes nothing
283
285
  * when repeated, whichever installation sends it, and neither does the id
284
286
  * of the installation's head. An UNCHANGED report that repeats its head on
285
- * the same UTC day writes nothing at all. An older event still counts in its
287
+ * the same UTC day only advances its daily observation. An older event counts in its
286
288
  * own hour but never replaces the head. An update failure is stored and
287
289
  * counted, but changes what no installation runs, so it moves no head.
288
290
  */
289
291
  const recordEvent = (db, event) => {
290
292
  assertBundleEventRow(event);
291
293
  return db.transaction(async (tx) => {
292
- const [existing, previous] = await Promise.all([tx.findOne("bundle_events", { id: event.id }), tx.findOne("bundle_event_heads", { install_id: event.install_id })]);
293
- if (existing !== null || previous?.id === event.id) return;
294
+ const dailyKey = {
295
+ install_id: event.install_id,
296
+ bucket_start_ms: dayOf(event.received_at_ms)
297
+ };
298
+ const [existing, previous, daily] = await Promise.all([
299
+ tx.findOne("bundle_events", { id: event.id }),
300
+ tx.findOne("bundle_event_heads", { install_id: event.install_id }),
301
+ event.type === "UPDATE_FAILED" ? null : tx.findOne("bundle_daily_heads", dailyKey)
302
+ ]);
303
+ if (existing !== null || previous?.id === event.id || daily?.id === event.id) return;
304
+ if (event.type !== "UPDATE_FAILED" && (daily === null || isNewer(event, daily))) {
305
+ const downloaded = event.type === "UPDATE_DOWNLOADED";
306
+ const releaseId = downloaded ? event.from_release_id : event.to_release_id;
307
+ const bundleId = downloaded ? event.from_bundle_id : event.to_bundle_id;
308
+ const next = {
309
+ ...dailyKey,
310
+ id: event.id,
311
+ received_at_ms: event.received_at_ms,
312
+ channel: event.channel,
313
+ platform: event.platform,
314
+ app_version: event.app_version,
315
+ release_id: releaseId ?? "",
316
+ bundle_kind: bundleId === NIL_UUID ? "builtin" : releaseId === null ? "unknown" : "release"
317
+ };
318
+ const count = (head, delta) => {
319
+ tx.aggregate("insights_distribution_history", {
320
+ channel: head.channel,
321
+ platform: head.platform,
322
+ app_version: head.app_version,
323
+ release_id: head.release_id,
324
+ bundle_kind: head.bundle_kind,
325
+ bucket_start_ms: head.bucket_start_ms
326
+ }, { installations: delta }, { shardBy: head.install_id });
327
+ };
328
+ if (daily !== null) {
329
+ if (daily.channel !== next.channel || daily.platform !== next.platform || daily.app_version !== next.app_version || daily.release_id !== next.release_id || daily.bundle_kind !== next.bundle_kind) {
330
+ count(daily, -1);
331
+ count(next, 1);
332
+ }
333
+ const { install_id: _, bucket_start_ms: __, ...fields } = next;
334
+ tx.update("bundle_daily_heads", daily, fields);
335
+ } else {
336
+ tx.create("bundle_daily_heads", next);
337
+ count(next, 1);
338
+ }
339
+ }
294
340
  if (previous !== null && repeatsHead(event, previous)) return;
295
341
  if (event.type !== "UNCHANGED") {
296
342
  tx.create("bundle_events", event);
@@ -354,6 +354,71 @@ const insightsDistribution = (retention) => (0, _hot_updater_plugin_core.defineA
354
354
  },
355
355
  retention
356
356
  });
357
+ /** The last running bundle observed per installation and UTC day. */
358
+ const bundleDailyHeads = (retention) => (0, _hot_updater_plugin_core.defineTable)({
359
+ install_id: {
360
+ type: "string",
361
+ maxLength: 255
362
+ },
363
+ bucket_start_ms: { type: "integer" },
364
+ id: {
365
+ type: "string",
366
+ maxLength: 36
367
+ },
368
+ received_at_ms: { type: "integer" },
369
+ channel: { type: "string" },
370
+ platform: {
371
+ type: "string",
372
+ maxLength: 16
373
+ },
374
+ app_version: { type: "string" },
375
+ release_id: {
376
+ type: "string",
377
+ maxLength: 36
378
+ },
379
+ bundle_kind: {
380
+ type: "string",
381
+ maxLength: 16
382
+ }
383
+ }, {
384
+ key: ["install_id", "bucket_start_ms"],
385
+ retention
386
+ });
387
+ /** Daily observations stay in their day when an installation reports again. */
388
+ const insightsDistributionHistory = (retention) => (0, _hot_updater_plugin_core.defineAggregate)({
389
+ channel: { type: "string" },
390
+ platform: {
391
+ type: "string",
392
+ maxLength: 16
393
+ },
394
+ app_version: { type: "string" },
395
+ release_id: {
396
+ type: "string",
397
+ maxLength: 36
398
+ },
399
+ bundle_kind: {
400
+ type: "string",
401
+ maxLength: 16
402
+ },
403
+ bucket_start_ms: { type: "integer" }
404
+ }, {
405
+ key: [
406
+ "channel",
407
+ "platform",
408
+ "app_version",
409
+ "release_id",
410
+ "bundle_kind",
411
+ "bucket_start_ms"
412
+ ],
413
+ gauges: ["installations"],
414
+ shards: GAUGE_SHARDS,
415
+ batched: true,
416
+ indexes: { byScope: {
417
+ eq: ["channel", "platform"],
418
+ sort: ["bucket_start_ms"]
419
+ } },
420
+ retention
421
+ });
357
422
  /** Latest events by the bundle they came from or went to, bucketed like the distribution. */
358
423
  const insightsLatestByBundle = (retention) => (0, _hot_updater_plugin_core.defineAggregate)({
359
424
  platform: {
@@ -473,6 +538,8 @@ const createInsightsSchema = ({ rawDays, dailyDays } = DEFAULT_INSIGHTS_RETENTIO
473
538
  insights_overview_lifetime: counters(),
474
539
  insights_sketches_lifetime: lifetimeSketches(),
475
540
  insights_distribution: insightsDistribution(daily),
541
+ bundle_daily_heads: bundleDailyHeads(daily),
542
+ insights_distribution_history: insightsDistributionHistory(daily),
476
543
  insights_latest_by_bundle: insightsLatestByBundle(daily),
477
544
  insights_outcomes: insightsOutcomes(hourly),
478
545
  insights_failures: insightsFailures(hourly)
@@ -263,6 +263,68 @@ declare const createInsightsSchema: ({ rawDays, dailyDays }?: InsightsRetention)
263
263
  readonly sort: readonly ["bucket_start_ms"];
264
264
  };
265
265
  }, never, readonly ["channel", "platform", "app_version", "release_id", "bucket_start_ms"]>;
266
+ readonly bundle_daily_heads: import("@hot-updater/plugin-core").TableDefinition<{
267
+ readonly install_id: {
268
+ readonly type: "string";
269
+ readonly maxLength: 255;
270
+ };
271
+ readonly bucket_start_ms: {
272
+ readonly type: "integer";
273
+ };
274
+ readonly id: {
275
+ readonly type: "string";
276
+ readonly maxLength: 36;
277
+ };
278
+ readonly received_at_ms: {
279
+ readonly type: "integer";
280
+ };
281
+ readonly channel: {
282
+ readonly type: "string";
283
+ };
284
+ readonly platform: {
285
+ readonly type: "string";
286
+ readonly maxLength: 16;
287
+ };
288
+ readonly app_version: {
289
+ readonly type: "string";
290
+ };
291
+ readonly release_id: {
292
+ readonly type: "string";
293
+ readonly maxLength: 36;
294
+ };
295
+ readonly bundle_kind: {
296
+ readonly type: "string";
297
+ readonly maxLength: 16;
298
+ };
299
+ }, Readonly<Record<string, import("@hot-updater/plugin-core").FieldType>>, {}, readonly ["install_id", "bucket_start_ms"]>;
300
+ readonly insights_distribution_history: import("@hot-updater/plugin-core").AggregateDefinition<{
301
+ readonly channel: {
302
+ readonly type: "string";
303
+ };
304
+ readonly platform: {
305
+ readonly type: "string";
306
+ readonly maxLength: 16;
307
+ };
308
+ readonly app_version: {
309
+ readonly type: "string";
310
+ };
311
+ readonly release_id: {
312
+ readonly type: "string";
313
+ readonly maxLength: 36;
314
+ };
315
+ readonly bundle_kind: {
316
+ readonly type: "string";
317
+ readonly maxLength: 16;
318
+ };
319
+ readonly bucket_start_ms: {
320
+ readonly type: "integer";
321
+ };
322
+ }, "installations", {
323
+ readonly byScope: {
324
+ readonly eq: readonly ["channel", "platform"];
325
+ readonly sort: readonly ["bucket_start_ms"];
326
+ };
327
+ }, never, readonly ["channel", "platform", "app_version", "release_id", "bundle_kind", "bucket_start_ms"]>;
266
328
  readonly insights_latest_by_bundle: import("@hot-updater/plugin-core").AggregateDefinition<{
267
329
  readonly platform: {
268
330
  readonly type: "string";
@@ -602,6 +664,68 @@ declare const insightsSchema: {
602
664
  readonly sort: readonly ["bucket_start_ms"];
603
665
  };
604
666
  }, never, readonly ["channel", "platform", "app_version", "release_id", "bucket_start_ms"]>;
667
+ readonly bundle_daily_heads: import("@hot-updater/plugin-core").TableDefinition<{
668
+ readonly install_id: {
669
+ readonly type: "string";
670
+ readonly maxLength: 255;
671
+ };
672
+ readonly bucket_start_ms: {
673
+ readonly type: "integer";
674
+ };
675
+ readonly id: {
676
+ readonly type: "string";
677
+ readonly maxLength: 36;
678
+ };
679
+ readonly received_at_ms: {
680
+ readonly type: "integer";
681
+ };
682
+ readonly channel: {
683
+ readonly type: "string";
684
+ };
685
+ readonly platform: {
686
+ readonly type: "string";
687
+ readonly maxLength: 16;
688
+ };
689
+ readonly app_version: {
690
+ readonly type: "string";
691
+ };
692
+ readonly release_id: {
693
+ readonly type: "string";
694
+ readonly maxLength: 36;
695
+ };
696
+ readonly bundle_kind: {
697
+ readonly type: "string";
698
+ readonly maxLength: 16;
699
+ };
700
+ }, Readonly<Record<string, import("@hot-updater/plugin-core").FieldType>>, {}, readonly ["install_id", "bucket_start_ms"]>;
701
+ readonly insights_distribution_history: import("@hot-updater/plugin-core").AggregateDefinition<{
702
+ readonly channel: {
703
+ readonly type: "string";
704
+ };
705
+ readonly platform: {
706
+ readonly type: "string";
707
+ readonly maxLength: 16;
708
+ };
709
+ readonly app_version: {
710
+ readonly type: "string";
711
+ };
712
+ readonly release_id: {
713
+ readonly type: "string";
714
+ readonly maxLength: 36;
715
+ };
716
+ readonly bundle_kind: {
717
+ readonly type: "string";
718
+ readonly maxLength: 16;
719
+ };
720
+ readonly bucket_start_ms: {
721
+ readonly type: "integer";
722
+ };
723
+ }, "installations", {
724
+ readonly byScope: {
725
+ readonly eq: readonly ["channel", "platform"];
726
+ readonly sort: readonly ["bucket_start_ms"];
727
+ };
728
+ }, never, readonly ["channel", "platform", "app_version", "release_id", "bundle_kind", "bucket_start_ms"]>;
605
729
  readonly insights_latest_by_bundle: import("@hot-updater/plugin-core").AggregateDefinition<{
606
730
  readonly platform: {
607
731
  readonly type: "string";