@hot-updater/plugin-insights 1.0.0-rc.33 → 1.0.0-rc.34

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.
@@ -112,8 +112,7 @@ const insights = (options = {}) => {
112
112
  fromReleaseId: result.fromReleaseId,
113
113
  toBundleId: result.toBundleId,
114
114
  toReleaseId: result.toReleaseId,
115
- updateStrategy: result.updateStrategy,
116
- ...result.status === "RECOVERED" && result.previousProcessExit !== null ? { metadata: { previousProcessExit: result.previousProcessExit } } : {}
115
+ updateStrategy: result.updateStrategy
117
116
  }));
118
117
  };
119
118
  const onUpdateCheck = (result) => {
@@ -168,8 +167,7 @@ const insights = (options = {}) => {
168
167
  ...error.resource === void 0 ? {} : { resource: error.resource },
169
168
  ...error.httpStatus === void 0 ? {} : { httpStatus: error.httpStatus },
170
169
  ...error.transport === void 0 ? {} : { transport: error.transport },
171
- ...error.originCode === void 0 ? {} : { originCode: error.originCode },
172
- ...error.previousProcessExit === void 0 ? {} : { previousProcessExit: error.previousProcessExit }
170
+ ...error.originCode === void 0 ? {} : { originCode: error.originCode }
173
171
  } }
174
172
  }, (now, installId) => require_eventId.createKeyedUUIDv7([
175
173
  installId,
@@ -111,8 +111,7 @@ const insights = (options = {}) => {
111
111
  fromReleaseId: result.fromReleaseId,
112
112
  toBundleId: result.toBundleId,
113
113
  toReleaseId: result.toReleaseId,
114
- updateStrategy: result.updateStrategy,
115
- ...result.status === "RECOVERED" && result.previousProcessExit !== null ? { metadata: { previousProcessExit: result.previousProcessExit } } : {}
114
+ updateStrategy: result.updateStrategy
116
115
  }));
117
116
  };
118
117
  const onUpdateCheck = (result) => {
@@ -167,8 +166,7 @@ const insights = (options = {}) => {
167
166
  ...error.resource === void 0 ? {} : { resource: error.resource },
168
167
  ...error.httpStatus === void 0 ? {} : { httpStatus: error.httpStatus },
169
168
  ...error.transport === void 0 ? {} : { transport: error.transport },
170
- ...error.originCode === void 0 ? {} : { originCode: error.originCode },
171
- ...error.previousProcessExit === void 0 ? {} : { previousProcessExit: error.previousProcessExit }
169
+ ...error.originCode === void 0 ? {} : { originCode: error.originCode }
172
170
  } }
