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

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.
@@ -150,42 +150,23 @@ const createValidatedInsightsModel = (model) => ({
150
150
  ]) || !isBundleFilter(input.filter) || !isTimestamp(input.sinceMs) || !isTimestamp(input.beforeReceivedAtMs) || input.sinceMs > input.beforeReceivedAtMs) invalidQuery();
151
151
  return validateCount(await model.countEvents(input));
152
152
  },
153
- async getReleaseActivity(input) {
153
+ async countEventSeries(input) {
154
154
  if (!require_eventRow.isRecord(input) || !hasOnlyKeys(input, [
155
- "releases",
156
- "scope",
155
+ "filter",
157
156
  "timeRange",
158
157
  "intervalMs"
159
- ])) invalidQuery();
160
- const releases = input.releases;
161
- const scope = input.scope;
162
- const timeRange = input.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);
166
- if (!releaseQuery && !scopeQuery) invalidQuery();
167
- const result = await model.getReleaseActivity(input);
168
- validateAggregateResult(result);
169
- if (!Array.isArray(result.data)) invalidResult();
170
- return result;
158
+ ]) || !isBundleFilter(input.filter) || !isTimeRange(input.timeRange) || !isTimestamp(input.intervalMs)) invalidQuery();
159
+ const { start, end } = input.timeRange;
160
+ const { intervalMs } = input;
161
+ if (intervalMs === 0 || intervalMs % 36e5 !== 0 || start % 36e5 !== 0 || (end - start) % intervalMs !== 0 || end - start > 7776e6) invalidQuery();
162
+ const points = await model.countEventSeries(input);
163
+ if (!Array.isArray(points) || points.length !== (end - start) / intervalMs || points.some((point, index) => !require_eventRow.isRecord(point) || !hasOnlyKeys(point, ["startMs", "events"]) || point.startMs !== start + index * intervalMs || !isTimestamp(point.events))) invalidResult();
164
+ return points;
171
165
  },
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
- }
166
+ async getReleaseActivity(input) {
167
+ if (!require_eventRow.isRecord(input) || !hasOnlyKeys(input, ["releases"]) || !Array.isArray(input.releases) || input.releases.length < 1 || input.releases.length > 100 || !input.releases.every(isReleaseReference)) invalidQuery();
168
+ const result = await model.getReleaseActivity(input);
169
+ if (!require_eventRow.isRecord(result) || !isTimestamp(result.measuredAtMs) || !Array.isArray(result.data) || result.data.some((item) => !require_eventRow.isRecord(item) || !isReleaseReference(item.release) || !require_eventRow.isRecord(item.metrics) || !isTimestamp(item.metrics.downloads) || !isTimestamp(item.metrics.launches) || !isTimestamp(item.metrics.failedLaunches))) invalidResult();
189
170
  return result;
190
171
  },
191
172
  async getAppUsage(input) {
@@ -150,42 +150,23 @@ const createValidatedInsightsModel = (model) => ({
150
150
  ]) || !isBundleFilter(input.filter) || !isTimestamp(input.sinceMs) || !isTimestamp(input.beforeReceivedAtMs) || input.sinceMs > input.beforeReceivedAtMs) invalidQuery();
151
151
  return validateCount(await model.countEvents(input));
152
152
  },
