@hot-updater/server 1.0.0-rc.0 → 1.0.0-rc.1
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/adapters/drizzle.cjs +3 -3
- package/dist/adapters/drizzle.mjs +3 -3
- package/dist/createHotUpdaterCore.cjs +6 -6
- package/dist/createHotUpdaterCore.d.cts +7 -7
- package/dist/createHotUpdaterCore.d.mts +7 -7
- package/dist/createHotUpdaterCore.mjs +6 -6
- package/dist/db/types.cjs +1 -1
- package/dist/db/types.mjs +1 -1
- package/dist/handler.cjs +5 -5
- package/dist/handler.mjs +5 -5
- package/dist/index.cjs +2 -2
- package/dist/index.d.cts +4 -4
- package/dist/index.d.mts +4 -4
- package/dist/index.mjs +2 -2
- package/dist/{analytics → insights}/bounded/activeOverview.cjs +1 -1
- package/dist/{analytics → insights}/bounded/activeOverview.mjs +1 -1
- package/dist/{analytics → insights}/bounded/installationSearch.cjs +1 -1
- package/dist/{analytics → insights}/bounded/installationSearch.mjs +1 -1
- package/dist/{analytics → insights}/bounded/persistence.cjs +1 -1
- package/dist/{analytics → insights}/bounded/persistence.mjs +1 -1
- package/dist/{analytics → insights}/bounded/provider.cjs +9 -9
- package/dist/insights/bounded/provider.d.cts +7 -0
- package/dist/insights/bounded/provider.d.mts +7 -0
- package/dist/{analytics → insights}/bounded/provider.mjs +10 -10
- package/dist/{analytics → insights}/bounded/scan.cjs +15 -15
- package/dist/{analytics → insights}/bounded/scan.mjs +16 -16
- package/dist/{analytics → insights}/domain.d.cts +12 -12
- package/dist/{analytics → insights}/domain.d.mts +12 -12
- package/dist/insights/errors.cjs +22 -0
- package/dist/insights/errors.mjs +20 -0
- package/dist/{analytics → insights}/eventInput.cjs +10 -10
- package/dist/{analytics → insights}/eventInput.mjs +11 -11
- package/dist/insights/persistence.d.cts +6 -0
- package/dist/insights/persistence.d.mts +6 -0
- package/dist/{analytics → insights}/queryInput.cjs +9 -9
- package/dist/{analytics → insights}/queryInput.mjs +10 -10
- package/dist/{analytics → insights}/routes.cjs +16 -16
- package/dist/{analytics → insights}/routes.mjs +16 -16
- package/dist/{analytics → insights}/types.d.cts +6 -6
- package/dist/{analytics → insights}/types.d.mts +6 -6
- package/dist/package.cjs +1 -1
- package/dist/package.mjs +1 -1
- package/package.json +4 -4
- package/src/adapters/drizzle.ts +3 -3
- package/src/apiKeys.integration.spec.ts +2 -2
- package/src/createHotUpdaterCore.ts +13 -13
- package/src/db/types.spec.ts +3 -3
- package/src/db/types.ts +7 -7
- package/src/handler-standalone.integration.spec.ts +1 -1
- package/src/handler.ts +12 -12
- package/src/index.ts +3 -3
- package/src/{analytics → insights}/bounded/provider.spec.ts +21 -21
- package/src/{analytics → insights}/bounded/provider.ts +18 -18
- package/src/{analytics → insights}/bounded/scan.ts +26 -26
- package/src/{analytics → insights}/domain.ts +10 -10
- package/src/insights/errors.ts +18 -0
- package/src/{analytics → insights}/eventInput.ts +11 -11
- package/src/{analytics/analytics.integration.spec.ts → insights/insights.integration.spec.ts} +6 -6
- package/src/insights/persistence.ts +10 -0
- package/src/{analytics → insights}/queryInput.ts +11 -11
- package/src/{analytics → insights}/routes.ts +18 -18
- package/src/{analytics → insights}/types.ts +7 -7
- package/dist/analytics/bounded/provider.d.cts +0 -7
- package/dist/analytics/bounded/provider.d.mts +0 -7
- package/dist/analytics/errors.cjs +0 -22
- package/dist/analytics/errors.mjs +0 -20
- package/dist/analytics/persistence.d.cts +0 -6
- package/dist/analytics/persistence.d.mts +0 -6
- package/src/analytics/errors.ts +0 -18
- package/src/analytics/persistence.ts +0 -10
- /package/src/{analytics → insights}/bounded/activeOverview.spec.ts +0 -0
- /package/src/{analytics → insights}/bounded/activeOverview.ts +0 -0
- /package/src/{analytics → insights}/bounded/installationSearch.spec.ts +0 -0
- /package/src/{analytics → insights}/bounded/installationSearch.ts +0 -0
- /package/src/{analytics → insights}/bounded/persistence.ts +0 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
const require_errors = require("../errors.cjs");
|
|
2
2
|
const require_activeOverview = require("./activeOverview.cjs");
|
|
3
|
-
//#region src/
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
3
|
+
//#region src/insights/bounded/scan.ts
|
|
4
|
+
const INSIGHTS_SCAN_MAX_ROWS = 5e4;
|
|
5
|
+
const INSIGHTS_MATERIALIZATION_LIMIT = INSIGHTS_SCAN_MAX_ROWS + 1;
|
|
6
|
+
const INSIGHTS_SCAN_PAGE_SIZE = 1e3;
|
|
7
|
+
const INSIGHTS_LOWER_BOUND_ID = "00000000-0000-0000-0000-000000000000";
|
|
8
8
|
const compareCodePoints = (left, right) => {
|
|
9
9
|
if (left < right) return -1;
|
|
10
10
|
if (left > right) return 1;
|
|
@@ -17,23 +17,23 @@ const materializeEventRows = async (scope) => {
|
|
|
17
17
|
const seenIds = /* @__PURE__ */ new Set();
|
|
18
18
|
let after = scope.lowerBoundMs === void 0 ? void 0 : {
|
|
19
19
|
receivedAtMs: scope.lowerBoundMs,
|
|
20
|
-
id:
|
|
20
|
+
id: INSIGHTS_LOWER_BOUND_ID
|
|
21
21
|
};
|
|
22
|
-
while (rows.length <
|
|
23
|
-
const limit = Math.min(
|
|
22
|
+
while (rows.length < INSIGHTS_MATERIALIZATION_LIMIT) {
|
|
23
|
+
const limit = Math.min(INSIGHTS_SCAN_PAGE_SIZE, INSIGHTS_MATERIALIZATION_LIMIT - rows.length);
|
|
24
24
|
const page = await scope.persistence.scan({
|
|
25
25
|
beforeReceivedAtMs: scope.cutoffMs,
|
|
26
26
|
...after === void 0 ? {} : { after },
|
|
27
27
|
limit
|
|
28
28
|
});
|
|
29
29
|
if (page.length === 0) break;
|
|
30
|
-
if (page.length > limit) throw new
|
|
30
|
+
if (page.length > limit) throw new InsightsPersistenceOrderError();
|
|
31
31
|
let previous = after === void 0 ? void 0 : {
|
|
32
32
|
received_at_ms: after.receivedAtMs,
|
|
33
33
|
id: after.id
|
|
34
34
|
};
|
|
35
35
|
for (const row of page) {
|
|
36
|
-
if (row.received_at_ms >= scope.cutoffMs || previous !== void 0 && compareEventOldest(previous, row) >= 0 || seenIds.has(row.id)) throw new
|
|
36
|
+
if (row.received_at_ms >= scope.cutoffMs || previous !== void 0 && compareEventOldest(previous, row) >= 0 || seenIds.has(row.id)) throw new InsightsPersistenceOrderError();
|
|
37
37
|
seenIds.add(row.id);
|
|
38
38
|
rows.push(row);
|
|
39
39
|
previous = row;
|
|
@@ -43,14 +43,14 @@ const materializeEventRows = async (scope) => {
|
|
|
43
43
|
receivedAtMs: last.received_at_ms,
|
|
44
44
|
id: last.id
|
|
45
45
|
};
|
|
46
|
-
if (rows.length > 5e4) throw new require_errors.
|
|
46
|
+
if (rows.length > 5e4) throw new require_errors.InsightsScanLimitExceededError(INSIGHTS_SCAN_MAX_ROWS);
|
|
47
47
|
}
|
|
48
48
|
return rows;
|
|
49
49
|
};
|
|
50
|
-
var
|
|
51
|
-
name = "
|
|
50
|
+
var InsightsPersistenceOrderError = class extends Error {
|
|
51
|
+
name = "InsightsPersistenceOrderError";
|
|
52
52
|
constructor() {
|
|
53
|
-
super("
|
|
53
|
+
super("Insights persistence did not advance its scan cursor.");
|
|
54
54
|
}
|
|
55
55
|
};
|
|
56
56
|
const materializeActiveRows = async (scope, window) => {
|
|
@@ -135,7 +135,7 @@ const collectEventActivity = (request) => {
|
|
|
135
135
|
};
|
|
136
136
|
};
|
|
137
137
|
//#endregion
|
|
138
|
-
exports.
|
|
138
|
+
exports.INSIGHTS_SCAN_MAX_ROWS = INSIGHTS_SCAN_MAX_ROWS;
|
|
139
139
|
exports.collectEventActivity = collectEventActivity;
|
|
140
140
|
exports.compareCodePoints = compareCodePoints;
|
|
141
141
|
exports.compareEventNewest = compareEventNewest;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { InsightsScanLimitExceededError } from "../errors.mjs";
|
|
2
2
|
import { ACTIVE_BUNDLE_EVENT_TYPES, getActiveWindowDefinition } from "./activeOverview.mjs";
|
|
3
|
-
//#region src/
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
3
|
+
//#region src/insights/bounded/scan.ts
|
|
4
|
+
const INSIGHTS_SCAN_MAX_ROWS = 5e4;
|
|
5
|
+
const INSIGHTS_MATERIALIZATION_LIMIT = INSIGHTS_SCAN_MAX_ROWS + 1;
|
|
6
|
+
const INSIGHTS_SCAN_PAGE_SIZE = 1e3;
|
|
7
|
+
const INSIGHTS_LOWER_BOUND_ID = "00000000-0000-0000-0000-000000000000";
|
|
8
8
|
const compareCodePoints = (left, right) => {
|
|
9
9
|
if (left < right) return -1;
|
|
10
10
|
if (left > right) return 1;
|
|
@@ -17,23 +17,23 @@ const materializeEventRows = async (scope) => {
|
|
|
17
17
|
const seenIds = /* @__PURE__ */ new Set();
|
|
18
18
|
let after = scope.lowerBoundMs === void 0 ? void 0 : {
|
|
19
19
|
receivedAtMs: scope.lowerBoundMs,
|
|
20
|
-
id:
|
|
20
|
+
id: INSIGHTS_LOWER_BOUND_ID
|
|
21
21
|
};
|
|
22
|
-
while (rows.length <
|
|
23
|
-
const limit = Math.min(
|
|
22
|
+
while (rows.length < INSIGHTS_MATERIALIZATION_LIMIT) {
|
|
23
|
+
const limit = Math.min(INSIGHTS_SCAN_PAGE_SIZE, INSIGHTS_MATERIALIZATION_LIMIT - rows.length);
|
|
24
24
|
const page = await scope.persistence.scan({
|
|
25
25
|
beforeReceivedAtMs: scope.cutoffMs,
|
|
26
26
|
...after === void 0 ? {} : { after },
|
|
27
27
|
limit
|
|
28
28
|
});
|
|
29
29
|
if (page.length === 0) break;
|
|
30
|
-
if (page.length > limit) throw new
|
|
30
|
+
if (page.length > limit) throw new InsightsPersistenceOrderError();
|
|
31
31
|
let previous = after === void 0 ? void 0 : {
|
|
32
32
|
received_at_ms: after.receivedAtMs,
|
|
33
33
|
id: after.id
|
|
34
34
|
};
|
|
35
35
|
for (const row of page) {
|
|
36
|
-
if (row.received_at_ms >= scope.cutoffMs || previous !== void 0 && compareEventOldest(previous, row) >= 0 || seenIds.has(row.id)) throw new
|
|
36
|
+
if (row.received_at_ms >= scope.cutoffMs || previous !== void 0 && compareEventOldest(previous, row) >= 0 || seenIds.has(row.id)) throw new InsightsPersistenceOrderError();
|
|
37
37
|
seenIds.add(row.id);
|
|
38
38
|
rows.push(row);
|
|
39
39
|
previous = row;
|
|
@@ -43,14 +43,14 @@ const materializeEventRows = async (scope) => {
|
|
|
43
43
|
receivedAtMs: last.received_at_ms,
|
|
44
44
|
id: last.id
|
|
45
45
|
};
|
|
46
|
-
if (rows.length > 5e4) throw new
|
|
46
|
+
if (rows.length > 5e4) throw new InsightsScanLimitExceededError(INSIGHTS_SCAN_MAX_ROWS);
|
|
47
47
|
}
|
|
48
48
|
return rows;
|
|
49
49
|
};
|
|
50
|
-
var
|
|
51
|
-
name = "
|
|
50
|
+
var InsightsPersistenceOrderError = class extends Error {
|
|
51
|
+
name = "InsightsPersistenceOrderError";
|
|
52
52
|
constructor() {
|
|
53
|
-
super("
|
|
53
|
+
super("Insights persistence did not advance its scan cursor.");
|
|
54
54
|
}
|
|
55
55
|
};
|
|
56
56
|
const materializeActiveRows = async (scope, window) => {
|
|
@@ -135,4 +135,4 @@ const collectEventActivity = (request) => {
|
|
|
135
135
|
};
|
|
136
136
|
};
|
|
137
137
|
//#endregion
|
|
138
|
-
export {
|
|
138
|
+
export { INSIGHTS_SCAN_MAX_ROWS, collectEventActivity, compareCodePoints, compareEventNewest, materializeActiveRows, materializeEventRows, materializeRowsForWindow };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//#region src/
|
|
1
|
+
//#region src/insights/domain.d.ts
|
|
2
2
|
type CreateBundleEventRequestBase = {
|
|
3
3
|
readonly installId: string;
|
|
4
4
|
readonly toBundleId: string;
|
|
@@ -37,7 +37,7 @@ type BundleEventSummary = {
|
|
|
37
37
|
type BundleEventSummaryByBundle = BundleEventSummary & {
|
|
38
38
|
readonly bundleId: string;
|
|
39
39
|
};
|
|
40
|
-
type
|
|
40
|
+
type BundleEventInsightsWindow = "24h" | "7d" | "30d" | "all";
|
|
41
41
|
type ActiveInstallationWindow = "24h" | "7d" | "30d";
|
|
42
42
|
type InstallationSearchRow = {
|
|
43
43
|
readonly installId: string;
|
|
@@ -72,23 +72,23 @@ type OffsetPaginationResult<TData> = {
|
|
|
72
72
|
readonly offset: number;
|
|
73
73
|
};
|
|
74
74
|
};
|
|
75
|
-
type
|
|
75
|
+
type InsightsSeriesPoint = {
|
|
76
76
|
readonly bucketStartMs: number;
|
|
77
77
|
readonly value: number;
|
|
78
78
|
};
|
|
79
|
-
type
|
|
79
|
+
type InsightsCohortPoint = {
|
|
80
80
|
readonly cohort: string;
|
|
81
81
|
readonly value: number;
|
|
82
82
|
};
|
|
83
|
-
type
|
|
83
|
+
type BundleEventInsightsResult = {
|
|
84
84
|
readonly summary: BundleEventSummary;
|
|
85
85
|
readonly series: {
|
|
86
|
-
readonly installed: readonly
|
|
87
|
-
readonly recovered: readonly
|
|
86
|
+
readonly installed: readonly InsightsSeriesPoint[];
|
|
87
|
+
readonly recovered: readonly InsightsSeriesPoint[];
|
|
88
88
|
};
|
|
89
89
|
readonly cohorts: {
|
|
90
|
-
readonly installed: readonly
|
|
91
|
-
readonly recovered: readonly
|
|
90
|
+
readonly installed: readonly InsightsCohortPoint[];
|
|
91
|
+
readonly recovered: readonly InsightsCohortPoint[];
|
|
92
92
|
};
|
|
93
93
|
readonly recentEvents: OffsetPaginationResult<InstallationHistoryRow>;
|
|
94
94
|
};
|
|
@@ -103,10 +103,10 @@ type ActiveInstallationOverview = {
|
|
|
103
103
|
readonly asOfMs: number;
|
|
104
104
|
readonly window: ActiveInstallationWindow;
|
|
105
105
|
readonly activeInstallations: number;
|
|
106
|
-
readonly series: readonly
|
|
106
|
+
readonly series: readonly InsightsSeriesPoint[];
|
|
107
107
|
readonly bundleSeries: readonly {
|
|
108
108
|
readonly bundleId: string;
|
|
109
|
-
readonly series: readonly
|
|
109
|
+
readonly series: readonly InsightsSeriesPoint[];
|
|
110
110
|
}[];
|
|
111
111
|
readonly bundles: readonly {
|
|
112
112
|
readonly bundleId: string;
|
|
@@ -118,4 +118,4 @@ type ActiveInstallationInput = {
|
|
|
118
118
|
readonly userId?: string;
|
|
119
119
|
};
|
|
120
120
|
//#endregion
|
|
121
|
-
export { ActiveInstallationInput, ActiveInstallationOverview, ActiveInstallationWindow,
|
|
121
|
+
export { ActiveInstallationInput, ActiveInstallationOverview, ActiveInstallationWindow, BundleEventInsightsResult, BundleEventInsightsWindow, BundleEventOverview, BundleEventSummary, BundleEventSummaryByBundle, CreateBundleEventRequest, CreateBundleEventRequestBase, InsightsCohortPoint, InsightsSeriesPoint, InstallationHistoryRow, InstallationSearchRow, OffsetPaginationResult };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//#region src/
|
|
1
|
+
//#region src/insights/domain.d.ts
|
|
2
2
|
type CreateBundleEventRequestBase = {
|
|
3
3
|
readonly installId: string;
|
|
4
4
|
readonly toBundleId: string;
|
|
@@ -37,7 +37,7 @@ type BundleEventSummary = {
|
|
|
37
37
|
type BundleEventSummaryByBundle = BundleEventSummary & {
|
|
38
38
|
readonly bundleId: string;
|
|
39
39
|
};
|
|
40
|
-
type
|
|
40
|
+
type BundleEventInsightsWindow = "24h" | "7d" | "30d" | "all";
|
|
41
41
|
type ActiveInstallationWindow = "24h" | "7d" | "30d";
|
|
42
42
|
type InstallationSearchRow = {
|
|
43
43
|
readonly installId: string;
|
|
@@ -72,23 +72,23 @@ type OffsetPaginationResult<TData> = {
|
|
|
72
72
|
readonly offset: number;
|
|
73
73
|
};
|
|
74
74
|
};
|
|
75
|
-
type
|
|
75
|
+
type InsightsSeriesPoint = {
|
|
76
76
|
readonly bucketStartMs: number;
|
|
77
77
|
readonly value: number;
|
|
78
78
|
};
|
|
79
|
-
type
|
|
79
|
+
type InsightsCohortPoint = {
|
|
80
80
|
readonly cohort: string;
|
|
81
81
|
readonly value: number;
|
|
82
82
|
};
|
|
83
|
-
type
|
|
83
|
+
type BundleEventInsightsResult = {
|
|
84
84
|
readonly summary: BundleEventSummary;
|
|
85
85
|
readonly series: {
|
|
86
|
-
readonly installed: readonly
|
|
87
|
-
readonly recovered: readonly
|
|
86
|
+
readonly installed: readonly InsightsSeriesPoint[];
|
|
87
|
+
readonly recovered: readonly InsightsSeriesPoint[];
|
|
88
88
|
};
|
|
89
89
|
readonly cohorts: {
|
|
90
|
-
readonly installed: readonly
|
|
91
|
-
readonly recovered: readonly
|
|
90
|
+
readonly installed: readonly InsightsCohortPoint[];
|
|
91
|
+
readonly recovered: readonly InsightsCohortPoint[];
|
|
92
92
|
};
|
|
93
93
|
readonly recentEvents: OffsetPaginationResult<InstallationHistoryRow>;
|
|
94
94
|
};
|
|
@@ -103,10 +103,10 @@ type ActiveInstallationOverview = {
|
|
|
103
103
|
readonly asOfMs: number;
|
|
104
104
|
readonly window: ActiveInstallationWindow;
|
|
105
105
|
readonly activeInstallations: number;
|
|
106
|
-
readonly series: readonly
|
|
106
|
+
readonly series: readonly InsightsSeriesPoint[];
|
|
107
107
|
readonly bundleSeries: readonly {
|
|
108
108
|
readonly bundleId: string;
|
|
109
|
-
readonly series: readonly
|
|
109
|
+
readonly series: readonly InsightsSeriesPoint[];
|
|
110
110
|
}[];
|
|
111
111
|
readonly bundles: readonly {
|
|
112
112
|
readonly bundleId: string;
|
|
@@ -118,4 +118,4 @@ type ActiveInstallationInput = {
|
|
|
118
118
|
readonly userId?: string;
|
|
119
119
|
};
|
|
120
120
|
//#endregion
|
|
121
|
-
export { ActiveInstallationInput, ActiveInstallationOverview, ActiveInstallationWindow,
|
|
121
|
+
export { ActiveInstallationInput, ActiveInstallationOverview, ActiveInstallationWindow, BundleEventInsightsResult, BundleEventInsightsWindow, BundleEventOverview, BundleEventSummary, BundleEventSummaryByBundle, CreateBundleEventRequest, CreateBundleEventRequestBase, InsightsCohortPoint, InsightsSeriesPoint, InstallationHistoryRow, InstallationSearchRow, OffsetPaginationResult };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
//#region src/insights/errors.ts
|
|
2
|
+
var InsightsScanLimitExceededError = class extends Error {
|
|
3
|
+
constructor(limit) {
|
|
4
|
+
super(`Insights event scan exceeded ${limit} rows.`);
|
|
5
|
+
this.limit = limit;
|
|
6
|
+
this.name = "InsightsScanLimitExceededError";
|
|
7
|
+
}
|
|
8
|
+
};
|
|
9
|
+
var InsightsBadRequestError = class extends Error {
|
|
10
|
+
name = "InsightsBadRequestError";
|
|
11
|
+
};
|
|
12
|
+
var InsightsPayloadTooLargeError = class extends Error {
|
|
13
|
+
name = "InsightsPayloadTooLargeError";
|
|
14
|
+
constructor(maximumBytes) {
|
|
15
|
+
super(`Event payload exceeds ${maximumBytes} bytes`);
|
|
16
|
+
this.maximumBytes = maximumBytes;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
//#endregion
|
|
20
|
+
exports.InsightsBadRequestError = InsightsBadRequestError;
|
|
21
|
+
exports.InsightsPayloadTooLargeError = InsightsPayloadTooLargeError;
|
|
22
|
+
exports.InsightsScanLimitExceededError = InsightsScanLimitExceededError;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
//#region src/insights/errors.ts
|
|
2
|
+
var InsightsScanLimitExceededError = class extends Error {
|
|
3
|
+
constructor(limit) {
|
|
4
|
+
super(`Insights event scan exceeded ${limit} rows.`);
|
|
5
|
+
this.limit = limit;
|
|
6
|
+
this.name = "InsightsScanLimitExceededError";
|
|
7
|
+
}
|
|
8
|
+
};
|
|
9
|
+
var InsightsBadRequestError = class extends Error {
|
|
10
|
+
name = "InsightsBadRequestError";
|
|
11
|
+
};
|
|
12
|
+
var InsightsPayloadTooLargeError = class extends Error {
|
|
13
|
+
name = "InsightsPayloadTooLargeError";
|
|
14
|
+
constructor(maximumBytes) {
|
|
15
|
+
super(`Event payload exceeds ${maximumBytes} bytes`);
|
|
16
|
+
this.maximumBytes = maximumBytes;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
//#endregion
|
|
20
|
+
export { InsightsBadRequestError, InsightsPayloadTooLargeError, InsightsScanLimitExceededError };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const require_errors = require("./errors.cjs");
|
|
2
|
-
//#region src/
|
|
2
|
+
//#region src/insights/eventInput.ts
|
|
3
3
|
const MAX_EVENT_STRING_LENGTH = 1024;
|
|
4
4
|
const EVENT_BODY_MAX_BYTES = 16 * 1024;
|
|
5
5
|
const eventKeys = new Set([
|
|
@@ -24,7 +24,7 @@ function isRecord(value) {
|
|
|
24
24
|
}
|
|
25
25
|
function requireStringField(payload, key) {
|
|
26
26
|
const value = payload[key];
|
|
27
|
-
if (typeof value !== "string" || value.length === 0 || value.length > MAX_EVENT_STRING_LENGTH) throw new require_errors.
|
|
27
|
+
if (typeof value !== "string" || value.length === 0 || value.length > MAX_EVENT_STRING_LENGTH) throw new require_errors.InsightsBadRequestError(`Invalid event field: ${key}`);
|
|
28
28
|
return value;
|
|
29
29
|
}
|
|
30
30
|
function requireNullableStringField(payload, key) {
|
|
@@ -34,7 +34,7 @@ function requireNullableStringField(payload, key) {
|
|
|
34
34
|
async function readBoundedText(request) {
|
|
35
35
|
const contentLength = request.headers.get("content-length");
|
|
36
36
|
const declaredByteLength = Number(contentLength);
|
|
37
|
-
if (contentLength !== null && Number.isSafeInteger(declaredByteLength) && declaredByteLength > 16384) throw new require_errors.
|
|
37
|
+
if (contentLength !== null && Number.isSafeInteger(declaredByteLength) && declaredByteLength > 16384) throw new require_errors.InsightsPayloadTooLargeError(EVENT_BODY_MAX_BYTES);
|
|
38
38
|
if (request.body === null) return "";
|
|
39
39
|
const reader = request.body.getReader();
|
|
40
40
|
const decoder = new TextDecoder();
|
|
@@ -46,7 +46,7 @@ async function readBoundedText(request) {
|
|
|
46
46
|
byteLength += result.value.byteLength;
|
|
47
47
|
if (byteLength > 16384) {
|
|
48
48
|
await reader.cancel();
|
|
49
|
-
throw new require_errors.
|
|
49
|
+
throw new require_errors.InsightsPayloadTooLargeError(EVENT_BODY_MAX_BYTES);
|
|
50
50
|
}
|
|
51
51
|
text += decoder.decode(result.value, { stream: true });
|
|
52
52
|
}
|
|
@@ -57,14 +57,14 @@ async function parseJson(request) {
|
|
|
57
57
|
try {
|
|
58
58
|
return JSON.parse(text);
|
|
59
59
|
} catch (error) {
|
|
60
|
-
if (error instanceof SyntaxError) throw new require_errors.
|
|
60
|
+
if (error instanceof SyntaxError) throw new require_errors.InsightsBadRequestError("Invalid event payload");
|
|
61
61
|
throw error;
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
function requireEvent(payload) {
|
|
65
|
-
if (!isRecord(payload) || Object.keys(payload).some((key) => !eventKeys.has(key))) throw new require_errors.
|
|
65
|
+
if (!isRecord(payload) || Object.keys(payload).some((key) => !eventKeys.has(key))) throw new require_errors.InsightsBadRequestError("Invalid event payload");
|
|
66
66
|
const platform = requireStringField(payload, "platform");
|
|
67
|
-
if (platform !== "ios" && platform !== "android") throw new require_errors.
|
|
67
|
+
if (platform !== "ios" && platform !== "android") throw new require_errors.InsightsBadRequestError("Invalid event field: platform");
|
|
68
68
|
const base = {
|
|
69
69
|
installId: requireStringField(payload, "installId"),
|
|
70
70
|
toBundleId: requireStringField(payload, "toBundleId"),
|
|
@@ -85,7 +85,7 @@ function requireEvent(payload) {
|
|
|
85
85
|
case "RECOVERED":
|
|
86
86
|
case "RELEASE_ADOPTED": {
|
|
87
87
|
const updateStrategy = requireStringField(payload, "updateStrategy");
|
|
88
|
-
if (updateStrategy !== "fingerprint" && updateStrategy !== "appVersion") throw new require_errors.
|
|
88
|
+
if (updateStrategy !== "fingerprint" && updateStrategy !== "appVersion") throw new require_errors.InsightsBadRequestError("Invalid event field: updateStrategy");
|
|
89
89
|
return {
|
|
90
90
|
...base,
|
|
91
91
|
type,
|
|
@@ -94,14 +94,14 @@ function requireEvent(payload) {
|
|
|
94
94
|
};
|
|
95
95
|
}
|
|
96
96
|
case "UNCHANGED":
|
|
97
|
-
if (payload.fromBundleId !== null || payload.updateStrategy !== null) throw new require_errors.
|
|
97
|
+
if (payload.fromBundleId !== null || payload.updateStrategy !== null) throw new require_errors.InsightsBadRequestError("Invalid unchanged event shape");
|
|
98
98
|
return {
|
|
99
99
|
...base,
|
|
100
100
|
type,
|
|
101
101
|
fromBundleId: null,
|
|
102
102
|
updateStrategy: null
|
|
103
103
|
};
|
|
104
|
-
default: throw new require_errors.
|
|
104
|
+
default: throw new require_errors.InsightsBadRequestError("Invalid event field: type");
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
107
|
async function parseBundleEventRequest(request) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
//#region src/
|
|
1
|
+
import { InsightsBadRequestError, InsightsPayloadTooLargeError } from "./errors.mjs";
|
|
2
|
+
//#region src/insights/eventInput.ts
|
|
3
3
|
const MAX_EVENT_STRING_LENGTH = 1024;
|
|
4
4
|
const EVENT_BODY_MAX_BYTES = 16 * 1024;
|
|
5
5
|
const eventKeys = new Set([
|
|
@@ -24,7 +24,7 @@ function isRecord(value) {
|
|
|
24
24
|
}
|
|
25
25
|
function requireStringField(payload, key) {
|
|
26
26
|
const value = payload[key];
|
|
27
|
-
if (typeof value !== "string" || value.length === 0 || value.length > MAX_EVENT_STRING_LENGTH) throw new
|
|
27
|
+
if (typeof value !== "string" || value.length === 0 || value.length > MAX_EVENT_STRING_LENGTH) throw new InsightsBadRequestError(`Invalid event field: ${key}`);
|
|
28
28
|
return value;
|
|
29
29
|
}
|
|
30
30
|
function requireNullableStringField(payload, key) {
|
|
@@ -34,7 +34,7 @@ function requireNullableStringField(payload, key) {
|
|
|
34
34
|
async function readBoundedText(request) {
|
|
35
35
|
const contentLength = request.headers.get("content-length");
|
|
36
36
|
const declaredByteLength = Number(contentLength);
|
|
37
|
-
if (contentLength !== null && Number.isSafeInteger(declaredByteLength) && declaredByteLength > 16384) throw new
|
|
37
|
+
if (contentLength !== null && Number.isSafeInteger(declaredByteLength) && declaredByteLength > 16384) throw new InsightsPayloadTooLargeError(EVENT_BODY_MAX_BYTES);
|
|
38
38
|
if (request.body === null) return "";
|
|
39
39
|
const reader = request.body.getReader();
|
|
40
40
|
const decoder = new TextDecoder();
|
|
@@ -46,7 +46,7 @@ async function readBoundedText(request) {
|
|
|
46
46
|
byteLength += result.value.byteLength;
|
|
47
47
|
if (byteLength > 16384) {
|
|
48
48
|
await reader.cancel();
|
|
49
|
-
throw new
|
|
49
|
+
throw new InsightsPayloadTooLargeError(EVENT_BODY_MAX_BYTES);
|
|
50
50
|
}
|
|
51
51
|
text += decoder.decode(result.value, { stream: true });
|
|
52
52
|
}
|
|
@@ -57,14 +57,14 @@ async function parseJson(request) {
|
|
|
57
57
|
try {
|
|
58
58
|
return JSON.parse(text);
|
|
59
59
|
} catch (error) {
|
|
60
|
-
if (error instanceof SyntaxError) throw new
|
|
60
|
+
if (error instanceof SyntaxError) throw new InsightsBadRequestError("Invalid event payload");
|
|
61
61
|
throw error;
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
function requireEvent(payload) {
|
|
65
|
-
if (!isRecord(payload) || Object.keys(payload).some((key) => !eventKeys.has(key))) throw new
|
|
65
|
+
if (!isRecord(payload) || Object.keys(payload).some((key) => !eventKeys.has(key))) throw new InsightsBadRequestError("Invalid event payload");
|
|
66
66
|
const platform = requireStringField(payload, "platform");
|
|
67
|
-
if (platform !== "ios" && platform !== "android") throw new
|
|
67
|
+
if (platform !== "ios" && platform !== "android") throw new InsightsBadRequestError("Invalid event field: platform");
|
|
68
68
|
const base = {
|
|
69
69
|
installId: requireStringField(payload, "installId"),
|
|
70
70
|
toBundleId: requireStringField(payload, "toBundleId"),
|
|
@@ -85,7 +85,7 @@ function requireEvent(payload) {
|
|
|
85
85
|
case "RECOVERED":
|
|
86
86
|
case "RELEASE_ADOPTED": {
|
|
87
87
|
const updateStrategy = requireStringField(payload, "updateStrategy");
|
|
88
|
-
if (updateStrategy !== "fingerprint" && updateStrategy !== "appVersion") throw new
|
|
88
|
+
if (updateStrategy !== "fingerprint" && updateStrategy !== "appVersion") throw new InsightsBadRequestError("Invalid event field: updateStrategy");
|
|
89
89
|
return {
|
|
90
90
|
...base,
|
|
91
91
|
type,
|
|
@@ -94,14 +94,14 @@ function requireEvent(payload) {
|
|
|
94
94
|
};
|
|
95
95
|
}
|
|
96
96
|
case "UNCHANGED":
|
|
97
|
-
if (payload.fromBundleId !== null || payload.updateStrategy !== null) throw new
|
|
97
|
+
if (payload.fromBundleId !== null || payload.updateStrategy !== null) throw new InsightsBadRequestError("Invalid unchanged event shape");
|
|
98
98
|
return {
|
|
99
99
|
...base,
|
|
100
100
|
type,
|
|
101
101
|
fromBundleId: null,
|
|
102
102
|
updateStrategy: null
|
|
103
103
|
};
|
|
104
|
-
default: throw new
|
|
104
|
+
default: throw new InsightsBadRequestError("Invalid event field: type");
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
107
|
async function parseBundleEventRequest(request) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const require_errors = require("./errors.cjs");
|
|
2
|
-
//#region src/
|
|
2
|
+
//#region src/insights/queryInput.ts
|
|
3
3
|
const EVENT_LIST_BOUNDS = {
|
|
4
4
|
defaultValue: 50,
|
|
5
5
|
maximum: 100,
|
|
@@ -14,7 +14,7 @@ function parseInteger(url, key, bounds) {
|
|
|
14
14
|
const value = url.searchParams.get(key);
|
|
15
15
|
if (value === null) return bounds.defaultValue;
|
|
16
16
|
const parsed = Number(value);
|
|
17
|
-
if (!Number.isSafeInteger(parsed) || parsed < bounds.minimum || bounds.maximum !== void 0 && parsed > bounds.maximum) throw new require_errors.
|
|
17
|
+
if (!Number.isSafeInteger(parsed) || parsed < bounds.minimum || bounds.maximum !== void 0 && parsed > bounds.maximum) throw new require_errors.InsightsBadRequestError(`Invalid '${key}' query parameter.`);
|
|
18
18
|
return parsed;
|
|
19
19
|
}
|
|
20
20
|
const parsePagination = (request) => {
|
|
@@ -24,9 +24,9 @@ const parsePagination = (request) => {
|
|
|
24
24
|
offset: parseInteger(url, "offset", EVENT_LIST_OFFSET_BOUNDS)
|
|
25
25
|
};
|
|
26
26
|
};
|
|
27
|
-
const
|
|
27
|
+
const parseInsightsQuery = (request) => {
|
|
28
28
|
const window = new URL(request.url).searchParams.get("window") ?? "24h";
|
|
29
|
-
if (window !== "24h" && window !== "7d" && window !== "30d" && window !== "all") throw new require_errors.
|
|
29
|
+
if (window !== "24h" && window !== "7d" && window !== "30d" && window !== "all") throw new require_errors.InsightsBadRequestError("Invalid 'window' query parameter.");
|
|
30
30
|
return {
|
|
31
31
|
...parsePagination(request),
|
|
32
32
|
window
|
|
@@ -35,13 +35,13 @@ const parseAnalyticsQuery = (request) => {
|
|
|
35
35
|
const parseActiveInstallationInput = (request) => {
|
|
36
36
|
const url = new URL(request.url);
|
|
37
37
|
const windows = url.searchParams.getAll("window");
|
|
38
|
-
if (windows.length > 1) throw new require_errors.
|
|
38
|
+
if (windows.length > 1) throw new require_errors.InsightsBadRequestError("Duplicate 'window' query parameter.");
|
|
39
39
|
const window = windows[0] ?? "30d";
|
|
40
|
-
if (window !== "24h" && window !== "7d" && window !== "30d") throw new require_errors.
|
|
40
|
+
if (window !== "24h" && window !== "7d" && window !== "30d") throw new require_errors.InsightsBadRequestError("Invalid 'window' query parameter.");
|
|
41
41
|
const userIds = url.searchParams.getAll("userId");
|
|
42
|
-
if (userIds.length > 1) throw new require_errors.
|
|
42
|
+
if (userIds.length > 1) throw new require_errors.InsightsBadRequestError("Duplicate 'userId' query parameter.");
|
|
43
43
|
const userId = userIds[0];
|
|
44
|
-
if (userId !== void 0 && (userId.length === 0 || userId.length > MAX_USER_ID_LENGTH)) throw new require_errors.
|
|
44
|
+
if (userId !== void 0 && (userId.length === 0 || userId.length > MAX_USER_ID_LENGTH)) throw new require_errors.InsightsBadRequestError("Invalid 'userId' query parameter.");
|
|
45
45
|
return userId === void 0 ? { window } : {
|
|
46
46
|
window,
|
|
47
47
|
userId
|
|
@@ -53,6 +53,6 @@ const parseSearchInput = (request) => ({
|
|
|
53
53
|
});
|
|
54
54
|
//#endregion
|
|
55
55
|
exports.parseActiveInstallationInput = parseActiveInstallationInput;
|
|
56
|
-
exports.
|
|
56
|
+
exports.parseInsightsQuery = parseInsightsQuery;
|
|
57
57
|
exports.parsePagination = parsePagination;
|
|
58
58
|
exports.parseSearchInput = parseSearchInput;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
//#region src/
|
|
1
|
+
import { InsightsBadRequestError } from "./errors.mjs";
|
|
2
|
+
//#region src/insights/queryInput.ts
|
|
3
3
|
const EVENT_LIST_BOUNDS = {
|
|
4
4
|
defaultValue: 50,
|
|
5
5
|
maximum: 100,
|
|
@@ -14,7 +14,7 @@ function parseInteger(url, key, bounds) {
|
|
|
14
14
|
const value = url.searchParams.get(key);
|
|
15
15
|
if (value === null) return bounds.defaultValue;
|
|
16
16
|
const parsed = Number(value);
|
|
17
|
-
if (!Number.isSafeInteger(parsed) || parsed < bounds.minimum || bounds.maximum !== void 0 && parsed > bounds.maximum) throw new
|
|
17
|
+
if (!Number.isSafeInteger(parsed) || parsed < bounds.minimum || bounds.maximum !== void 0 && parsed > bounds.maximum) throw new InsightsBadRequestError(`Invalid '${key}' query parameter.`);
|
|
18
18
|
return parsed;
|
|
19
19
|
}
|
|
20
20
|
const parsePagination = (request) => {
|
|
@@ -24,9 +24,9 @@ const parsePagination = (request) => {
|
|
|
24
24
|
offset: parseInteger(url, "offset", EVENT_LIST_OFFSET_BOUNDS)
|
|
25
25
|
};
|
|
26
26
|
};
|
|
27
|
-
const
|
|
27
|
+
const parseInsightsQuery = (request) => {
|
|
28
28
|
const window = new URL(request.url).searchParams.get("window") ?? "24h";
|
|
29
|
-
if (window !== "24h" && window !== "7d" && window !== "30d" && window !== "all") throw new
|
|
29
|
+
if (window !== "24h" && window !== "7d" && window !== "30d" && window !== "all") throw new InsightsBadRequestError("Invalid 'window' query parameter.");
|
|
30
30
|
return {
|
|
31
31
|
...parsePagination(request),
|
|
32
32
|
window
|
|
@@ -35,13 +35,13 @@ const parseAnalyticsQuery = (request) => {
|
|
|
35
35
|
const parseActiveInstallationInput = (request) => {
|
|
36
36
|
const url = new URL(request.url);
|
|
37
37
|
const windows = url.searchParams.getAll("window");
|
|
38
|
-
if (windows.length > 1) throw new
|
|
38
|
+
if (windows.length > 1) throw new InsightsBadRequestError("Duplicate 'window' query parameter.");
|
|
39
39
|
const window = windows[0] ?? "30d";
|
|
40
|
-
if (window !== "24h" && window !== "7d" && window !== "30d") throw new
|
|
40
|
+
if (window !== "24h" && window !== "7d" && window !== "30d") throw new InsightsBadRequestError("Invalid 'window' query parameter.");
|
|
41
41
|
const userIds = url.searchParams.getAll("userId");
|
|
42
|
-
if (userIds.length > 1) throw new
|
|
42
|
+
if (userIds.length > 1) throw new InsightsBadRequestError("Duplicate 'userId' query parameter.");
|
|
43
43
|
const userId = userIds[0];
|
|
44
|
-
if (userId !== void 0 && (userId.length === 0 || userId.length > MAX_USER_ID_LENGTH)) throw new
|
|
44
|
+
if (userId !== void 0 && (userId.length === 0 || userId.length > MAX_USER_ID_LENGTH)) throw new InsightsBadRequestError("Invalid 'userId' query parameter.");
|
|
45
45
|
return userId === void 0 ? { window } : {
|
|
46
46
|
window,
|
|
47
47
|
userId
|
|
@@ -52,4 +52,4 @@ const parseSearchInput = (request) => ({
|
|
|
52
52
|
query: new URL(request.url).searchParams.get("query")?.trim() ?? ""
|
|
53
53
|
});
|
|
54
54
|
//#endregion
|
|
55
|
-
export { parseActiveInstallationInput,
|
|
55
|
+
export { parseActiveInstallationInput, parseInsightsQuery, parsePagination, parseSearchInput };
|