@hot-updater/plugin-insights 1.0.0-rc.36 → 1.0.0-rc.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/server/contract.cjs +8 -4
- package/dist/server/contract.mjs +8 -4
- package/dist/server/domain.d.cts +30 -4
- package/dist/server/domain.d.mts +30 -4
- package/dist/server/eventRow.cjs +12 -2
- package/dist/server/eventRow.d.cts +33 -1
- package/dist/server/eventRow.d.mts +33 -1
- package/dist/server/eventRow.mjs +12 -2
- package/dist/server/index.cjs +1 -1
- package/dist/server/index.d.cts +2 -2
- package/dist/server/index.d.mts +2 -2
- package/dist/server/index.mjs +1 -1
- package/dist/server/modelTypes.d.cts +3 -2
- package/dist/server/modelTypes.d.mts +3 -2
- package/dist/server/overview.cjs +7 -5
- package/dist/server/overview.mjs +7 -5
- package/dist/server/provider.cjs +13 -1
- package/dist/server/provider.mjs +13 -1
- package/dist/server/reads.cjs +2 -3
- package/dist/server/reads.mjs +2 -3
- package/dist/server/recordEvent.cjs +136 -15
- package/dist/server/recordEvent.mjs +137 -16
- package/dist/server/schema.cjs +46 -10
- package/dist/server/schema.mjs +43 -11
- package/package.json +5 -5
package/dist/server/contract.cjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const require_eventRow = require("./eventRow.cjs");
|
|
2
|
+
const require_schema = require("./schema.cjs");
|
|
2
3
|
let _hot_updater_plugin_core = require("@hot-updater/plugin-core");
|
|
3
4
|
//#region src/server/contract.ts
|
|
4
5
|
const encoder = new TextEncoder();
|
|
@@ -30,7 +31,7 @@ const isBundleFilter = (value, withKind = false) => {
|
|
|
30
31
|
"type",
|
|
31
32
|
...withKind ? ["kind"] : []
|
|
32
33
|
];
|
|
33
|
-
return value.type === "RECOVERED" ? isText(value.fromBundleId) && hasOnlyKeys(value, [...keys, "fromBundleId"]) : (value.type === "UPDATE_DOWNLOADED" || value.type === "UPDATE_APPLIED" || value.type === "UPDATE_FAILED") && isText(value.toBundleId) && hasOnlyKeys(value, [...keys, "toBundleId"]);
|
|
34
|
+
return value.type === "RECOVERED" ? isText(value.fromBundleId) && hasOnlyKeys(value, [...keys, "fromBundleId"]) : (value.type === "UPDATE_DOWNLOADED" || value.type === "UPDATE_APPLIED" || value.type === "UPDATE_FAILED" || value.type === "UNCHANGED") && isText(value.toBundleId) && hasOnlyKeys(value, [...keys, "toBundleId"]);
|
|
34
35
|
};
|
|
35
36
|
const isEventFilter = (value) => {
|
|
36
37
|
if (!require_eventRow.isRecord(value)) return false;
|
|
@@ -49,12 +50,15 @@ const validateRow = (model, row, result = false) => {
|
|
|
49
50
|
};
|
|
50
51
|
/** Throws `invalid-data` unless `row` is a complete, well-formed bundle event row. */
|
|
51
52
|
const assertBundleEventRow = (row) => validateRow("bundle_events", row);
|
|
52
|
-
/**
|
|
53
|
-
|
|
53
|
+
/**
|
|
54
|
+
* Downloads, applies, recoveries, failed updates, and the UNCHANGED rows
|
|
55
|
+
* kept for a change belong in installation history.
|
|
56
|
+
*/
|
|
57
|
+
const isInsightsMovementEvent = (event) => event.type === "UPDATE_DOWNLOADED" || event.type === "UPDATE_APPLIED" || event.type === "RECOVERED" || event.type === "UPDATE_FAILED" || event.type === "UNCHANGED";
|
|
54
58
|
const matchesInsightsEventFilter = (event, filter) => {
|
|
55
59
|
if (filter.kind === "all") return true;
|
|
56
60
|
if (filter.kind === "installationMovement") return event.install_id === filter.installId && isInsightsMovementEvent(event);
|
|
57
|
-
return event.type === filter.type && event.platform === filter.platform && event.channel === filter.channel && (
|
|
61
|
+
return event.type === filter.type && event.platform === filter.platform && event.channel === filter.channel && require_schema.bundleRefsOf(event).includes(require_schema.bundleRefOfFilter(filter));
|
|
58
62
|
};
|
|
59
63
|
const invalidQuery = () => {
|
|
60
64
|
throw new _hot_updater_plugin_core.DatabaseAdapterInputError("invalid-query");
|
package/dist/server/contract.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isRecord, validateBundleEventFields } from "./eventRow.mjs";
|
|
2
|
+
import { bundleRefOfFilter, bundleRefsOf } from "./schema.mjs";
|
|
2
3
|
import { DatabaseAdapterInputError, isUUIDv7 } from "@hot-updater/plugin-core";
|
|
3
4
|
//#region src/server/contract.ts
|
|
4
5
|
const encoder = new TextEncoder();
|
|
@@ -30,7 +31,7 @@ const isBundleFilter = (value, withKind = false) => {
|
|
|
30
31
|
"type",
|
|
31
32
|
...withKind ? ["kind"] : []
|
|
32
33
|
];
|
|
33
|
-
return value.type === "RECOVERED" ? isText(value.fromBundleId) && hasOnlyKeys(value, [...keys, "fromBundleId"]) : (value.type === "UPDATE_DOWNLOADED" || value.type === "UPDATE_APPLIED" || value.type === "UPDATE_FAILED") && isText(value.toBundleId) && hasOnlyKeys(value, [...keys, "toBundleId"]);
|
|
34
|
+
return value.type === "RECOVERED" ? isText(value.fromBundleId) && hasOnlyKeys(value, [...keys, "fromBundleId"]) : (value.type === "UPDATE_DOWNLOADED" || value.type === "UPDATE_APPLIED" || value.type === "UPDATE_FAILED" || value.type === "UNCHANGED") && isText(value.toBundleId) && hasOnlyKeys(value, [...keys, "toBundleId"]);
|
|
34
35
|
};
|
|
35
36
|
const isEventFilter = (value) => {
|
|
36
37
|
if (!isRecord(value)) return false;
|
|
@@ -49,12 +50,15 @@ const validateRow = (model, row, result = false) => {
|
|
|
49
50
|
};
|
|
50
51
|
/** Throws `invalid-data` unless `row` is a complete, well-formed bundle event row. */
|
|
51
52
|
const assertBundleEventRow = (row) => validateRow("bundle_events", row);
|
|
52
|
-
/**
|
|
53
|
-
|
|
53
|
+
/**
|
|
54
|
+
* Downloads, applies, recoveries, failed updates, and the UNCHANGED rows
|
|
55
|
+
* kept for a change belong in installation history.
|
|
56
|
+
*/
|
|
57
|
+
const isInsightsMovementEvent = (event) => event.type === "UPDATE_DOWNLOADED" || event.type === "UPDATE_APPLIED" || event.type === "RECOVERED" || event.type === "UPDATE_FAILED" || event.type === "UNCHANGED";
|
|
54
58
|
const matchesInsightsEventFilter = (event, filter) => {
|
|
55
59
|
if (filter.kind === "all") return true;
|
|
56
60
|
if (filter.kind === "installationMovement") return event.install_id === filter.installId && isInsightsMovementEvent(event);
|
|
57
|
-
return event.type === filter.type && event.platform === filter.platform && event.channel === filter.channel && (
|
|
61
|
+
return event.type === filter.type && event.platform === filter.platform && event.channel === filter.channel && bundleRefsOf(event).includes(bundleRefOfFilter(filter));
|
|
58
62
|
};
|
|
59
63
|
const invalidQuery = () => {
|
|
60
64
|
throw new DatabaseAdapterInputError("invalid-query");
|
package/dist/server/domain.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BundleEventFailure, BundleEventFailureReason, BundleEventFailureStage, DatabaseBundleEventMetadata } from "./eventRow.cjs";
|
|
1
|
+
import { BundleEventChangeKind, BundleEventFailure, BundleEventFailureReason, BundleEventFailureStage, DatabaseBundleEventMetadata } from "./eventRow.cjs";
|
|
2
2
|
import { InsightsScope } from "./modelTypes.cjs";
|
|
3
3
|
import { UpdateHttpResponse } from "@hot-updater/protocol";
|
|
4
4
|
//#region src/server/domain.d.ts
|
|
@@ -82,6 +82,22 @@ type CreateBundleEventRequest = (Movement & {
|
|
|
82
82
|
readonly updateStrategy: null;
|
|
83
83
|
});
|
|
84
84
|
type ActiveInstallationWindow = "24h" | "7d" | "30d";
|
|
85
|
+
/**
|
|
86
|
+
* What a kept UNCHANGED report changed against its installation's previous
|
|
87
|
+
* report: the running bundle or release, the app version, or the channel,
|
|
88
|
+
* with the previous values; `first_seen` alone for an installation's first
|
|
89
|
+
* report to this server.
|
|
90
|
+
*/
|
|
91
|
+
type EventHistoryChange = {
|
|
92
|
+
readonly kinds: readonly BundleEventChangeKind[];
|
|
93
|
+
readonly previous?: {
|
|
94
|
+
readonly bundleId: string;
|
|
95
|
+
readonly releaseId: string | null;
|
|
96
|
+
readonly appVersion: string;
|
|
97
|
+
readonly channel: string;
|
|
98
|
+
readonly minBundleId?: string;
|
|
99
|
+
};
|
|
100
|
+
};
|
|
85
101
|
type EventHistoryRow = {
|
|
86
102
|
readonly toReleaseId?: string;
|
|
87
103
|
readonly sdkVersion?: string;
|
|
@@ -105,11 +121,21 @@ type EventHistoryRow = {
|
|
|
105
121
|
readonly delivery?: NonNullable<DatabaseBundleEventMetadata["delivery"]>;
|
|
106
122
|
/** `UPDATE_DOWNLOADED`: a patch failed, and the files or the archive came instead. */
|
|
107
123
|
readonly patchFallback?: true;
|
|
124
|
+
/** `UNCHANGED`: what the report changed, the reason it was kept. */
|
|
125
|
+
readonly change?: EventHistoryChange;
|
|
126
|
+
/** A download or apply that arrived after its target already ran: it counted nothing. */
|
|
127
|
+
readonly late?: true;
|
|
128
|
+
/** A launch or crash whose download report never arrived, counted with it. */
|
|
129
|
+
readonly impliedDownload?: true;
|
|
108
130
|
};
|
|
109
|
-
type InstallationHistoryRow = EventHistoryRow & {
|
|
131
|
+
type InstallationHistoryRow = EventHistoryRow & ({
|
|
110
132
|
readonly type: "UPDATE_DOWNLOADED" | "UPDATE_APPLIED" | "RECOVERED" | "UPDATE_FAILED";
|
|
111
133
|
readonly fromBundleId: string;
|
|
112
|
-
}
|
|
134
|
+
} | {
|
|
135
|
+
readonly type: "UNCHANGED";
|
|
136
|
+
readonly fromBundleId: null;
|
|
137
|
+
readonly change: EventHistoryChange;
|
|
138
|
+
});
|
|
113
139
|
type InsightsHttpResponse = UpdateHttpResponse & {
|
|
114
140
|
readonly receivedAtMs: number;
|
|
115
141
|
};
|
|
@@ -160,4 +186,4 @@ type ReportingOverview = InsightsScope & {
|
|
|
160
186
|
};
|
|
161
187
|
};
|
|
162
188
|
//#endregion
|
|
163
|
-
export { ActiveInstallationWindow, BundleEventFailureInput, CreateBundleEventRequest, CreateBundleEventRequestBase, CursorPage, EventCursorPage, EventHistoryRow, InsightsBundleSelection, InsightsCountMeasurement, InsightsHttpResponse, InstallationHistoryRow, InstallationRow, ReportingOverview };
|
|
189
|
+
export { ActiveInstallationWindow, BundleEventFailureInput, CreateBundleEventRequest, CreateBundleEventRequestBase, CursorPage, EventCursorPage, EventHistoryChange, EventHistoryRow, InsightsBundleSelection, InsightsCountMeasurement, InsightsHttpResponse, InstallationHistoryRow, InstallationRow, ReportingOverview };
|
package/dist/server/domain.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BundleEventFailure, BundleEventFailureReason, BundleEventFailureStage, DatabaseBundleEventMetadata } from "./eventRow.mjs";
|
|
1
|
+
import { BundleEventChangeKind, BundleEventFailure, BundleEventFailureReason, BundleEventFailureStage, DatabaseBundleEventMetadata } from "./eventRow.mjs";
|
|
2
2
|
import { InsightsScope } from "./modelTypes.mjs";
|
|
3
3
|
import { UpdateHttpResponse } from "@hot-updater/protocol";
|
|
4
4
|
//#region src/server/domain.d.ts
|
|
@@ -82,6 +82,22 @@ type CreateBundleEventRequest = (Movement & {
|
|
|
82
82
|
readonly updateStrategy: null;
|
|
83
83
|
});
|
|
84
84
|
type ActiveInstallationWindow = "24h" | "7d" | "30d";
|
|
85
|
+
/**
|
|
86
|
+
* What a kept UNCHANGED report changed against its installation's previous
|
|
87
|
+
* report: the running bundle or release, the app version, or the channel,
|
|
88
|
+
* with the previous values; `first_seen` alone for an installation's first
|
|
89
|
+
* report to this server.
|
|
90
|
+
*/
|
|
91
|
+
type EventHistoryChange = {
|
|
92
|
+
readonly kinds: readonly BundleEventChangeKind[];
|
|
93
|
+
readonly previous?: {
|
|
94
|
+
readonly bundleId: string;
|
|
95
|
+
readonly releaseId: string | null;
|
|
96
|
+
readonly appVersion: string;
|
|
97
|
+
readonly channel: string;
|
|
98
|
+
readonly minBundleId?: string;
|
|
99
|
+
};
|
|
100
|
+
};
|
|
85
101
|
type EventHistoryRow = {
|
|
86
102
|
readonly toReleaseId?: string;
|
|
87
103
|
readonly sdkVersion?: string;
|
|
@@ -105,11 +121,21 @@ type EventHistoryRow = {
|
|
|
105
121
|
readonly delivery?: NonNullable<DatabaseBundleEventMetadata["delivery"]>;
|
|
106
122
|
/** `UPDATE_DOWNLOADED`: a patch failed, and the files or the archive came instead. */
|
|
107
123
|
readonly patchFallback?: true;
|
|
124
|
+
/** `UNCHANGED`: what the report changed, the reason it was kept. */
|
|
125
|
+
readonly change?: EventHistoryChange;
|
|
126
|
+
/** A download or apply that arrived after its target already ran: it counted nothing. */
|
|
127
|
+
readonly late?: true;
|
|
128
|
+
/** A launch or crash whose download report never arrived, counted with it. */
|
|
129
|
+
readonly impliedDownload?: true;
|
|
108
130
|
};
|
|
109
|
-
type InstallationHistoryRow = EventHistoryRow & {
|
|
131
|
+
type InstallationHistoryRow = EventHistoryRow & ({
|
|
110
132
|
readonly type: "UPDATE_DOWNLOADED" | "UPDATE_APPLIED" | "RECOVERED" | "UPDATE_FAILED";
|
|
111
133
|
readonly fromBundleId: string;
|
|
112
|
-
}
|
|
134
|
+
} | {
|
|
135
|
+
readonly type: "UNCHANGED";
|
|
136
|
+
readonly fromBundleId: null;
|
|
137
|
+
readonly change: EventHistoryChange;
|
|
138
|
+
});
|
|
113
139
|
type InsightsHttpResponse = UpdateHttpResponse & {
|
|
114
140
|
readonly receivedAtMs: number;
|
|
115
141
|
};
|
|
@@ -160,4 +186,4 @@ type ReportingOverview = InsightsScope & {
|
|
|
160
186
|
};
|
|
161
187
|
};
|
|
162
188
|
//#endregion
|
|
163
|
-
export { ActiveInstallationWindow, BundleEventFailureInput, CreateBundleEventRequest, CreateBundleEventRequestBase, CursorPage, EventCursorPage, EventHistoryRow, InsightsBundleSelection, InsightsCountMeasurement, InsightsHttpResponse, InstallationHistoryRow, InstallationRow, ReportingOverview };
|
|
189
|
+
export { ActiveInstallationWindow, BundleEventFailureInput, CreateBundleEventRequest, CreateBundleEventRequestBase, CursorPage, EventCursorPage, EventHistoryChange, EventHistoryRow, InsightsBundleSelection, InsightsCountMeasurement, InsightsHttpResponse, InstallationHistoryRow, InstallationRow, ReportingOverview };
|
package/dist/server/eventRow.cjs
CHANGED
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
let _hot_updater_plugin_core = require("@hot-updater/plugin-core");
|
|
2
2
|
//#region src/server/eventRow.ts
|
|
3
|
+
const CHANGE_KINDS = /* @__PURE__ */ new Set([
|
|
4
|
+
"first_seen",
|
|
5
|
+
"bundle",
|
|
6
|
+
"release",
|
|
7
|
+
"app_version",
|
|
8
|
+
"channel",
|
|
9
|
+
"native_build"
|
|
10
|
+
]);
|
|
11
|
+
const isBundleEventChange = (value) => (0, _hot_updater_plugin_core.isDatabaseJsonObject)(value) && Array.isArray(value.kinds) && value.kinds.length > 0 && value.kinds.every((kind) => CHANGE_KINDS.has(kind)) && (value.previous === null || (0, _hot_updater_plugin_core.isDatabaseJsonObject)(value.previous) && typeof value.previous.bundle_id === "string" && (value.previous.release_id === null || typeof value.previous.release_id === "string") && typeof value.previous.app_version === "string" && typeof value.previous.channel === "string" && isOptional(value.previous, "min_bundle_id", isString));
|
|
3
12
|
const isOptional = (value, key, valid) => !Object.hasOwn(value, key) || valid(value[key]);
|
|
4
13
|
const isString = (value) => typeof value === "string";
|
|
5
14
|
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
15
|
/** An `UPDATE_FAILED` report's stored failure: a stage and a reason, and what else the client knew. */
|
|
7
16
|
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, "min_bundle_id", isString) && isOptional(value, "failure", isDatabaseBundleEventFailure) && isOptional(value, "http_response", isDatabaseHttpResponse) && isOptional(value, "delivery", isString) && isOptional(value, "patch_fallback", (flag) => typeof flag === "boolean");
|
|
17
|
+
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, "min_bundle_id", isString) && isOptional(value, "failure", isDatabaseBundleEventFailure) && isOptional(value, "http_response", isDatabaseHttpResponse) && isOptional(value, "delivery", isString) && isOptional(value, "patch_fallback", (flag) => typeof flag === "boolean") && isOptional(value, "change", isBundleEventChange) && isOptional(value, "late", (flag) => flag === true) && isOptional(value, "implied_download", (flag) => flag === true);
|
|
9
18
|
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
10
19
|
/**
|
|
11
20
|
* The built-in bundle a report says its installation runs: the running
|
|
@@ -36,13 +45,14 @@ const BUNDLE_EVENT_FIELDS = {
|
|
|
36
45
|
metadata: isDatabaseBundleEventMetadata,
|
|
37
46
|
received_at_ms: (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0
|
|
38
47
|
};
|
|
48
|
+
const hasMetadata = (row, key) => isRecord(row.metadata) && Object.hasOwn(row.metadata, key);
|
|
39
49
|
const updateStrategyOf = (row) => isRecord(row.metadata) ? row.metadata.update_strategy : void 0;
|
|
40
50
|
/**
|
|
41
51
|
* A movement, or a failed update, names the bundle it left and how the
|
|
42
52
|
* device updates, and a failed update says what failed; an unchanged report
|
|
43
53
|
* names neither.
|
|
44
54
|
*/
|
|
45
|
-
const hasEventInvariants = (row) => (row.type === "UPDATE_DOWNLOADED" || row.type === "UPDATE_APPLIED" || row.type === "RECOVERED" || row.type === "UPDATE_FAILED") && typeof row.from_bundle_id === "string" && (updateStrategyOf(row) === "fingerprint" || updateStrategyOf(row) === "appVersion") && (row.type !== "UPDATE_FAILED" || isRecord(row.metadata) && isRecord(row.metadata.failure)) || row.type === "UNCHANGED" && row.from_bundle_id === null && updateStrategyOf(row) === null;
|
|
55
|
+
const hasEventInvariants = (row) => (row.type === "UPDATE_DOWNLOADED" || row.type === "UPDATE_APPLIED" || row.type === "RECOVERED" || row.type === "UPDATE_FAILED") && typeof row.from_bundle_id === "string" && (updateStrategyOf(row) === "fingerprint" || updateStrategyOf(row) === "appVersion") && (row.type !== "UPDATE_FAILED" || isRecord(row.metadata) && isRecord(row.metadata.failure)) && !hasMetadata(row, "change") && (!hasMetadata(row, "late") || row.type === "UPDATE_DOWNLOADED" || row.type === "UPDATE_APPLIED") && (!hasMetadata(row, "implied_download") || row.type === "UPDATE_APPLIED" || row.type === "RECOVERED") || row.type === "UNCHANGED" && !hasMetadata(row, "late") && row.from_bundle_id === null && updateStrategyOf(row) === null;
|
|
46
56
|
/**
|
|
47
57
|
* Throws unless `row` has exactly the bundle event fields, each of its type,
|
|
48
58
|
* and the invariants of its event type: `invalid-field` for an unknown field,
|
|
@@ -25,6 +25,25 @@ type DatabaseHttpResponse = DatabaseJsonObject & {
|
|
|
25
25
|
readonly body_truncated: boolean;
|
|
26
26
|
readonly received_at_ms: number;
|
|
27
27
|
};
|
|
28
|
+
/** What a kept UNCHANGED row changed against its installation's head. */
|
|
29
|
+
type BundleEventChangeKind = "first_seen" | "bundle" | "release" | "app_version" | "channel" | "native_build";
|
|
30
|
+
/**
|
|
31
|
+
* An UNCHANGED report the server keeps: what it changed against its
|
|
32
|
+
* installation's head, and the running values the head held before it. A
|
|
33
|
+
* report from an installation without a head is `first_seen` alone, with no
|
|
34
|
+
* previous values: the installation is new to this server, not necessarily
|
|
35
|
+
* to its device.
|
|
36
|
+
*/
|
|
37
|
+
type BundleEventChange = DatabaseJsonObject & {
|
|
38
|
+
readonly kinds: BundleEventChangeKind[];
|
|
39
|
+
readonly previous: (DatabaseJsonObject & {
|
|
40
|
+
readonly bundle_id: string;
|
|
41
|
+
readonly release_id: string | null;
|
|
42
|
+
readonly app_version: string;
|
|
43
|
+
readonly channel: string;
|
|
44
|
+
readonly min_bundle_id?: string;
|
|
45
|
+
}) | null;
|
|
46
|
+
};
|
|
28
47
|
/** Ancillary report data; queryable identity and lifecycle fields stay on the row. */
|
|
29
48
|
type DatabaseBundleEventMetadata = DatabaseJsonObject & {
|
|
30
49
|
readonly cohort: string;
|
|
@@ -40,6 +59,19 @@ type DatabaseBundleEventMetadata = DatabaseJsonObject & {
|
|
|
40
59
|
readonly delivery?: "patch" | "manifest" | "archive" | "unknown";
|
|
41
60
|
/** `UPDATE_DOWNLOADED`: a patch failed and the full files came instead. */
|
|
42
61
|
readonly patch_fallback?: boolean;
|
|
62
|
+
/** A kept `UNCHANGED` row: what it changed, which the server sets. */
|
|
63
|
+
readonly change?: BundleEventChange;
|
|
64
|
+
/**
|
|
65
|
+
* A download or apply that arrived after its installation already ran its
|
|
66
|
+
* target, as when a reload cut the first runtime's report short: the
|
|
67
|
+
* server keeps it in history, but it moves and counts nothing.
|
|
68
|
+
*/
|
|
69
|
+
readonly late?: true;
|
|
70
|
+
/**
|
|
71
|
+
* A launch or crash of a bundle whose download report never arrived: the
|
|
72
|
+
* server counted that download with it.
|
|
73
|
+
*/
|
|
74
|
+
readonly implied_download?: true;
|
|
43
75
|
};
|
|
44
76
|
type BundleEventRowBase = {
|
|
45
77
|
readonly id: string;
|
|
@@ -70,4 +102,4 @@ type BundleEventRow = BundleEventRowBase & ({
|
|
|
70
102
|
readonly from_bundle_id: null;
|
|
71
103
|
});
|
|
72
104
|
//#endregion
|
|
73
|
-
export { BundleEventFailure, BundleEventFailureReason, BundleEventFailureStage, BundleEventRow, BundleEventRowBase, DatabaseBundleEventMetadata, DatabaseHttpResponse };
|
|
105
|
+
export { BundleEventChange, BundleEventChangeKind, BundleEventFailure, BundleEventFailureReason, BundleEventFailureStage, BundleEventRow, BundleEventRowBase, DatabaseBundleEventMetadata, DatabaseHttpResponse };
|
|
@@ -25,6 +25,25 @@ type DatabaseHttpResponse = DatabaseJsonObject & {
|
|
|
25
25
|
readonly body_truncated: boolean;
|
|
26
26
|
readonly received_at_ms: number;
|
|
27
27
|
};
|
|
28
|
+
/** What a kept UNCHANGED row changed against its installation's head. */
|
|
29
|
+
type BundleEventChangeKind = "first_seen" | "bundle" | "release" | "app_version" | "channel" | "native_build";
|
|
30
|
+
/**
|
|
31
|
+
* An UNCHANGED report the server keeps: what it changed against its
|
|
32
|
+
* installation's head, and the running values the head held before it. A
|
|
33
|
+
* report from an installation without a head is `first_seen` alone, with no
|
|
34
|
+
* previous values: the installation is new to this server, not necessarily
|
|
35
|
+
* to its device.
|
|
36
|
+
*/
|
|
37
|
+
type BundleEventChange = DatabaseJsonObject & {
|
|
38
|
+
readonly kinds: BundleEventChangeKind[];
|
|
39
|
+
readonly previous: (DatabaseJsonObject & {
|
|
40
|
+
readonly bundle_id: string;
|
|
41
|
+
readonly release_id: string | null;
|
|
42
|
+
readonly app_version: string;
|
|
43
|
+
readonly channel: string;
|
|
44
|
+
readonly min_bundle_id?: string;
|
|
45
|
+
}) | null;
|
|
46
|
+
};
|
|
28
47
|
/** Ancillary report data; queryable identity and lifecycle fields stay on the row. */
|
|
29
48
|
type DatabaseBundleEventMetadata = DatabaseJsonObject & {
|
|
30
49
|
readonly cohort: string;
|
|
@@ -40,6 +59,19 @@ type DatabaseBundleEventMetadata = DatabaseJsonObject & {
|
|
|
40
59
|
readonly delivery?: "patch" | "manifest" | "archive" | "unknown";
|
|
41
60
|
/** `UPDATE_DOWNLOADED`: a patch failed and the full files came instead. */
|
|
42
61
|
readonly patch_fallback?: boolean;
|
|
62
|
+
/** A kept `UNCHANGED` row: what it changed, which the server sets. */
|
|
63
|
+
readonly change?: BundleEventChange;
|
|
64
|
+
/**
|
|
65
|
+
* A download or apply that arrived after its installation already ran its
|
|
66
|
+
* target, as when a reload cut the first runtime's report short: the
|
|
67
|
+
* server keeps it in history, but it moves and counts nothing.
|
|
68
|
+
*/
|
|
69
|
+
readonly late?: true;
|
|
70
|
+
/**
|
|
71
|
+
* A launch or crash of a bundle whose download report never arrived: the
|
|
72
|
+
* server counted that download with it.
|
|
73
|
+
*/
|
|
74
|
+
readonly implied_download?: true;
|
|
43
75
|
};
|
|
44
76
|
type BundleEventRowBase = {
|
|
45
77
|
readonly id: string;
|
|
@@ -70,4 +102,4 @@ type BundleEventRow = BundleEventRowBase & ({
|
|
|
70
102
|
readonly from_bundle_id: null;
|
|
71
103
|
});
|
|
72
104
|
//#endregion
|
|
73
|
-
export { BundleEventFailure, BundleEventFailureReason, BundleEventFailureStage, BundleEventRow, BundleEventRowBase, DatabaseBundleEventMetadata, DatabaseHttpResponse };
|
|
105
|
+
export { BundleEventChange, BundleEventChangeKind, BundleEventFailure, BundleEventFailureReason, BundleEventFailureStage, BundleEventRow, BundleEventRowBase, DatabaseBundleEventMetadata, DatabaseHttpResponse };
|
package/dist/server/eventRow.mjs
CHANGED
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
import { DatabaseAdapterInputError, isDatabaseJsonObject } from "@hot-updater/plugin-core";
|
|
2
2
|
//#region src/server/eventRow.ts
|
|
3
|
+
const CHANGE_KINDS = /* @__PURE__ */ new Set([
|
|
4
|
+
"first_seen",
|
|
5
|
+
"bundle",
|
|
6
|
+
"release",
|
|
7
|
+
"app_version",
|
|
8
|
+
"channel",
|
|
9
|
+
"native_build"
|
|
10
|
+
]);
|
|
11
|
+
const isBundleEventChange = (value) => isDatabaseJsonObject(value) && Array.isArray(value.kinds) && value.kinds.length > 0 && value.kinds.every((kind) => CHANGE_KINDS.has(kind)) && (value.previous === null || isDatabaseJsonObject(value.previous) && typeof value.previous.bundle_id === "string" && (value.previous.release_id === null || typeof value.previous.release_id === "string") && typeof value.previous.app_version === "string" && typeof value.previous.channel === "string" && isOptional(value.previous, "min_bundle_id", isString));
|
|
3
12
|
const isOptional = (value, key, valid) => !Object.hasOwn(value, key) || valid(value[key]);
|
|
4
13
|
const isString = (value) => typeof value === "string";
|
|
5
14
|
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
15
|
/** An `UPDATE_FAILED` report's stored failure: a stage and a reason, and what else the client knew. */
|
|
7
16
|
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, "min_bundle_id", isString) && isOptional(value, "failure", isDatabaseBundleEventFailure) && isOptional(value, "http_response", isDatabaseHttpResponse) && isOptional(value, "delivery", isString) && isOptional(value, "patch_fallback", (flag) => typeof flag === "boolean");
|
|
17
|
+
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, "min_bundle_id", isString) && isOptional(value, "failure", isDatabaseBundleEventFailure) && isOptional(value, "http_response", isDatabaseHttpResponse) && isOptional(value, "delivery", isString) && isOptional(value, "patch_fallback", (flag) => typeof flag === "boolean") && isOptional(value, "change", isBundleEventChange) && isOptional(value, "late", (flag) => flag === true) && isOptional(value, "implied_download", (flag) => flag === true);
|
|
9
18
|
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
10
19
|
/**
|
|
11
20
|
* The built-in bundle a report says its installation runs: the running
|
|
@@ -36,13 +45,14 @@ const BUNDLE_EVENT_FIELDS = {
|
|
|
36
45
|
metadata: isDatabaseBundleEventMetadata,
|
|
37
46
|
received_at_ms: (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0
|
|
38
47
|
};
|
|
48
|
+
const hasMetadata = (row, key) => isRecord(row.metadata) && Object.hasOwn(row.metadata, key);
|
|
39
49
|
const updateStrategyOf = (row) => isRecord(row.metadata) ? row.metadata.update_strategy : void 0;
|
|
40
50
|
/**
|
|
41
51
|
* A movement, or a failed update, names the bundle it left and how the
|
|
42
52
|
* device updates, and a failed update says what failed; an unchanged report
|
|
43
53
|
* names neither.
|
|
44
54
|
*/
|
|
45
|
-
const hasEventInvariants = (row) => (row.type === "UPDATE_DOWNLOADED" || row.type === "UPDATE_APPLIED" || row.type === "RECOVERED" || row.type === "UPDATE_FAILED") && typeof row.from_bundle_id === "string" && (updateStrategyOf(row) === "fingerprint" || updateStrategyOf(row) === "appVersion") && (row.type !== "UPDATE_FAILED" || isRecord(row.metadata) && isRecord(row.metadata.failure)) || row.type === "UNCHANGED" && row.from_bundle_id === null && updateStrategyOf(row) === null;
|
|
55
|
+
const hasEventInvariants = (row) => (row.type === "UPDATE_DOWNLOADED" || row.type === "UPDATE_APPLIED" || row.type === "RECOVERED" || row.type === "UPDATE_FAILED") && typeof row.from_bundle_id === "string" && (updateStrategyOf(row) === "fingerprint" || updateStrategyOf(row) === "appVersion") && (row.type !== "UPDATE_FAILED" || isRecord(row.metadata) && isRecord(row.metadata.failure)) && !hasMetadata(row, "change") && (!hasMetadata(row, "late") || row.type === "UPDATE_DOWNLOADED" || row.type === "UPDATE_APPLIED") && (!hasMetadata(row, "implied_download") || row.type === "UPDATE_APPLIED" || row.type === "RECOVERED") || row.type === "UNCHANGED" && !hasMetadata(row, "late") && row.from_bundle_id === null && updateStrategyOf(row) === null;
|
|
46
56
|
/**
|
|
47
57
|
* Throws unless `row` has exactly the bundle event fields, each of its type,
|
|
48
58
|
* and the invariants of its event type: `invalid-field` for an unknown field,
|
package/dist/server/index.cjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_schema = require("./schema.cjs");
|
|
2
3
|
const require_model = require("./model.cjs");
|
|
3
4
|
const require_official = require("./official.cjs");
|
|
4
5
|
const require_errors = require("./errors.cjs");
|
|
5
6
|
const require_provider = require("./provider.cjs");
|
|
6
|
-
const require_schema = require("./schema.cjs");
|
|
7
7
|
const require_recordEvent = require("./recordEvent.cjs");
|
|
8
8
|
const require_reads = require("./reads.cjs");
|
|
9
9
|
const require_routes = require("./routes.cjs");
|
package/dist/server/index.d.cts
CHANGED
|
@@ -2,7 +2,7 @@ import { BundleEventFailure, BundleEventFailureReason, BundleEventFailureStage,
|
|
|
2
2
|
import { InsightsBundleEventFilter, InsightsCountEventSeriesInput, InsightsCountEventsInput, InsightsCountLatestEventsInput, InsightsCoverage, InsightsEventCursor, InsightsEventFilter, InsightsEventSeriesPoint, InsightsFindLatestEventsInput, InsightsGetAppUsageInput, InsightsGetAppUsageResult, InsightsGetReleaseActivityInput, InsightsGetReleaseActivityResult, InsightsListEventsInput, InsightsModel, InsightsRecordEventInput, InsightsScope, InsightsTimeRange, ReleaseActivityMetrics, ReleaseReference } from "./modelTypes.cjs";
|
|
3
3
|
import { DEFAULT_INSIGHTS_RETENTION, InsightsRetention, InsightsSchema, createInsightsSchema, insightsSchema } from "./schema.cjs";
|
|
4
4
|
import { InsightsFailureBreakdown, InsightsUpdateFailures, InsightsUpdateFailuresInput } from "./reads.cjs";
|
|
5
|
-
import { ActiveInstallationWindow, BundleEventFailureInput, CreateBundleEventRequest, CreateBundleEventRequestBase, CursorPage, EventCursorPage, EventHistoryRow, InsightsBundleSelection, InsightsCountMeasurement, InsightsHttpResponse, InstallationHistoryRow, InstallationRow, ReportingOverview } from "./domain.cjs";
|
|
5
|
+
import { ActiveInstallationWindow, BundleEventFailureInput, CreateBundleEventRequest, CreateBundleEventRequestBase, CursorPage, EventCursorPage, EventHistoryChange, EventHistoryRow, InsightsBundleSelection, InsightsCountMeasurement, InsightsHttpResponse, InstallationHistoryRow, InstallationRow, ReportingOverview } from "./domain.cjs";
|
|
6
6
|
import { InsightsBadRequestError } from "./errors.cjs";
|
|
7
7
|
import { createInsightsModel } from "./model.cjs";
|
|
8
8
|
import { InsightsEventPageInput, InsightsInstallationEventPageInput, InsightsProvider, InsightsUserInstallationPageInput } from "./types.cjs";
|
|
@@ -422,4 +422,4 @@ declare const insights: (options?: InsightsOptions) => import("@hot-updater/plug
|
|
|
422
422
|
endpoints: PluginEndpoint[];
|
|
423
423
|
}, {}>;
|
|
424
424
|
//#endregion
|
|
425
|
-
export { type ActiveInstallationWindow, type BundleEventFailure, type BundleEventFailureInput, type BundleEventFailureReason, type BundleEventFailureStage, type BundleEventRow, type BundleEventRowBase, type CreateBundleEventRequest, type CreateBundleEventRequestBase, type CursorPage, DEFAULT_INSIGHTS_RETENTION, type DatabaseBundleEventMetadata, type EventCursorPage, type EventHistoryRow, InsightsApi, InsightsBadRequestError, type InsightsBundleEventFilter, type InsightsBundleSelection, type InsightsCountEventSeriesInput, type InsightsCountEventsInput, type InsightsCountLatestEventsInput, type InsightsCountMeasurement, type InsightsCoverage, type InsightsEventCursor, type InsightsEventFilter, type InsightsEventPageInput, type InsightsEventSeriesPoint, type InsightsFailureBreakdown, type InsightsFindLatestEventsInput, type InsightsGetAppUsageInput, type InsightsGetAppUsageResult, type InsightsGetReleaseActivityInput, type InsightsGetReleaseActivityResult, type InsightsHttpResponse, type InsightsIdentityParts, type InsightsInstallationEventPageInput, type InsightsListEventsInput, type InsightsModel, InsightsOptions, type InsightsProvider, type InsightsRecordEventInput, type InsightsRetention, type InsightsSchema, type InsightsScope, type InsightsTimeRange, type InsightsUpdateFailures, type InsightsUpdateFailuresInput, type InsightsUserInstallationPageInput, type InstallationHistoryRow, type InstallationRow, type ReleaseActivityMetrics, type ReleaseReference, type ReportingOverview, createInsightsModel, createInsightsProvider, createInsightsSchema, insights, insightsIdentity, insightsSchema };
|
|
425
|
+
export { type ActiveInstallationWindow, type BundleEventFailure, type BundleEventFailureInput, type BundleEventFailureReason, type BundleEventFailureStage, type BundleEventRow, type BundleEventRowBase, type CreateBundleEventRequest, type CreateBundleEventRequestBase, type CursorPage, DEFAULT_INSIGHTS_RETENTION, type DatabaseBundleEventMetadata, type EventCursorPage, type EventHistoryChange, type EventHistoryRow, InsightsApi, InsightsBadRequestError, type InsightsBundleEventFilter, type InsightsBundleSelection, type InsightsCountEventSeriesInput, type InsightsCountEventsInput, type InsightsCountLatestEventsInput, type InsightsCountMeasurement, type InsightsCoverage, type InsightsEventCursor, type InsightsEventFilter, type InsightsEventPageInput, type InsightsEventSeriesPoint, type InsightsFailureBreakdown, type InsightsFindLatestEventsInput, type InsightsGetAppUsageInput, type InsightsGetAppUsageResult, type InsightsGetReleaseActivityInput, type InsightsGetReleaseActivityResult, type InsightsHttpResponse, type InsightsIdentityParts, type InsightsInstallationEventPageInput, type InsightsListEventsInput, type InsightsModel, InsightsOptions, type InsightsProvider, type InsightsRecordEventInput, type InsightsRetention, type InsightsSchema, type InsightsScope, type InsightsTimeRange, type InsightsUpdateFailures, type InsightsUpdateFailuresInput, type InsightsUserInstallationPageInput, type InstallationHistoryRow, type InstallationRow, type ReleaseActivityMetrics, type ReleaseReference, type ReportingOverview, createInsightsModel, createInsightsProvider, createInsightsSchema, insights, insightsIdentity, insightsSchema };
|
package/dist/server/index.d.mts
CHANGED
|
@@ -2,7 +2,7 @@ import { BundleEventFailure, BundleEventFailureReason, BundleEventFailureStage,
|
|
|
2
2
|
import { InsightsBundleEventFilter, InsightsCountEventSeriesInput, InsightsCountEventsInput, InsightsCountLatestEventsInput, InsightsCoverage, InsightsEventCursor, InsightsEventFilter, InsightsEventSeriesPoint, InsightsFindLatestEventsInput, InsightsGetAppUsageInput, InsightsGetAppUsageResult, InsightsGetReleaseActivityInput, InsightsGetReleaseActivityResult, InsightsListEventsInput, InsightsModel, InsightsRecordEventInput, InsightsScope, InsightsTimeRange, ReleaseActivityMetrics, ReleaseReference } from "./modelTypes.mjs";
|
|
3
3
|
import { DEFAULT_INSIGHTS_RETENTION, InsightsRetention, InsightsSchema, createInsightsSchema, insightsSchema } from "./schema.mjs";
|
|
4
4
|
import { InsightsFailureBreakdown, InsightsUpdateFailures, InsightsUpdateFailuresInput } from "./reads.mjs";
|
|
5
|
-
import { ActiveInstallationWindow, BundleEventFailureInput, CreateBundleEventRequest, CreateBundleEventRequestBase, CursorPage, EventCursorPage, EventHistoryRow, InsightsBundleSelection, InsightsCountMeasurement, InsightsHttpResponse, InstallationHistoryRow, InstallationRow, ReportingOverview } from "./domain.mjs";
|
|
5
|
+
import { ActiveInstallationWindow, BundleEventFailureInput, CreateBundleEventRequest, CreateBundleEventRequestBase, CursorPage, EventCursorPage, EventHistoryChange, EventHistoryRow, InsightsBundleSelection, InsightsCountMeasurement, InsightsHttpResponse, InstallationHistoryRow, InstallationRow, ReportingOverview } from "./domain.mjs";
|
|
6
6
|
import { InsightsBadRequestError } from "./errors.mjs";
|
|
7
7
|
import { createInsightsModel } from "./model.mjs";
|
|
8
8
|
import { InsightsEventPageInput, InsightsInstallationEventPageInput, InsightsProvider, InsightsUserInstallationPageInput } from "./types.mjs";
|
|
@@ -422,4 +422,4 @@ declare const insights: (options?: InsightsOptions) => import("@hot-updater/plug
|
|
|
422
422
|
endpoints: PluginEndpoint[];
|
|
423
423
|
}, {}>;
|
|
424
424
|
//#endregion
|
|
425
|
-
export { type ActiveInstallationWindow, type BundleEventFailure, type BundleEventFailureInput, type BundleEventFailureReason, type BundleEventFailureStage, type BundleEventRow, type BundleEventRowBase, type CreateBundleEventRequest, type CreateBundleEventRequestBase, type CursorPage, DEFAULT_INSIGHTS_RETENTION, type DatabaseBundleEventMetadata, type EventCursorPage, type EventHistoryRow, InsightsApi, InsightsBadRequestError, type InsightsBundleEventFilter, type InsightsBundleSelection, type InsightsCountEventSeriesInput, type InsightsCountEventsInput, type InsightsCountLatestEventsInput, type InsightsCountMeasurement, type InsightsCoverage, type InsightsEventCursor, type InsightsEventFilter, type InsightsEventPageInput, type InsightsEventSeriesPoint, type InsightsFailureBreakdown, type InsightsFindLatestEventsInput, type InsightsGetAppUsageInput, type InsightsGetAppUsageResult, type InsightsGetReleaseActivityInput, type InsightsGetReleaseActivityResult, type InsightsHttpResponse, type InsightsIdentityParts, type InsightsInstallationEventPageInput, type InsightsListEventsInput, type InsightsModel, InsightsOptions, type InsightsProvider, type InsightsRecordEventInput, type InsightsRetention, type InsightsSchema, type InsightsScope, type InsightsTimeRange, type InsightsUpdateFailures, type InsightsUpdateFailuresInput, type InsightsUserInstallationPageInput, type InstallationHistoryRow, type InstallationRow, type ReleaseActivityMetrics, type ReleaseReference, type ReportingOverview, createInsightsModel, createInsightsProvider, createInsightsSchema, insights, insightsIdentity, insightsSchema };
|
|
425
|
+
export { type ActiveInstallationWindow, type BundleEventFailure, type BundleEventFailureInput, type BundleEventFailureReason, type BundleEventFailureStage, type BundleEventRow, type BundleEventRowBase, type CreateBundleEventRequest, type CreateBundleEventRequestBase, type CursorPage, DEFAULT_INSIGHTS_RETENTION, type DatabaseBundleEventMetadata, type EventCursorPage, type EventHistoryChange, type EventHistoryRow, InsightsApi, InsightsBadRequestError, type InsightsBundleEventFilter, type InsightsBundleSelection, type InsightsCountEventSeriesInput, type InsightsCountEventsInput, type InsightsCountLatestEventsInput, type InsightsCountMeasurement, type InsightsCoverage, type InsightsEventCursor, type InsightsEventFilter, type InsightsEventPageInput, type InsightsEventSeriesPoint, type InsightsFailureBreakdown, type InsightsFindLatestEventsInput, type InsightsGetAppUsageInput, type InsightsGetAppUsageResult, type InsightsGetReleaseActivityInput, type InsightsGetReleaseActivityResult, type InsightsHttpResponse, type InsightsIdentityParts, type InsightsInstallationEventPageInput, type InsightsListEventsInput, type InsightsModel, InsightsOptions, type InsightsProvider, type InsightsRecordEventInput, type InsightsRetention, type InsightsSchema, type InsightsScope, type InsightsTimeRange, type InsightsUpdateFailures, type InsightsUpdateFailuresInput, type InsightsUserInstallationPageInput, type InstallationHistoryRow, type InstallationRow, type ReleaseActivityMetrics, type ReleaseReference, type ReportingOverview, createInsightsModel, createInsightsProvider, createInsightsSchema, insights, insightsIdentity, insightsSchema };
|
package/dist/server/index.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import { DEFAULT_INSIGHTS_RETENTION, createInsightsSchema, insightsSchema } from "./schema.mjs";
|
|
1
2
|
import { createInsightsModel } from "./model.mjs";
|
|
2
3
|
import { markOfficial } from "./official.mjs";
|
|
3
4
|
import { InsightsBadRequestError } from "./errors.mjs";
|
|
4
5
|
import { createInsightsProvider } from "./provider.mjs";
|
|
5
|
-
import { DEFAULT_INSIGHTS_RETENTION, createInsightsSchema, insightsSchema } from "./schema.mjs";
|
|
6
6
|
import { insightsIdentity, recordEvent } from "./recordEvent.mjs";
|
|
7
7
|
import { countEventSeries, countEvents, countLatestEvents, findLatestEvents, getAppUsage, getReleaseActivity, getUpdateFailures, listEvents } from "./reads.mjs";
|
|
8
8
|
import { INSIGHTS_ROUTES, createInsightsRouteHandlers } from "./routes.mjs";
|
|
@@ -10,13 +10,14 @@ interface InsightsScope {
|
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
12
|
* Raw event predicates; recovery is attributed to the source bundle. An
|
|
13
|
-
* UNCHANGED
|
|
13
|
+
* UNCHANGED filter matches the kept rows that moved an installation to the
|
|
14
|
+
* bundle without an apply report.
|
|
14
15
|
*/
|
|
15
16
|
type InsightsBundleEventFilter = InsightsScope & ({
|
|
16
17
|
readonly type: "RECOVERED";
|
|
17
18
|
readonly fromBundleId: string;
|
|
18
19
|
} | {
|
|
19
|
-
readonly type: "UPDATE_DOWNLOADED" | "UPDATE_APPLIED" | "UPDATE_FAILED";
|
|
20
|
+
readonly type: "UPDATE_DOWNLOADED" | "UPDATE_APPLIED" | "UPDATE_FAILED" | "UNCHANGED";
|
|
20
21
|
readonly toBundleId: string;
|
|
21
22
|
});
|
|
22
23
|
type InsightsEventFilter = {
|
|
@@ -10,13 +10,14 @@ interface InsightsScope {
|
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
12
|
* Raw event predicates; recovery is attributed to the source bundle. An
|
|
13
|
-
* UNCHANGED
|
|
13
|
+
* UNCHANGED filter matches the kept rows that moved an installation to the
|
|
14
|
+
* bundle without an apply report.
|
|
14
15
|
*/
|
|
15
16
|
type InsightsBundleEventFilter = InsightsScope & ({
|
|
16
17
|
readonly type: "RECOVERED";
|
|
17
18
|
readonly fromBundleId: string;
|
|
18
19
|
} | {
|
|
19
|
-
readonly type: "UPDATE_DOWNLOADED" | "UPDATE_APPLIED" | "UPDATE_FAILED";
|
|
20
|
+
readonly type: "UPDATE_DOWNLOADED" | "UPDATE_APPLIED" | "UPDATE_FAILED" | "UNCHANGED";
|
|
20
21
|
readonly toBundleId: string;
|
|
21
22
|
});
|
|
22
23
|
type InsightsEventFilter = {
|
package/dist/server/overview.cjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const require_schema = require("./schema.cjs");
|
|
1
2
|
//#region src/server/overview.ts
|
|
2
3
|
const encodeIdentity = (identity) => [
|
|
3
4
|
identity.scopeKind,
|
|
@@ -64,18 +65,19 @@ const insightsOverviewDeltas = (event) => {
|
|
|
64
65
|
bucketStartMs
|
|
65
66
|
}, windowed);
|
|
66
67
|
};
|
|
67
|
-
|
|
68
|
+
const implied = event.metadata.implied_download === true ? 1 : 0;
|
|
69
|
+
if (!require_schema.isLateEvent(event) && event.type === "UPDATE_DOWNLOADED") metric(event.to_release_id, {
|
|
68
70
|
downloads: 1,
|
|
69
71
|
applies: 0,
|
|
70
72
|
failedLaunches: 0
|
|
71
73
|
});
|
|
72
|
-
else if (event
|
|
73
|
-
downloads:
|
|
74
|
+
else if (!require_schema.isLateEvent(event) && require_schema.isLaunch(event)) metric(event.to_release_id, {
|
|
75
|
+
downloads: implied,
|
|
74
76
|
applies: 1,
|
|
75
77
|
failedLaunches: 0
|
|
76
78
|
});
|
|
77
|
-
else if (event.type === "RECOVERED") metric(event.from_release_id, {
|
|
78
|
-
downloads:
|
|
79
|
+
else if (!require_schema.isLateEvent(event) && event.type === "RECOVERED") metric(event.from_release_id, {
|
|
80
|
+
downloads: implied,
|
|
79
81
|
applies: 0,
|
|
80
82
|
failedLaunches: 1
|
|
81
83
|
});
|
package/dist/server/overview.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isLateEvent, isLaunch } from "./schema.mjs";
|
|
1
2
|
//#region src/server/overview.ts
|
|
2
3
|
const encodeIdentity = (identity) => [
|
|
3
4
|
identity.scopeKind,
|
|
@@ -64,18 +65,19 @@ const insightsOverviewDeltas = (event) => {
|
|
|
64
65
|
bucketStartMs
|
|
65
66
|
}, windowed);
|
|
66
67
|
};
|
|
67
|
-
|
|
68
|
+
const implied = event.metadata.implied_download === true ? 1 : 0;
|
|
69
|
+
if (!isLateEvent(event) && event.type === "UPDATE_DOWNLOADED") metric(event.to_release_id, {
|
|
68
70
|
downloads: 1,
|
|
69
71
|
applies: 0,
|
|
70
72
|
failedLaunches: 0
|
|
71
73
|
});
|
|
72
|
-
else if (event
|
|
73
|
-
downloads:
|
|
74
|
+
else if (!isLateEvent(event) && isLaunch(event)) metric(event.to_release_id, {
|
|
75
|
+
downloads: implied,
|
|
74
76
|
applies: 1,
|
|
75
77
|
failedLaunches: 0
|
|
76
78
|
});
|
|
77
|
-
else if (event.type === "RECOVERED") metric(event.from_release_id, {
|
|
78
|
-
downloads:
|
|
79
|
+
else if (!isLateEvent(event) && event.type === "RECOVERED") metric(event.from_release_id, {
|
|
80
|
+
downloads: implied,
|
|
79
81
|
applies: 0,
|
|
80
82
|
failedLaunches: 1
|
|
81
83
|
});
|
package/dist/server/provider.cjs
CHANGED
|
@@ -166,7 +166,19 @@ const eventDetails = ({ type, metadata }) => ({
|
|
|
166
166
|
} } : {},
|
|
167
167
|
...type === "UPDATE_FAILED" && metadata.failure !== void 0 ? { failure: toFailure(metadata.failure) } : {},
|
|
168
168
|
...type === "UPDATE_DOWNLOADED" && metadata.delivery !== void 0 ? { delivery: metadata.delivery } : {},
|
|
169
|
-
...type === "UPDATE_DOWNLOADED" && metadata.patch_fallback === true ? { patchFallback: true } : {}
|
|
169
|
+
...type === "UPDATE_DOWNLOADED" && metadata.patch_fallback === true ? { patchFallback: true } : {},
|
|
170
|
+
...type === "UNCHANGED" && metadata.change !== void 0 ? { change: {
|
|
171
|
+
kinds: metadata.change.kinds,
|
|
172
|
+
...metadata.change.previous === null ? {} : { previous: {
|
|
173
|
+
bundleId: metadata.change.previous.bundle_id,
|
|
174
|
+
releaseId: metadata.change.previous.release_id,
|
|
175
|
+
appVersion: metadata.change.previous.app_version,
|
|
176
|
+
channel: metadata.change.previous.channel,
|
|
177
|
+
...metadata.change.previous.min_bundle_id === void 0 ? {} : { minBundleId: metadata.change.previous.min_bundle_id }
|
|
178
|
+
} }
|
|
179
|
+
} } : {},
|
|
180
|
+
...metadata.late === true ? { late: true } : {},
|
|
181
|
+
...metadata.implied_download === true ? { impliedDownload: true } : {}
|
|
170
182
|
});
|
|
171
183
|
const toEventHistoryRow = (row) => ({
|
|
172
184
|
...row.to_release_id == null ? {} : { toReleaseId: row.to_release_id },
|
package/dist/server/provider.mjs
CHANGED
|
@@ -166,7 +166,19 @@ const eventDetails = ({ type, metadata }) => ({
|
|
|
166
166
|
} } : {},
|
|
167
167
|
...type === "UPDATE_FAILED" && metadata.failure !== void 0 ? { failure: toFailure(metadata.failure) } : {},
|
|
168
168
|
...type === "UPDATE_DOWNLOADED" && metadata.delivery !== void 0 ? { delivery: metadata.delivery } : {},
|
|
169
|
-
...type === "UPDATE_DOWNLOADED" && metadata.patch_fallback === true ? { patchFallback: true } : {}
|
|
169
|
+
...type === "UPDATE_DOWNLOADED" && metadata.patch_fallback === true ? { patchFallback: true } : {},
|
|
170
|
+
...type === "UNCHANGED" && metadata.change !== void 0 ? { change: {
|
|
171
|
+
kinds: metadata.change.kinds,
|
|
172
|
+
...metadata.change.previous === null ? {} : { previous: {
|
|
173
|
+
bundleId: metadata.change.previous.bundle_id,
|
|
174
|
+
releaseId: metadata.change.previous.release_id,
|
|
175
|
+
appVersion: metadata.change.previous.app_version,
|
|
176
|
+
channel: metadata.change.previous.channel,
|
|
177
|
+
...metadata.change.previous.min_bundle_id === void 0 ? {} : { minBundleId: metadata.change.previous.min_bundle_id }
|
|
178
|
+
} }
|
|
179
|
+
} } : {},
|
|
180
|
+
...metadata.late === true ? { late: true } : {},
|
|
181
|
+
...metadata.implied_download === true ? { impliedDownload: true } : {}
|
|
170
182
|
});
|
|
171
183
|
const toEventHistoryRow = (row) => ({
|
|
172
184
|
...row.to_release_id == null ? {} : { toReleaseId: row.to_release_id },
|
package/dist/server/reads.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
require("./provider.cjs");
|
|
2
1
|
const require_schema = require("./schema.cjs");
|
|
2
|
+
require("./provider.cjs");
|
|
3
3
|
const require_recordEvent = require("./recordEvent.cjs");
|
|
4
4
|
let _hot_updater_plugin_core = require("@hot-updater/plugin-core");
|
|
5
5
|
//#region src/server/reads.ts
|
|
@@ -36,12 +36,11 @@ const EVENT_FIELDS = [
|
|
|
36
36
|
];
|
|
37
37
|
/** A stored event or head, as the event row it holds. */
|
|
38
38
|
const toEvent = (row) => Object.fromEntries(EVENT_FIELDS.map((field) => [field, row[field]]));
|
|
39
|
-
const bundleRef = (filter) => filter.type === "RECOVERED" ? `from:${filter.fromBundleId}` : `to:${filter.toBundleId}`;
|
|
40
39
|
const scopeOf = (filter) => ({
|
|
41
40
|
platform: filter.platform,
|
|
42
41
|
channel: filter.channel,
|
|
43
42
|
type: filter.type,
|
|
44
|
-
bundle_ref:
|
|
43
|
+
bundle_ref: require_schema.bundleRefOfFilter(filter)
|
|
45
44
|
});
|
|
46
45
|
/** One UTC day of a global or bundle list, newest first. */
|
|
47
46
|
const eventsOfDay = (db, filter, day, range, limit) => filter.kind === "all" ? db.findMany("bundle_events", {
|
package/dist/server/reads.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { DAILY_EVENTS, DAY_MS, HOUR_MS, bundleRefOfFilter } from "./schema.mjs";
|
|
1
2
|
import "./provider.mjs";
|
|
2
|
-
import { DAILY_EVENTS, DAY_MS, HOUR_MS } from "./schema.mjs";
|
|
3
3
|
import { PAIR_FIELD, bundlePairKey, insightsIdentity } from "./recordEvent.mjs";
|
|
4
4
|
import { DatabaseAdapterInputError, compareUtf8, countDistinct, mergeDistinct } from "@hot-updater/plugin-core";
|
|
5
5
|
//#region src/server/reads.ts
|
|
@@ -36,12 +36,11 @@ const EVENT_FIELDS = [
|
|
|
36
36
|
];
|
|
37
37
|
/** A stored event or head, as the event row it holds. */
|
|
38
38
|
const toEvent = (row) => Object.fromEntries(EVENT_FIELDS.map((field) => [field, row[field]]));
|
|
39
|
-
const bundleRef = (filter) => filter.type === "RECOVERED" ? `from:${filter.fromBundleId}` : `to:${filter.toBundleId}`;
|
|
40
39
|
const scopeOf = (filter) => ({
|
|
41
40
|
platform: filter.platform,
|
|
42
41
|
channel: filter.channel,
|
|
43
42
|
type: filter.type,
|
|
44
|
-
bundle_ref:
|
|
43
|
+
bundle_ref: bundleRefOfFilter(filter)
|
|
45
44
|
});
|
|
46
45
|
/** One UTC day of a global or bundle list, newest first. */
|
|
47
46
|
const eventsOfDay = (db, filter, day, range, limit) => filter.kind === "all" ? db.findMany("bundle_events", {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const require_eventRow = require("./eventRow.cjs");
|
|
2
|
+
const require_schema = require("./schema.cjs");
|
|
2
3
|
const require_contract = require("./contract.cjs");
|
|
3
4
|
const require_overview = require("./overview.cjs");
|
|
4
|
-
const require_schema = require("./schema.cjs");
|
|
5
5
|
let _hot_updater_plugin_core = require("@hot-updater/plugin-core");
|
|
6
6
|
//#region src/server/recordEvent.ts
|
|
7
7
|
const hourOf = (ms) => ms - ms % require_schema.HOUR_MS;
|
|
@@ -136,16 +136,16 @@ const countEvent = (tx, event) => {
|
|
|
136
136
|
}
|
|
137
137
|
};
|
|
138
138
|
/**
|
|
139
|
-
* A stored event's outcome rows: its bundle filter's hour,
|
|
140
|
-
*
|
|
139
|
+
* A stored event's outcome rows: its bundle filter's hour, when it has one
|
|
140
|
+
* (`bundleRefsOf`), and every stored event's UTC day.
|
|
141
141
|
*/
|
|
142
142
|
const countOutcome = (tx, event) => {
|
|
143
143
|
const shardBy = event.install_id;
|
|
144
|
-
|
|
144
|
+
for (const bundleRef of require_schema.bundleRefsOf(event)) tx.aggregate("insights_outcomes", {
|
|
145
145
|
platform: event.platform,
|
|
146
146
|
channel: event.channel,
|
|
147
147
|
type: event.type,
|
|
148
|
-
bundle_ref:
|
|
148
|
+
bundle_ref: bundleRef,
|
|
149
149
|
bucket_start_ms: hourOf(event.received_at_ms)
|
|
150
150
|
}, { events: 1 }, { shardBy });
|
|
151
151
|
tx.aggregate("insights_outcomes", {
|
|
@@ -249,6 +249,126 @@ const countFailure = (tx, event) => {
|
|
|
249
249
|
}, { events: 1 }, { shardBy });
|
|
250
250
|
};
|
|
251
251
|
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;
|
|
252
|
+
/** What a head says its installation runs: a download leaves it at the source. */
|
|
253
|
+
const runningOf = (head) => head.type === "UPDATE_DOWNLOADED" ? {
|
|
254
|
+
bundle_id: head.from_bundle_id,
|
|
255
|
+
release_id: head.from_release_id
|
|
256
|
+
} : {
|
|
257
|
+
bundle_id: head.to_bundle_id,
|
|
258
|
+
release_id: head.to_release_id
|
|
259
|
+
};
|
|
260
|
+
/** The native build a report or head came from, when its SDK says. */
|
|
261
|
+
const minBundleIdOf = (row) => row.metadata?.min_bundle_id;
|
|
262
|
+
/** When the client made a report, from the UUIDv7 it gave it. */
|
|
263
|
+
const madeAt = (id) => (0, _hot_updater_plugin_core.isUUIDv7)(id) ? (0, _hot_updater_plugin_core.extractTimestampFromUUIDv7)(id) : null;
|
|
264
|
+
/**
|
|
265
|
+
* A report that arrived after its installation moved past it: a download or
|
|
266
|
+
* an apply of the bundle the head already runs, or an UNCHANGED report of the
|
|
267
|
+
* bundle an apply head left, made before that apply. A reload can cut one
|
|
268
|
+
* runtime's report short and deliver it after the next runtime's. It stays
|
|
269
|
+
* in history but moves and counts nothing.
|
|
270
|
+
*/
|
|
271
|
+
const isLate = (event, head) => {
|
|
272
|
+
if (head === null) return false;
|
|
273
|
+
if (event.type === "UPDATE_DOWNLOADED" || event.type === "UPDATE_APPLIED") return event.to_bundle_id === runningOf(head).bundle_id;
|
|
274
|
+
if (event.type === "UNCHANGED" && head.type === "UPDATE_APPLIED" && event.to_bundle_id === head.from_bundle_id) {
|
|
275
|
+
const made = madeAt(event.id);
|
|
276
|
+
const applied = madeAt(head.id);
|
|
277
|
+
return made !== null && applied !== null && made < applied;
|
|
278
|
+
}
|
|
279
|
+
return false;
|
|
280
|
+
};
|
|
281
|
+
/**
|
|
282
|
+
* What an UNCHANGED report changes against its installation's head: the
|
|
283
|
+
* running bundle or release, the app version, the channel, or the native
|
|
284
|
+
* build (when both name it), with the head's values before it; `first_seen`
|
|
285
|
+
* when the installation has no head. A report the head already answers, one
|
|
286
|
+
* older than the head, a user switch and any other event type change
|
|
287
|
+
* nothing, so no row keeps them.
|
|
288
|
+
*/
|
|
289
|
+
const changeOf = (event, head) => {
|
|
290
|
+
if (event.type !== "UNCHANGED") return null;
|
|
291
|
+
if (head === null) return {
|
|
292
|
+
kinds: ["first_seen"],
|
|
293
|
+
previous: null
|
|
294
|
+
};
|
|
295
|
+
if (!isNewer(event, head)) return null;
|
|
296
|
+
const running = runningOf(head);
|
|
297
|
+
const build = minBundleIdOf(event);
|
|
298
|
+
const headBuild = minBundleIdOf(head);
|
|
299
|
+
const kinds = [];
|
|
300
|
+
if (event.to_bundle_id !== running.bundle_id) kinds.push("bundle");
|
|
301
|
+
if (event.to_release_id !== running.release_id) kinds.push("release");
|
|
302
|
+
if (event.app_version !== head.app_version) kinds.push("app_version");
|
|
303
|
+
if (event.channel !== head.channel) kinds.push("channel");
|
|
304
|
+
if (build !== void 0 && headBuild !== void 0 && build !== headBuild) kinds.push("native_build");
|
|
305
|
+
return kinds.length === 0 ? null : {
|
|
306
|
+
kinds,
|
|
307
|
+
previous: {
|
|
308
|
+
bundle_id: running.bundle_id,
|
|
309
|
+
release_id: running.release_id,
|
|
310
|
+
app_version: head.app_version,
|
|
311
|
+
channel: head.channel,
|
|
312
|
+
...headBuild === void 0 ? {} : { min_bundle_id: headBuild }
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
};
|
|
316
|
+
/**
|
|
317
|
+
* Whether a launch or a crash comes with no download report for its target:
|
|
318
|
+
* its head shows neither that download nor the target already running. The
|
|
319
|
+
* native build's built-in bundle is never downloaded, and without a head the
|
|
320
|
+
* server knows nothing of the installation's past, so it implies nothing.
|
|
321
|
+
*/
|
|
322
|
+
const impliesDownload = (event, head) => {
|
|
323
|
+
const target = event.type === "RECOVERED" ? {
|
|
324
|
+
bundle: event.from_bundle_id,
|
|
325
|
+
release: event.from_release_id
|
|
326
|
+
} : require_schema.isLaunch(event) ? {
|
|
327
|
+
bundle: event.to_bundle_id,
|
|
328
|
+
release: event.to_release_id
|
|
329
|
+
} : null;
|
|
330
|
+
if (target === null || target.release === null || target.bundle === minBundleIdOf(event)) return false;
|
|
331
|
+
if (head === null) return false;
|
|
332
|
+
if (head.type === "UPDATE_DOWNLOADED" && head.to_bundle_id === target.bundle) return false;
|
|
333
|
+
return runningOf(head).bundle_id !== target.bundle;
|
|
334
|
+
};
|
|
335
|
+
/**
|
|
336
|
+
* The event as recorded, with what only the server sets: what a kept
|
|
337
|
+
* UNCHANGED report changed, a late report's flag, and a download its launch
|
|
338
|
+
* or crash implied.
|
|
339
|
+
*/
|
|
340
|
+
const recordedOf = (event, head) => {
|
|
341
|
+
const { change: _change, late: _late, implied_download: _implied, ...metadata } = event.metadata;
|
|
342
|
+
const reported = {
|
|
343
|
+
...event,
|
|
344
|
+
metadata
|
|
345
|
+
};
|
|
346
|
+
const late = isLate(reported, head);
|
|
347
|
+
const change = late ? null : changeOf(reported, head);
|
|
348
|
+
const withChange = change === null ? reported : {
|
|
349
|
+
...reported,
|
|
350
|
+
metadata: {
|
|
351
|
+
...metadata,
|
|
352
|
+
change
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
return {
|
|
356
|
+
recorded: late && reported.type !== "UNCHANGED" ? {
|
|
357
|
+
...reported,
|
|
358
|
+
metadata: {
|
|
359
|
+
...metadata,
|
|
360
|
+
late: true
|
|
361
|
+
}
|
|
362
|
+
} : !late && impliesDownload(withChange, head) ? {
|
|
363
|
+
...withChange,
|
|
364
|
+
metadata: {
|
|
365
|
+
...withChange.metadata,
|
|
366
|
+
implied_download: true
|
|
367
|
+
}
|
|
368
|
+
} : withChange,
|
|
369
|
+
late
|
|
370
|
+
};
|
|
371
|
+
};
|
|
252
372
|
/** What an UNCHANGED report must share with its installation's head to repeat it. */
|
|
253
373
|
const REPEATED_FIELDS = [
|
|
254
374
|
"channel",
|
|
@@ -283,23 +403,24 @@ const recordEvent = (db, event) => {
|
|
|
283
403
|
const [existing, previous] = await Promise.all([tx.findOne("bundle_events", { id: event.id }), tx.findOne("bundle_event_heads", { install_id: event.install_id })]);
|
|
284
404
|
if (existing !== null || previous?.id === event.id) return;
|
|
285
405
|
if (previous !== null && repeatsHead(event, previous)) return;
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
406
|
+
const { recorded, late } = recordedOf(event, previous);
|
|
407
|
+
if (recorded.type !== "UNCHANGED" || recorded.metadata.change !== void 0 && !late) {
|
|
408
|
+
tx.create("bundle_events", recorded);
|
|
409
|
+
countOutcome(tx, recorded);
|
|
289
410
|
}
|
|
290
|
-
if (
|
|
291
|
-
countFailure(tx,
|
|
411
|
+
if (recorded.type === "UPDATE_FAILED") {
|
|
412
|
+
countFailure(tx, recorded);
|
|
292
413
|
return;
|
|
293
414
|
}
|
|
294
|
-
countEvent(tx,
|
|
295
|
-
if (previous !== null && !isNewer(
|
|
296
|
-
if (previous === null) tx.create("bundle_event_heads",
|
|
415
|
+
countEvent(tx, recorded);
|
|
416
|
+
if (previous !== null && (late || !isNewer(recorded, previous))) return;
|
|
417
|
+
if (previous === null) tx.create("bundle_event_heads", recorded);
|
|
297
418
|
else {
|
|
298
|
-
const { install_id: _, ...fields } =
|
|
419
|
+
const { install_id: _, ...fields } = recorded;
|
|
299
420
|
countHead(tx, previous, -1);
|
|
300
421
|
tx.update("bundle_event_heads", previous, fields);
|
|
301
422
|
}
|
|
302
|
-
countHead(tx,
|
|
423
|
+
countHead(tx, recorded, 1);
|
|
303
424
|
});
|
|
304
425
|
};
|
|
305
426
|
//#endregion
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { runningBuiltinBundleId } from "./eventRow.mjs";
|
|
2
|
+
import { DAILY_EVENTS, DAY_MS, HOUR_MS, bundleRefsOf, isLaunch } from "./schema.mjs";
|
|
2
3
|
import { assertBundleEventRow } from "./contract.mjs";
|
|
3
4
|
import { insightsKey, insightsOverviewDeltas, insightsOverviewId } from "./overview.mjs";
|
|
4
|
-
import {
|
|
5
|
-
import { addDistinct, compareUtf8 } from "@hot-updater/plugin-core";
|
|
5
|
+
import { addDistinct, compareUtf8, extractTimestampFromUUIDv7, isUUIDv7 } from "@hot-updater/plugin-core";
|
|
6
6
|
//#region src/server/recordEvent.ts
|
|
7
7
|
const hourOf = (ms) => ms - ms % HOUR_MS;
|
|
8
8
|
const dayOf = (ms) => ms - ms % DAY_MS;
|
|
@@ -136,16 +136,16 @@ const countEvent = (tx, event) => {
|
|
|
136
136
|
}
|
|
137
137
|
};
|
|
138
138
|
/**
|
|
139
|
-
* A stored event's outcome rows: its bundle filter's hour,
|
|
140
|
-
*
|
|
139
|
+
* A stored event's outcome rows: its bundle filter's hour, when it has one
|
|
140
|
+
* (`bundleRefsOf`), and every stored event's UTC day.
|
|
141
141
|
*/
|
|
142
142
|
const countOutcome = (tx, event) => {
|
|
143
143
|
const shardBy = event.install_id;
|
|
144
|
-
|
|
144
|
+
for (const bundleRef of bundleRefsOf(event)) tx.aggregate("insights_outcomes", {
|
|
145
145
|
platform: event.platform,
|
|
146
146
|
channel: event.channel,
|
|
147
147
|
type: event.type,
|
|
148
|
-
bundle_ref:
|
|
148
|
+
bundle_ref: bundleRef,
|
|
149
149
|
bucket_start_ms: hourOf(event.received_at_ms)
|
|
150
150
|
}, { events: 1 }, { shardBy });
|
|
151
151
|
tx.aggregate("insights_outcomes", {
|
|
@@ -249,6 +249,126 @@ const countFailure = (tx, event) => {
|
|
|
249
249
|
}, { events: 1 }, { shardBy });
|
|
250
250
|
};
|
|
251
251
|
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;
|
|
252
|
+
/** What a head says its installation runs: a download leaves it at the source. */
|
|
253
|
+
const runningOf = (head) => head.type === "UPDATE_DOWNLOADED" ? {
|
|
254
|
+
bundle_id: head.from_bundle_id,
|
|
255
|
+
release_id: head.from_release_id
|
|
256
|
+
} : {
|
|
257
|
+
bundle_id: head.to_bundle_id,
|
|
258
|
+
release_id: head.to_release_id
|
|
259
|
+
};
|
|
260
|
+
/** The native build a report or head came from, when its SDK says. */
|
|
261
|
+
const minBundleIdOf = (row) => row.metadata?.min_bundle_id;
|
|
262
|
+
/** When the client made a report, from the UUIDv7 it gave it. */
|
|
263
|
+
const madeAt = (id) => isUUIDv7(id) ? extractTimestampFromUUIDv7(id) : null;
|
|
264
|
+
/**
|
|
265
|
+
* A report that arrived after its installation moved past it: a download or
|
|
266
|
+
* an apply of the bundle the head already runs, or an UNCHANGED report of the
|
|
267
|
+
* bundle an apply head left, made before that apply. A reload can cut one
|
|
268
|
+
* runtime's report short and deliver it after the next runtime's. It stays
|
|
269
|
+
* in history but moves and counts nothing.
|
|
270
|
+
*/
|
|
271
|
+
const isLate = (event, head) => {
|
|
272
|
+
if (head === null) return false;
|
|
273
|
+
if (event.type === "UPDATE_DOWNLOADED" || event.type === "UPDATE_APPLIED") return event.to_bundle_id === runningOf(head).bundle_id;
|
|
274
|
+
if (event.type === "UNCHANGED" && head.type === "UPDATE_APPLIED" && event.to_bundle_id === head.from_bundle_id) {
|
|
275
|
+
const made = madeAt(event.id);
|
|
276
|
+
const applied = madeAt(head.id);
|
|
277
|
+
return made !== null && applied !== null && made < applied;
|
|
278
|
+
}
|
|
279
|
+
return false;
|
|
280
|
+
};
|
|
281
|
+
/**
|
|
282
|
+
* What an UNCHANGED report changes against its installation's head: the
|
|
283
|
+
* running bundle or release, the app version, the channel, or the native
|
|
284
|
+
* build (when both name it), with the head's values before it; `first_seen`
|
|
285
|
+
* when the installation has no head. A report the head already answers, one
|
|
286
|
+
* older than the head, a user switch and any other event type change
|
|
287
|
+
* nothing, so no row keeps them.
|
|
288
|
+
*/
|
|
289
|
+
const changeOf = (event, head) => {
|
|
290
|
+
if (event.type !== "UNCHANGED") return null;
|
|
291
|
+
if (head === null) return {
|
|
292
|
+
kinds: ["first_seen"],
|
|
293
|
+
previous: null
|
|
294
|
+
};
|
|
295
|
+
if (!isNewer(event, head)) return null;
|
|
296
|
+
const running = runningOf(head);
|
|
297
|
+
const build = minBundleIdOf(event);
|
|
298
|
+
const headBuild = minBundleIdOf(head);
|
|
299
|
+
const kinds = [];
|
|
300
|
+
if (event.to_bundle_id !== running.bundle_id) kinds.push("bundle");
|
|
301
|
+
if (event.to_release_id !== running.release_id) kinds.push("release");
|
|
302
|
+
if (event.app_version !== head.app_version) kinds.push("app_version");
|
|
303
|
+
if (event.channel !== head.channel) kinds.push("channel");
|
|
304
|
+
if (build !== void 0 && headBuild !== void 0 && build !== headBuild) kinds.push("native_build");
|
|
305
|
+
return kinds.length === 0 ? null : {
|
|
306
|
+
kinds,
|
|
307
|
+
previous: {
|
|
308
|
+
bundle_id: running.bundle_id,
|
|
309
|
+
release_id: running.release_id,
|
|
310
|
+
app_version: head.app_version,
|
|
311
|
+
channel: head.channel,
|
|
312
|
+
...headBuild === void 0 ? {} : { min_bundle_id: headBuild }
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
};
|
|
316
|
+
/**
|
|
317
|
+
* Whether a launch or a crash comes with no download report for its target:
|
|
318
|
+
* its head shows neither that download nor the target already running. The
|
|
319
|
+
* native build's built-in bundle is never downloaded, and without a head the
|
|
320
|
+
* server knows nothing of the installation's past, so it implies nothing.
|
|
321
|
+
*/
|
|
322
|
+
const impliesDownload = (event, head) => {
|
|
323
|
+
const target = event.type === "RECOVERED" ? {
|
|
324
|
+
bundle: event.from_bundle_id,
|
|
325
|
+
release: event.from_release_id
|
|
326
|
+
} : isLaunch(event) ? {
|
|
327
|
+
bundle: event.to_bundle_id,
|
|
328
|
+
release: event.to_release_id
|
|
329
|
+
} : null;
|
|
330
|
+
if (target === null || target.release === null || target.bundle === minBundleIdOf(event)) return false;
|
|
331
|
+
if (head === null) return false;
|
|
332
|
+
if (head.type === "UPDATE_DOWNLOADED" && head.to_bundle_id === target.bundle) return false;
|
|
333
|
+
return runningOf(head).bundle_id !== target.bundle;
|
|
334
|
+
};
|
|
335
|
+
/**
|
|
336
|
+
* The event as recorded, with what only the server sets: what a kept
|
|
337
|
+
* UNCHANGED report changed, a late report's flag, and a download its launch
|
|
338
|
+
* or crash implied.
|
|
339
|
+
*/
|
|
340
|
+
const recordedOf = (event, head) => {
|
|
341
|
+
const { change: _change, late: _late, implied_download: _implied, ...metadata } = event.metadata;
|
|
342
|
+
const reported = {
|
|
343
|
+
...event,
|
|
344
|
+
metadata
|
|
345
|
+
};
|
|
346
|
+
const late = isLate(reported, head);
|
|
347
|
+
const change = late ? null : changeOf(reported, head);
|
|
348
|
+
const withChange = change === null ? reported : {
|
|
349
|
+
...reported,
|
|
350
|
+
metadata: {
|
|
351
|
+
...metadata,
|
|
352
|
+
change
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
return {
|
|
356
|
+
recorded: late && reported.type !== "UNCHANGED" ? {
|
|
357
|
+
...reported,
|
|
358
|
+
metadata: {
|
|
359
|
+
...metadata,
|
|
360
|
+
late: true
|
|
361
|
+
}
|
|
362
|
+
} : !late && impliesDownload(withChange, head) ? {
|
|
363
|
+
...withChange,
|
|
364
|
+
metadata: {
|
|
365
|
+
...withChange.metadata,
|
|
366
|
+
implied_download: true
|
|
367
|
+
}
|
|
368
|
+
} : withChange,
|
|
369
|
+
late
|
|
370
|
+
};
|
|
371
|
+
};
|
|
252
372
|
/** What an UNCHANGED report must share with its installation's head to repeat it. */
|
|
253
373
|
const REPEATED_FIELDS = [
|
|
254
374
|
"channel",
|
|
@@ -283,23 +403,24 @@ const recordEvent = (db, event) => {
|
|
|
283
403
|
const [existing, previous] = await Promise.all([tx.findOne("bundle_events", { id: event.id }), tx.findOne("bundle_event_heads", { install_id: event.install_id })]);
|
|
284
404
|
if (existing !== null || previous?.id === event.id) return;
|
|
285
405
|
if (previous !== null && repeatsHead(event, previous)) return;
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
406
|
+
const { recorded, late } = recordedOf(event, previous);
|
|
407
|
+
if (recorded.type !== "UNCHANGED" || recorded.metadata.change !== void 0 && !late) {
|
|
408
|
+
tx.create("bundle_events", recorded);
|
|
409
|
+
countOutcome(tx, recorded);
|
|
289
410
|
}
|
|
290
|
-
if (
|
|
291
|
-
countFailure(tx,
|
|
411
|
+
if (recorded.type === "UPDATE_FAILED") {
|
|
412
|
+
countFailure(tx, recorded);
|
|
292
413
|
return;
|
|
293
414
|
}
|
|
294
|
-
countEvent(tx,
|
|
295
|
-
if (previous !== null && !isNewer(
|
|
296
|
-
if (previous === null) tx.create("bundle_event_heads",
|
|
415
|
+
countEvent(tx, recorded);
|
|
416
|
+
if (previous !== null && (late || !isNewer(recorded, previous))) return;
|
|
417
|
+
if (previous === null) tx.create("bundle_event_heads", recorded);
|
|
297
418
|
else {
|
|
298
|
-
const { install_id: _, ...fields } =
|
|
419
|
+
const { install_id: _, ...fields } = recorded;
|
|
299
420
|
countHead(tx, previous, -1);
|
|
300
421
|
tx.update("bundle_event_heads", previous, fields);
|
|
301
422
|
}
|
|
302
|
-
countHead(tx,
|
|
423
|
+
countHead(tx, recorded, 1);
|
|
303
424
|
});
|
|
304
425
|
};
|
|
305
426
|
//#endregion
|
package/dist/server/schema.cjs
CHANGED
|
@@ -10,18 +10,53 @@ const bucketRetention = (days) => ({
|
|
|
10
10
|
field: "bucket_start_ms",
|
|
11
11
|
days
|
|
12
12
|
});
|
|
13
|
+
/** Every kind of row the table keeps belongs in installation history. */
|
|
13
14
|
const MOVEMENTS = /* @__PURE__ */ new Set([
|
|
14
15
|
"UPDATE_DOWNLOADED",
|
|
15
16
|
"UPDATE_APPLIED",
|
|
16
17
|
"RECOVERED",
|
|
17
|
-
"UPDATE_FAILED"
|
|
18
|
+
"UPDATE_FAILED",
|
|
19
|
+
"UNCHANGED"
|
|
18
20
|
]);
|
|
19
21
|
/** A failed update check: it names no target bundle, so no bundle list shows it. */
|
|
20
22
|
const isFailedCheck = (row) => row.type === "UPDATE_FAILED" && row.metadata?.failure?.stage === "check";
|
|
23
|
+
const metadataOf = (row) => row.metadata ?? {};
|
|
24
|
+
/** A download or apply that arrived after its target ran: it counts nothing. */
|
|
25
|
+
const isLateEvent = (row) => metadataOf(row).late === true;
|
|
21
26
|
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
27
|
+
* Whether a stored row launched its installation into a bundle release: an
|
|
28
|
+
* apply, or a kept UNCHANGED row that moved the installation to another
|
|
29
|
+
* bundle of a release, as when the apply report never arrived or a rollback
|
|
30
|
+
* to the built-in bundle took it there. A first report, a release change of
|
|
31
|
+
* the bundle already running, and a late row launch nothing.
|
|
32
|
+
*/
|
|
33
|
+
const isLaunch = (row) => {
|
|
34
|
+
if (isLateEvent(row)) return false;
|
|
35
|
+
if (row.type === "UPDATE_APPLIED") return true;
|
|
36
|
+
if (row.type !== "UNCHANGED") return false;
|
|
37
|
+
const kinds = metadataOf(row).change?.kinds ?? [];
|
|
38
|
+
return kinds.includes("bundle") && !kinds.includes("first_seen") && row.to_release_id !== null;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* The bundle a stored row's bundle filter matches: `from:<bundle>` for a
|
|
42
|
+
* recovery; `to:<bundle>` for a download, an apply, and an update failure;
|
|
43
|
+
* `on:<bundle>` for an UNCHANGED row that launched a bundle (`isLaunch`), a
|
|
44
|
+
* key no other row uses. None for a failed check, which targets no bundle, a
|
|
45
|
+
* late row, or any other UNCHANGED row.
|
|
46
|
+
*/
|
|
47
|
+
const bundleRefsOf = (row) => {
|
|
48
|
+
if (isFailedCheck(row) || isLateEvent(row)) return [];
|
|
49
|
+
if (row.type === "RECOVERED") return [`from:${row.from_bundle_id}`];
|
|
50
|
+
if (row.type === "UNCHANGED") return isLaunch(row) ? [`on:${row.to_bundle_id}`] : [];
|
|
51
|
+
return [`to:${row.to_bundle_id}`];
|
|
52
|
+
};
|
|
53
|
+
/** The key a bundle filter reads, as `bundleRefsOf` writes it. */
|
|
54
|
+
const bundleRefOfFilter = (filter) => filter.type === "RECOVERED" ? `from:${filter.fromBundleId}` : filter.type === "UNCHANGED" ? `on:${filter.toBundleId}` : `to:${filter.toBundleId}`;
|
|
55
|
+
/**
|
|
56
|
+
* Downloads, applies, recoveries, update failures, and the UNCHANGED
|
|
57
|
+
* reports that changed what their installation runs, whole. An UNCHANGED
|
|
58
|
+
* report that changes nothing counts as a launch and moves its
|
|
59
|
+
* installation's head, but no row keeps it.
|
|
25
60
|
*/
|
|
26
61
|
const bundleEvents = (days) => (0, _hot_updater_plugin_core.defineTable)({
|
|
27
62
|
id: {
|
|
@@ -76,19 +111,16 @@ const bundleEvents = (days) => (0, _hot_updater_plugin_core.defineTable)({
|
|
|
76
111
|
type: "integer",
|
|
77
112
|
compute: (row) => row.received_at_ms - row.received_at_ms % DAY_MS
|
|
78
113
|
},
|
|
79
|
-
/**
|
|
114
|
+
/** Every stored event belongs in installation history. */
|
|
80
115
|
movement_install_id: {
|
|
81
116
|
type: "string",
|
|
82
117
|
compute: (row) => MOVEMENTS.has(row.type) ? row.install_id : null
|
|
83
118
|
},
|
|
84
|
-
/**
|
|
85
|
-
* The bundle a bundle filter matches: `from:<bundle>` for RECOVERED,
|
|
86
|
-
* none for a failed check, else `to:<bundle>`.
|
|
87
|
-
*/
|
|
119
|
+
/** The bundle a bundle filter matches (`bundleRefsOf`). */
|
|
88
120
|
bundle_ref: {
|
|
89
121
|
type: "string",
|
|
90
122
|
multi: true,
|
|
91
|
-
compute: (row) =>
|
|
123
|
+
compute: (row) => bundleRefsOf(row)
|
|
92
124
|
}
|
|
93
125
|
},
|
|
94
126
|
indexes: {
|
|
@@ -534,6 +566,10 @@ exports.DAILY_EVENTS = DAILY_EVENTS;
|
|
|
534
566
|
exports.DAY_MS = DAY_MS;
|
|
535
567
|
exports.DEFAULT_INSIGHTS_RETENTION = DEFAULT_INSIGHTS_RETENTION;
|
|
536
568
|
exports.HOUR_MS = HOUR_MS;
|
|
569
|
+
exports.bundleRefOfFilter = bundleRefOfFilter;
|
|
570
|
+
exports.bundleRefsOf = bundleRefsOf;
|
|
537
571
|
exports.createInsightsSchema = createInsightsSchema;
|
|
538
572
|
exports.insightsSchema = insightsSchema;
|
|
539
573
|
exports.isFailedCheck = isFailedCheck;
|
|
574
|
+
exports.isLateEvent = isLateEvent;
|
|
575
|
+
exports.isLaunch = isLaunch;
|
package/dist/server/schema.mjs
CHANGED
|
@@ -10,18 +10,53 @@ const bucketRetention = (days) => ({
|
|
|
10
10
|
field: "bucket_start_ms",
|
|
11
11
|
days
|
|
12
12
|
});
|
|
13
|
+
/** Every kind of row the table keeps belongs in installation history. */
|
|
13
14
|
const MOVEMENTS = /* @__PURE__ */ new Set([
|
|
14
15
|
"UPDATE_DOWNLOADED",
|
|
15
16
|
"UPDATE_APPLIED",
|
|
16
17
|
"RECOVERED",
|
|
17
|
-
"UPDATE_FAILED"
|
|
18
|
+
"UPDATE_FAILED",
|
|
19
|
+
"UNCHANGED"
|
|
18
20
|
]);
|
|
19
21
|
/** A failed update check: it names no target bundle, so no bundle list shows it. */
|
|
20
22
|
const isFailedCheck = (row) => row.type === "UPDATE_FAILED" && row.metadata?.failure?.stage === "check";
|
|
23
|
+
const metadataOf = (row) => row.metadata ?? {};
|
|
24
|
+
/** A download or apply that arrived after its target ran: it counts nothing. */
|
|
25
|
+
const isLateEvent = (row) => metadataOf(row).late === true;
|
|
21
26
|
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
27
|
+
* Whether a stored row launched its installation into a bundle release: an
|
|
28
|
+
* apply, or a kept UNCHANGED row that moved the installation to another
|
|
29
|
+
* bundle of a release, as when the apply report never arrived or a rollback
|
|
30
|
+
* to the built-in bundle took it there. A first report, a release change of
|
|
31
|
+
* the bundle already running, and a late row launch nothing.
|
|
32
|
+
*/
|
|
33
|
+
const isLaunch = (row) => {
|
|
34
|
+
if (isLateEvent(row)) return false;
|
|
35
|
+
if (row.type === "UPDATE_APPLIED") return true;
|
|
36
|
+
if (row.type !== "UNCHANGED") return false;
|
|
37
|
+
const kinds = metadataOf(row).change?.kinds ?? [];
|
|
38
|
+
return kinds.includes("bundle") && !kinds.includes("first_seen") && row.to_release_id !== null;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* The bundle a stored row's bundle filter matches: `from:<bundle>` for a
|
|
42
|
+
* recovery; `to:<bundle>` for a download, an apply, and an update failure;
|
|
43
|
+
* `on:<bundle>` for an UNCHANGED row that launched a bundle (`isLaunch`), a
|
|
44
|
+
* key no other row uses. None for a failed check, which targets no bundle, a
|
|
45
|
+
* late row, or any other UNCHANGED row.
|
|
46
|
+
*/
|
|
47
|
+
const bundleRefsOf = (row) => {
|
|
48
|
+
if (isFailedCheck(row) || isLateEvent(row)) return [];
|
|
49
|
+
if (row.type === "RECOVERED") return [`from:${row.from_bundle_id}`];
|
|
50
|
+
if (row.type === "UNCHANGED") return isLaunch(row) ? [`on:${row.to_bundle_id}`] : [];
|
|
51
|
+
return [`to:${row.to_bundle_id}`];
|
|
52
|
+
};
|
|
53
|
+
/** The key a bundle filter reads, as `bundleRefsOf` writes it. */
|
|
54
|
+
const bundleRefOfFilter = (filter) => filter.type === "RECOVERED" ? `from:${filter.fromBundleId}` : filter.type === "UNCHANGED" ? `on:${filter.toBundleId}` : `to:${filter.toBundleId}`;
|
|
55
|
+
/**
|
|
56
|
+
* Downloads, applies, recoveries, update failures, and the UNCHANGED
|
|
57
|
+
* reports that changed what their installation runs, whole. An UNCHANGED
|
|
58
|
+
* report that changes nothing counts as a launch and moves its
|
|
59
|
+
* installation's head, but no row keeps it.
|
|
25
60
|
*/
|
|
26
61
|
const bundleEvents = (days) => defineTable({
|
|
27
62
|
id: {
|
|
@@ -76,19 +111,16 @@ const bundleEvents = (days) => defineTable({
|
|
|
76
111
|
type: "integer",
|
|
77
112
|
compute: (row) => row.received_at_ms - row.received_at_ms % DAY_MS
|
|
78
113
|
},
|
|
79
|
-
/**
|
|
114
|
+
/** Every stored event belongs in installation history. */
|
|
80
115
|
movement_install_id: {
|
|
81
116
|
type: "string",
|
|
82
117
|
compute: (row) => MOVEMENTS.has(row.type) ? row.install_id : null
|
|
83
118
|
},
|
|
84
|
-
/**
|
|
85
|
-
* The bundle a bundle filter matches: `from:<bundle>` for RECOVERED,
|
|
86
|
-
* none for a failed check, else `to:<bundle>`.
|
|
87
|
-
*/
|
|
119
|
+
/** The bundle a bundle filter matches (`bundleRefsOf`). */
|
|
88
120
|
bundle_ref: {
|
|
89
121
|
type: "string",
|
|
90
122
|
multi: true,
|
|
91
|
-
compute: (row) =>
|
|
123
|
+
compute: (row) => bundleRefsOf(row)
|
|
92
124
|
}
|
|
93
125
|
},
|
|
94
126
|
indexes: {
|
|
@@ -530,4 +562,4 @@ const createInsightsSchema = ({ rawDays, dailyDays } = DEFAULT_INSIGHTS_RETENTIO
|
|
|
530
562
|
};
|
|
531
563
|
const insightsSchema = createInsightsSchema();
|
|
532
564
|
//#endregion
|
|
533
|
-
export { DAILY_EVENTS, DAY_MS, DEFAULT_INSIGHTS_RETENTION, HOUR_MS, createInsightsSchema, insightsSchema, isFailedCheck };
|
|
565
|
+
export { DAILY_EVENTS, DAY_MS, DEFAULT_INSIGHTS_RETENTION, HOUR_MS, bundleRefOfFilter, bundleRefsOf, createInsightsSchema, insightsSchema, isFailedCheck, isLateEvent, isLaunch };
|
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.
|
|
4
|
+
"version": "1.0.0-rc.37",
|
|
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.
|
|
36
|
+
"@hot-updater/protocol": "1.0.0-rc.37"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
|
-
"@hot-updater/plugin-core": "^1.0.0-rc.
|
|
39
|
+
"@hot-updater/plugin-core": "^1.0.0-rc.37"
|
|
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/
|
|
50
|
-
"@hot-updater/
|
|
49
|
+
"@hot-updater/test-utils": "1.0.0-rc.37",
|
|
50
|
+
"@hot-updater/plugin-core": "1.0.0-rc.37"
|
|
51
51
|
},
|
|
52
52
|
"nx": {
|
|
53
53
|
"implicitDependencies": [
|