@hot-updater/plugin-insights 1.0.0-rc.35 → 1.0.0-rc.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,7 @@ import { BundleEventFailure, BundleEventFailureReason, BundleEventFailureStage,
2
2
  import { InsightsBundleEventFilter, InsightsCountEventSeriesInput, InsightsCountEventsInput, InsightsCountLatestEventsInput, InsightsCoverage, InsightsEventCursor, InsightsEventFilter, InsightsEventSeriesPoint, InsightsFindLatestEventsInput, InsightsGetAppUsageInput, InsightsGetAppUsageResult, InsightsGetReleaseActivityInput, InsightsGetReleaseActivityResult, InsightsListEventsInput, InsightsModel, InsightsRecordEventInput, InsightsScope, InsightsTimeRange, ReleaseActivityMetrics, ReleaseReference } from "./modelTypes.mjs";
3
3
  import { DEFAULT_INSIGHTS_RETENTION, InsightsRetention, InsightsSchema, createInsightsSchema, insightsSchema } from "./schema.mjs";
4
4
  import { InsightsFailureBreakdown, InsightsUpdateFailures, InsightsUpdateFailuresInput } from "./reads.mjs";
5
- import { ActiveInstallationWindow, BundleEventFailureInput, CreateBundleEventRequest, CreateBundleEventRequestBase, CursorPage, EventCursorPage, EventHistoryRow, InsightsBundleSelection, InsightsCountMeasurement, InsightsHttpResponse, InstallationHistoryRow, InstallationRow, ReportingOverview } from "./domain.mjs";
5
+ import { ActiveInstallationWindow, BundleEventFailureInput, CreateBundleEventRequest, CreateBundleEventRequestBase, CursorPage, EventCursorPage, EventHistoryChange, EventHistoryRow, InsightsBundleSelection, InsightsCountMeasurement, InsightsHttpResponse, InstallationHistoryRow, InstallationRow, ReportingOverview } from "./domain.mjs";
6
6
  import { InsightsBadRequestError } from "./errors.mjs";
7
7
  import { createInsightsModel } from "./model.mjs";
8
8
  import { InsightsEventPageInput, InsightsInstallationEventPageInput, InsightsProvider, InsightsUserInstallationPageInput } from "./types.mjs";
@@ -284,6 +284,38 @@ declare const insights: (options?: InsightsOptions) => import("@hot-updater/plug
284
284
  readonly sort: readonly ["bucket_start_ms"];
285
285
  };
286
286
  }, never, readonly ["channel", "platform", "app_version", "release_id", "bucket_start_ms"]>;