153
- async getReleaseActivity(input) {
153
+ async countEventSeries(input) {
154
154
  if (!isRecord(input) || !hasOnlyKeys(input, [
155
- "releases",
156
- "scope",
155
+ "filter",
157
156
  "timeRange",
158
157
  "intervalMs"
159
- ])) invalidQuery();
160
- const releases = input.releases;
161
- const scope = input.scope;
162
- const timeRange = input.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);
166
- if (!releaseQuery && !scopeQuery) invalidQuery();
167
- const result = await model.getReleaseActivity(input);
168
- validateAggregateResult(result);
169
- if (!Array.isArray(result.data)) invalidResult();
170
- return result;
158
+ ]) || !isBundleFilter(input.filter) || !isTimeRange(input.timeRange) || !isTimestamp(input.intervalMs)) invalidQuery();
159
+ const { start, end } = input.timeRange;
160
+ const { intervalMs } = input;
161
+ if (intervalMs === 0 || intervalMs % 36e5 !== 0 || start % 36e5 !== 0 || (end - start) % intervalMs !== 0 || end - start > 7776e6) invalidQuery();
162
+ const points = await model.countEventSeries(input);
163
+ if (!Array.isArray(points) || points.length !== (end - start) / intervalMs || points.some((point, index) => !isRecord(point) || !hasOnlyKeys(point, ["startMs", "events"]) || point.startMs !== start + index * intervalMs || !isTimestamp(point.events))) invalidResult();
164
+ return points;
171
165
  },
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
- }
166
+ async getReleaseActivity(input) {
167
+ if (!isRecord(input) || !hasOnlyKeys(input, ["releases"]) || !Array.isArray(input.releases) || input.releases.length < 1 || input.releases.length > 100 || !input.releases.every(isReleaseReference)) invalidQuery();
168
+ const result = await model.getReleaseActivity(input);
169
+ if (!isRecord(result) || !isTimestamp(result.measuredAtMs) || !Array.isArray(result.data) || result.data.some((item) => !isRecord(item) || !isReleaseReference(item.release) || !isRecord(item.metrics) || !isTimestamp(item.metrics.downloads) || !isTimestamp(item.metrics.launches) || !isTimestamp(item.metrics.failedLaunches))) invalidResult();
189
170
  return result;
190
171
  },
