@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.
@@ -154,19 +154,40 @@ const createValidatedInsightsModel = (model) => ({
154
154
  if (!require_eventRow.isRecord(input) || !hasOnlyKeys(input, [
155
155
  "releases",
156
156
  "scope",
157
- "timeRange"
157
+ "timeRange",
158
+ "intervalMs"
158
159
  ])) invalidQuery();
159
160
  const releases = input.releases;
160
161
  const scope = input.scope;
161
162
  const timeRange = input.timeRange;
162
- const releaseQuery = Array.isArray(releases) && releases.length > 0 && releases.length <= 100 && releases.every(isReleaseReference) && scope === void 0 && (timeRange === void 0 || isTimeRange(timeRange));
163
- const scopeQuery = releases === void 0 && require_eventRow.isRecord(scope) && hasOnlyKeys(scope, ["platform", "channel"]) && hasScope(scope) && isTimeRange(timeRange);
163
+ const intervalMs = input.intervalMs;
164
+ const releaseQuery = Array.isArray(releases) && releases.length > 0 && releases.length <= 100 && releases.every(isReleaseReference) && scope === void 0 && (timeRange === void 0 ? intervalMs === void 0 : isTimeRange(timeRange) && (intervalMs === void 0 || isTimestamp(intervalMs) && intervalMs > 0 && intervalMs % 36e5 === 0 && (timeRange.end - timeRange.start) / intervalMs <= 8784));
165
+ const scopeQuery = releases === void 0 && intervalMs === void 0 && require_eventRow.isRecord(scope) && hasOnlyKeys(scope, ["platform", "channel"]) && hasScope(scope) && isTimeRange(timeRange);
164
166
  if (!releaseQuery && !scopeQuery) invalidQuery();
165
167
  const result = await model.getReleaseActivity(input);
166
168
  validateAggregateResult(result);
167
169
  if (!Array.isArray(result.data)) invalidResult();
168
170
  return result;
169
171
  },
172
+ async getDistributionHistory(input) {
173
+ if (!require_eventRow.isRecord(input) || !hasOnlyKeys(input, [
174
+ "channel",
175
+ "platform",
176
+ "timeRange"
177
+ ]) || !hasScope(input) || !isTimeRange(input.timeRange) || input.timeRange.start % 864e5 !== 0 || input.timeRange.end % 864e5 !== 0 || input.timeRange.end - input.timeRange.start > 26784e5) invalidQuery();
178
+ const result = await model.getDistributionHistory(input);
179
+ validateAggregateResult(result);
180
+ if (!Array.isArray(result.points)) invalidResult();
181
+ for (const point of result.points) {
182
+ if (!isTimestamp(point.startMs) || !Array.isArray(point.bundles)) invalidResult();
183
+ for (const bundle of point.bundles) if (!isText(bundle.appVersion) || bundle.releaseId !== null && !isText(bundle.releaseId) || ![
184
+ "release",
185
+ "builtin",
186
+ "unknown"
187
+ ].includes(bundle.bundleKind) || !isTimestamp(bundle.installations)) invalidResult();
188
+ }
189
+ return result;
190
+ },
170
191
  async getAppUsage(input) {
171
192
  if (!require_eventRow.isRecord(input) || !hasOnlyKeys(input, [
172
193
  "channel",
@@ -154,19 +154,40 @@ const createValidatedInsightsModel = (model) => ({
154
154
  if (!isRecord(input) || !hasOnlyKeys(input, [
155
155
  "releases",
156
156
  "scope",
157
- "timeRange"
157
+ "timeRange",
158
+ "intervalMs"
158
159
  ])) invalidQuery();
159
160
  const releases = input.releases;
160
161
  const scope = input.scope;
161
162
  const timeRange = input.timeRange;
162
- const releaseQuery = Array.isArray(releases) && releases.length > 0 && releases.length <= 100 && releases.every(isReleaseReference) && scope === void 0 && (timeRange === void 0 || isTimeRange(timeRange));
163
- const scopeQuery = releases === void 0 && isRecord(scope) && hasOnlyKeys(scope, ["platform", "channel"]) && hasScope(scope) && isTimeRange(timeRange);
163
+ const intervalMs = input.intervalMs;
164
+ const releaseQuery = Array.isArray(releases) && releases.length > 0 && releases.length <= 100 && releases.every(isReleaseReference) && scope === void 0 && (timeRange === void 0 ? intervalMs === void 0 : isTimeRange(timeRange) && (intervalMs === void 0 || isTimestamp(intervalMs) && intervalMs > 0 && intervalMs % 36e5 === 0 && (timeRange.end - timeRange.start) / intervalMs <= 8784));
165
+ const scopeQuery = releases === void 0 && intervalMs === void 0 && isRecord(scope) && hasOnlyKeys(scope, ["platform", "channel"]) && hasScope(scope) && isTimeRange(timeRange);
164
166
  if (!releaseQuery && !scopeQuery) invalidQuery();
165
167
  const result = await model.getReleaseActivity(input);
166
168
  validateAggregateResult(result);
167
169
  if (!Array.isArray(result.data)) invalidResult();
168
170
  return result;
169
171
  },
172
+ async getDistributionHistory(input) {
173
+ if (!isRecord(input) || !hasOnlyKeys(input, [
174
+ "channel",
175
+ "platform",
176
+ "timeRange"
177
+ ]) || !hasScope(input) || !isTimeRange(input.timeRange) || input.timeRange.start % 864e5 !== 0 || input.timeRange.end % 864e5 !== 0 || input.timeRange.end - input.timeRange.start > 26784e5) invalidQuery();
178
+ const result = await model.getDistributionHistory(input);
179
+ validateAggregateResult(result);
180
+ if (!Array.isArray(result.points)) invalidResult();
181
+ for (const point of result.points) {
182
+ if (!isTimestamp(point.startMs) || !Array.isArray(point.bundles)) invalidResult();
183
+ for (const bundle of point.bundles) if (!isText(bundle.appVersion) || bundle.releaseId !== null && !isText(bundle.releaseId) || ![
184
+ "release",
185
+ "builtin",
186
+ "unknown"
187
+ ].includes(bundle.bundleKind) || !isTimestamp(bundle.installations)) invalidResult();
188
+ }
189
+ return result;
190
+ },
170
191
  async getAppUsage(input) {
171
192
  if (!isRecord(input) || !hasOnlyKeys(input, [
172
193
  "channel",
@@ -19,6 +19,7 @@ const createInsightsApi = (db, now, retention) => ({
19
19
  countLatestEvents: (input) => require_reads.countLatestEvents(db, input),
20
20
  countEvents: (input) => require_reads.countEvents(db, input),
21
21
  getReleaseActivity: (input) => require_reads.getReleaseActivity(db, input, now, retention),
22
+ getDistributionHistory: (input) => require_reads.getDistributionHistory(db, input, now, retention),
22
23
  getAppUsage: (input) => require_reads.getAppUsage(db, input, now, retention),
23
24
  /** A release's or a channel's update failures, and their breakdown over a time range. */
24
25
  getUpdateFailures: (input) => require_reads.getUpdateFailures(db, input, now, retention)
@@ -87,7 +88,7 @@ const insights = (options = {}) => {
87
88
  const plugin = (0, _hot_updater_plugin_core.definePlugin)({
88
89
  id: "insights",
89
90
  namespace: false,
90
- schemaVersion: "1.2.0",
91
+ schemaVersion: "1.3.0",
91
92
  schema: require_schema.createInsightsSchema(retention),
92
93
  init: ({ db, now }) => {
93
94
  const api = createInsightsApi(db, now, retention);
@@ -102,6 +103,32 @@ const insights = (options = {}) => {
102
103
  handler: (request, params) => routes[handler](params, request)
103
104
  })),
104
105
  failuresEndpoint(api),
106
+ {
107
+ access: "admin",
108
+ method: "GET",
109
+ path: "/distribution-history",
110
+ handler: async (request) => {
111
+ const query = new URL(request.url).searchParams;
112
+ try {
113
+ const result = await require_model.createInsightsModel(api).getDistributionHistory({
114
+ platform: query.get("platform"),
115
+ channel: query.get("channel") ?? "",
116
+ timeRange: {
117
+ start: Number(query.get("start") ?? NaN),
118
+ end: Number(query.get("end") ?? NaN)
119
+ }
120
+ });
121
+ return Response.json(result, { headers: { "cache-control": "private, no-store" } });
122
+ } catch (error) {
123
+ if (error instanceof _hot_updater_plugin_core.DatabaseAdapterInputError) return Response.json({ error: "Choose a platform, channel, and up to 31 whole UTC days." }, { status: 400 });
124
+ if (!(0, _hot_updater_plugin_core.isDatabaseBusyError)(error)) throw error;
125
+ return Response.json({ error: "Service unavailable" }, {
126
+ status: 503,
127
+ headers: { "retry-after": "5" }
128
+ });
129
+ }
130
+ }
131
+ },
105
132
  {
106
133
  access: "admin",
107
134
  method: "GET",
@@ -1,5 +1,5 @@
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
5
  import { ActiveInstallationWindow, BundleEventFailureInput, CreateBundleEventRequest, CreateBundleEventRequestBase, CursorPage, EventCursorPage, EventHistoryRow, InsightsBundleSelection, InsightsCountMeasurement, InsightsHttpResponse, InstallationHistoryRow, InstallationRow, ReportingOverview } from "./domain.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 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 };
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,5 +1,5 @@
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
5
  import { ActiveInstallationWindow, BundleEventFailureInput, CreateBundleEventRequest, CreateBundleEventRequestBase, CursorPage, EventCursorPage, EventHistoryRow, InsightsBundleSelection, InsightsCountMeasurement, InsightsHttpResponse, InstallationHistoryRow, InstallationRow, ReportingOverview } from "./domain.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 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 };
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 };