287
+ readonly insights_builtin_distribution: import("@hot-updater/plugin-core").AggregateDefinition<{
288
+ readonly channel: {
289
+ readonly type: "string";
290
+ };
291
+ readonly platform: {
292
+ readonly type: "string";
293
+ readonly maxLength: 16;
294
+ };
295
+ readonly app_version: {
296
+ readonly type: "string";
297
+ };
298
+ readonly release_id: {
299
+ readonly type: "string";
300
+ readonly maxLength: 36;
301
+ };
302
+ readonly builtin_bundle_id: {
303
+ readonly type: "string";
304
+ readonly maxLength: 36;
305
+ };
306
+ readonly bucket_start_ms: {
307
+ readonly type: "integer";
308
+ };
309
+ }, "latest_installations", {
310
+ readonly byScope: {
311
+ readonly eq: readonly ["channel", "platform"];
312
+ readonly sort: readonly ["bucket_start_ms"];
313
+ };
314
+ readonly byVersion: {
315
+ readonly eq: readonly ["channel", "platform", "app_version"];
316
+ readonly sort: readonly ["bucket_start_ms"];
317
+ };
318
+ }, never, readonly ["channel", "platform", "app_version", "release_id", "builtin_bundle_id", "bucket_start_ms"]>;
287
319
  readonly insights_latest_by_bundle: import("@hot-updater/plugin-core").AggregateDefinition<{
288
320
  readonly platform: {
289
321
  readonly type: "string";
@@ -390,4 +422,4 @@ declare const insights: (options?: InsightsOptions) => import("@hot-updater/plug
390
422
  endpoints: PluginEndpoint[];
391
423
  }, {}>;
392
424
  //#endregion
393
- export { type ActiveInstallationWindow, type BundleEventFailure, type BundleEventFailureInput, type BundleEventFailureReason, type BundleEventFailureStage, type BundleEventRow, type BundleEventRowBase, type CreateBundleEventRequest, type CreateBundleEventRequestBase, type CursorPage, DEFAULT_INSIGHTS_RETENTION, type DatabaseBundleEventMetadata, type EventCursorPage, type EventHistoryRow, InsightsApi, InsightsBadRequestError, type InsightsBundleEventFilter, type InsightsBundleSelection, type InsightsCountEventSeriesInput, type InsightsCountEventsInput, type InsightsCountLatestEventsInput, type InsightsCountMeasurement, type InsightsCoverage, type InsightsEventCursor, type InsightsEventFilter, type InsightsEventPageInput, type InsightsEventSeriesPoint, type InsightsFailureBreakdown, type InsightsFindLatestEventsInput, type InsightsGetAppUsageInput, type InsightsGetAppUsageResult, type InsightsGetReleaseActivityInput, type InsightsGetReleaseActivityResult, type InsightsHttpResponse, type InsightsIdentityParts, type InsightsInstallationEventPageInput, type InsightsListEventsInput, type InsightsModel, InsightsOptions, type InsightsProvider, type InsightsRecordEventInput, type InsightsRetention, type InsightsSchema, type InsightsScope, type InsightsTimeRange, type InsightsUpdateFailures, type InsightsUpdateFailuresInput, type InsightsUserInstallationPageInput, type InstallationHistoryRow, type InstallationRow, type ReleaseActivityMetrics, type ReleaseReference, type ReportingOverview, createInsightsModel, createInsightsProvider, createInsightsSchema, insights, insightsIdentity, insightsSchema };
425
+ export { type ActiveInstallationWindow, type BundleEventFailure, type BundleEventFailureInput, type BundleEventFailureReason, type BundleEventFailureStage, type BundleEventRow, type BundleEventRowBase, type CreateBundleEventRequest, type CreateBundleEventRequestBase, type CursorPage, DEFAULT_INSIGHTS_RETENTION, type DatabaseBundleEventMetadata, type EventCursorPage, type EventHistoryChange, type EventHistoryRow, InsightsApi, InsightsBadRequestError, type InsightsBundleEventFilter, type InsightsBundleSelection, type InsightsCountEventSeriesInput, type InsightsCountEventsInput, type InsightsCountLatestEventsInput, type InsightsCountMeasurement, type InsightsCoverage, type InsightsEventCursor, type InsightsEventFilter, type InsightsEventPageInput, type InsightsEventSeriesPoint, type InsightsFailureBreakdown, type InsightsFindLatestEventsInput, type InsightsGetAppUsageInput, type InsightsGetAppUsageResult, type InsightsGetReleaseActivityInput, type InsightsGetReleaseActivityResult, type InsightsHttpResponse, type InsightsIdentityParts, type InsightsInstallationEventPageInput, type InsightsListEventsInput, type InsightsModel, InsightsOptions, type InsightsProvider, type InsightsRecordEventInput, type InsightsRetention, type InsightsSchema, type InsightsScope, type InsightsTimeRange, type InsightsUpdateFailures, type InsightsUpdateFailuresInput, type InsightsUserInstallationPageInput, type InstallationHistoryRow, type InstallationRow, type ReleaseActivityMetrics, type ReleaseReference, type ReportingOverview, createInsightsModel, createInsightsProvider, createInsightsSchema, insights, insightsIdentity, insightsSchema };
@@ -1,8 +1,8 @@
1
+ import { DEFAULT_INSIGHTS_RETENTION, createInsightsSchema, insightsSchema } from "./schema.mjs";
1
2
  import { createInsightsModel } from "./model.mjs";
2
3
  import { markOfficial } from "./official.mjs";
3
4
  import { InsightsBadRequestError } from "./errors.mjs";
4
5
  import { createInsightsProvider } from "./provider.mjs";
5
- import { DEFAULT_INSIGHTS_RETENTION, createInsightsSchema, insightsSchema } from "./schema.mjs";
6
6
  import { insightsIdentity, recordEvent } from "./recordEvent.mjs";
7
7
  import { countEventSeries, countEvents, countLatestEvents, findLatestEvents, getAppUsage, getReleaseActivity, getUpdateFailures, listEvents } from "./reads.mjs";
8
8
  import { INSIGHTS_ROUTES, createInsightsRouteHandlers } from "./routes.mjs";
@@ -10,13 +10,14 @@ interface InsightsScope {
10
10
  }
11
11
  /**
12
12
  * Raw event predicates; recovery is attributed to the source bundle. An
13
- * UNCHANGED report is kept as no event, so no filter names it.
13
+ * UNCHANGED filter matches the kept rows that moved an installation to the
14
+ * bundle without an apply report.
14
15
  */
15
16
  type InsightsBundleEventFilter = InsightsScope & ({
16
17
  readonly type: "RECOVERED";
17
18
  readonly fromBundleId: string;
18
19
  } | {
19
- readonly type: "UPDATE_DOWNLOADED" | "UPDATE_APPLIED" | "UPDATE_FAILED";
20
+ readonly type: "UPDATE_DOWNLOADED" | "UPDATE_APPLIED" | "UPDATE_FAILED" | "UNCHANGED";
20
21
  readonly toBundleId: string;
21
22
  });
22
23
  type InsightsEventFilter = {
@@ -139,7 +140,10 @@ interface InsightsGetAppUsageResult {
139
140
  readonly bundleDistribution: readonly {
140
141
  readonly appVersion: string;
141
142
  readonly platform: "ios" | "android";
143
+ /** The release the installations run; null for none. */
142
144
  readonly releaseId: string | null;
145
+ /** The native build's built-in bundle, when they run it. */
146
+ readonly builtinBundleId: string | null;
143
147
  readonly installations: number;
144
148
  }[];
145
149
  readonly measuredAtMs: number;
@@ -10,13 +10,14 @@ interface InsightsScope {
10
10
  }
11
11
  /**
12
12
  * Raw event predicates; recovery is attributed to the source bundle. An
13
- * UNCHANGED report is kept as no event, so no filter names it.
13
+ * UNCHANGED filter matches the kept rows that moved an installation to the
14
+ * bundle without an apply report.
14
15
  */
15
16
  type InsightsBundleEventFilter = InsightsScope & ({
16
17
  readonly type: "RECOVERED";
17
18
  readonly fromBundleId: string;
18
19
  } | {
19
- readonly type: "UPDATE_DOWNLOADED" | "UPDATE_APPLIED" | "UPDATE_FAILED";
20
+ readonly type: "UPDATE_DOWNLOADED" | "UPDATE_APPLIED" | "UPDATE_FAILED" | "UNCHANGED";
20
21
  readonly toBundleId: string;
21
22
  });
22
23
  type InsightsEventFilter = {
@@ -139,7 +140,10 @@ interface InsightsGetAppUsageResult {
139
140
  readonly bundleDistribution: readonly {
140
141
  readonly appVersion: string;
141
142
  readonly platform: "ios" | "android";
143
+ /** The release the installations run; null for none. */
142
144
  readonly releaseId: string | null;
145
+ /** The native build's built-in bundle, when they run it. */
146
+ readonly builtinBundleId: string | null;
143
147
  readonly installations: number;
144
148
  }[];
145
149
  readonly measuredAtMs: number;
@@ -1,3 +1,4 @@
1
+ const require_schema = require("./schema.cjs");
1
2
  //#region src/server/overview.ts
2
3
  const encodeIdentity = (identity) => [
3
4
  identity.scopeKind,
@@ -64,18 +65,19 @@ const insightsOverviewDeltas = (event) => {
64
65
  bucketStartMs
65
66
  }, windowed);
66
67
  };
67
- if (event.type === "UPDATE_DOWNLOADED") metric(event.to_release_id, {
68
+ const implied = event.metadata.implied_download === true ? 1 : 0;
69
+ if (!require_schema.isLateEvent(event) && event.type === "UPDATE_DOWNLOADED") metric(event.to_release_id, {
68
70
  downloads: 1,
69
71
  applies: 0,
70
72
  failedLaunches: 0
71
73
  });
72
- else if (event.type === "UPDATE_APPLIED") metric(event.to_release_id, {
73
- downloads: 0,
74
+ else if (!require_schema.isLateEvent(event) && require_schema.isLaunch(event)) metric(event.to_release_id, {
75
+ downloads: implied,
74
76
  applies: 1,
75
77
  failedLaunches: 0
76
78
  });
77
- else if (event.type === "RECOVERED") metric(event.from_release_id, {
78
- downloads: 0,
79
+ else if (!require_schema.isLateEvent(event) && event.type === "RECOVERED") metric(event.from_release_id, {
80
+ downloads: implied,
79
81
  applies: 0,
80
82
  failedLaunches: 1
81
83
  });
@@ -1,14 +1,14 @@
1
1
  import "./eventRow.cjs";
2
2
  //#region src/server/overview.d.ts
3
3
  type InsightsOverviewIdentity = {
4
- readonly scopeKind: "release" | "channel" | "usage" | "distribution";
5
- readonly releaseKind: "all" | "specific" | "embedded";
4
+ readonly scopeKind: "release" | "channel" | "usage";
5
+ readonly releaseKind: "all" | "specific";
6
6
  readonly releaseId: string;
7
7
  readonly channel: string;
8
8
  readonly platform: "all" | "ios" | "android";
9
9
  readonly appVersionKind: "all" | "specific";
10
10
  readonly appVersion: string;
11
- readonly periodKind: "lifetime" | "hour" | "latest";
11
+ readonly periodKind: "lifetime" | "hour";
12
12
  readonly bucketStartMs: number;
13
13
  };
14
14
  //#endregion
@@ -1,14 +1,14 @@
1
1
  import "./eventRow.mjs";
2
2
  //#region src/server/overview.d.ts
3
3
  type InsightsOverviewIdentity = {
4
- readonly scopeKind: "release" | "channel" | "usage" | "distribution";
5
- readonly releaseKind: "all" | "specific" | "embedded";
4
+ readonly scopeKind: "release" | "channel" | "usage";
5
+ readonly releaseKind: "all" | "specific";
6
6
  readonly releaseId: string;
7
7
  readonly channel: string;
8
8
  readonly platform: "all" | "ios" | "android";
9
9
  readonly appVersionKind: "all" | "specific";
10
10
  readonly appVersion: string;
11
- readonly periodKind: "lifetime" | "hour" | "latest";
11
+ readonly periodKind: "lifetime" | "hour";
12
12
  readonly bucketStartMs: number;
13
13
  };
14
14
  //#endregion
@@ -1,3 +1,4 @@
1
+ import { isLateEvent, isLaunch } from "./schema.mjs";
1
2
  //#region src/server/overview.ts
2
3
  const encodeIdentity = (identity) => [
3
4
  identity.scopeKind,
@@ -64,18 +65,19 @@ const insightsOverviewDeltas = (event) => {
64
65
  bucketStartMs
65
66
  }, windowed);
66
67
  };
67
- if (event.type === "UPDATE_DOWNLOADED") metric(event.to_release_id, {
68
+ const implied = event.metadata.implied_download === true ? 1 : 0;
69
+ if (!isLateEvent(event) && event.type === "UPDATE_DOWNLOADED") metric(event.to_release_id, {
68
70
  downloads: 1,
69
71
  applies: 0,
70
72
  failedLaunches: 0
71
73
  });
72
- else if (event.type === "UPDATE_APPLIED") metric(event.to_release_id, {
73
- downloads: 0,
74
+ else if (!isLateEvent(event) && isLaunch(event)) metric(event.to_release_id, {
75
+ downloads: implied,
74
76
  applies: 1,
75
77
  failedLaunches: 0
76
78
  });
77
- else if (event.type === "RECOVERED") metric(event.from_release_id, {
78
- downloads: 0,
79
+ else if (!isLateEvent(event) && event.type === "RECOVERED") metric(event.from_release_id, {
80
+ downloads: implied,
79
81
  applies: 0,
80
82
  failedLaunches: 1
81
83
  });
@@ -166,11 +166,24 @@ const eventDetails = ({ type, metadata }) => ({
166
166
  } } : {},
167
167
  ...type === "UPDATE_FAILED" && metadata.failure !== void 0 ? { failure: toFailure(metadata.failure) } : {},
168
168
  ...type === "UPDATE_DOWNLOADED" && metadata.delivery !== void 0 ? { delivery: metadata.delivery } : {},
169
- ...type === "UPDATE_DOWNLOADED" && metadata.patch_fallback === true ? { patchFallback: true } : {}
169
+ ...type === "UPDATE_DOWNLOADED" && metadata.patch_fallback === true ? { patchFallback: true } : {},
170
+ ...type === "UNCHANGED" && metadata.change !== void 0 ? { change: {
171
+ kinds: metadata.change.kinds,
172
+ ...metadata.change.previous === null ? {} : { previous: {
173
+ bundleId: metadata.change.previous.bundle_id,
174
+ releaseId: metadata.change.previous.release_id,
175
+ appVersion: metadata.change.previous.app_version,
176
+ channel: metadata.change.previous.channel,
177
+ ...metadata.change.previous.min_bundle_id === void 0 ? {} : { minBundleId: metadata.change.previous.min_bundle_id }
178
+ } }
179
+ } } : {},
180
+ ...metadata.late === true ? { late: true } : {},
181
+ ...metadata.implied_download === true ? { impliedDownload: true } : {}
170
182
  });
171
183
  const toEventHistoryRow = (row) => ({
172
184
  ...row.to_release_id == null ? {} : { toReleaseId: row.to_release_id },
173
185
  ...row.metadata.sdk_version == null ? {} : { sdkVersion: row.metadata.sdk_version },
186
+ ...row.metadata.min_bundle_id === void 0 ? {} : { minBundleId: row.metadata.min_bundle_id },
174
187
  appVersion: row.app_version,
175
188
  channel: row.channel,
176
189
  cohort: row.metadata.cohort,
@@ -186,6 +199,7 @@ const toEventHistoryRow = (row) => ({
186
199
  });
187
200
  const toInstallationRow = (row) => ({
188
201
  ...eventDetails(row).httpResponse ? { httpResponse: eventDetails(row).httpResponse } : {},
202
+ ...row.metadata.min_bundle_id === void 0 ? {} : { minBundleId: row.metadata.min_bundle_id },
189
203
  appVersion: row.app_version,
190
204
  channel: row.channel,
191
205
  cohort: row.metadata.cohort,
@@ -166,11 +166,24 @@ const eventDetails = ({ type, metadata }) => ({
166
166
  } } : {},
167
167
  ...type === "UPDATE_FAILED" && metadata.failure !== void 0 ? { failure: toFailure(metadata.failure) } : {},
168
168
  ...type === "UPDATE_DOWNLOADED" && metadata.delivery !== void 0 ? { delivery: metadata.delivery } : {},
169
- ...type === "UPDATE_DOWNLOADED" && metadata.patch_fallback === true ? { patchFallback: true } : {}
169
+ ...type === "UPDATE_DOWNLOADED" && metadata.patch_fallback === true ? { patchFallback: true } : {},
170
+ ...type === "UNCHANGED" && metadata.change !== void 0 ? { change: {
171
+ kinds: metadata.change.kinds,
172
+ ...metadata.change.previous === null ? {} : { previous: {
173
+ bundleId: metadata.change.previous.bundle_id,
174
+ releaseId: metadata.change.previous.release_id,
175
+ appVersion: metadata.change.previous.app_version,
176
+ channel: metadata.change.previous.channel,
177
+ ...metadata.change.previous.min_bundle_id === void 0 ? {} : { minBundleId: metadata.change.previous.min_bundle_id }
178
+ } }
179
+ } } : {},
180
+ ...metadata.late === true ? { late: true } : {},
181
+ ...metadata.implied_download === true ? { impliedDownload: true } : {}
170
182
  });
171
183
  const toEventHistoryRow = (row) => ({
172
184
  ...row.to_release_id == null ? {} : { toReleaseId: row.to_release_id },
173
185
  ...row.metadata.sdk_version == null ? {} : { sdkVersion: row.metadata.sdk_version },
186
+ ...row.metadata.min_bundle_id === void 0 ? {} : { minBundleId: row.metadata.min_bundle_id },
174
187
  appVersion: row.app_version,
175
188
  channel: row.channel,
176
189
  cohort: row.metadata.cohort,
@@ -186,6 +199,7 @@ const toEventHistoryRow = (row) => ({
186
199
  });
187
200
  const toInstallationRow = (row) => ({
188
201
  ...eventDetails(row).httpResponse ? { httpResponse: eventDetails(row).httpResponse } : {},
202
+ ...row.metadata.min_bundle_id === void 0 ? {} : { minBundleId: row.metadata.min_bundle_id },
189
203
  appVersion: row.app_version,
190
204
  channel: row.channel,
191
205
  cohort: row.metadata.cohort,
@@ -1,5 +1,5 @@
1
- require("./provider.cjs");
2
1
  const require_schema = require("./schema.cjs");
2
+ require("./provider.cjs");
3
3
  const require_recordEvent = require("./recordEvent.cjs");
4
4
  let _hot_updater_plugin_core = require("@hot-updater/plugin-core");
5
5
  //#region src/server/reads.ts
@@ -36,12 +36,11 @@ const EVENT_FIELDS = [
36
36
  ];
37
37
  /** A stored event or head, as the event row it holds. */
38
38
  const toEvent = (row) => Object.fromEntries(EVENT_FIELDS.map((field) => [field, row[field]]));
39
- const bundleRef = (filter) => filter.type === "RECOVERED" ? `from:${filter.fromBundleId}` : `to:${filter.toBundleId}`;
40
39
  const scopeOf = (filter) => ({
41
40
  platform: filter.platform,
42
41
  channel: filter.channel,
43
42
  type: filter.type,
44
- bundle_ref: bundleRef(filter)
43
+ bundle_ref: require_schema.bundleRefOfFilter(filter)
45
44
  });
46
45
  /** One UTC day of a global or bundle list, newest first. */
47
46
  const eventsOfDay = (db, filter, day, range, limit) => filter.kind === "all" ? db.findMany("bundle_events", {
@@ -410,26 +409,49 @@ const getAppUsage = async (db, input, now, retention) => {
410
409
  lt: timeRange.end
411
410
  };
412
411
  const distribution = [];
413
- for (const platform of reported) distribution.push(...await drain((page) => input.appVersion === void 0 ? db.findAggregates("insights_distribution", {
414
- index: "byScope",
415
- where: {
416
- channel: input.channel,
417
- platform
418
- },
419
- range,
420
- limit: PAGE,
421
- ...page
422
- }) : db.findAggregates("insights_distribution", {
423
- index: "byVersion",
424
- where: {
425
- channel: input.channel,
426
- platform,
427
- app_version: input.appVersion
428
- },
429
- range,
430
- limit: PAGE,
431
- ...page
432
- })));
412
+ const builtinDistribution = [];
413
+ for (const platform of reported) {
414
+ distribution.push(...await drain((page) => input.appVersion === void 0 ? db.findAggregates("insights_distribution", {
415
+ index: "byScope",
416
+ where: {
417
+ channel: input.channel,
418
+ platform
419
+ },
420
+ range,
421
+ limit: PAGE,
422
+ ...page
423
+ }) : db.findAggregates("insights_distribution", {
424
+ index: "byVersion",
425
+ where: {
426
+ channel: input.channel,
427
+ platform,
428
+ app_version: input.appVersion
429
+ },
430
+ range,
431
+ limit: PAGE,
432
+ ...page
433
+ })));
434
+ builtinDistribution.push(...await drain((page) => input.appVersion === void 0 ? db.findAggregates("insights_builtin_distribution", {
435
+ index: "byScope",
436
+ where: {
437
+ channel: input.channel,
438
+ platform
439
+ },
440
+ range,
441
+ limit: PAGE,
442
+ ...page
443
+ }) : db.findAggregates("insights_builtin_distribution", {
444
+ index: "byVersion",
445
+ where: {
446
+ channel: input.channel,
447
+ platform,
448
+ app_version: input.appVersion
449
+ },
450
+ range,
451
+ limit: PAGE,
452
+ ...page
453
+ })));
454
+ }
433
455
  const points = [];
434
456
  for (let start = timeRange.start; start < timeRange.end; start += intervalMs) points.push({
435
457
  startMs: start,
@@ -438,22 +460,31 @@ const getAppUsage = async (db, input, now, retention) => {
438
460
  const versions = /* @__PURE__ */ new Map();
439
461
  const platforms = /* @__PURE__ */ new Map();
440
462
  const bundles = /* @__PURE__ */ new Map();
441
- for (const row of distribution) {
442
- const count = row.latest_installations;
443
- versions.set(row.app_version, (versions.get(row.app_version) ?? 0) + count);
444
- platforms.set(row.platform, (platforms.get(row.platform) ?? 0) + count);
445
- const releaseId = row.release_id === "" ? null : row.release_id;
463
+ const countBundle = (row, releaseId, builtinBundleId, count) => {
446
464
  const key = JSON.stringify([
447
465
  row.app_version,
448
466
  row.platform,
449
- releaseId
467
+ releaseId,
468
+ builtinBundleId
450
469
  ]);
451
470
  bundles.set(key, {
452
471
  appVersion: row.app_version,
453
472
  platform: row.platform,
454
473
  releaseId,
474
+ builtinBundleId,
455
475
  installations: (bundles.get(key)?.installations ?? 0) + count
456
476
  });
477
+ };
478
+ for (const row of distribution) {
479
+ const count = row.latest_installations;
480
+ versions.set(row.app_version, (versions.get(row.app_version) ?? 0) + count);
481
+ platforms.set(row.platform, (platforms.get(row.platform) ?? 0) + count);
482
+ countBundle(row, row.release_id || null, null, count);
483
+ }
484
+ for (const row of builtinDistribution) {
485
+ const releaseId = row.release_id || null;
486
+ countBundle(row, releaseId, null, -row.latest_installations);
487
+ countBundle(row, releaseId, row.builtin_bundle_id, row.latest_installations);
457
488
  }
458
489
  const sortedVersions = byInstallations(versions);
459
490
  return {
@@ -1,5 +1,5 @@
1
+ import { DAILY_EVENTS, DAY_MS, HOUR_MS, bundleRefOfFilter } from "./schema.mjs";
1
2
  import "./provider.mjs";
2
- import { DAILY_EVENTS, DAY_MS, HOUR_MS } from "./schema.mjs";
3
3
  import { PAIR_FIELD, bundlePairKey, insightsIdentity } from "./recordEvent.mjs";
4
4
  import { DatabaseAdapterInputError, compareUtf8, countDistinct, mergeDistinct } from "@hot-updater/plugin-core";
5
5
  //#region src/server/reads.ts
@@ -36,12 +36,11 @@ const EVENT_FIELDS = [
36
36
  ];
37
37
  /** A stored event or head, as the event row it holds. */
38
38
  const toEvent = (row) => Object.fromEntries(EVENT_FIELDS.map((field) => [field, row[field]]));
39
- const bundleRef = (filter) => filter.type === "RECOVERED" ? `from:${filter.fromBundleId}` : `to:${filter.toBundleId}`;
40
39
  const scopeOf = (filter) => ({
41
40
  platform: filter.platform,
42
41
  channel: filter.channel,
43
42
  type: filter.type,
44
- bundle_ref: bundleRef(filter)
43
+ bundle_ref: bundleRefOfFilter(filter)
45
44
  });
46
45
  /** One UTC day of a global or bundle list, newest first. */
47
46
  const eventsOfDay = (db, filter, day, range, limit) => filter.kind === "all" ? db.findMany("bundle_events", {
@@ -410,26 +409,49 @@ const getAppUsage = async (db, input, now, retention) => {
410
409
  lt: timeRange.end
411
410
  };
412
411
  const distribution = [];
413
- for (const platform of reported) distribution.push(...await drain((page) => input.appVersion === void 0 ? db.findAggregates("insights_distribution", {
414
- index: "byScope",
415
- where: {
416
- channel: input.channel,
417
- platform
418
- },
419
- range,
420
- limit: PAGE,
421
- ...page
422
- }) : db.findAggregates("insights_distribution", {
423
- index: "byVersion",
424
- where: {
425
- channel: input.channel,
426
- platform,
427
- app_version: input.appVersion
428
- },
429
- range,
430
- limit: PAGE,
431
- ...page
432
- })));
412
+ const builtinDistribution = [];
413
+ for (const platform of reported) {
414
+ distribution.push(...await drain((page) => input.appVersion === void 0 ? db.findAggregates("insights_distribution", {
415
+ index: "byScope",
416
+ where: {
417
+ channel: input.channel,
418
+ platform
419
+ },
420
+ range,
421
+ limit: PAGE,
422
+ ...page
423
+ }) : db.findAggregates("insights_distribution", {
424
+ index: "byVersion",
425
+ where: {
426
+ channel: input.channel,
427
+ platform,
428
+ app_version: input.appVersion
429
+ },
430
+ range,
431
+ limit: PAGE,
432
+ ...page
433
+ })));
434
+ builtinDistribution.push(...await drain((page) => input.appVersion === void 0 ? db.findAggregates("insights_builtin_distribution", {
435
+ index: "byScope",
436
+ where: {
437
+ channel: input.channel,
438
+ platform
439
+ },
440
+ range,
441
+ limit: PAGE,
442
+ ...page
443
+ }) : db.findAggregates("insights_builtin_distribution", {
444
+ index: "byVersion",
445
+ where: {
446
+ channel: input.channel,
447
+ platform,
448
+ app_version: input.appVersion
449
+ },
450
+ range,
451
+ limit: PAGE,
452
+ ...page
453
+ })));
454
+ }
433
455
  const points = [];
434
456
  for (let start = timeRange.start; start < timeRange.end; start += intervalMs) points.push({
435
457
  startMs: start,
@@ -438,22 +460,31 @@ const getAppUsage = async (db, input, now, retention) => {
438
460
  const versions = /* @__PURE__ */ new Map();
439
461
  const platforms = /* @__PURE__ */ new Map();
440
462
  const bundles = /* @__PURE__ */ new Map();
441
- for (const row of distribution) {
442
- const count = row.latest_installations;
443
- versions.set(row.app_version, (versions.get(row.app_version) ?? 0) + count);
444
- platforms.set(row.platform, (platforms.get(row.platform) ?? 0) + count);
445
- const releaseId = row.release_id === "" ? null : row.release_id;
463
+ const countBundle = (row, releaseId, builtinBundleId, count) => {
446
464
  const key = JSON.stringify([
447
465
  row.app_version,
448
466
  row.platform,
449
- releaseId
467
+ releaseId,
468
+ builtinBundleId
450
469
  ]);
451
470
  bundles.set(key, {
452
471
  appVersion: row.app_version,
453
472
  platform: row.platform,
454
473
  releaseId,
474
+ builtinBundleId,
455
475
  installations: (bundles.get(key)?.installations ?? 0) + count
456
476
  });
477
+ };
478
+ for (const row of distribution) {
479
+ const count = row.latest_installations;
480
+ versions.set(row.app_version, (versions.get(row.app_version) ?? 0) + count);
481
+ platforms.set(row.platform, (platforms.get(row.platform) ?? 0) + count);
482
+ countBundle(row, row.release_id || null, null, count);
483
+ }
484
+ for (const row of builtinDistribution) {
485
+ const releaseId = row.release_id || null;
486
+ countBundle(row, releaseId, null, -row.latest_installations);
487
+ countBundle(row, releaseId, row.builtin_bundle_id, row.latest_installations);
457
488
  }
458
489
  const sortedVersions = byInstallations(versions);
459
490
  return {