191
172
  async getAppUsage(input) {
@@ -18,8 +18,8 @@ const createInsightsApi = (db, now, retention) => ({
18
18
  findLatestEvents: (input) => require_reads.findLatestEvents(db, input),
19
19
  countLatestEvents: (input) => require_reads.countLatestEvents(db, input),
20
20
  countEvents: (input) => require_reads.countEvents(db, input),
21
- getReleaseActivity: (input) => require_reads.getReleaseActivity(db, input, now, retention),
22
- getDistributionHistory: (input) => require_reads.getDistributionHistory(db, input, now, retention),
21
+ countEventSeries: (input) => require_reads.countEventSeries(db, input),
22
+ getReleaseActivity: (input) => require_reads.getReleaseActivity(db, input, now),
23
23
  getAppUsage: (input) => require_reads.getAppUsage(db, input, now, retention),
24
24
  /** A release's or a channel's update failures, and their breakdown over a time range. */
25
25
  getUpdateFailures: (input) => require_reads.getUpdateFailures(db, input, now, retention)
@@ -88,7 +88,7 @@ const insights = (options = {}) => {
88
88
  const plugin = (0, _hot_updater_plugin_core.definePlugin)({
89
89
  id: "insights",
90
90
  namespace: false,
91
- schemaVersion: "1.3.0",
91
+ schemaVersion: "1.2.0",
92
92
  schema: require_schema.createInsightsSchema(retention),
93
93
  init: ({ db, now }) => {
94
94
  const api = createInsightsApi(db, now, retention);
@@ -103,32 +103,6 @@ const insights = (options = {}) => {
103
103
  handler: (request, params) => routes[handler](params, request)
104
104
  })),
105
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
- },
132
106
  {
133
107
  access: "admin",
134
108
  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, InsightsGetDistributionHistoryInput, InsightsGetDistributionHistoryResult, InsightsGetReleaseActivityInput, InsightsGetReleaseActivityResult, InsightsListEventsInput, InsightsModel, InsightsRecordEventInput, InsightsScope, InsightsTimeRange, ReleaseActivityMetrics, ReleaseReference } from "./modelTypes.cjs";
2
+ import { InsightsBundleEventFilter, InsightsCountEventSeriesInput, InsightsCountEventsInput, InsightsCountLatestEventsInput, InsightsCoverage, InsightsEventCursor, InsightsEventFilter, InsightsEventSeriesPoint, InsightsFindLatestEventsInput, InsightsGetAppUsageInput, InsightsGetAppUsageResult, 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";
@@ -19,8 +19,8 @@ declare const createInsightsApi: (db: HotUpdaterDatabase<InsightsSchema>, now: (
19
19
  findLatestEvents: (input: InsightsFindLatestEventsInput) => Promise<readonly BundleEventRow[]>;
20
20
  countLatestEvents: (input: InsightsCountLatestEventsInput) => Promise<number>;
21
21
  countEvents: (input: InsightsCountEventsInput) => Promise<number>;
22
+ countEventSeries: (input: InsightsCountEventSeriesInput) => Promise<readonly InsightsEventSeriesPoint[]>;
22
23
  getReleaseActivity: (input: InsightsGetReleaseActivityInput) => Promise<InsightsGetReleaseActivityResult>;
23
- getDistributionHistory: (input: InsightsGetDistributionHistoryInput) => Promise<InsightsGetDistributionHistoryResult>;
24
24
  getAppUsage: (input: InsightsGetAppUsageInput) => Promise<InsightsGetAppUsageResult>;
25
25
  /** A release's or a channel's update failures, and their breakdown over a time range. */
26
26
  getUpdateFailures: (input: InsightsUpdateFailuresInput) => Promise<InsightsUpdateFailures>;
@@ -100,10 +100,6 @@ declare const insights: (options?: InsightsOptions) => import("@hot-updater/plug
100
100
  movement_install_id: "string";
101
101
  bundle_ref: "string";
102
102
  }, {
103
- readonly recent: {
104
- readonly eq: readonly ["channel", "platform", "day"];
105
- readonly sort: readonly ["received_at_ms"];
106
- };
107
103
  readonly movementsByInstall: {
108
104
  readonly eq: readonly ["movement_install_id"];
109
105
  readonly sort: readonly ["received_at_ms"];
@@ -203,7 +199,7 @@ declare const insights: (options?: InsightsOptions) => import("@hot-updater/plug
203
199
  readonly eq: readonly ["identity"];
204
200
  readonly sort: readonly ["bucket_start_ms"];
205
201
  };
206
- }, "launch_users" | "activity_users" | "failed_users", readonly ["identity", "bucket_start_ms"]>;
202
+ }, "activity_users" | "failed_users", readonly ["identity", "bucket_start_ms"]>;
207
203
  readonly insights_overview_daily: import("@hot-updater/plugin-core").AggregateDefinition<{
208
204
  readonly identity: {
209
205
  readonly type: "string";
@@ -231,7 +227,7 @@ declare const insights: (options?: InsightsOptions) => import("@hot-updater/plug
231
227
  readonly eq: readonly ["identity"];
232
228
  readonly sort: readonly ["bucket_start_ms"];
233
229
  };
234
- }, "launch_users" | "activity_users" | "failed_users", readonly ["identity", "bucket_start_ms"]>;
230
+ }, "activity_users" | "failed_users", readonly ["identity", "bucket_start_ms"]>;
235
231
  readonly insights_overview_lifetime: import("@hot-updater/plugin-core").AggregateDefinition<{
236
232
  readonly identity: {
237
233
  readonly type: "string";
@@ -288,68 +284,6 @@ declare const insights: (options?: InsightsOptions) => import("@hot-updater/plug
288
284
  readonly sort: readonly ["bucket_start_ms"];
289
285
  };
290
286
  }, 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"]>;
353
287
  readonly insights_latest_by_bundle: import("@hot-updater/plugin-core").AggregateDefinition<{
354
288
  readonly platform: {
355
289
  readonly type: "string";
@@ -447,8 +381,8 @@ declare const insights: (options?: InsightsOptions) => import("@hot-updater/plug
447
381
  findLatestEvents: (input: InsightsFindLatestEventsInput) => Promise<readonly BundleEventRow[]>;
448
382
  countLatestEvents: (input: InsightsCountLatestEventsInput) => Promise<number>;
449
383
  countEvents: (input: InsightsCountEventsInput) => Promise<number>;
384
+ countEventSeries: (input: InsightsCountEventSeriesInput) => Promise<readonly InsightsEventSeriesPoint[]>;
450
385
  getReleaseActivity: (input: InsightsGetReleaseActivityInput) => Promise<InsightsGetReleaseActivityResult>;
451
- getDistributionHistory: (input: InsightsGetDistributionHistoryInput) => Promise<InsightsGetDistributionHistoryResult>;
452
386
  getAppUsage: (input: InsightsGetAppUsageInput) => Promise<InsightsGetAppUsageResult>;
453
387
  /** A release's or a channel's update failures, and their breakdown over a time range. */
454
388
  getUpdateFailures: (input: InsightsUpdateFailuresInput) => Promise<InsightsUpdateFailures>;
@@ -456,4 +390,4 @@ declare const insights: (options?: InsightsOptions) => import("@hot-updater/plug
456
390
  endpoints: PluginEndpoint[];
457
391
  }, {}>;
458
392
  //#endregion
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 };
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 };
@@ -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, InsightsGetDistributionHistoryInput, InsightsGetDistributionHistoryResult, InsightsGetReleaseActivityInput, InsightsGetReleaseActivityResult, InsightsListEventsInput, InsightsModel, InsightsRecordEventInput, InsightsScope, InsightsTimeRange, ReleaseActivityMetrics, ReleaseReference } from "./modelTypes.mjs";
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
5
  import { ActiveInstallationWindow, BundleEventFailureInput, CreateBundleEventRequest, CreateBundleEventRequestBase, CursorPage, EventCursorPage, EventHistoryRow, InsightsBundleSelection, InsightsCountMeasurement, InsightsHttpResponse, InstallationHistoryRow, InstallationRow, ReportingOverview } from "./domain.mjs";
@@ -19,8 +19,8 @@ declare const createInsightsApi: (db: HotUpdaterDatabase<InsightsSchema>, now: (
19
19
  findLatestEvents: (input: InsightsFindLatestEventsInput) => Promise<readonly BundleEventRow[]>;
20
20
  countLatestEvents: (input: InsightsCountLatestEventsInput) => Promise<number>;
21
21
  countEvents: (input: InsightsCountEventsInput) => Promise<number>;
22
+ countEventSeries: (input: InsightsCountEventSeriesInput) => Promise<readonly InsightsEventSeriesPoint[]>;
22
23
  getReleaseActivity: (input: InsightsGetReleaseActivityInput) => Promise<InsightsGetReleaseActivityResult>;
23
- getDistributionHistory: (input: InsightsGetDistributionHistoryInput) => Promise<InsightsGetDistributionHistoryResult>;
24
24
  getAppUsage: (input: InsightsGetAppUsageInput) => Promise<InsightsGetAppUsageResult>;
25
25
  /** A release's or a channel's update failures, and their breakdown over a time range. */
26
26
  getUpdateFailures: (input: InsightsUpdateFailuresInput) => Promise<InsightsUpdateFailures>;
@@ -100,10 +100,6 @@ declare const insights: (options?: InsightsOptions) => import("@hot-updater/plug
100
100
  movement_install_id: "string";
101
101
  bundle_ref: "string";
102
102
  }, {
103
- readonly recent: {
104
- readonly eq: readonly ["channel", "platform", "day"];
105
- readonly sort: readonly ["received_at_ms"];
106
- };
107
103
  readonly movementsByInstall: {
108
104
  readonly eq: readonly ["movement_install_id"];
109
105
  readonly sort: readonly ["received_at_ms"];
@@ -203,7 +199,7 @@ declare const insights: (options?: InsightsOptions) => import("@hot-updater/plug
203
199
  readonly eq: readonly ["identity"];
204
200
  readonly sort: readonly ["bucket_start_ms"];
205
201
  };
206
- }, "launch_users" | "activity_users" | "failed_users", readonly ["identity", "bucket_start_ms"]>;
202
+ }, "activity_users" | "failed_users", readonly ["identity", "bucket_start_ms"]>;
207
203
  readonly insights_overview_daily: import("@hot-updater/plugin-core").AggregateDefinition<{
208
204
  readonly identity: {
209
205
  readonly type: "string";
@@ -231,7 +227,7 @@ declare const insights: (options?: InsightsOptions) => import("@hot-updater/plug
231
227
  readonly eq: readonly ["identity"];
232
228
  readonly sort: readonly ["bucket_start_ms"];
233
229
  };
234
- }, "launch_users" | "activity_users" | "failed_users", readonly ["identity", "bucket_start_ms"]>;
230
+ }, "activity_users" | "failed_users", readonly ["identity", "bucket_start_ms"]>;
235
231
  readonly insights_overview_lifetime: import("@hot-updater/plugin-core").AggregateDefinition<{
236
232
  readonly identity: {
237
233
  readonly type: "string";
@@ -288,68 +284,6 @@ declare const insights: (options?: InsightsOptions) => import("@hot-updater/plug
288
284
  readonly sort: readonly ["bucket_start_ms"];
289
285
  };
290
286
  }, 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"]>;
353
287
  readonly insights_latest_by_bundle: import("@hot-updater/plugin-core").AggregateDefinition<{
354
288
  readonly platform: {
355
289
  readonly type: "string";
@@ -447,8 +381,8 @@ declare const insights: (options?: InsightsOptions) => import("@hot-updater/plug
447
381
  findLatestEvents: (input: InsightsFindLatestEventsInput) => Promise<readonly BundleEventRow[]>;
448
382
  countLatestEvents: (input: InsightsCountLatestEventsInput) => Promise<number>;
449
383
  countEvents: (input: InsightsCountEventsInput) => Promise<number>;
384
+ countEventSeries: (input: InsightsCountEventSeriesInput) => Promise<readonly InsightsEventSeriesPoint[]>;
450
385
  getReleaseActivity: (input: InsightsGetReleaseActivityInput) => Promise<InsightsGetReleaseActivityResult>;
451
- getDistributionHistory: (input: InsightsGetDistributionHistoryInput) => Promise<InsightsGetDistributionHistoryResult>;
452
386
  getAppUsage: (input: InsightsGetAppUsageInput) => Promise<InsightsGetAppUsageResult>;
453
387
  /** A release's or a channel's update failures, and their breakdown over a time range. */
454
388
  getUpdateFailures: (input: InsightsUpdateFailuresInput) => Promise<InsightsUpdateFailures>;
@@ -456,4 +390,4 @@ declare const insights: (options?: InsightsOptions) => import("@hot-updater/plug
456
390
  endpoints: PluginEndpoint[];
457
391
  }, {}>;
458
392
  //#endregion
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 };
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 };
@@ -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, getDistributionHistory, getReleaseActivity, getUpdateFailures, listEvents } from "./reads.mjs";
7
+ import { countEventSeries, countEvents, countLatestEvents, findLatestEvents, getAppUsage, 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
@@ -17,8 +17,8 @@ const createInsightsApi = (db, now, retention) => ({
17
17
  findLatestEvents: (input) => findLatestEvents(db, input),
18
18
  countLatestEvents: (input) => countLatestEvents(db, input),
19
19
  countEvents: (input) => countEvents(db, input),
20
- getReleaseActivity: (input) => getReleaseActivity(db, input, now, retention),
21
- getDistributionHistory: (input) => getDistributionHistory(db, input, now, retention),
20
+ countEventSeries: (input) => countEventSeries(db, input),
21
+ getReleaseActivity: (input) => getReleaseActivity(db, input, now),
22
22
  getAppUsage: (input) => getAppUsage(db, input, now, retention),
23
23
  /** A release's or a channel's update failures, and their breakdown over a time range. */
24
24
  getUpdateFailures: (input) => getUpdateFailures(db, input, now, retention)
@@ -87,7 +87,7 @@ const insights = (options = {}) => {
87
87
  const plugin = definePlugin({
88
88
  id: "insights",
89
89
  namespace: false,
90
- schemaVersion: "1.3.0",
90
+ schemaVersion: "1.2.0",
91
91
  schema: createInsightsSchema(retention),
92
92
  init: ({ db, now }) => {
93
93
  const api = createInsightsApi(db, now, retention);
@@ -102,32 +102,6 @@ const insights = (options = {}) => {
102
102
  handler: (request, params) => routes[handler](params, request)
103
103
  })),
104
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
- },
131
105
  {
132
106
  access: "admin",
133
107
  method: "GET",
@@ -10,9 +10,9 @@ const createInsightsModel = (api) => require_contract.createValidatedInsightsMod
10
10
  findLatestEvents: api.findLatestEvents,
11
11
  countLatestEvents: api.countLatestEvents,
12
12
  countEvents: api.countEvents,
13
+ countEventSeries: api.countEventSeries,
13
14
  getReleaseActivity: api.getReleaseActivity,
14
- getAppUsage: api.getAppUsage,
15
- getDistributionHistory: api.getDistributionHistory
15
+ getAppUsage: api.getAppUsage
16
16
  });
17
17
  //#endregion
18
18
  exports.createInsightsModel = createInsightsModel;
@@ -10,9 +10,9 @@ const createInsightsModel = (api) => createValidatedInsightsModel({
10
10
  findLatestEvents: api.findLatestEvents,
11
11
  countLatestEvents: api.countLatestEvents,
12
12
  countEvents: api.countEvents,
13
+ countEventSeries: api.countEventSeries,
13
14
  getReleaseActivity: api.getReleaseActivity,
14
- getAppUsage: api.getAppUsage,
15
- getDistributionHistory: api.getDistributionHistory
15
+ getAppUsage: api.getAppUsage
16
16
  });
17
17
  //#endregion
18
18
  export { createInsightsModel };
@@ -59,6 +59,19 @@ interface InsightsCountEventsInput {
59
59
  readonly sinceMs: number;
60
60
  readonly beforeReceivedAtMs: number;
61
61
  }
62
+ /** One bundle filter's stored events in each interval of a time range. */
63
+ interface InsightsCountEventSeriesInput {
64
+ readonly filter: InsightsBundleEventFilter;
65
+ /** Whole UTC hours, at most 90 days, that a whole number of intervals fill. */
66
+ readonly timeRange: InsightsTimeRange;
67
+ /** Whole hours each point spans, from `timeRange.start`. */
68
+ readonly intervalMs: number;
69
+ }
70
+ /** Stored events in [startMs, startMs + intervalMs). */
71
+ interface InsightsEventSeriesPoint {
72
+ readonly startMs: number;
73
+ readonly events: number;
74
+ }
62
75
  interface ReleaseReference {
63
76
  readonly releaseId: string;
64
77
  readonly platform: "ios" | "android";
@@ -77,44 +90,22 @@ type InsightsCoverage = {
77
90
  readonly kind: "partial";
78
91
  readonly sinceMs: number | null;
79
92
  };
93
+ /**
94
+ * A release's counts since its first report, from its lifetime counters,
95
+ * which are kept.
96
+ */
80
97
  interface ReleaseActivityMetrics {
81
98
  readonly downloads: number;
82
99
  readonly launches: number;
83
100
  readonly failedLaunches: number;
84
- /** Omitted for lifetime-only bundle-row reads. */
85
- readonly uniqueUsers?: number;
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
- */
90
- readonly series?: readonly {
91
- readonly startMs: number;
92
- readonly downloads: number;
93
- readonly launches: number;
94
- readonly failedLaunches: number;
95
- }[];
96
101
  }
97
- type InsightsGetReleaseActivityInput = {
102
+ interface InsightsGetReleaseActivityInput {
103
+ /** 1 to 100 releases. */
98
104
  readonly releases: readonly ReleaseReference[];
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;
106
- readonly scope?: never;
107
- } | {
108
- readonly scope: InsightsScope;
109
- readonly timeRange: InsightsTimeRange;
110
- readonly intervalMs?: never;
111
- readonly releases?: never;
112
- };
105
+ }
113
106
  interface InsightsGetReleaseActivityResult {
114
- readonly coverage: InsightsCoverage;
115
107
  readonly data: readonly {
116
- readonly release?: ReleaseReference;
117
- readonly scope?: InsightsScope;
108
+ readonly release: ReleaseReference;
118
109
  readonly metrics: ReleaseActivityMetrics;
119
110
  }[];
120
111
  readonly measuredAtMs: number;
@@ -150,23 +141,6 @@ interface InsightsGetAppUsageResult {
150
141
  }[];
151
142
  readonly measuredAtMs: number;
152
143
  }
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
- }
170
144
  interface InsightsModel {
171
145
  /**
172
146
  * Persist the immutable event once. A private latest-event index, if used,
@@ -195,12 +169,15 @@ interface InsightsModel {
195
169
  countLatestEvents(input: InsightsCountLatestEventsInput): Promise<number>;
196
170
  /** Count accepted reports in [sinceMs, beforeReceivedAtMs), across all pages. */
197
171
  countEvents(input: InsightsCountEventsInput): Promise<number>;
198
- /** Read maintained release/scope summaries without scanning raw events. */
172
+ /**
173
+ * A bundle filter's stored events in each interval, every interval
174
+ * present in order, from the hourly counts that `countEvents` reads.
175
+ */
176
+ countEventSeries(input: InsightsCountEventSeriesInput): Promise<readonly InsightsEventSeriesPoint[]>;
177
+ /** Releases' lifetime counters, without scanning raw events. */
199
178
  getReleaseActivity(input: InsightsGetReleaseActivityInput): Promise<InsightsGetReleaseActivityResult>;
200
- /** Daily last-observed running bundles, one per reporting installation. No historical backfill. */
201
- getDistributionHistory(input: InsightsGetDistributionHistoryInput): Promise<InsightsGetDistributionHistoryResult>;
202
179
  /** Read maintained App usage summaries and latest-report distribution. */
203
180
  getAppUsage(input: InsightsGetAppUsageInput): Promise<InsightsGetAppUsageResult>;
204
181
  }
205
182
  //#endregion
206
- export { InsightsBundleEventFilter, InsightsCountEventsInput, InsightsCountLatestEventsInput, InsightsCoverage, InsightsEventCursor, InsightsEventFilter, InsightsFindLatestEventsInput, InsightsGetAppUsageInput, InsightsGetAppUsageResult, InsightsGetDistributionHistoryInput, InsightsGetDistributionHistoryResult, InsightsGetReleaseActivityInput, InsightsGetReleaseActivityResult, InsightsListEventsInput, InsightsModel, InsightsRecordEventInput, InsightsScope, InsightsTimeRange, ReleaseActivityMetrics, ReleaseReference };
183
+ export { InsightsBundleEventFilter, InsightsCountEventSeriesInput, InsightsCountEventsInput, InsightsCountLatestEventsInput, InsightsCoverage, InsightsEventCursor, InsightsEventFilter, InsightsEventSeriesPoint, InsightsFindLatestEventsInput, InsightsGetAppUsageInput, InsightsGetAppUsageResult, InsightsGetReleaseActivityInput, InsightsGetReleaseActivityResult, InsightsListEventsInput, InsightsModel, InsightsRecordEventInput, InsightsScope, InsightsTimeRange, ReleaseActivityMetrics, ReleaseReference };