173
171
  }, (now, installId) => createKeyedUUIDv7([
174
172
  installId,
@@ -18,8 +18,6 @@ type BundleEventFailureInput = {
18
18
  /** Original client error text, bounded to fit the event payload. */
19
19
  readonly errorMessage?: string;
20
20
  readonly errorStack?: string;
21
- /** Android's `ApplicationExitInfo` reason for the process before this one. */
22
- readonly previousProcessExit?: string;
23
21
  };
24
22
  /**
25
23
  * The body of `POST /events`. Fields the server does not know are ignored, so
@@ -62,13 +60,7 @@ type CreateBundleEventRequest = (Movement & {
62
60
  readonly patchFallback?: true;
63
61
  };
64
62
  }) | (Movement & {
65
- readonly type: "UPDATE_APPLIED";
66
- }) | (Movement & {
67
- readonly type: "RECOVERED";
68
- readonly metadata?: {
69
- /** Android's `ApplicationExitInfo` reason for the crashed process. */
70
- readonly previousProcessExit?: string;
71
- };
63
+ readonly type: "UPDATE_APPLIED" | "RECOVERED";
72
64
  }) | (Movement & {
73
65
  /**
74
66
  * An update check, download, or install that failed: `fromBundleId`
@@ -105,8 +97,6 @@ type EventHistoryRow = {
105
97
  readonly delivery?: NonNullable<DatabaseBundleEventMetadata["delivery"]>;
106
98
  /** `UPDATE_DOWNLOADED`: a patch failed, and the files or the archive came instead. */
107
99
  readonly patchFallback?: true;
108
- /** `RECOVERED`: Android's reason the crashed process exited. */
109
- readonly previousProcessExit?: string;
110
100
  };
111
101
  type InstallationHistoryRow = EventHistoryRow & {
112
102
  readonly type: "UPDATE_DOWNLOADED" | "UPDATE_APPLIED" | "RECOVERED" | "UPDATE_FAILED";
@@ -18,8 +18,6 @@ type BundleEventFailureInput = {
18
18
  /** Original client error text, bounded to fit the event payload. */
19
19
  readonly errorMessage?: string;
20
20
  readonly errorStack?: string;
21
- /** Android's `ApplicationExitInfo` reason for the process before this one. */
22
- readonly previousProcessExit?: string;
23
21
  };
24
22
  /**
25
23
  * The body of `POST /events`. Fields the server does not know are ignored, so
@@ -62,13 +60,7 @@ type CreateBundleEventRequest = (Movement & {
62
60
  readonly patchFallback?: true;
63
61
  };
64
62
  }) | (Movement & {
65
- readonly type: "UPDATE_APPLIED";
66
- }) | (Movement & {
67
- readonly type: "RECOVERED";
68
- readonly metadata?: {
69
- /** Android's `ApplicationExitInfo` reason for the crashed process. */
70
- readonly previousProcessExit?: string;
71
- };
63
+ readonly type: "UPDATE_APPLIED" | "RECOVERED";
72
64
  }) | (Movement & {
73
65
  /**
74
66
  * An update check, download, or install that failed: `fromBundleId`
@@ -105,8 +97,6 @@ type EventHistoryRow = {
105
97
  readonly delivery?: NonNullable<DatabaseBundleEventMetadata["delivery"]>;
106
98
  /** `UPDATE_DOWNLOADED`: a patch failed, and the files or the archive came instead. */
107
99
  readonly patchFallback?: true;
108
- /** `RECOVERED`: Android's reason the crashed process exited. */
109
- readonly previousProcessExit?: string;
110
100
  };
111
101
  type InstallationHistoryRow = EventHistoryRow & {
112
102
  readonly type: "UPDATE_DOWNLOADED" | "UPDATE_APPLIED" | "RECOVERED" | "UPDATE_FAILED";
@@ -69,7 +69,7 @@ const DELIVERIES = [
69
69
  "manifest",
70
70
  "archive"
71
71
  ];
72
- /** A storage error code or an Android exit reason: letters, digits, and `._-`. */
72
+ /** A storage error code: letters, digits, and `._-`. */
73
73
  const CODE = /^[A-Za-z0-9._-]{1,64}$/;
74
74
  const readCode = (value) => typeof value === "string" && CODE.test(value) ? value : void 0;
75
75
  const readMetadata = (payload) => isRecord(payload.metadata) ? payload.metadata : {};
@@ -85,7 +85,6 @@ function readFailure(value) {
85
85
  const originCode = readCode(failure.originCode);
86
86
  const errorMessage = typeof failure.errorMessage === "string" && failure.errorMessage.length <= 2048 ? failure.errorMessage : void 0;
87
87
  const errorStack = typeof failure.errorStack === "string" && failure.errorStack.length <= 4096 ? failure.errorStack : void 0;
88
- const previousProcessExit = readCode(failure.previousProcessExit);
89
88
  return {
90
89
  stage: oneOf(STAGES, failure.stage),
91
90
  reason: oneOf(REASONS, failure.reason),
@@ -94,8 +93,7 @@ function readFailure(value) {
94
93
  ...failure.transport == null ? {} : { transport: oneOf(TRANSPORTS, failure.transport) },
95
94
  ...originCode === void 0 ? {} : { originCode },
96
95
  ...errorMessage === void 0 ? {} : { errorMessage },
97
- ...errorStack === void 0 ? {} : { errorStack },
98
- ...previousProcessExit === void 0 ? {} : { previousProcessExit }
96
+ ...errorStack === void 0 ? {} : { errorStack }
99
97
  };
100
98
  }
101
99
  function readHttpResponse(value) {
@@ -185,21 +183,11 @@ function requireEvent(payload) {
185
183
  ...Object.keys(read).length === 0 ? {} : { metadata: read }
186
184
  };
187
185
  }
188
- case "UPDATE_APPLIED": return {
186
+ case "UPDATE_APPLIED":
187
+ case "RECOVERED": return {
189
188
  ...movement(),
190
189
  type
191
190
  };
192
- case "RECOVERED": {
193
- const previousProcessExit = readCode(metadata.previousProcessExit);
194
- return {
195
- ...movement(),
196
- type,
197
- ...previousProcessExit === void 0 ? {} : { metadata: {
198
- ...httpMetadata,
199
- previousProcessExit
200
- } }
201
- };
202
- }
203
191
  case "UPDATE_FAILED": return {
204
192
  ...movement(),
205
193
  type,
@@ -265,24 +253,13 @@ function createBundleEventRow(input) {
265
253
  }
266
254
  };
267
255
  }
268
- case "UPDATE_APPLIED": return {
256
+ case "UPDATE_APPLIED":
257
+ case "RECOVERED": return {
269
258
  ...base,
270
259
  from_bundle_id: input.fromBundleId,
271
260
  type: input.type,
272
261
  metadata
273
262
  };
274
- case "RECOVERED": {
275
- const exit = input.metadata?.previousProcessExit;
276
- return {
277
- ...base,
278
- from_bundle_id: input.fromBundleId,
279
- type: input.type,
280
- metadata: {
281
- ...metadata,
282
- ...exit === void 0 ? {} : { previous_process_exit: exit }
283
- }
284
- };
285
- }
286
263
  case "UPDATE_FAILED": {
287
264
  const { failure } = input.metadata;
288
265
  return {
@@ -299,8 +276,7 @@ function createBundleEventRow(input) {
299
276
  ...failure.resource === void 0 ? {} : { resource: failure.resource },
300
277
  ...failure.httpStatus === void 0 ? {} : { http_status: failure.httpStatus },
301
278
  ...failure.transport === void 0 ? {} : { transport: failure.transport },
302
- ...failure.originCode === void 0 ? {} : { origin_code: failure.originCode },
303
- ...failure.previousProcessExit === void 0 ? {} : { previous_process_exit: failure.previousProcessExit }
279
+ ...failure.originCode === void 0 ? {} : { origin_code: failure.originCode }
304
280
  }
305
281
  }
306
282
  };
@@ -69,7 +69,7 @@ const DELIVERIES = [
69
69
  "manifest",
70
70
  "archive"
71
71
  ];
72
- /** A storage error code or an Android exit reason: letters, digits, and `._-`. */
72
+ /** A storage error code: letters, digits, and `._-`. */
73
73
  const CODE = /^[A-Za-z0-9._-]{1,64}$/;
74
74
  const readCode = (value) => typeof value === "string" && CODE.test(value) ? value : void 0;
75
75
  const readMetadata = (payload) => isRecord(payload.metadata) ? payload.metadata : {};
@@ -85,7 +85,6 @@ function readFailure(value) {
85
85
  const originCode = readCode(failure.originCode);
86
86
  const errorMessage = typeof failure.errorMessage === "string" && failure.errorMessage.length <= 2048 ? failure.errorMessage : void 0;
87
87
  const errorStack = typeof failure.errorStack === "string" && failure.errorStack.length <= 4096 ? failure.errorStack : void 0;
88
- const previousProcessExit = readCode(failure.previousProcessExit);
89
88
  return {
90
89
  stage: oneOf(STAGES, failure.stage),
91
90
  reason: oneOf(REASONS, failure.reason),
@@ -94,8 +93,7 @@ function readFailure(value) {
94
93
  ...failure.transport == null ? {} : { transport: oneOf(TRANSPORTS, failure.transport) },
95
94
  ...originCode === void 0 ? {} : { originCode },
96
95
  ...errorMessage === void 0 ? {} : { errorMessage },
97
- ...errorStack === void 0 ? {} : { errorStack },
98
- ...previousProcessExit === void 0 ? {} : { previousProcessExit }
96
+ ...errorStack === void 0 ? {} : { errorStack }
99
97
  };
100
98
  }
101
99
  function readHttpResponse(value) {
@@ -185,21 +183,11 @@ function requireEvent(payload) {
185
183
  ...Object.keys(read).length === 0 ? {} : { metadata: read }
186
184
  };
187
185
  }
188
- case "UPDATE_APPLIED": return {
186
+ case "UPDATE_APPLIED":
187
+ case "RECOVERED": return {
189
188
  ...movement(),
190
189
  type
191
190
  };
192
- case "RECOVERED": {
193
- const previousProcessExit = readCode(metadata.previousProcessExit);
194
- return {
195
- ...movement(),
196
- type,
197
- ...previousProcessExit === void 0 ? {} : { metadata: {
198
- ...httpMetadata,
199
- previousProcessExit
200
- } }
201
- };
202
- }
203
191
  case "UPDATE_FAILED": return {
204
192
  ...movement(),
205
193
  type,
@@ -265,24 +253,13 @@ function createBundleEventRow(input) {
265
253
  }
266
254
  };
267
255
  }
268
- case "UPDATE_APPLIED": return {
256
+ case "UPDATE_APPLIED":
257
+ case "RECOVERED": return {
269
258
  ...base,
270
259
  from_bundle_id: input.fromBundleId,
271
260
  type: input.type,
272
261
  metadata
273
262
  };
274
- case "RECOVERED": {
275
- const exit = input.metadata?.previousProcessExit;
276
- return {
277
- ...base,
278
- from_bundle_id: input.fromBundleId,
279
- type: input.type,
280
- metadata: {
281
- ...metadata,
282
- ...exit === void 0 ? {} : { previous_process_exit: exit }
283
- }
284
- };
285
- }
286
263
  case "UPDATE_FAILED": {
287
264
  const { failure } = input.metadata;
288
265
  return {
@@ -299,8 +276,7 @@ function createBundleEventRow(input) {
299
276
  ...failure.resource === void 0 ? {} : { resource: failure.resource },
300
277
  ...failure.httpStatus === void 0 ? {} : { http_status: failure.httpStatus },
301
278
  ...failure.transport === void 0 ? {} : { transport: failure.transport },
302
- ...failure.originCode === void 0 ? {} : { origin_code: failure.originCode },
303
- ...failure.previousProcessExit === void 0 ? {} : { previous_process_exit: failure.previousProcessExit }
279
+ ...failure.originCode === void 0 ? {} : { origin_code: failure.originCode }
304
280
  }
305
281
  }
306
282
  };
@@ -4,8 +4,8 @@ const isOptional = (value, key, valid) => !Object.hasOwn(value, key) || valid(va
4
4
  const isString = (value) => typeof value === "string";
5
5
  const isDatabaseHttpResponse = (value) => (0, _hot_updater_plugin_core.isDatabaseJsonObject)(value) && (value.resource === "catalog" || value.resource === "artifact") && isString(value.path) && Number.isSafeInteger(value.status) && (value.body === null || isString(value.body)) && typeof value.body_truncated === "boolean" && Number.isSafeInteger(value.received_at_ms);
6
6
  /** An `UPDATE_FAILED` report's stored failure: a stage and a reason, and what else the client knew. */
7
- const isDatabaseBundleEventFailure = (value) => (0, _hot_updater_plugin_core.isDatabaseJsonObject)(value) && isString(value.stage) && isString(value.reason) && isOptional(value, "resource", isString) && isOptional(value, "http_status", (status) => Number.isSafeInteger(status)) && isOptional(value, "transport", isString) && isOptional(value, "origin_code", isString) && isOptional(value, "error_message", isString) && isOptional(value, "error_stack", isString) && isOptional(value, "previous_process_exit", isString);
8
- const isDatabaseBundleEventMetadata = (value) => (0, _hot_updater_plugin_core.isDatabaseJsonObject)(value) && typeof value.cohort === "string" && (value.update_strategy === null || value.update_strategy === "fingerprint" || value.update_strategy === "appVersion") && (value.fingerprint_hash === null || typeof value.fingerprint_hash === "string") && (value.sdk_version === null || typeof value.sdk_version === "string") && isOptional(value, "failure", isDatabaseBundleEventFailure) && isOptional(value, "http_response", isDatabaseHttpResponse) && isOptional(value, "delivery", isString) && isOptional(value, "patch_fallback", (flag) => typeof flag === "boolean") && isOptional(value, "previous_process_exit", isString);
7
+ const isDatabaseBundleEventFailure = (value) => (0, _hot_updater_plugin_core.isDatabaseJsonObject)(value) && isString(value.stage) && isString(value.reason) && isOptional(value, "resource", isString) && isOptional(value, "http_status", (status) => Number.isSafeInteger(status)) && isOptional(value, "transport", isString) && isOptional(value, "origin_code", isString) && isOptional(value, "error_message", isString) && isOptional(value, "error_stack", isString);
8
+ const isDatabaseBundleEventMetadata = (value) => (0, _hot_updater_plugin_core.isDatabaseJsonObject)(value) && typeof value.cohort === "string" && (value.update_strategy === null || value.update_strategy === "fingerprint" || value.update_strategy === "appVersion") && (value.fingerprint_hash === null || typeof value.fingerprint_hash === "string") && (value.sdk_version === null || typeof value.sdk_version === "string") && isOptional(value, "failure", isDatabaseBundleEventFailure) && isOptional(value, "http_response", isDatabaseHttpResponse) && isOptional(value, "delivery", isString) && isOptional(value, "patch_fallback", (flag) => typeof flag === "boolean");
9
9
  const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
10
10
  const isIdentity = (value) => typeof value === "string" && value.length > 0 && value.length <= 255;
11
11
  const isTextOrNull = (value) => value === null || typeof value === "string";
@@ -16,8 +16,6 @@ type BundleEventFailure = DatabaseJsonObject & {
16
16
  readonly origin_code?: string;
17
17
  readonly error_message?: string;
18
18
  readonly error_stack?: string;
19
- /** Android's `ApplicationExitInfo` reason for the process before this one. */
20
- readonly previous_process_exit?: string;
21
19
  };
22
20
  type DatabaseHttpResponse = DatabaseJsonObject & {
23
21
  readonly resource: "catalog" | "artifact";
@@ -40,8 +38,6 @@ type DatabaseBundleEventMetadata = DatabaseJsonObject & {
40
38
  readonly delivery?: "patch" | "manifest" | "archive" | "unknown";
41
39
  /** `UPDATE_DOWNLOADED`: a patch failed and the full files came instead. */
42
40
  readonly patch_fallback?: boolean;
43
- /** `RECOVERED`: Android's `ApplicationExitInfo` reason for the crashed process. */
44
- readonly previous_process_exit?: string;
45
41
  };
46
42
  type BundleEventRowBase = {
47
43
  readonly id: string;
@@ -16,8 +16,6 @@ type BundleEventFailure = DatabaseJsonObject & {
16
16
  readonly origin_code?: string;
17
17
  readonly error_message?: string;
18
18
  readonly error_stack?: string;
19
- /** Android's `ApplicationExitInfo` reason for the process before this one. */
20
- readonly previous_process_exit?: string;
21
19
  };
22
20
  type DatabaseHttpResponse = DatabaseJsonObject & {
23
21
  readonly resource: "catalog" | "artifact";
@@ -40,8 +38,6 @@ type DatabaseBundleEventMetadata = DatabaseJsonObject & {
40
38
  readonly delivery?: "patch" | "manifest" | "archive" | "unknown";
41
39
  /** `UPDATE_DOWNLOADED`: a patch failed and the full files came instead. */
42
40
  readonly patch_fallback?: boolean;
43
- /** `RECOVERED`: Android's `ApplicationExitInfo` reason for the crashed process. */
44
- readonly previous_process_exit?: string;
45
41
  };
46
42
  type BundleEventRowBase = {
47
43
  readonly id: string;
@@ -4,8 +4,8 @@ const isOptional = (value, key, valid) => !Object.hasOwn(value, key) || valid(va
4
4
  const isString = (value) => typeof value === "string";
5
5
  const isDatabaseHttpResponse = (value) => isDatabaseJsonObject(value) && (value.resource === "catalog" || value.resource === "artifact") && isString(value.path) && Number.isSafeInteger(value.status) && (value.body === null || isString(value.body)) && typeof value.body_truncated === "boolean" && Number.isSafeInteger(value.received_at_ms);
6
6
  /** An `UPDATE_FAILED` report's stored failure: a stage and a reason, and what else the client knew. */
7
- const isDatabaseBundleEventFailure = (value) => isDatabaseJsonObject(value) && isString(value.stage) && isString(value.reason) && isOptional(value, "resource", isString) && isOptional(value, "http_status", (status) => Number.isSafeInteger(status)) && isOptional(value, "transport", isString) && isOptional(value, "origin_code", isString) && isOptional(value, "error_message", isString) && isOptional(value, "error_stack", isString) && isOptional(value, "previous_process_exit", isString);
8
- const isDatabaseBundleEventMetadata = (value) => isDatabaseJsonObject(value) && typeof value.cohort === "string" && (value.update_strategy === null || value.update_strategy === "fingerprint" || value.update_strategy === "appVersion") && (value.fingerprint_hash === null || typeof value.fingerprint_hash === "string") && (value.sdk_version === null || typeof value.sdk_version === "string") && isOptional(value, "failure", isDatabaseBundleEventFailure) && isOptional(value, "http_response", isDatabaseHttpResponse) && isOptional(value, "delivery", isString) && isOptional(value, "patch_fallback", (flag) => typeof flag === "boolean") && isOptional(value, "previous_process_exit", isString);
7
+ const isDatabaseBundleEventFailure = (value) => isDatabaseJsonObject(value) && isString(value.stage) && isString(value.reason) && isOptional(value, "resource", isString) && isOptional(value, "http_status", (status) => Number.isSafeInteger(status)) && isOptional(value, "transport", isString) && isOptional(value, "origin_code", isString) && isOptional(value, "error_message", isString) && isOptional(value, "error_stack", isString);
8
+ const isDatabaseBundleEventMetadata = (value) => isDatabaseJsonObject(value) && typeof value.cohort === "string" && (value.update_strategy === null || value.update_strategy === "fingerprint" || value.update_strategy === "appVersion") && (value.fingerprint_hash === null || typeof value.fingerprint_hash === "string") && (value.sdk_version === null || typeof value.sdk_version === "string") && isOptional(value, "failure", isDatabaseBundleEventFailure) && isOptional(value, "http_response", isDatabaseHttpResponse) && isOptional(value, "delivery", isString) && isOptional(value, "patch_fallback", (flag) => typeof flag === "boolean");
9
9
  const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
10
10
  const isIdentity = (value) => typeof value === "string" && value.length > 0 && value.length <= 255;
11
11
  const isTextOrNull = (value) => value === null || typeof value === "string";
@@ -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.4.0",
91
+ schemaVersion: "1.0.0",
92
92
  schema: require_schema.createInsightsSchema(retention),
93
93
  init: ({ db, now }) => {
94
94
  const api = createInsightsApi(db, 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.4.0",
90
+ schemaVersion: "1.0.0",
91
91
  schema: createInsightsSchema(retention),
92
92
  init: ({ db, now }) => {
93
93
  const api = createInsightsApi(db, now, retention);
@@ -152,10 +152,9 @@ const toFailure = (failure) => ({
152
152
  ...failure.resource === void 0 ? {} : { resource: failure.resource },
153
153
  ...failure.http_status === void 0 ? {} : { httpStatus: failure.http_status },
154
154
  ...failure.transport === void 0 ? {} : { transport: failure.transport },
155
- ...failure.origin_code === void 0 ? {} : { originCode: failure.origin_code },
156
- ...failure.previous_process_exit === void 0 ? {} : { previousProcessExit: failure.previous_process_exit }
155
+ ...failure.origin_code === void 0 ? {} : { originCode: failure.origin_code }
157
156
  });
158
- /** What a row's metadata adds for its type: a failure, a delivery, an exit reason. */
157
+ /** What a row's metadata adds for its type: a failure or a delivery. */
159
158
  const eventDetails = ({ type, metadata }) => ({
160
159
  ...metadata.http_response !== void 0 ? { httpResponse: {
161
160
  resource: metadata.http_response.resource,
@@ -167,8 +166,7 @@ const eventDetails = ({ type, metadata }) => ({
167
166
  } } : {},
168
167
  ...type === "UPDATE_FAILED" && metadata.failure !== void 0 ? { failure: toFailure(metadata.failure) } : {},
169
168
  ...type === "UPDATE_DOWNLOADED" && metadata.delivery !== void 0 ? { delivery: metadata.delivery } : {},
170
- ...type === "UPDATE_DOWNLOADED" && metadata.patch_fallback === true ? { patchFallback: true } : {},
171
- ...type === "RECOVERED" && metadata.previous_process_exit !== void 0 ? { previousProcessExit: metadata.previous_process_exit } : {}
169
+ ...type === "UPDATE_DOWNLOADED" && metadata.patch_fallback === true ? { patchFallback: true } : {}
172
170
  });
173
171
  const toEventHistoryRow = (row) => ({
174
172
  ...row.to_release_id == null ? {} : { toReleaseId: row.to_release_id },
@@ -152,10 +152,9 @@ const toFailure = (failure) => ({
152
152
  ...failure.resource === void 0 ? {} : { resource: failure.resource },
153
153
  ...failure.http_status === void 0 ? {} : { httpStatus: failure.http_status },
154
154
  ...failure.transport === void 0 ? {} : { transport: failure.transport },
155
- ...failure.origin_code === void 0 ? {} : { originCode: failure.origin_code },
156
- ...failure.previous_process_exit === void 0 ? {} : { previousProcessExit: failure.previous_process_exit }
155
+ ...failure.origin_code === void 0 ? {} : { originCode: failure.origin_code }
157
156
  });
158
- /** What a row's metadata adds for its type: a failure, a delivery, an exit reason. */
157
+ /** What a row's metadata adds for its type: a failure or a delivery. */
159
158
  const eventDetails = ({ type, metadata }) => ({
160
159
  ...metadata.http_response !== void 0 ? { httpResponse: {
161
160
  resource: metadata.http_response.resource,
@@ -167,8 +166,7 @@ const eventDetails = ({ type, metadata }) => ({
167
166
  } } : {},
168
167
  ...type === "UPDATE_FAILED" && metadata.failure !== void 0 ? { failure: toFailure(metadata.failure) } : {},
169
168
  ...type === "UPDATE_DOWNLOADED" && metadata.delivery !== void 0 ? { delivery: metadata.delivery } : {},
170
- ...type === "UPDATE_DOWNLOADED" && metadata.patch_fallback === true ? { patchFallback: true } : {},
171
- ...type === "RECOVERED" && metadata.previous_process_exit !== void 0 ? { previousProcessExit: metadata.previous_process_exit } : {}
169
+ ...type === "UPDATE_DOWNLOADED" && metadata.patch_fallback === true ? { patchFallback: true } : {}
172
170
  });
173
171
  const toEventHistoryRow = (row) => ({
174
172
  ...row.to_release_id == null ? {} : { toReleaseId: row.to_release_id },
@@ -474,21 +474,18 @@ const isText = (value, maxLength) => typeof value === "string" && value.length >
474
474
  const failed = (...rows) => (0, _hot_updater_plugin_core.countDistinct)((0, _hot_updater_plugin_core.mergeDistinct)(rows.flat().map((row) => row.failed_users)));
475
475
  /** Entries of a count map, most first, then in key order. */
476
476
  const mostFirst = (counts) => [...counts].sort(([leftKey, left], [rightKey, right]) => right.events - left.events || (0, _hot_updater_plugin_core.compareUtf8)(leftKey, rightKey)).map(([, entry]) => entry);
477
- /** Failure rows as their breakdown, and recovery rows as exit reasons. */
477
+ /** The stages a failure row records: where an update failed. */
478
+ const FAILURE_STAGES = /* @__PURE__ */ new Set([
479
+ "check",
480
+ "download",
481
+ "install",
482
+ "unknown"
483
+ ]);
484
+ /** Failure rows as their breakdown by stage, reason, and detail. */
478
485
  const breakdownOf = (rows) => {
479
486
  const failures = /* @__PURE__ */ new Map();
480
- const exits = /* @__PURE__ */ new Map();
481
487
  for (const { stage, reason, detail, events } of rows) {
482
488
  if (events <= 0) continue;
483
- if (stage === "launch") {
484
- const exit = exits.get(reason) ?? {
485
- value: reason,
486
- events: 0
487
- };
488
- exit.events += events;
489
- exits.set(reason, exit);
490
- continue;
491
- }
492
489
  const key = JSON.stringify([stage, reason]);
493
490
  const entry = failures.get(key) ?? {
494
491
  value: {
@@ -507,37 +504,30 @@ const breakdownOf = (rows) => {
507
504
  entry.details.set(detail, known);
508
505
  failures.set(key, entry);
509
506
  }
510
- return {
511
- breakdown: mostFirst(failures).map(({ value, events }) => {
512
- const { details } = failures.get(JSON.stringify([value.stage, value.reason]));
513
- return {
514
- ...value,
515
- events,
516
- details: mostFirst(details).map(({ value: detail, events }) => {
517
- const [resource, httpStatus, originCode, transport] = JSON.parse(detail);
518
- return {
519
- resource,
520
- httpStatus,
521
- originCode,
522
- transport,
523
- events
524
- };
525
- })
526
- };
527
- }),
528
- byExitReason: mostFirst(exits).map(({ value, events }) => ({
529
- exitReason: value,
530
- events
531
- }))
532
- };
507
+ return mostFirst(failures).map(({ value, events }) => {
508
+ const { details } = failures.get(JSON.stringify([value.stage, value.reason]));
509
+ return {
510
+ ...value,
511
+ events,
512
+ details: mostFirst(details).map(({ value: detail, events }) => {
513
+ const [resource, httpStatus, originCode, transport] = JSON.parse(detail);
514
+ return {
515
+ resource,
516
+ httpStatus,
517
+ originCode,
518
+ transport,
519
+ events
520
+ };
521
+ })
522
+ };
523
+ });
533
524
  };
534
525
  /**
535
526
  * A release's or a channel's update failures. Since a release's first
536
527
  * report: its lifetime counters and failed installations. Over a time range
537
- * of at most 30 days: failures, failed checks, the breakdown by stage,
538
- * reason, and detail, and recoveries by exit reason, all summed from the
539
- * breakdown's hourly rows; downloads and failed launches from the counters;
540
- * failed installations from their sketches. A release reads hourly rows, a
528
+ * of at most 30 days: failures, failed checks, and the breakdown by stage,
529
+ * reason, and detail, all summed from the breakdown's hourly rows; downloads
530
+ * from the counters; failed installations from their sketches. A release reads hourly rows, a
541
531
  * channel daily rows past 48 hours like its activity; the breakdown's rows
542
532
  * are hourly, so its coverage is the raw period's.
543
533
  */
@@ -624,13 +614,12 @@ const getUpdateFailures = async (db, input, now, retention) => {
624
614
  }))
625
615
  ]);
626
616
  const total = (field) => counters.reduce((sum, row) => sum + row[field], 0);
627
- const scoped = releaseId === void 0 ? rows : rows.filter((row) => row.release_id === releaseId);
617
+ const scoped = rows.filter((row) => FAILURE_STAGES.has(row.stage) && (releaseId === void 0 || row.release_id === releaseId));
628
618
  const failuresAt = (counted) => scoped.reduce((sum, row) => sum + (counted(row.stage) ? Math.max(0, row.events) : 0), 0);
629
- const { breakdown, byExitReason } = breakdownOf(scoped);
630
619
  return {
631
620
  coverage: coverageOf(timeRange.start, kept.hour),
632
621
  measuredAtMs: at,
633
- failedUpdates: failuresAt((stage) => stage !== "check" && stage !== "launch"),
622
+ failedUpdates: failuresAt((stage) => stage !== "check"),
634
623
  failedInstallations: failed(failures),
635
624
  downloads: total("downloads"),
636
625
  patchDownloads: total("patch_downloads"),
@@ -640,11 +629,7 @@ const getUpdateFailures = async (db, input, now, retention) => {
640
629
  failedInstallations: failed(checks),
641
630
  activeInstallations: (0, _hot_updater_plugin_core.countDistinct)((0, _hot_updater_plugin_core.mergeDistinct)(active.map((row) => row.activity_users)))
642
631
  } } : {},
643
- breakdown,
644
- recoveries: {
645
- failedLaunches: total("failed_launches"),
646
- byExitReason
647
- }
632
+ breakdown: breakdownOf(scoped)
648
633
  };
649
634
  };
650
635
  //#endregion
@@ -44,17 +44,6 @@ interface InsightsUpdateFailures {
44
44
  };
45
45
  /** Over a time range: failures by stage and reason, most first. */
46
46
  readonly breakdown?: readonly InsightsFailureBreakdown[];
47
- /**
48
- * Over a time range: failed launches, and the recoveries whose crashed
49
- * process exited for a reason Android 11+ reported, by reason, most first.
50
- */
51
- readonly recoveries?: {
52
- readonly failedLaunches: number;
53
- readonly byExitReason: readonly {
54
- readonly exitReason: string;
55
- readonly events: number;
56
- }[];
57
- };
58
47
  }
59
48
  //#endregion
60
49
  export { InsightsFailureBreakdown, InsightsUpdateFailures, InsightsUpdateFailuresInput };
@@ -44,17 +44,6 @@ interface InsightsUpdateFailures {
44
44
  };
45
45
  /** Over a time range: failures by stage and reason, most first. */
46
46
  readonly breakdown?: readonly InsightsFailureBreakdown[];
47
- /**
48
- * Over a time range: failed launches, and the recoveries whose crashed
49
- * process exited for a reason Android 11+ reported, by reason, most first.
50
- */
51
- readonly recoveries?: {
52
- readonly failedLaunches: number;
53
- readonly byExitReason: readonly {
54
- readonly exitReason: string;
55
- readonly events: number;
56
- }[];
57
- };
58
47
  }
59
48
  //#endregion
60
49
  export { InsightsFailureBreakdown, InsightsUpdateFailures, InsightsUpdateFailuresInput };
@@ -474,21 +474,18 @@ const isText = (value, maxLength) => typeof value === "string" && value.length >
474
474
  const failed = (...rows) => countDistinct(mergeDistinct(rows.flat().map((row) => row.failed_users)));
475
475
  /** Entries of a count map, most first, then in key order. */
476
476
  const mostFirst = (counts) => [...counts].sort(([leftKey, left], [rightKey, right]) => right.events - left.events || compareUtf8(leftKey, rightKey)).map(([, entry]) => entry);
477
- /** Failure rows as their breakdown, and recovery rows as exit reasons. */
477
+ /** The stages a failure row records: where an update failed. */
478
+ const FAILURE_STAGES = /* @__PURE__ */ new Set([
479
+ "check",
480
+ "download",
481
+ "install",
482
+ "unknown"
483
+ ]);
484
+ /** Failure rows as their breakdown by stage, reason, and detail. */
478
485
  const breakdownOf = (rows) => {
479
486
  const failures = /* @__PURE__ */ new Map();
480
- const exits = /* @__PURE__ */ new Map();
481
487
  for (const { stage, reason, detail, events } of rows) {
482
488
  if (events <= 0) continue;
483
- if (stage === "launch") {
484
- const exit = exits.get(reason) ?? {
485
- value: reason,
486
- events: 0
487
- };
488
- exit.events += events;
489
- exits.set(reason, exit);
490
- continue;
491
- }
492
489
  const key = JSON.stringify([stage, reason]);
493
490
  const entry = failures.get(key) ?? {
494
491
  value: {
@@ -507,37 +504,30 @@ const breakdownOf = (rows) => {
507
504
  entry.details.set(detail, known);
508
505
  failures.set(key, entry);
509
506
  }
510
- return {
511
- breakdown: mostFirst(failures).map(({ value, events }) => {
512
- const { details } = failures.get(JSON.stringify([value.stage, value.reason]));
513
- return {
514
- ...value,
515
- events,
516
- details: mostFirst(details).map(({ value: detail, events }) => {
517
- const [resource, httpStatus, originCode, transport] = JSON.parse(detail);
518
- return {
519
- resource,
520
- httpStatus,
521
- originCode,
522
- transport,
523
- events
524
- };
525
- })
526
- };
527
- }),
528
- byExitReason: mostFirst(exits).map(({ value, events }) => ({
529
- exitReason: value,
530
- events
531
- }))
532
- };
507
+ return mostFirst(failures).map(({ value, events }) => {
508
+ const { details } = failures.get(JSON.stringify([value.stage, value.reason]));
509
+ return {
510
+ ...value,
511
+ events,
512
+ details: mostFirst(details).map(({ value: detail, events }) => {
513
+ const [resource, httpStatus, originCode, transport] = JSON.parse(detail);
514
+ return {
515
+ resource,
516
+ httpStatus,
517
+ originCode,
518
+ transport,
519
+ events
520
+ };
521
+ })
522
+ };
523
+ });
533
524
  };
534
525
  /**
535
526
  * A release's or a channel's update failures. Since a release's first
536
527
  * report: its lifetime counters and failed installations. Over a time range
537
- * of at most 30 days: failures, failed checks, the breakdown by stage,
538
- * reason, and detail, and recoveries by exit reason, all summed from the
539
- * breakdown's hourly rows; downloads and failed launches from the counters;
540
- * failed installations from their sketches. A release reads hourly rows, a
528
+ * of at most 30 days: failures, failed checks, and the breakdown by stage,
529
+ * reason, and detail, all summed from the breakdown's hourly rows; downloads
530
+ * from the counters; failed installations from their sketches. A release reads hourly rows, a
541
531
  * channel daily rows past 48 hours like its activity; the breakdown's rows
542
532
  * are hourly, so its coverage is the raw period's.
543
533
  */
@@ -624,13 +614,12 @@ const getUpdateFailures = async (db, input, now, retention) => {
624
614
  }))
625
615
  ]);
626
616
  const total = (field) => counters.reduce((sum, row) => sum + row[field], 0);
627
- const scoped = releaseId === void 0 ? rows : rows.filter((row) => row.release_id === releaseId);
617
+ const scoped = rows.filter((row) => FAILURE_STAGES.has(row.stage) && (releaseId === void 0 || row.release_id === releaseId));
628
618
  const failuresAt = (counted) => scoped.reduce((sum, row) => sum + (counted(row.stage) ? Math.max(0, row.events) : 0), 0);
629
- const { breakdown, byExitReason } = breakdownOf(scoped);
630
619
  return {
631
620
  coverage: coverageOf(timeRange.start, kept.hour),
632
621
  measuredAtMs: at,
633
- failedUpdates: failuresAt((stage) => stage !== "check" && stage !== "launch"),
622
+ failedUpdates: failuresAt((stage) => stage !== "check"),
634
623
  failedInstallations: failed(failures),
635
624
  downloads: total("downloads"),
636
625
  patchDownloads: total("patch_downloads"),
@@ -640,11 +629,7 @@ const getUpdateFailures = async (db, input, now, retention) => {
640
629
  failedInstallations: failed(checks),
641
630
  activeInstallations: countDistinct(mergeDistinct(active.map((row) => row.activity_users)))
642
631
  } } : {},
643
- breakdown,
644
- recoveries: {
645
- failedLaunches: total("failed_launches"),
646
- byExitReason
647
- }
632
+ breakdown: breakdownOf(scoped)
648
633
  };
649
634
  };
650
635
  //#endregion
@@ -235,23 +235,6 @@ const countFailure = (tx, event) => {
235
235
  detail: failureDetail(failure)
236
236
  }, { events: 1 }, { shardBy });
237
237
  };
238
- /**
239
- * A recovery's exit reason, which Android 11+ reports, in the breakdown of
240
- * the release whose launch failed: stage `launch`, the reason as its reason.
241
- */
242
- const countExit = (tx, event) => {
243
- const exit = event.metadata.previous_process_exit;
244
- if (event.type !== "RECOVERED" || exit === void 0) return;
245
- tx.aggregate("insights_failures", {
246
- platform: event.platform,
247
- channel: event.channel,
248
- bucket_start_ms: hourOf(event.received_at_ms),
249
- release_id: event.from_release_id ?? "",
250
- stage: "launch",
251
- reason: exit,
252
- detail: ""
253
- }, { events: 1 }, { shardBy: event.install_id });
254
- };
255
238
  const isNewer = (event, head) => event.received_at_ms !== head.received_at_ms ? event.received_at_ms > head.received_at_ms : (0, _hot_updater_plugin_core.compareUtf8)(event.id, head.id) > 0;
256
239
  /** What an UNCHANGED report must share with its installation's head to repeat it. */
257
240
  const REPEATED_FIELDS = [
@@ -296,7 +279,6 @@ const recordEvent = (db, event) => {
296
279
  return;
297
280
  }
298
281
  countEvent(tx, event);
299
- countExit(tx, event);
300
282
  if (previous !== null && !isNewer(event, previous)) return;
301
283
  if (previous === null) tx.create("bundle_event_heads", event);
302
284
  else {
@@ -235,23 +235,6 @@ const countFailure = (tx, event) => {
235
235
  detail: failureDetail(failure)
236
236
  }, { events: 1 }, { shardBy });
237
237
  };
238
- /**
239
- * A recovery's exit reason, which Android 11+ reports, in the breakdown of
240
- * the release whose launch failed: stage `launch`, the reason as its reason.
241
- */
242
- const countExit = (tx, event) => {
243
- const exit = event.metadata.previous_process_exit;
244
- if (event.type !== "RECOVERED" || exit === void 0) return;
245
- tx.aggregate("insights_failures", {
246
- platform: event.platform,
247
- channel: event.channel,
248
- bucket_start_ms: hourOf(event.received_at_ms),
249
- release_id: event.from_release_id ?? "",
250
- stage: "launch",
251
- reason: exit,
252
- detail: ""
253
- }, { events: 1 }, { shardBy: event.install_id });
254
- };
255
238
  const isNewer = (event, head) => event.received_at_ms !== head.received_at_ms ? event.received_at_ms > head.received_at_ms : compareUtf8(event.id, head.id) > 0;
256
239
  /** What an UNCHANGED report must share with its installation's head to repeat it. */
257
240
  const REPEATED_FIELDS = [
@@ -296,7 +279,6 @@ const recordEvent = (db, event) => {
296
279
  return;
297
280
  }
298
281
  countEvent(tx, event);
299
- countExit(tx, event);
300
282
  if (previous !== null && !isNewer(event, previous)) return;
301
283
  if (previous === null) tx.create("bundle_event_heads", event);
302
284
  else {
@@ -265,14 +265,12 @@ const lifetimeSketches = () => (0, _hot_updater_plugin_core.defineAggregate)(ide
265
265
  indexes: { window }
266
266
  });
267
267
  /**
268
- * Update failures by what failed, and recoveries by why the crashed process
269
- * exited, a row an hour. `release_id` is the failed update's target release,
270
- * or empty for a failed check or an unknown target, and the recovered
271
- * release for a recovery. A failure's `stage` and `reason` say where and why
272
- * it failed, and `detail` what else the client knew, as the JSON array
273
- * `[resource, httpStatus, originCode, transport]`. A recovery's stage is
274
- * `launch` and its reason the exit reason, with an empty detail. Every index
275
- * is in key order, so its counters stay blind increments.
268
+ * Update failures by what failed, a row an hour. `release_id` is the failed
269
+ * update's target release, or empty for a failed check or an unknown target.
270
+ * `stage` and `reason` say where and why it failed, and `detail` what else
271
+ * the client knew, as the JSON array `[resource, httpStatus, originCode,
272
+ * transport]`. Every index is in key order, so its counters stay blind
273
+ * increments.
276
274
  */
277
275
  const insightsFailures = (retention) => (0, _hot_updater_plugin_core.defineAggregate)({
278
276
  platform: {
@@ -265,14 +265,12 @@ const lifetimeSketches = () => defineAggregate(identityFields, {
265
265
  indexes: { window }
266
266
  });
267
267
  /**
268
- * Update failures by what failed, and recoveries by why the crashed process
269
- * exited, a row an hour. `release_id` is the failed update's target release,
270
- * or empty for a failed check or an unknown target, and the recovered
271
- * release for a recovery. A failure's `stage` and `reason` say where and why
272
- * it failed, and `detail` what else the client knew, as the JSON array
273
- * `[resource, httpStatus, originCode, transport]`. A recovery's stage is
274
- * `launch` and its reason the exit reason, with an empty detail. Every index
275
- * is in key order, so its counters stay blind increments.
268
+ * Update failures by what failed, a row an hour. `release_id` is the failed
269
+ * update's target release, or empty for a failed check or an unknown target.
270
+ * `stage` and `reason` say where and why it failed, and `detail` what else
271
+ * the client knew, as the JSON array `[resource, httpStatus, originCode,
272
+ * transport]`. Every index is in key order, so its counters stay blind
273
+ * increments.
276
274
  */
277
275
  const insightsFailures = (retention) => defineAggregate({
278
276
  platform: {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hot-updater/plugin-insights",
3
3
  "type": "module",
4
- "version": "1.0.0-rc.33",
4
+ "version": "1.0.0-rc.34",
5
5
  "description": "Hot Updater's Insights plugin: launch, download, and update failure events, for the server and the React Native app",
6
6
  "exports": {
7
7
  "./client": {
@@ -33,10 +33,10 @@
33
33
  "package.json"
34
34
  ],
35
35
  "dependencies": {
36
- "@hot-updater/protocol": "1.0.0-rc.29"
36
+ "@hot-updater/protocol": "1.0.0-rc.30"
37
37
  },
38
38
  "peerDependencies": {
39
- "@hot-updater/plugin-core": "^1.0.0-rc.29"
39
+ "@hot-updater/plugin-core": "^1.0.0-rc.30"
40
40
  },
41
41
  "peerDependenciesMeta": {
42
42
  "@hot-updater/plugin-core": {
@@ -46,8 +46,8 @@
46
46
  "devDependencies": {
47
47
  "@types/node": "^20",
48
48
  "vitest": "4.1.4",
49
- "@hot-updater/plugin-core": "1.0.0-rc.29",
50
- "@hot-updater/test-utils": "1.0.0-rc.31"
49
+ "@hot-updater/plugin-core": "1.0.0-rc.30",
50
+ "@hot-updater/test-utils": "1.0.0-rc.32"
51
51
  },
52
52
  "nx": {
53
53
  "implicitDependencies": [