@hot-updater/plugin-insights 1.0.0-rc.28 → 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.
@@ -1,8 +1,8 @@
1
1
  import { BundleEventFailure, BundleEventFailureReason, BundleEventFailureStage, BundleEventRow, BundleEventRowBase, DatabaseBundleEventMetadata } from "./eventRow.cjs";
2
- import { InsightsBundleEventFilter, InsightsCountEventsInput, InsightsCountLatestEventsInput, InsightsCoverage, InsightsEventCursor, InsightsEventFilter, InsightsFindLatestEventsInput, InsightsGetAppUsageInput, InsightsGetAppUsageResult, InsightsGetReleaseActivityInput, InsightsGetReleaseActivityResult, InsightsListEventsInput, InsightsModel, InsightsRecordEventInput, InsightsScope, InsightsTimeRange, ReleaseActivityMetrics, ReleaseReference } from "./modelTypes.cjs";
2
+ import { InsightsBundleEventFilter, InsightsCountEventsInput, InsightsCountLatestEventsInput, InsightsCoverage, InsightsEventCursor, InsightsEventFilter, InsightsFindLatestEventsInput, InsightsGetAppUsageInput, InsightsGetAppUsageResult, InsightsGetDistributionHistoryInput, InsightsGetDistributionHistoryResult, InsightsGetReleaseActivityInput, InsightsGetReleaseActivityResult, InsightsListEventsInput, InsightsModel, InsightsRecordEventInput, InsightsScope, InsightsTimeRange, ReleaseActivityMetrics, ReleaseReference } from "./modelTypes.cjs";
3
3
  import { DEFAULT_INSIGHTS_RETENTION, InsightsRetention, InsightsSchema, createInsightsSchema, insightsSchema } from "./schema.cjs";
4
4
  import { InsightsFailureBreakdown, InsightsUpdateFailures, InsightsUpdateFailuresInput } from "./reads.cjs";
5
- import { ActiveInstallationWindow, BundleEventFailureInput, CreateBundleEventRequest, CreateBundleEventRequestBase, CursorPage, EventCursorPage, EventHistoryRow, InsightsBundleSelection, InsightsCountMeasurement, InstallationHistoryRow, InstallationRow, ReportingOverview } from "./domain.cjs";
5
+ import { ActiveInstallationWindow, BundleEventFailureInput, CreateBundleEventRequest, CreateBundleEventRequestBase, CursorPage, EventCursorPage, EventHistoryRow, InsightsBundleSelection, InsightsCountMeasurement, InsightsHttpResponse, InstallationHistoryRow, InstallationRow, ReportingOverview } from "./domain.cjs";
6
6
  import { InsightsBadRequestError } from "./errors.cjs";
7
7
  import { createInsightsModel } from "./model.cjs";
8
8
  import { InsightsEventPageInput, InsightsInstallationEventPageInput, InsightsProvider, InsightsUserInstallationPageInput } from "./types.cjs";
@@ -20,6 +20,7 @@ declare const createInsightsApi: (db: HotUpdaterDatabase<InsightsSchema>, now: (
20
20
  countLatestEvents: (input: InsightsCountLatestEventsInput) => Promise<number>;
21
21
  countEvents: (input: InsightsCountEventsInput) => Promise<number>;
22
22
  getReleaseActivity: (input: InsightsGetReleaseActivityInput) => Promise<InsightsGetReleaseActivityResult>;
23
+ getDistributionHistory: (input: InsightsGetDistributionHistoryInput) => Promise<InsightsGetDistributionHistoryResult>;
23
24
  getAppUsage: (input: InsightsGetAppUsageInput) => Promise<InsightsGetAppUsageResult>;
24
25
  /** A release's or a channel's update failures, and their breakdown over a time range. */
25
26
  getUpdateFailures: (input: InsightsUpdateFailuresInput) => Promise<InsightsUpdateFailures>;
@@ -287,6 +288,68 @@ declare const insights: (options?: InsightsOptions) => import("@hot-updater/plug
287
288
  readonly sort: readonly ["bucket_start_ms"];
288
289
  };
289
290
  }, never, readonly ["channel", "platform", "app_version", "release_id", "bucket_start_ms"]>;
291
+ readonly bundle_daily_heads: import("@hot-updater/plugin-core").TableDefinition<{
292
+ readonly install_id: {
293
+ readonly type: "string";
294
+ readonly maxLength: 255;
295
+ };
296
+ readonly bucket_start_ms: {
297
+ readonly type: "integer";
298
+ };
299
+ readonly id: {
300
+ readonly type: "string";
301
+ readonly maxLength: 36;
302
+ };
303
+ readonly received_at_ms: {
304
+ readonly type: "integer";
305
+ };
306
+ readonly channel: {
307
+ readonly type: "string";
308
+ };
309
+ readonly platform: {
310
+ readonly type: "string";
311
+ readonly maxLength: 16;
312
+ };
313
+ readonly app_version: {
314
+ readonly type: "string";
315
+ };
316
+ readonly release_id: {
317
+ readonly type: "string";
318
+ readonly maxLength: 36;
319
+ };
320
+ readonly bundle_kind: {
321
+ readonly type: "string";
322
+ readonly maxLength: 16;
323
+ };
324
+ }, Readonly<Record<string, import("@hot-updater/plugin-core").FieldType>>, {}, readonly ["install_id", "bucket_start_ms"]>;
325
+ readonly insights_distribution_history: import("@hot-updater/plugin-core").AggregateDefinition<{
326
+ readonly channel: {
327
+ readonly type: "string";
328
+ };
329
+ readonly platform: {
330
+ readonly type: "string";
331
+ readonly maxLength: 16;
332
+ };
333
+ readonly app_version: {
334
+ readonly type: "string";
335
+ };
336
+ readonly release_id: {
337
+ readonly type: "string";
338
+ readonly maxLength: 36;
339
+ };
340
+ readonly bundle_kind: {
341
+ readonly type: "string";
342
+ readonly maxLength: 16;
343
+ };
344
+ readonly bucket_start_ms: {
345
+ readonly type: "integer";
346
+ };
347
+ }, "installations", {
348
+ readonly byScope: {
349
+ readonly eq: readonly ["channel", "platform"];
350
+ readonly sort: readonly ["bucket_start_ms"];
351
+ };
352
+ }, never, readonly ["channel", "platform", "app_version", "release_id", "bundle_kind", "bucket_start_ms"]>;
290
353
  readonly insights_latest_by_bundle: import("@hot-updater/plugin-core").AggregateDefinition<{
291
354
  readonly platform: {
292
355
  readonly type: "string";
@@ -385,6 +448,7 @@ declare const insights: (options?: InsightsOptions) => import("@hot-updater/plug
385
448
  countLatestEvents: (input: InsightsCountLatestEventsInput) => Promise<number>;
386
449
  countEvents: (input: InsightsCountEventsInput) => Promise<number>;
387
450
  getReleaseActivity: (input: InsightsGetReleaseActivityInput) => Promise<InsightsGetReleaseActivityResult>;
451
+ getDistributionHistory: (input: InsightsGetDistributionHistoryInput) => Promise<InsightsGetDistributionHistoryResult>;
388
452
  getAppUsage: (input: InsightsGetAppUsageInput) => Promise<InsightsGetAppUsageResult>;
389
453
  /** A release's or a channel's update failures, and their breakdown over a time range. */
390
454
  getUpdateFailures: (input: InsightsUpdateFailuresInput) => Promise<InsightsUpdateFailures>;
@@ -392,4 +456,4 @@ declare const insights: (options?: InsightsOptions) => import("@hot-updater/plug
392
456
  endpoints: PluginEndpoint[];
393
457
  }, {}>;
394
458
  //#endregion
395
- 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 InsightsCountEventsInput, type InsightsCountLatestEventsInput, type InsightsCountMeasurement, type InsightsCoverage, type InsightsEventCursor, type InsightsEventFilter, type InsightsEventPageInput, type InsightsFailureBreakdown, type InsightsFindLatestEventsInput, type InsightsGetAppUsageInput, type InsightsGetAppUsageResult, type InsightsGetReleaseActivityInput, type InsightsGetReleaseActivityResult, 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 };
459
+ 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 InsightsCountEventsInput, type InsightsCountLatestEventsInput, type InsightsCountMeasurement, type InsightsCoverage, type InsightsEventCursor, type InsightsEventFilter, type InsightsEventPageInput, type InsightsFailureBreakdown, type InsightsFindLatestEventsInput, type InsightsGetAppUsageInput, type InsightsGetAppUsageResult, type InsightsGetDistributionHistoryInput, type InsightsGetDistributionHistoryResult, 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
1
  import { BundleEventFailure, BundleEventFailureReason, BundleEventFailureStage, BundleEventRow, BundleEventRowBase, DatabaseBundleEventMetadata } from "./eventRow.mjs";
2
- import { InsightsBundleEventFilter, InsightsCountEventsInput, InsightsCountLatestEventsInput, InsightsCoverage, InsightsEventCursor, InsightsEventFilter, InsightsFindLatestEventsInput, InsightsGetAppUsageInput, InsightsGetAppUsageResult, InsightsGetReleaseActivityInput, InsightsGetReleaseActivityResult, InsightsListEventsInput, InsightsModel, InsightsRecordEventInput, InsightsScope, InsightsTimeRange, ReleaseActivityMetrics, ReleaseReference } from "./modelTypes.mjs";
2
+ import { InsightsBundleEventFilter, InsightsCountEventsInput, InsightsCountLatestEventsInput, InsightsCoverage, InsightsEventCursor, InsightsEventFilter, InsightsFindLatestEventsInput, InsightsGetAppUsageInput, InsightsGetAppUsageResult, InsightsGetDistributionHistoryInput, InsightsGetDistributionHistoryResult, 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, InstallationHistoryRow, InstallationRow, ReportingOverview } from "./domain.mjs";
5
+ import { ActiveInstallationWindow, BundleEventFailureInput, CreateBundleEventRequest, CreateBundleEventRequestBase, CursorPage, EventCursorPage, 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";
@@ -20,6 +20,7 @@ declare const createInsightsApi: (db: HotUpdaterDatabase<InsightsSchema>, now: (
20
20
  countLatestEvents: (input: InsightsCountLatestEventsInput) => Promise<number>;
21
21
  countEvents: (input: InsightsCountEventsInput) => Promise<number>;
22
22
  getReleaseActivity: (input: InsightsGetReleaseActivityInput) => Promise<InsightsGetReleaseActivityResult>;
23
+ getDistributionHistory: (input: InsightsGetDistributionHistoryInput) => Promise<InsightsGetDistributionHistoryResult>;
23
24
  getAppUsage: (input: InsightsGetAppUsageInput) => Promise<InsightsGetAppUsageResult>;
24
25
  /** A release's or a channel's update failures, and their breakdown over a time range. */
25
26
  getUpdateFailures: (input: InsightsUpdateFailuresInput) => Promise<InsightsUpdateFailures>;
@@ -287,6 +288,68 @@ declare const insights: (options?: InsightsOptions) => import("@hot-updater/plug
287
288
  readonly sort: readonly ["bucket_start_ms"];
288
289
  };
289
290
  }, never, readonly ["channel", "platform", "app_version", "release_id", "bucket_start_ms"]>;
291
+ readonly bundle_daily_heads: import("@hot-updater/plugin-core").TableDefinition<{
292
+ readonly install_id: {
293
+ readonly type: "string";
294
+ readonly maxLength: 255;
295
+ };
296
+ readonly bucket_start_ms: {
297
+ readonly type: "integer";
298
+ };
299
+ readonly id: {
300
+ readonly type: "string";
301
+ readonly maxLength: 36;
302
+ };
303
+ readonly received_at_ms: {
304
+ readonly type: "integer";
305
+ };
306
+ readonly channel: {
307
+ readonly type: "string";
308
+ };
309
+ readonly platform: {
310
+ readonly type: "string";
311
+ readonly maxLength: 16;
312
+ };
313
+ readonly app_version: {
314
+ readonly type: "string";
315
+ };
316
+ readonly release_id: {
317
+ readonly type: "string";
318
+ readonly maxLength: 36;
319
+ };
320
+ readonly bundle_kind: {
321
+ readonly type: "string";
322
+ readonly maxLength: 16;
323
+ };
324
+ }, Readonly<Record<string, import("@hot-updater/plugin-core").FieldType>>, {}, readonly ["install_id", "bucket_start_ms"]>;
325
+ readonly insights_distribution_history: import("@hot-updater/plugin-core").AggregateDefinition<{
326
+ readonly channel: {
327
+ readonly type: "string";
328
+ };
329
+ readonly platform: {
330
+ readonly type: "string";
331
+ readonly maxLength: 16;
332
+ };
333
+ readonly app_version: {
334
+ readonly type: "string";
335
+ };
336
+ readonly release_id: {
337
+ readonly type: "string";
338
+ readonly maxLength: 36;
339
+ };
340
+ readonly bundle_kind: {
341
+ readonly type: "string";
342
+ readonly maxLength: 16;
343
+ };
344
+ readonly bucket_start_ms: {
345
+ readonly type: "integer";
346
+ };
347
+ }, "installations", {
348
+ readonly byScope: {
349
+ readonly eq: readonly ["channel", "platform"];
350
+ readonly sort: readonly ["bucket_start_ms"];
351
+ };
352
+ }, never, readonly ["channel", "platform", "app_version", "release_id", "bundle_kind", "bucket_start_ms"]>;
290
353
  readonly insights_latest_by_bundle: import("@hot-updater/plugin-core").AggregateDefinition<{
291
354
  readonly platform: {
292
355
  readonly type: "string";
@@ -385,6 +448,7 @@ declare const insights: (options?: InsightsOptions) => import("@hot-updater/plug
385
448
  countLatestEvents: (input: InsightsCountLatestEventsInput) => Promise<number>;
386
449
  countEvents: (input: InsightsCountEventsInput) => Promise<number>;
387
450
  getReleaseActivity: (input: InsightsGetReleaseActivityInput) => Promise<InsightsGetReleaseActivityResult>;
451
+ getDistributionHistory: (input: InsightsGetDistributionHistoryInput) => Promise<InsightsGetDistributionHistoryResult>;
388
452
  getAppUsage: (input: InsightsGetAppUsageInput) => Promise<InsightsGetAppUsageResult>;
389
453
  /** A release's or a channel's update failures, and their breakdown over a time range. */
390
454
  getUpdateFailures: (input: InsightsUpdateFailuresInput) => Promise<InsightsUpdateFailures>;
@@ -392,4 +456,4 @@ declare const insights: (options?: InsightsOptions) => import("@hot-updater/plug
392
456
  endpoints: PluginEndpoint[];
393
457
  }, {}>;
394
458
  //#endregion
395
- 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 InsightsCountEventsInput, type InsightsCountLatestEventsInput, type InsightsCountMeasurement, type InsightsCoverage, type InsightsEventCursor, type InsightsEventFilter, type InsightsEventPageInput, type InsightsFailureBreakdown, type InsightsFindLatestEventsInput, type InsightsGetAppUsageInput, type InsightsGetAppUsageResult, type InsightsGetReleaseActivityInput, type InsightsGetReleaseActivityResult, 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 };
459
+ 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 InsightsCountEventsInput, type InsightsCountLatestEventsInput, type InsightsCountMeasurement, type InsightsCoverage, type InsightsEventCursor, type InsightsEventFilter, type InsightsEventPageInput, type InsightsFailureBreakdown, type InsightsFindLatestEventsInput, type InsightsGetAppUsageInput, type InsightsGetAppUsageResult, type InsightsGetDistributionHistoryInput, type InsightsGetDistributionHistoryResult, 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 };
@@ -4,7 +4,7 @@ import { InsightsBadRequestError } from "./errors.mjs";
4
4
  import { createInsightsProvider } from "./provider.mjs";
5
5
  import { DEFAULT_INSIGHTS_RETENTION, createInsightsSchema, insightsSchema } from "./schema.mjs";
6
6
  import { insightsIdentity, recordEvent } from "./recordEvent.mjs";
7
- import { countEvents, countLatestEvents, findLatestEvents, getAppUsage, getReleaseActivity, getUpdateFailures, listEvents } from "./reads.mjs";
7
+ import { countEvents, countLatestEvents, findLatestEvents, getAppUsage, getDistributionHistory, getReleaseActivity, getUpdateFailures, listEvents } from "./reads.mjs";
8
8
  import { INSIGHTS_ROUTES, createInsightsRouteHandlers } from "./routes.mjs";
9
9
  import { DatabaseAdapterInputError, HotUpdaterConfigError, definePlugin, isDatabaseBusyError } from "@hot-updater/plugin-core";
10
10
  //#region src/server/index.ts
@@ -18,6 +18,7 @@ const createInsightsApi = (db, now, retention) => ({
18
18
  countLatestEvents: (input) => countLatestEvents(db, input),
19
19
  countEvents: (input) => countEvents(db, input),
20
20
  getReleaseActivity: (input) => getReleaseActivity(db, input, now, retention),
21
+ getDistributionHistory: (input) => getDistributionHistory(db, input, now, retention),
21
22
  getAppUsage: (input) => getAppUsage(db, input, now, retention),
22
23
  /** A release's or a channel's update failures, and their breakdown over a time range. */
23
24
  getUpdateFailures: (input) => getUpdateFailures(db, input, now, retention)
@@ -86,7 +87,7 @@ const insights = (options = {}) => {
86
87
  const plugin = definePlugin({
87
88
  id: "insights",
88
89
  namespace: false,
89
- schemaVersion: "1.2.0",
90
+ schemaVersion: "1.3.0",
90
91
  schema: createInsightsSchema(retention),
91
92
  init: ({ db, now }) => {
92
93
  const api = createInsightsApi(db, now, retention);
@@ -101,6 +102,32 @@ const insights = (options = {}) => {
101
102
  handler: (request, params) => routes[handler](params, request)
102
103
  })),
103
104
  failuresEndpoint(api),
105
+ {
106
+ access: "admin",
107
+ method: "GET",
108
+ path: "/distribution-history",
109
+ handler: async (request) => {
110
+ const query = new URL(request.url).searchParams;
111
+ try {
112
+ const result = await createInsightsModel(api).getDistributionHistory({
113
+ platform: query.get("platform"),
114
+ channel: query.get("channel") ?? "",
115
+ timeRange: {
116
+ start: Number(query.get("start") ?? NaN),
117
+ end: Number(query.get("end") ?? NaN)
118
+ }
119
+ });
120
+ return Response.json(result, { headers: { "cache-control": "private, no-store" } });
121
+ } catch (error) {
122
+ if (error instanceof DatabaseAdapterInputError) return Response.json({ error: "Choose a platform, channel, and up to 31 whole UTC days." }, { status: 400 });
123
+ if (!isDatabaseBusyError(error)) throw error;
124
+ return Response.json({ error: "Service unavailable" }, {
125
+ status: 503,
126
+ headers: { "retry-after": "5" }
127
+ });
128
+ }
129
+ }
130
+ },
104
131
  {
105
132
  access: "admin",
106
133
  method: "GET",
@@ -11,7 +11,8 @@ const createInsightsModel = (api) => require_contract.createValidatedInsightsMod
11
11
  countLatestEvents: api.countLatestEvents,
12
12
  countEvents: api.countEvents,
13
13
  getReleaseActivity: api.getReleaseActivity,
14
- getAppUsage: api.getAppUsage
14
+ getAppUsage: api.getAppUsage,
15
+ getDistributionHistory: api.getDistributionHistory
15
16
  });
16
17
  //#endregion
17
18
  exports.createInsightsModel = createInsightsModel;
@@ -11,7 +11,8 @@ const createInsightsModel = (api) => createValidatedInsightsModel({
11
11
  countLatestEvents: api.countLatestEvents,
12
12
  countEvents: api.countEvents,
13
13
  getReleaseActivity: api.getReleaseActivity,
14
- getAppUsage: api.getAppUsage
14
+ getAppUsage: api.getAppUsage,
15
+ getDistributionHistory: api.getDistributionHistory
15
16
  });
16
17
  //#endregion
17
18
  export { createInsightsModel };
@@ -83,9 +83,13 @@ interface ReleaseActivityMetrics {
83
83
  readonly failedLaunches: number;
84
84
  /** Omitted for lifetime-only bundle-row reads. */
85
85
  readonly uniqueUsers?: number;
86
- /** Daily points, present only for period reads. */
86
+ /**
87
+ * Points of each UTC day, or of each `intervalMs` from the period's start
88
+ * on a release read that asks for one; present only for period reads.
89
+ */
87
90
  readonly series?: readonly {
88
91
  readonly startMs: number;
92
+ readonly downloads: number;
89
93
  readonly launches: number;
90
94
  readonly failedLaunches: number;
91
95
  }[];
@@ -93,10 +97,17 @@ interface ReleaseActivityMetrics {
93
97
  type InsightsGetReleaseActivityInput = {
94
98
  readonly releases: readonly ReleaseReference[];
95
99
  readonly timeRange?: InsightsTimeRange;
100
+ /**
101
+ * Whole hours each series point spans, from `timeRange.start`, with
102
+ * every point present; omitted, the series has a point per UTC day
103
+ * with reports. Only with a `timeRange`.
104
+ */
105
+ readonly intervalMs?: number;
96
106
  readonly scope?: never;
97
107
  } | {
98
108
  readonly scope: InsightsScope;
99
109
  readonly timeRange: InsightsTimeRange;
110
+ readonly intervalMs?: never;
100
111
  readonly releases?: never;
101
112
  };
102
113
  interface InsightsGetReleaseActivityResult {
@@ -139,6 +150,23 @@ interface InsightsGetAppUsageResult {
139
150
  }[];
140
151
  readonly measuredAtMs: number;
141
152
  }
153
+ /** Whole UTC days, at most 31, including the current unfinished day. */
154
+ interface InsightsGetDistributionHistoryInput extends InsightsScope {
155
+ readonly timeRange: InsightsTimeRange;
156
+ }
157
+ interface InsightsGetDistributionHistoryResult {
158
+ readonly coverage: InsightsCoverage;
159
+ readonly measuredAtMs: number;
160
+ readonly points: readonly {
161
+ readonly startMs: number;
162
+ readonly bundles: readonly {
163
+ readonly appVersion: string;
164
+ readonly releaseId: string | null;
165
+ readonly bundleKind: "release" | "builtin" | "unknown";
166
+ readonly installations: number;
167
+ }[];
168
+ }[];
169
+ }
142
170
  interface InsightsModel {
143
171
  /**
144
172
  * Persist the immutable event once. A private latest-event index, if used,
@@ -169,8 +197,10 @@ interface InsightsModel {
169
197
  countEvents(input: InsightsCountEventsInput): Promise<number>;
170
198
  /** Read maintained release/scope summaries without scanning raw events. */
171
199
  getReleaseActivity(input: InsightsGetReleaseActivityInput): Promise<InsightsGetReleaseActivityResult>;
200
+ /** Daily last-observed running bundles, one per reporting installation. No historical backfill. */
201
+ getDistributionHistory(input: InsightsGetDistributionHistoryInput): Promise<InsightsGetDistributionHistoryResult>;
172
202
  /** Read maintained App usage summaries and latest-report distribution. */
173
203
  getAppUsage(input: InsightsGetAppUsageInput): Promise<InsightsGetAppUsageResult>;
174
204
  }
175
205
  //#endregion
176
- export { InsightsBundleEventFilter, InsightsCountEventsInput, InsightsCountLatestEventsInput, InsightsCoverage, InsightsEventCursor, InsightsEventFilter, InsightsFindLatestEventsInput, InsightsGetAppUsageInput, InsightsGetAppUsageResult, InsightsGetReleaseActivityInput, InsightsGetReleaseActivityResult, InsightsListEventsInput, InsightsModel, InsightsRecordEventInput, InsightsScope, InsightsTimeRange, ReleaseActivityMetrics, ReleaseReference };
206
+ export { InsightsBundleEventFilter, InsightsCountEventsInput, InsightsCountLatestEventsInput, InsightsCoverage, InsightsEventCursor, InsightsEventFilter, InsightsFindLatestEventsInput, InsightsGetAppUsageInput, InsightsGetAppUsageResult, InsightsGetDistributionHistoryInput, InsightsGetDistributionHistoryResult, InsightsGetReleaseActivityInput, InsightsGetReleaseActivityResult, InsightsListEventsInput, InsightsModel, InsightsRecordEventInput, InsightsScope, InsightsTimeRange, ReleaseActivityMetrics, ReleaseReference };
@@ -83,9 +83,13 @@ interface ReleaseActivityMetrics {
83
83
  readonly failedLaunches: number;
84
84
  /** Omitted for lifetime-only bundle-row reads. */
85
85
  readonly uniqueUsers?: number;
86
- /** Daily points, present only for period reads. */
86
+ /**
87
+ * Points of each UTC day, or of each `intervalMs` from the period's start
88
+ * on a release read that asks for one; present only for period reads.
89
+ */
87
90
  readonly series?: readonly {
88
91
  readonly startMs: number;
92
+ readonly downloads: number;
89
93
  readonly launches: number;
90
94
  readonly failedLaunches: number;
91
95
  }[];
@@ -93,10 +97,17 @@ interface ReleaseActivityMetrics {
93
97
  type InsightsGetReleaseActivityInput = {
94
98
  readonly releases: readonly ReleaseReference[];
95
99
  readonly timeRange?: InsightsTimeRange;
100
+ /**
101
+ * Whole hours each series point spans, from `timeRange.start`, with
102
+ * every point present; omitted, the series has a point per UTC day
103
+ * with reports. Only with a `timeRange`.
104
+ */
105
+ readonly intervalMs?: number;
96
106
  readonly scope?: never;
97
107
  } | {
98
108
  readonly scope: InsightsScope;
99
109
  readonly timeRange: InsightsTimeRange;
110
+ readonly intervalMs?: never;
100
111
  readonly releases?: never;
101
112
  };
102
113
  interface InsightsGetReleaseActivityResult {
@@ -139,6 +150,23 @@ interface InsightsGetAppUsageResult {
139
150
  }[];
140
151
  readonly measuredAtMs: number;
141
152
  }
153
+ /** Whole UTC days, at most 31, including the current unfinished day. */
154
+ interface InsightsGetDistributionHistoryInput extends InsightsScope {
155
+ readonly timeRange: InsightsTimeRange;
156
+ }
157
+ interface InsightsGetDistributionHistoryResult {
158
+ readonly coverage: InsightsCoverage;
159
+ readonly measuredAtMs: number;
160
+ readonly points: readonly {
161
+ readonly startMs: number;
162
+ readonly bundles: readonly {
163
+ readonly appVersion: string;
164
+ readonly releaseId: string | null;
165
+ readonly bundleKind: "release" | "builtin" | "unknown";
166
+ readonly installations: number;
167
+ }[];
168
+ }[];
169
+ }
142
170
  interface InsightsModel {
143
171
  /**
144
172
  * Persist the immutable event once. A private latest-event index, if used,
@@ -169,8 +197,10 @@ interface InsightsModel {
169
197
  countEvents(input: InsightsCountEventsInput): Promise<number>;
170
198
  /** Read maintained release/scope summaries without scanning raw events. */
171
199
  getReleaseActivity(input: InsightsGetReleaseActivityInput): Promise<InsightsGetReleaseActivityResult>;
200
+ /** Daily last-observed running bundles, one per reporting installation. No historical backfill. */
201
+ getDistributionHistory(input: InsightsGetDistributionHistoryInput): Promise<InsightsGetDistributionHistoryResult>;
172
202
  /** Read maintained App usage summaries and latest-report distribution. */
173
203
  getAppUsage(input: InsightsGetAppUsageInput): Promise<InsightsGetAppUsageResult>;
174
204
  }
175
205
  //#endregion
176
- export { InsightsBundleEventFilter, InsightsCountEventsInput, InsightsCountLatestEventsInput, InsightsCoverage, InsightsEventCursor, InsightsEventFilter, InsightsFindLatestEventsInput, InsightsGetAppUsageInput, InsightsGetAppUsageResult, InsightsGetReleaseActivityInput, InsightsGetReleaseActivityResult, InsightsListEventsInput, InsightsModel, InsightsRecordEventInput, InsightsScope, InsightsTimeRange, ReleaseActivityMetrics, ReleaseReference };
206
+ export { InsightsBundleEventFilter, InsightsCountEventsInput, InsightsCountLatestEventsInput, InsightsCoverage, InsightsEventCursor, InsightsEventFilter, InsightsFindLatestEventsInput, InsightsGetAppUsageInput, InsightsGetAppUsageResult, InsightsGetDistributionHistoryInput, InsightsGetDistributionHistoryResult, InsightsGetReleaseActivityInput, InsightsGetReleaseActivityResult, InsightsListEventsInput, InsightsModel, InsightsRecordEventInput, InsightsScope, InsightsTimeRange, ReleaseActivityMetrics, ReleaseReference };
@@ -157,6 +157,14 @@ const toFailure = (failure) => ({
157
157
  });
158
158
  /** What a row's metadata adds for its type: a failure, a delivery, an exit reason. */
159
159
  const eventDetails = ({ type, metadata }) => ({
160
+ ...metadata.http_response !== void 0 ? { httpResponse: {
161
+ resource: metadata.http_response.resource,
162
+ path: metadata.http_response.path,
163
+ status: metadata.http_response.status,
164
+ body: metadata.http_response.body,
165
+ bodyTruncated: metadata.http_response.body_truncated,
166
+ receivedAtMs: metadata.http_response.received_at_ms
167
+ } } : {},
160
168
  ...type === "UPDATE_FAILED" && metadata.failure !== void 0 ? { failure: toFailure(metadata.failure) } : {},
161
169
  ...type === "UPDATE_DOWNLOADED" && metadata.delivery !== void 0 ? { delivery: metadata.delivery } : {},
162
170
  ...type === "UPDATE_DOWNLOADED" && metadata.patch_fallback === true ? { patchFallback: true } : {},
@@ -179,6 +187,7 @@ const toEventHistoryRow = (row) => ({
179
187
  ...eventDetails(row)
180
188
  });
181
189
  const toInstallationRow = (row) => ({
190
+ ...eventDetails(row).httpResponse ? { httpResponse: eventDetails(row).httpResponse } : {},
182
191
  appVersion: row.app_version,
183
192
  channel: row.channel,
184
193
  cohort: row.metadata.cohort,
@@ -157,6 +157,14 @@ const toFailure = (failure) => ({
157
157
  });
158
158
  /** What a row's metadata adds for its type: a failure, a delivery, an exit reason. */
159
159
  const eventDetails = ({ type, metadata }) => ({
160
+ ...metadata.http_response !== void 0 ? { httpResponse: {
161
+ resource: metadata.http_response.resource,
162
+ path: metadata.http_response.path,
163
+ status: metadata.http_response.status,
164
+ body: metadata.http_response.body,
165
+ bodyTruncated: metadata.http_response.body_truncated,
166
+ receivedAtMs: metadata.http_response.received_at_ms
167
+ } } : {},
160
168
  ...type === "UPDATE_FAILED" && metadata.failure !== void 0 ? { failure: toFailure(metadata.failure) } : {},
161
169
  ...type === "UPDATE_DOWNLOADED" && metadata.delivery !== void 0 ? { delivery: metadata.delivery } : {},
162
170
  ...type === "UPDATE_DOWNLOADED" && metadata.patch_fallback === true ? { patchFallback: true } : {},
@@ -179,6 +187,7 @@ const toEventHistoryRow = (row) => ({
179
187
  ...eventDetails(row)
180
188
  });
181
189
  const toInstallationRow = (row) => ({
190
+ ...eventDetails(row).httpResponse ? { httpResponse: eventDetails(row).httpResponse } : {},
182
191
  appVersion: row.app_version,
183
192
  channel: row.channel,
184
193
  cohort: row.metadata.cohort,
@@ -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;