@hot-updater/firebase 1.0.0-rc.1 → 1.0.0-rc.14
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/firebase/functions/index.cjs +990 -693
- package/dist/firebase/public/firestore.indexes.json +46 -33
- package/dist/firebase/public/functions/_package.json +2 -2
- package/dist/iac/index.cjs +318 -183
- package/dist/iac/index.d.cts +1 -5
- package/dist/iac/index.d.mts +1 -5
- package/dist/iac/index.mjs +314 -179
- package/dist/index.cjs +121 -49
- package/dist/index.d.cts +2 -4
- package/dist/index.d.mts +1 -3
- package/dist/index.mjs +124 -52
- package/package.json +9 -9
|
@@ -6,7 +6,7 @@ let firebase_admin_firestore = require("firebase-admin/firestore");
|
|
|
6
6
|
let firebase_admin_storage = require("firebase-admin/storage");
|
|
7
7
|
//#endregion
|
|
8
8
|
//#region ../../packages/server/dist/version.mjs
|
|
9
|
-
const HOT_UPDATER_SERVER_VERSION = "1.0.0-rc.
|
|
9
|
+
const HOT_UPDATER_SERVER_VERSION = "1.0.0-rc.14";
|
|
10
10
|
//#endregion
|
|
11
11
|
//#region ../../packages/server/dist/handlerVersionRoutes.mjs
|
|
12
12
|
const createVersionRouteHandlers = () => ({ version: async () => Response.json({
|
|
@@ -339,6 +339,7 @@ const resolveManifestAssetStorageUri = ({ assetBaseStorageUri, assetPath, downlo
|
|
|
339
339
|
//#endregion
|
|
340
340
|
//#region ../plugin-core/dist/databasePluginCrudValidationErrors.mjs
|
|
341
341
|
var DatabasePluginInputError = class extends Error {
|
|
342
|
+
code;
|
|
342
343
|
name = "DatabasePluginInputError";
|
|
343
344
|
constructor(code) {
|
|
344
345
|
super(`Invalid database plugin input: ${code}`);
|
|
@@ -381,6 +382,7 @@ const isDatabaseJsonValue = (value) => {
|
|
|
381
382
|
};
|
|
382
383
|
const isDatabaseJsonObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && isDatabaseJsonValue(value);
|
|
383
384
|
const isDatabaseMetadataObject = (value) => isDatabaseJsonObject(value) && (!Object.hasOwn(value, "app_version") || typeof value["app_version"] === "string");
|
|
385
|
+
const isDatabaseBundleEventMetadata = (value) => isDatabaseJsonObject(value) && (value.username === null || typeof value.username === "string") && 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");
|
|
384
386
|
//#endregion
|
|
385
387
|
//#region ../../packages/core/dist/index.mjs
|
|
386
388
|
const stripBundleArtifactMetadata = (metadata) => metadata;
|
|
@@ -493,7 +495,7 @@ function getRolledOutNumericCohorts(bundleId, rolloutCohortCount) {
|
|
|
493
495
|
return Array.from({ length: normalizedRolloutCount }, (_, position) => positiveMod(multiplier * position + offset, NUMERIC_COHORT_SIZE)).map((zeroBasedCohort) => zeroBasedCohort + 1).sort((left, right) => left - right);
|
|
494
496
|
}
|
|
495
497
|
const RELEASE_CATALOG_FALLBACK_POLICY = "BUILTIN_IF_ACTIVE_INELIGIBLE";
|
|
496
|
-
const MAX_COMPILED_CATALOG_BYTES =
|
|
498
|
+
const MAX_COMPILED_CATALOG_BYTES = 262144;
|
|
497
499
|
const BASE64URL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
498
500
|
const MAX_CATALOG_SEGMENT_LENGTH = 255;
|
|
499
501
|
function assertCatalogSegment(value, name) {
|
|
@@ -600,7 +602,7 @@ const isUUIDv7 = (value) => typeof value === "string" && UUID_V7_PATTERN.test(va
|
|
|
600
602
|
//#endregion
|
|
601
603
|
//#region ../plugin-core/dist/uuidv7.mjs
|
|
602
604
|
function createUUIDv7FromTimestampHex(timestampHex) {
|
|
603
|
-
const randomBytes = new Uint8Array(10);
|
|
605
|
+
const randomBytes = /* @__PURE__ */ new Uint8Array(10);
|
|
604
606
|
crypto.getRandomValues(randomBytes);
|
|
605
607
|
const randomHex = Array.from(randomBytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
606
608
|
const randA = randomHex.slice(0, 3);
|
|
@@ -683,7 +685,6 @@ const databaseFields = {
|
|
|
683
685
|
"type",
|
|
684
686
|
"install_id",
|
|
685
687
|
"user_id",
|
|
686
|
-
"username",
|
|
687
688
|
"from_bundle_id",
|
|
688
689
|
"from_release_id",
|
|
689
690
|
"to_release_id",
|
|
@@ -691,10 +692,7 @@ const databaseFields = {
|
|
|
691
692
|
"platform",
|
|
692
693
|
"app_version",
|
|
693
694
|
"channel",
|
|
694
|
-
"
|
|
695
|
-
"update_strategy",
|
|
696
|
-
"fingerprint_hash",
|
|
697
|
-
"sdk_version",
|
|
695
|
+
"metadata",
|
|
698
696
|
"received_at_ms"
|
|
699
697
|
],
|
|
700
698
|
api_keys: [
|
|
@@ -709,7 +707,7 @@ const databaseFields = {
|
|
|
709
707
|
};
|
|
710
708
|
//#endregion
|
|
711
709
|
//#region ../plugin-core/dist/databasePluginCrudValidationFields.mjs
|
|
712
|
-
const isRecord$
|
|
710
|
+
const isRecord$3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
713
711
|
const isChannelText = (value) => {
|
|
714
712
|
if (typeof value !== "string" || value.length === 0) return false;
|
|
715
713
|
let codePointCount = 0;
|
|
@@ -719,6 +717,8 @@ const isChannelText = (value) => {
|
|
|
719
717
|
}
|
|
720
718
|
return true;
|
|
721
719
|
};
|
|
720
|
+
const isInsightsIdentityText = (value) => typeof value === "string" && value.length > 0 && value.length <= 255;
|
|
721
|
+
const isNullableInsightsIdentityText = (value) => value === null || isInsightsIdentityText(value);
|
|
722
722
|
const modelValidators = {
|
|
723
723
|
bundles: {
|
|
724
724
|
id: (value) => typeof value === "string",
|
|
@@ -793,10 +793,9 @@ const modelValidators = {
|
|
|
793
793
|
},
|
|
794
794
|
bundle_events: {
|
|
795
795
|
id: (value) => typeof value === "string",
|
|
796
|
-
type: (value) => value === "
|
|
797
|
-
install_id:
|
|
798
|
-
user_id:
|
|
799
|
-
username: (value) => value === null || typeof value === "string",
|
|
796
|
+
type: (value) => value === "UPDATE_DOWNLOADED" || value === "UPDATE_APPLIED" || value === "RECOVERED" || value === "UNCHANGED",
|
|
797
|
+
install_id: isInsightsIdentityText,
|
|
798
|
+
user_id: isNullableInsightsIdentityText,
|
|
800
799
|
from_bundle_id: (value) => value === null || typeof value === "string",
|
|
801
800
|
from_release_id: (value) => value === null || typeof value === "string",
|
|
802
801
|
to_release_id: (value) => value === null || typeof value === "string",
|
|
@@ -804,10 +803,7 @@ const modelValidators = {
|
|
|
804
803
|
platform: (value) => value === "ios" || value === "android",
|
|
805
804
|
app_version: (value) => typeof value === "string",
|
|
806
805
|
channel: (value) => typeof value === "string",
|
|
807
|
-
|
|
808
|
-
update_strategy: (value) => value === null || value === "fingerprint" || value === "appVersion",
|
|
809
|
-
fingerprint_hash: (value) => value === null || typeof value === "string",
|
|
810
|
-
sdk_version: (value) => value === null || typeof value === "string",
|
|
806
|
+
metadata: isDatabaseBundleEventMetadata,
|
|
811
807
|
received_at_ms: (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0
|
|
812
808
|
},
|
|
813
809
|
api_keys: {
|
|
@@ -820,7 +816,7 @@ const modelValidators = {
|
|
|
820
816
|
revoked_at_ms: (value) => value === null || typeof value === "number" && Number.isSafeInteger(value) && value >= 0
|
|
821
817
|
}
|
|
822
818
|
};
|
|
823
|
-
const stringFields = new Set([
|
|
819
|
+
const stringFields = /* @__PURE__ */ new Set([
|
|
824
820
|
"id",
|
|
825
821
|
"platform",
|
|
826
822
|
"file_hash",
|
|
@@ -861,7 +857,7 @@ const stringFields = new Set([
|
|
|
861
857
|
"prefix",
|
|
862
858
|
"role"
|
|
863
859
|
]);
|
|
864
|
-
const numberFields = new Set([
|
|
860
|
+
const numberFields = /* @__PURE__ */ new Set([
|
|
865
861
|
"archive_byte_size",
|
|
866
862
|
"byte_size",
|
|
867
863
|
"rollout_cohort_count",
|
|
@@ -874,13 +870,13 @@ const numberFields = new Set([
|
|
|
874
870
|
"created_at_ms",
|
|
875
871
|
"revoked_at_ms"
|
|
876
872
|
]);
|
|
877
|
-
const booleanFields = new Set([
|
|
873
|
+
const booleanFields = /* @__PURE__ */ new Set([
|
|
878
874
|
"should_force_update",
|
|
879
875
|
"enabled",
|
|
880
876
|
"is_tombstone"
|
|
881
877
|
]);
|
|
882
878
|
const sortableFields = {
|
|
883
|
-
bundles: new Set([
|
|
879
|
+
bundles: /* @__PURE__ */ new Set([
|
|
884
880
|
"id",
|
|
885
881
|
"platform",
|
|
886
882
|
"file_hash",
|
|
@@ -897,7 +893,7 @@ const sortableFields = {
|
|
|
897
893
|
"manifest_file_hash",
|
|
898
894
|
"asset_base_storage_uri"
|
|
899
895
|
]),
|
|
900
|
-
bundle_patches: new Set([
|
|
896
|
+
bundle_patches: /* @__PURE__ */ new Set([
|
|
901
897
|
"id",
|
|
902
898
|
"bundle_id",
|
|
903
899
|
"base_bundle_id",
|
|
@@ -907,7 +903,7 @@ const sortableFields = {
|
|
|
907
903
|
"byte_size",
|
|
908
904
|
"order_index"
|
|
909
905
|
]),
|
|
910
|
-
releases: new Set([
|
|
906
|
+
releases: /* @__PURE__ */ new Set([
|
|
911
907
|
"id",
|
|
912
908
|
"revision",
|
|
913
909
|
"scope_key",
|
|
@@ -927,7 +923,7 @@ const sortableFields = {
|
|
|
927
923
|
"created_at_ms",
|
|
928
924
|
"updated_at_ms"
|
|
929
925
|
]),
|
|
930
|
-
release_catalogs: new Set([
|
|
926
|
+
release_catalogs: /* @__PURE__ */ new Set([
|
|
931
927
|
"scope_key",
|
|
932
928
|
"catalog_id",
|
|
933
929
|
"strategy",
|
|
@@ -941,8 +937,8 @@ const sortableFields = {
|
|
|
941
937
|
"is_tombstone",
|
|
942
938
|
"updated_at_ms"
|
|
943
939
|
]),
|
|
944
|
-
channels: new Set(["id", "name"]),
|
|
945
|
-
bundle_events: new Set([
|
|
940
|
+
channels: /* @__PURE__ */ new Set(["id", "name"]),
|
|
941
|
+
bundle_events: /* @__PURE__ */ new Set([
|
|
946
942
|
"id",
|
|
947
943
|
"type",
|
|
948
944
|
"install_id",
|
|
@@ -952,6 +948,8 @@ const sortableFields = {
|
|
|
952
948
|
"from_release_id",
|
|
953
949
|
"to_release_id",
|
|
954
950
|
"to_bundle_id",
|
|
951
|
+
"pending_bundle_id",
|
|
952
|
+
"pending_release_id",
|
|
955
953
|
"platform",
|
|
956
954
|
"app_version",
|
|
957
955
|
"channel",
|
|
@@ -961,7 +959,7 @@ const sortableFields = {
|
|
|
961
959
|
"sdk_version",
|
|
962
960
|
"received_at_ms"
|
|
963
961
|
]),
|
|
964
|
-
api_keys: new Set([
|
|
962
|
+
api_keys: /* @__PURE__ */ new Set([
|
|
965
963
|
"id",
|
|
966
964
|
"hash",
|
|
967
965
|
"name",
|
|
@@ -993,9 +991,9 @@ const hasValidReleaseInvariants = (data) => {
|
|
|
993
991
|
const fingerprintHash = data.fingerprint_hash;
|
|
994
992
|
return (kind === "BUNDLE" && typeof bundleId === "string" || kind === "EMBEDDED" && bundleId === null) && (strategy === "APP_VERSION" && typeof targetAppVersion === "string" && fingerprintHash === null || strategy === "FINGERPRINT" && targetAppVersion === null && typeof fingerprintHash === "string");
|
|
995
993
|
};
|
|
996
|
-
const hasValidBundleEventInvariants = (data) => (data.type === "
|
|
994
|
+
const hasValidBundleEventInvariants = (data) => (data.type === "UPDATE_DOWNLOADED" || data.type === "UPDATE_APPLIED" || data.type === "RECOVERED") && typeof data.from_bundle_id === "string" && ((isRecord$3(data.metadata) ? data.metadata.update_strategy : void 0) === "fingerprint" || (isRecord$3(data.metadata) ? data.metadata.update_strategy : void 0) === "appVersion") || data.type === "UNCHANGED" && data.from_bundle_id === null && (isRecord$3(data.metadata) ? data.metadata.update_strategy : void 0) === null;
|
|
997
995
|
const validateCreateData = (model, data) => {
|
|
998
|
-
if (!isRecord$
|
|
996
|
+
if (!isRecord$3(data)) throw new DatabasePluginInputError("invalid-data");
|
|
999
997
|
validateFields(model, Object.keys(data));
|
|
1000
998
|
for (const field of databaseFields[model]) {
|
|
1001
999
|
const validator = modelValidators[model][field];
|
|
@@ -1012,7 +1010,7 @@ const selectRow = (row, input) => {
|
|
|
1012
1010
|
return Object.fromEntries(select.map((field) => [field, Reflect.get(row, field)]));
|
|
1013
1011
|
};
|
|
1014
1012
|
const validateResult = (model, row, select) => {
|
|
1015
|
-
if (!isRecord$
|
|
1013
|
+
if (!isRecord$3(row)) throw new DatabasePluginInputError("invalid-result");
|
|
1016
1014
|
const fields = select ?? databaseFields[model];
|
|
1017
1015
|
for (const field of fields) {
|
|
1018
1016
|
const validator = modelValidators[model][field];
|
|
@@ -1029,10 +1027,148 @@ const validateResult = (model, row, select) => {
|
|
|
1029
1027
|
if (model === "bundle_events" && [
|
|
1030
1028
|
"type",
|
|
1031
1029
|
"from_bundle_id",
|
|
1032
|
-
"
|
|
1030
|
+
"metadata"
|
|
1033
1031
|
].every((field) => Object.hasOwn(row, field)) && !hasValidBundleEventInvariants(row)) throw new DatabasePluginInputError("invalid-result");
|
|
1034
1032
|
};
|
|
1035
1033
|
//#endregion
|
|
1034
|
+
//#region ../plugin-core/dist/insightsContract.mjs
|
|
1035
|
+
const encoder = new TextEncoder();
|
|
1036
|
+
/** Exact UTF-8 byte ordering, without case folding or Unicode normalization. */
|
|
1037
|
+
const compareInsightsText = (left, right) => {
|
|
1038
|
+
const a = encoder.encode(left);
|
|
1039
|
+
const b = encoder.encode(right);
|
|
1040
|
+
for (let index = 0; index < Math.min(a.length, b.length); index += 1) if (a[index] !== b[index]) return a[index] - b[index];
|
|
1041
|
+
return a.length - b.length;
|
|
1042
|
+
};
|
|
1043
|
+
const isWellFormedText = (value) => {
|
|
1044
|
+
for (const character of value) {
|
|
1045
|
+
const point = character.codePointAt(0);
|
|
1046
|
+
if (point >= 55296 && point <= 57343) return false;
|
|
1047
|
+
}
|
|
1048
|
+
return true;
|
|
1049
|
+
};
|
|
1050
|
+
const isIdentity = (value) => typeof value === "string" && value.length > 0 && value.length <= 255 && isWellFormedText(value);
|
|
1051
|
+
const isText = (value) => typeof value === "string" && value.length > 0 && isWellFormedText(value);
|
|
1052
|
+
const isTimestamp = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
1053
|
+
const isLimit = (value) => isTimestamp(value) && value >= 1 && value <= 101;
|
|
1054
|
+
const hasOnlyKeys$1 = (value, keys) => Object.keys(value).every((key) => keys.includes(key));
|
|
1055
|
+
const hasScope = (value) => (value.platform === "ios" || value.platform === "android") && isText(value.channel);
|
|
1056
|
+
const isBundleFilter = (value, withKind = false) => {
|
|
1057
|
+
if (!isRecord$3(value) || !hasScope(value)) return false;
|
|
1058
|
+
const keys = [
|
|
1059
|
+
"platform",
|
|
1060
|
+
"channel",
|
|
1061
|
+
"type",
|
|
1062
|
+
...withKind ? ["kind"] : []
|
|
1063
|
+
];
|
|
1064
|
+
return value.type === "RECOVERED" ? isText(value.fromBundleId) && hasOnlyKeys$1(value, [...keys, "fromBundleId"]) : (value.type === "UPDATE_DOWNLOADED" || value.type === "UPDATE_APPLIED" || value.type === "UNCHANGED") && isText(value.toBundleId) && hasOnlyKeys$1(value, [...keys, "toBundleId"]);
|
|
1065
|
+
};
|
|
1066
|
+
const isEventFilter = (value) => {
|
|
1067
|
+
if (!isRecord$3(value)) return false;
|
|
1068
|
+
if (value.kind === "all") return hasOnlyKeys$1(value, ["kind"]);
|
|
1069
|
+
if (value.kind === "installationMovement") return isIdentity(value.installId) && hasOnlyKeys$1(value, ["kind", "installId"]);
|
|
1070
|
+
return value.kind === "bundle" && isBundleFilter(value, true);
|
|
1071
|
+
};
|
|
1072
|
+
const validateRow = (model, row, result = false) => {
|
|
1073
|
+
try {
|
|
1074
|
+
validateCreateData(model, row);
|
|
1075
|
+
if (!isRecord$3(row) || typeof row.id !== "string" || !isUUIDv7(row.id) || [...Object.values(row), ...isRecord$3(row.metadata) ? Object.values(row.metadata) : []].some((value) => typeof value === "string" && !isWellFormedText(value))) throw new DatabasePluginInputError("invalid-data");
|
|
1076
|
+
} catch (error) {
|
|
1077
|
+
if (result) throw new DatabasePluginInputError("invalid-result");
|
|
1078
|
+
throw error;
|
|
1079
|
+
}
|
|
1080
|
+
};
|
|
1081
|
+
/** Downloads and applied transitions belong in installation history. */
|
|
1082
|
+
const isInsightsMovementEvent = (event) => event.type === "UPDATE_DOWNLOADED" || event.type === "UPDATE_APPLIED" || event.type === "RECOVERED";
|
|
1083
|
+
const matchesInsightsEventFilter = (event, filter) => {
|
|
1084
|
+
if (filter.kind === "all") return true;
|
|
1085
|
+
if (filter.kind === "installationMovement") return event.install_id === filter.installId && isInsightsMovementEvent(event);
|
|
1086
|
+
return event.type === filter.type && event.platform === filter.platform && event.channel === filter.channel && (filter.type === "RECOVERED" ? event.from_bundle_id === filter.fromBundleId : event.to_bundle_id === filter.toBundleId);
|
|
1087
|
+
};
|
|
1088
|
+
const invalidQuery = () => {
|
|
1089
|
+
throw new DatabasePluginInputError("invalid-query");
|
|
1090
|
+
};
|
|
1091
|
+
const invalidResult = () => {
|
|
1092
|
+
throw new DatabasePluginInputError("invalid-result");
|
|
1093
|
+
};
|
|
1094
|
+
const validateCount = (count) => isTimestamp(count) ? count : invalidResult();
|
|
1095
|
+
/** Validate custom and bundled providers at the same public boundary. */
|
|
1096
|
+
const createValidatedInsightsModel = (model) => ({
|
|
1097
|
+
async recordEvent(input) {
|
|
1098
|
+
if (!isRecord$3(input) || !hasOnlyKeys$1(input, ["event"])) throw new DatabasePluginInputError("invalid-data");
|
|
1099
|
+
validateRow("bundle_events", input.event);
|
|
1100
|
+
await model.recordEvent(input);
|
|
1101
|
+
},
|
|
1102
|
+
async listEvents(input) {
|
|
1103
|
+
if (!isRecord$3(input) || !hasOnlyKeys$1(input, [
|
|
1104
|
+
"filter",
|
|
1105
|
+
"sinceMs",
|
|
1106
|
+
"beforeReceivedAtMs",
|
|
1107
|
+
"after",
|
|
1108
|
+
"limit"
|
|
1109
|
+
]) || !isEventFilter(input.filter) || !isLimit(input.limit) || !isTimestamp(input.beforeReceivedAtMs) || input.sinceMs !== void 0 && !isTimestamp(input.sinceMs)) invalidQuery();
|
|
1110
|
+
const sinceMs = input.sinceMs ?? 0;
|
|
1111
|
+
if (sinceMs > input.beforeReceivedAtMs || input.after !== void 0 && (!isRecord$3(input.after) || !hasOnlyKeys$1(input.after, ["receivedAtMs", "id"]) || !isTimestamp(input.after.receivedAtMs) || typeof input.after.id !== "string" || !isUUIDv7(input.after.id) || input.after.receivedAtMs < sinceMs || input.after.receivedAtMs >= input.beforeReceivedAtMs)) invalidQuery();
|
|
1112
|
+
const rows = await model.listEvents(input);
|
|
1113
|
+
if (!Array.isArray(rows) || rows.length > input.limit) invalidResult();
|
|
1114
|
+
let previous = input.after;
|
|
1115
|
+
for (const row of rows) {
|
|
1116
|
+
validateRow("bundle_events", row, true);
|
|
1117
|
+
if (row.received_at_ms < sinceMs || row.received_at_ms >= input.beforeReceivedAtMs || !matchesInsightsEventFilter(row, input.filter) || previous !== void 0 && (row.received_at_ms > previous.receivedAtMs || row.received_at_ms === previous.receivedAtMs && row.id >= previous.id)) invalidResult();
|
|
1118
|
+
previous = {
|
|
1119
|
+
receivedAtMs: row.received_at_ms,
|
|
1120
|
+
id: row.id
|
|
1121
|
+
};
|
|
1122
|
+
}
|
|
1123
|
+
return rows;
|
|
1124
|
+
},
|
|
1125
|
+
async findLatestEvents(input) {
|
|
1126
|
+
if (!isRecord$3(input)) invalidQuery();
|
|
1127
|
+
if ("installId" in input) {
|
|
1128
|
+
if (!hasOnlyKeys$1(input, ["installId"]) || !isIdentity(input.installId)) invalidQuery();
|
|
1129
|
+
} else if (!hasOnlyKeys$1(input, [
|
|
1130
|
+
"userId",
|
|
1131
|
+
"afterInstallId",
|
|
1132
|
+
"limit"
|
|
1133
|
+
]) || !isIdentity(input.userId) || !isLimit(input.limit) || input.afterInstallId !== void 0 && !isIdentity(input.afterInstallId)) invalidQuery();
|
|
1134
|
+
const rows = await model.findLatestEvents(input);
|
|
1135
|
+
if (!Array.isArray(rows) || rows.length > ("installId" in input ? 1 : input.limit)) invalidResult();
|
|
1136
|
+
let previous = "installId" in input ? void 0 : input.afterInstallId;
|
|
1137
|
+
for (const row of rows) {
|
|
1138
|
+
validateRow("bundle_events", row, true);
|
|
1139
|
+
if ("installId" in input ? row.install_id !== input.installId : row.user_id !== input.userId || previous !== void 0 && compareInsightsText(row.install_id, previous) <= 0) invalidResult();
|
|
1140
|
+
previous = row.install_id;
|
|
1141
|
+
}
|
|
1142
|
+
return rows;
|
|
1143
|
+
},
|
|
1144
|
+
async countLatestEvents(input) {
|
|
1145
|
+
if (!isRecord$3(input) || !hasOnlyKeys$1(input, [
|
|
1146
|
+
"platform",
|
|
1147
|
+
"channel",
|
|
1148
|
+
"sinceMs",
|
|
1149
|
+
"bundle"
|
|
1150
|
+
]) || !hasScope(input) || !isTimestamp(input.sinceMs) || input.bundle !== void 0 && (!Array.isArray(input.bundle) || input.bundle.length < 1 || input.bundle.length > 2 || input.bundle.some((bundle) => !isRecord$3(bundle) || !hasOnlyKeys$1(bundle, [
|
|
1151
|
+
"field",
|
|
1152
|
+
"value",
|
|
1153
|
+
"types"
|
|
1154
|
+
]) || bundle.field !== "from_bundle_id" && bundle.field !== "to_bundle_id" || !isText(bundle.value) || !Array.isArray(bundle.types) || bundle.types.length === 0 || bundle.types.some((type) => ![
|
|
1155
|
+
"UNCHANGED",
|
|
1156
|
+
"UPDATE_DOWNLOADED",
|
|
1157
|
+
"UPDATE_APPLIED",
|
|
1158
|
+
"RECOVERED"
|
|
1159
|
+
].includes(type))))) invalidQuery();
|
|
1160
|
+
return validateCount(await model.countLatestEvents(input));
|
|
1161
|
+
},
|
|
1162
|
+
async countEvents(input) {
|
|
1163
|
+
if (!isRecord$3(input) || !hasOnlyKeys$1(input, [
|
|
1164
|
+
"filter",
|
|
1165
|
+
"sinceMs",
|
|
1166
|
+
"beforeReceivedAtMs"
|
|
1167
|
+
]) || !isBundleFilter(input.filter) || !isTimestamp(input.sinceMs) || !isTimestamp(input.beforeReceivedAtMs) || input.sinceMs > input.beforeReceivedAtMs) invalidQuery();
|
|
1168
|
+
return validateCount(await model.countEvents(input));
|
|
1169
|
+
}
|
|
1170
|
+
});
|
|
1171
|
+
//#endregion
|
|
1036
1172
|
//#region ../plugin-core/dist/databasePluginCrudValidationMutations.mjs
|
|
1037
1173
|
const validateMutationWhere = (where) => {
|
|
1038
1174
|
if (where.length === 0) throw new DatabasePluginInputError("empty-mutation-where");
|
|
@@ -1040,10 +1176,10 @@ const validateMutationWhere = (where) => {
|
|
|
1040
1176
|
const validateUpdateWhere = (model, where) => {
|
|
1041
1177
|
const selector = where[0];
|
|
1042
1178
|
const primaryField = model === "release_catalogs" ? "scope_key" : "id";
|
|
1043
|
-
if (where.length !== 1 || !isRecord$
|
|
1179
|
+
if (where.length !== 1 || !isRecord$3(selector) || selector.field !== primaryField || selector.operator !== void 0 && selector.operator !== "eq" || typeof selector.value !== "string" || selector.connector !== void 0 || selector.mode !== void 0) throw new DatabasePluginInputError("invalid-update-selector");
|
|
1044
1180
|
};
|
|
1045
1181
|
const validateBundleUpdateData = (update) => {
|
|
1046
|
-
if (!isRecord$
|
|
1182
|
+
if (!isRecord$3(update)) throw new DatabasePluginInputError("invalid-data");
|
|
1047
1183
|
for (const [field, value] of Object.entries(update)) {
|
|
1048
1184
|
if (field === "id") throw new DatabasePluginInputError("invalid-data");
|
|
1049
1185
|
validateField("bundles", field);
|
|
@@ -1052,9 +1188,9 @@ const validateBundleUpdateData = (update) => {
|
|
|
1052
1188
|
}
|
|
1053
1189
|
};
|
|
1054
1190
|
const validateApiKeyUpdateData = (update) => {
|
|
1055
|
-
if (!isRecord$
|
|
1191
|
+
if (!isRecord$3(update) || Reflect.ownKeys(update).length !== 1 || !Object.hasOwn(update, "revoked_at_ms") || !modelValidators.api_keys.revoked_at_ms(Reflect.get(update, "revoked_at_ms"))) throw new DatabasePluginInputError("invalid-data");
|
|
1056
1192
|
};
|
|
1057
|
-
const RELEASE_MUTABLE_FIELDS = new Set([
|
|
1193
|
+
const RELEASE_MUTABLE_FIELDS = /* @__PURE__ */ new Set([
|
|
1058
1194
|
"revision",
|
|
1059
1195
|
"scope_key",
|
|
1060
1196
|
"target_app_version",
|
|
@@ -1067,7 +1203,7 @@ const RELEASE_MUTABLE_FIELDS = new Set([
|
|
|
1067
1203
|
"updated_at_ms"
|
|
1068
1204
|
]);
|
|
1069
1205
|
const validateReleaseUpdateData = (update) => {
|
|
1070
|
-
if (!isRecord$
|
|
1206
|
+
if (!isRecord$3(update) || Reflect.ownKeys(update).length === 0) throw new DatabasePluginInputError("invalid-data");
|
|
1071
1207
|
for (const [field, value] of Object.entries(update)) {
|
|
1072
1208
|
if (!RELEASE_MUTABLE_FIELDS.has(field)) throw new DatabasePluginInputError("invalid-data");
|
|
1073
1209
|
const validator = modelValidators.releases[field];
|
|
@@ -1075,7 +1211,7 @@ const validateReleaseUpdateData = (update) => {
|
|
|
1075
1211
|
}
|
|
1076
1212
|
};
|
|
1077
1213
|
const validateReleaseCatalogUpdateData = (update) => {
|
|
1078
|
-
if (!isRecord$
|
|
1214
|
+
if (!isRecord$3(update)) throw new DatabasePluginInputError("invalid-data");
|
|
1079
1215
|
const expectedFields = Object.keys(modelValidators.release_catalogs).filter((field) => field !== "scope_key");
|
|
1080
1216
|
if (Reflect.ownKeys(update).length !== expectedFields.length || expectedFields.some((field) => !Object.hasOwn(update, field))) throw new DatabasePluginInputError("invalid-data");
|
|
1081
1217
|
for (const [field, value] of Object.entries(update)) {
|
|
@@ -1155,7 +1291,7 @@ const validateWhere$1 = (model, where) => {
|
|
|
1155
1291
|
if (where === void 0) return;
|
|
1156
1292
|
if (!Array.isArray(where)) throw new DatabasePluginInputError("invalid-query");
|
|
1157
1293
|
for (const item of where) {
|
|
1158
|
-
if (!isRecord$
|
|
1294
|
+
if (!isRecord$3(item)) throw new DatabasePluginInputError("invalid-query");
|
|
1159
1295
|
if (item.connector !== void 0 && item.connector !== "AND" && item.connector !== "OR") throw new DatabasePluginInputError("invalid-query");
|
|
1160
1296
|
validateWhereValue(model, item);
|
|
1161
1297
|
}
|
|
@@ -1172,7 +1308,7 @@ const validateOrderBy = (model, orderBy) => {
|
|
|
1172
1308
|
if (!Array.isArray(orderBy) || orderBy.length === 0) throw new DatabasePluginInputError("invalid-query");
|
|
1173
1309
|
const fields = /* @__PURE__ */ new Set();
|
|
1174
1310
|
return orderBy.map((clause) => {
|
|
1175
|
-
if (!isRecord$
|
|
1311
|
+
if (!isRecord$3(clause) || typeof clause.field !== "string") throw new DatabasePluginInputError("invalid-query");
|
|
1176
1312
|
validateField(model, clause.field);
|
|
1177
1313
|
if (!sortableFields[model].has(clause.field)) throw new DatabasePluginInputError("invalid-query");
|
|
1178
1314
|
if (clause.direction !== "asc" && clause.direction !== "desc") throw new DatabasePluginInputError("invalid-query");
|
|
@@ -1184,7 +1320,7 @@ const validateOrderBy = (model, orderBy) => {
|
|
|
1184
1320
|
};
|
|
1185
1321
|
const validateDistinctOn = (model, distinctOn, orderBy) => {
|
|
1186
1322
|
if (distinctOn === void 0) return;
|
|
1187
|
-
if (!isRecord$
|
|
1323
|
+
if (!isRecord$3(distinctOn)) throw new DatabasePluginInputError("invalid-distinct");
|
|
1188
1324
|
const fields = validateDistinctFields(model, distinctOn.fields);
|
|
1189
1325
|
if (fields === void 0 || orderBy === void 0) throw new DatabasePluginInputError("invalid-distinct");
|
|
1190
1326
|
for (const [index, field] of fields.entries()) if (orderBy[index]?.field !== field) throw new DatabasePluginInputError("invalid-distinct");
|
|
@@ -1198,7 +1334,7 @@ const validateBundlePagination = (options) => {
|
|
|
1198
1334
|
if (!Number.isSafeInteger(options.limit) || options.limit <= 0 || options.page !== void 0 && (!Number.isSafeInteger(options.page) || options.page <= 0) || options.page !== void 0 && options.cursor !== void 0) throw new DatabasePluginInputError("invalid-pagination");
|
|
1199
1335
|
const cursor = options.cursor;
|
|
1200
1336
|
if (cursor === void 0) return;
|
|
1201
|
-
if (!isRecord$
|
|
1337
|
+
if (!isRecord$3(cursor)) throw new DatabasePluginInputError("invalid-pagination");
|
|
1202
1338
|
const hasAfter = Object.hasOwn(cursor, "after");
|
|
1203
1339
|
if (hasAfter === Object.hasOwn(cursor, "before")) throw new DatabasePluginInputError("invalid-pagination");
|
|
1204
1340
|
const value = hasAfter ? cursor.after : cursor.before;
|
|
@@ -1293,7 +1429,91 @@ const createTransactionDatabasePlugin = (implementation) => {
|
|
|
1293
1429
|
//#region ../plugin-core/dist/createDatabasePlugin.mjs
|
|
1294
1430
|
const PAGE_SIZE$1 = 100;
|
|
1295
1431
|
const compareChannelRows = (left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0;
|
|
1432
|
+
const toInsightsBundleWhere = (filter) => [
|
|
1433
|
+
{
|
|
1434
|
+
field: "platform",
|
|
1435
|
+
value: filter.platform
|
|
1436
|
+
},
|
|
1437
|
+
{
|
|
1438
|
+
field: "channel",
|
|
1439
|
+
value: filter.channel
|
|
1440
|
+
},
|
|
1441
|
+
{
|
|
1442
|
+
field: "type",
|
|
1443
|
+
value: filter.type
|
|
1444
|
+
},
|
|
1445
|
+
filter.type === "RECOVERED" ? {
|
|
1446
|
+
field: "from_bundle_id",
|
|
1447
|
+
value: filter.fromBundleId
|
|
1448
|
+
} : {
|
|
1449
|
+
field: "to_bundle_id",
|
|
1450
|
+
value: filter.toBundleId
|
|
1451
|
+
}
|
|
1452
|
+
];
|
|
1453
|
+
const toInsightsEventRanges = (filter) => {
|
|
1454
|
+
if (filter.kind === "all") return [[]];
|
|
1455
|
+
if (filter.kind === "bundle") return [toInsightsBundleWhere(filter)];
|
|
1456
|
+
return [
|
|
1457
|
+
"UPDATE_DOWNLOADED",
|
|
1458
|
+
"UPDATE_APPLIED",
|
|
1459
|
+
"RECOVERED"
|
|
1460
|
+
].map((type) => [{
|
|
1461
|
+
field: "install_id",
|
|
1462
|
+
value: filter.installId
|
|
1463
|
+
}, {
|
|
1464
|
+
field: "type",
|
|
1465
|
+
value: type
|
|
1466
|
+
}]);
|
|
1467
|
+
};
|
|
1468
|
+
const listInsightsEventRange = async (crud, input, filterWhere) => {
|
|
1469
|
+
const where = [...filterWhere, {
|
|
1470
|
+
field: "received_at_ms",
|
|
1471
|
+
operator: "gte",
|
|
1472
|
+
value: input.sinceMs ?? 0
|
|
1473
|
+
}];
|
|
1474
|
+
const sameTimestamp = input.after === void 0 ? [] : await crud.findMany({
|
|
1475
|
+
model: "bundle_events",
|
|
1476
|
+
where: [
|
|
1477
|
+
...filterWhere,
|
|
1478
|
+
{
|
|
1479
|
+
field: "received_at_ms",
|
|
1480
|
+
value: input.after.receivedAtMs
|
|
1481
|
+
},
|
|
1482
|
+
{
|
|
1483
|
+
field: "id",
|
|
1484
|
+
operator: "lt",
|
|
1485
|
+
value: input.after.id
|
|
1486
|
+
}
|
|
1487
|
+
],
|
|
1488
|
+
orderBy: [{
|
|
1489
|
+
field: "id",
|
|
1490
|
+
direction: "desc"
|
|
1491
|
+
}],
|
|
1492
|
+
limit: input.limit,
|
|
1493
|
+
offset: 0
|
|
1494
|
+
});
|
|
1495
|
+
if (sameTimestamp.length === input.limit) return sameTimestamp;
|
|
1496
|
+
const older = await crud.findMany({
|
|
1497
|
+
model: "bundle_events",
|
|
1498
|
+
where: [...where, {
|
|
1499
|
+
field: "received_at_ms",
|
|
1500
|
+
operator: "lt",
|
|
1501
|
+
value: input.after?.receivedAtMs ?? input.beforeReceivedAtMs
|
|
1502
|
+
}],
|
|
1503
|
+
orderBy: [{
|
|
1504
|
+
field: "received_at_ms",
|
|
1505
|
+
direction: "desc"
|
|
1506
|
+
}, {
|
|
1507
|
+
field: "id",
|
|
1508
|
+
direction: "desc"
|
|
1509
|
+
}],
|
|
1510
|
+
limit: input.limit - sameTimestamp.length,
|
|
1511
|
+
offset: 0
|
|
1512
|
+
});
|
|
1513
|
+
return [...sameTimestamp, ...older];
|
|
1514
|
+
};
|
|
1296
1515
|
var DatabaseAtomicCommitUnsupportedError = class extends Error {
|
|
1516
|
+
pluginName;
|
|
1297
1517
|
name = "DatabaseAtomicCommitUnsupportedError";
|
|
1298
1518
|
constructor(pluginName) {
|
|
1299
1519
|
super(`Database plugin "${pluginName}" cannot atomically commit changes across models.`);
|
|
@@ -1314,6 +1534,7 @@ var DatabaseRowReferencedError = class extends Error {
|
|
|
1314
1534
|
}
|
|
1315
1535
|
};
|
|
1316
1536
|
var DatabaseCommitConflictError = class extends Error {
|
|
1537
|
+
result;
|
|
1317
1538
|
name = "DatabaseCommitConflictError";
|
|
1318
1539
|
constructor(result) {
|
|
1319
1540
|
super("Database commit precondition failed.");
|
|
@@ -1541,12 +1762,6 @@ const applyChange = async (database, change, changeIndex) => {
|
|
|
1541
1762
|
}
|
|
1542
1763
|
return;
|
|
1543
1764
|
}
|
|
1544
|
-
case "insights":
|
|
1545
|
-
await database.create({
|
|
1546
|
-
model: "bundle_events",
|
|
1547
|
-
data: change.row
|
|
1548
|
-
});
|
|
1549
|
-
return;
|
|
1550
1765
|
case "apiKeys": switch (change.operation) {
|
|
1551
1766
|
case "insert":
|
|
1552
1767
|
await database.create({
|
|
@@ -1618,10 +1833,10 @@ const hasOnlyKeys = (value, keys) => {
|
|
|
1618
1833
|
return Reflect.ownKeys(value).length === keys.length && keys.every((key) => Object.hasOwn(value, key));
|
|
1619
1834
|
};
|
|
1620
1835
|
const validateWhere = (where, field, validateValue = (value) => typeof value === "string") => {
|
|
1621
|
-
if (!isRecord$
|
|
1836
|
+
if (!isRecord$3(where) || !hasOnlyKeys(where, [field]) || !validateValue(Reflect.get(where, field))) throw new DatabasePluginInputError("invalid-data");
|
|
1622
1837
|
};
|
|
1623
1838
|
const validateDatabaseChange = (change) => {
|
|
1624
|
-
if (!isRecord$
|
|
1839
|
+
if (!isRecord$3(change)) throw new DatabasePluginInputError("invalid-data");
|
|
1625
1840
|
switch (change.model) {
|
|
1626
1841
|
case "bundles": switch (change.operation) {
|
|
1627
1842
|
case "insert":
|
|
@@ -1728,14 +1943,6 @@ const validateDatabaseChange = (change) => {
|
|
|
1728
1943
|
return;
|
|
1729
1944
|
default: throw new DatabasePluginInputError("invalid-operation");
|
|
1730
1945
|
}
|
|
1731
|
-
case "insights":
|
|
1732
|
-
if (change.operation !== "insert" || !hasOnlyKeys(change, [
|
|
1733
|
-
"model",
|
|
1734
|
-
"operation",
|
|
1735
|
-
"row"
|
|
1736
|
-
])) throw new DatabasePluginInputError("invalid-operation");
|
|
1737
|
-
validateCreateData("bundle_events", change.row);
|
|
1738
|
-
return;
|
|
1739
1946
|
case "apiKeys": switch (change.operation) {
|
|
1740
1947
|
case "insert":
|
|
1741
1948
|
if (!hasOnlyKeys(change, [
|
|
@@ -1754,7 +1961,7 @@ const validateDatabaseChange = (change) => {
|
|
|
1754
1961
|
"update"
|
|
1755
1962
|
])) throw new DatabasePluginInputError("invalid-data");
|
|
1756
1963
|
validateWhere(change.where, "id");
|
|
1757
|
-
if (!isRecord$
|
|
1964
|
+
if (!isRecord$3(change.update)) throw new DatabasePluginInputError("invalid-data");
|
|
1758
1965
|
if (!hasOnlyKeys(change.update, ["revokedAtMs"])) throw new DatabasePluginInputError("invalid-data");
|
|
1759
1966
|
validateApiKeyUpdateData({ revoked_at_ms: change.update.revokedAtMs });
|
|
1760
1967
|
return;
|
|
@@ -1764,7 +1971,7 @@ const validateDatabaseChange = (change) => {
|
|
|
1764
1971
|
}
|
|
1765
1972
|
};
|
|
1766
1973
|
const validateDatabaseCommitExpectation = (expectation) => {
|
|
1767
|
-
if (!isRecord$
|
|
1974
|
+
if (!isRecord$3(expectation)) throw new DatabasePluginInputError("invalid-data");
|
|
1768
1975
|
if (expectation.model === "releases") {
|
|
1769
1976
|
if (!hasOnlyKeys(expectation, [
|
|
1770
1977
|
"model",
|
|
@@ -1784,7 +1991,7 @@ const validateDatabaseCommitExpectation = (expectation) => {
|
|
|
1784
1991
|
throw new DatabasePluginInputError("invalid-model");
|
|
1785
1992
|
};
|
|
1786
1993
|
function validateDatabaseCommit(input) {
|
|
1787
|
-
if (!isRecord$
|
|
1994
|
+
if (!isRecord$3(input) || !Array.isArray(input.changes) || (Object.hasOwn(input, "expectations") ? !hasOnlyKeys(input, ["changes", "expectations"]) || !Array.isArray(input.expectations) : !hasOnlyKeys(input, ["changes"]))) throw new DatabasePluginInputError("invalid-data");
|
|
1788
1995
|
input.changes.forEach(validateDatabaseChange);
|
|
1789
1996
|
if (Array.isArray(input.expectations)) input.expectations.forEach(validateDatabaseCommitExpectation);
|
|
1790
1997
|
}
|
|
@@ -2004,36 +2211,31 @@ const createDatabasePluginAdapter = (name, implementation) => {
|
|
|
2004
2211
|
}
|
|
2005
2212
|
},
|
|
2006
2213
|
insights: {
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2214
|
+
recordEvent: (input) => implementation.recordInsights(input),
|
|
2215
|
+
async listEvents(input) {
|
|
2216
|
+
const ranges = await Promise.all(toInsightsEventRanges(input.filter).map((where) => listInsightsEventRange(crud, input, where)));
|
|
2217
|
+
if (ranges.length === 1) return ranges[0];
|
|
2218
|
+
return ranges.flat().sort((left, right) => right.received_at_ms - left.received_at_ms || compareInsightsText(right.id, left.id)).slice(0, input.limit);
|
|
2012
2219
|
},
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2220
|
+
findLatestEvents: (input) => implementation.findLatestInsightsEvents(input),
|
|
2221
|
+
countLatestEvents: (input) => implementation.countLatestInsightsEvents(input),
|
|
2222
|
+
countEvents(input) {
|
|
2223
|
+
return crud.count({
|
|
2224
|
+
model: "bundle_events",
|
|
2225
|
+
where: [
|
|
2226
|
+
...toInsightsBundleWhere(input.filter),
|
|
2227
|
+
{
|
|
2228
|
+
field: "received_at_ms",
|
|
2229
|
+
operator: "gte",
|
|
2230
|
+
value: input.sinceMs
|
|
2231
|
+
},
|
|
2232
|
+
{
|
|
2019
2233
|
field: "received_at_ms",
|
|
2020
2234
|
operator: "lt",
|
|
2021
2235
|
value: input.beforeReceivedAtMs
|
|
2022
|
-
}
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
direction: "asc"
|
|
2026
|
-
}, {
|
|
2027
|
-
field: "id",
|
|
2028
|
-
direction: "asc"
|
|
2029
|
-
}],
|
|
2030
|
-
limit: PAGE_SIZE$1,
|
|
2031
|
-
offset
|
|
2032
|
-
});
|
|
2033
|
-
rows.push(...page.filter((row) => input.after === void 0 || row.received_at_ms > input.after.receivedAtMs || row.received_at_ms === input.after.receivedAtMs && row.id > input.after.id));
|
|
2034
|
-
if (page.length < PAGE_SIZE$1) break;
|
|
2035
|
-
}
|
|
2036
|
-
return rows.slice(0, input.limit);
|
|
2236
|
+
}
|
|
2237
|
+
]
|
|
2238
|
+
});
|
|
2037
2239
|
}
|
|
2038
2240
|
},
|
|
2039
2241
|
apiKeys: {
|
|
@@ -2077,7 +2279,13 @@ const createDatabasePluginAdapter = (name, implementation) => {
|
|
|
2077
2279
|
...implementation.dispose ? { dispose: implementation.dispose } : {}
|
|
2078
2280
|
};
|
|
2079
2281
|
};
|
|
2080
|
-
const createDatabasePlugin = (options) => ({
|
|
2282
|
+
const createDatabasePlugin = (options) => ({
|
|
2283
|
+
...options,
|
|
2284
|
+
models: {
|
|
2285
|
+
...options.models,
|
|
2286
|
+
insights: createValidatedInsightsModel(options.models.insights)
|
|
2287
|
+
}
|
|
2288
|
+
});
|
|
2081
2289
|
//#endregion
|
|
2082
2290
|
//#region ../plugin-core/dist/createStoragePlugin.mjs
|
|
2083
2291
|
const createStoragePlugin = (options) => ({ ...options });
|
|
@@ -2212,6 +2420,8 @@ const rowsToBundles = (bundleRows, patchRows, referencedBundleRows) => {
|
|
|
2212
2420
|
//#endregion
|
|
2213
2421
|
//#region ../plugin-core/dist/databaseClientUpdates.mjs
|
|
2214
2422
|
var DatabasePatchUpdateUnsupportedError = class extends Error {
|
|
2423
|
+
bundleId;
|
|
2424
|
+
pluginName;
|
|
2215
2425
|
name = "DatabasePatchUpdateUnsupportedError";
|
|
2216
2426
|
constructor(bundleId, pluginName) {
|
|
2217
2427
|
super(`Database plugin "${pluginName}" cannot atomically replace patches for bundle "${bundleId}".`);
|
|
@@ -2300,7 +2510,8 @@ const hydrateRows = async (database, ownerRows) => {
|
|
|
2300
2510
|
const patchRows = await database.models.bundlePatches.findByBundleIds(ownerRows.map(({ id }) => id));
|
|
2301
2511
|
const ownerIds = new Set(ownerRows.map(({ id }) => id));
|
|
2302
2512
|
const referencedIds = [...new Set(patchRows.map(({ base_bundle_id }) => base_bundle_id).filter((id) => !ownerIds.has(id)))];
|
|
2303
|
-
|
|
2513
|
+
const referencedRows = referencedIds.length === 0 ? [] : await loadBundleRows(database, { id: { in: referencedIds } });
|
|
2514
|
+
return rowsToBundles(ownerRows, patchRows, referencedRows);
|
|
2304
2515
|
};
|
|
2305
2516
|
const cursorIdFilter = (cursor, direction) => {
|
|
2306
2517
|
if (cursor?.after) return { [direction === "desc" ? "lt" : "gt"]: cursor.after };
|
|
@@ -2352,6 +2563,7 @@ const responsePage = async (database, options) => {
|
|
|
2352
2563
|
//#endregion
|
|
2353
2564
|
//#region ../plugin-core/dist/databaseClient.mjs
|
|
2354
2565
|
var DatabaseBundleNotFoundError = class extends Error {
|
|
2566
|
+
bundleId;
|
|
2355
2567
|
name = "DatabaseBundleNotFoundError";
|
|
2356
2568
|
constructor(bundleId) {
|
|
2357
2569
|
super(`Bundle "${bundleId}" was not found.`);
|
|
@@ -2359,6 +2571,8 @@ var DatabaseBundleNotFoundError = class extends Error {
|
|
|
2359
2571
|
}
|
|
2360
2572
|
};
|
|
2361
2573
|
var DatabasePatchInsertUnsupportedError = class extends Error {
|
|
2574
|
+
bundleId;
|
|
2575
|
+
pluginName;
|
|
2362
2576
|
name = "DatabasePatchInsertUnsupportedError";
|
|
2363
2577
|
constructor(bundleId, pluginName) {
|
|
2364
2578
|
super(`Database plugin "${pluginName}" cannot atomically insert patches for bundle "${bundleId}".`);
|
|
@@ -2377,12 +2591,13 @@ const insertChanges = (bundle) => [{
|
|
|
2377
2591
|
}))];
|
|
2378
2592
|
const updateChanges = (bundleId, update) => {
|
|
2379
2593
|
const rowUpdate = bundleUpdateToRow(update);
|
|
2594
|
+
const patchesPresent = Object.hasOwn(update, "patches");
|
|
2380
2595
|
return [{
|
|
2381
2596
|
model: "bundles",
|
|
2382
2597
|
operation: "update",
|
|
2383
2598
|
where: { id: bundleId },
|
|
2384
2599
|
update: rowUpdate
|
|
2385
|
-
}, ...
|
|
2600
|
+
}, ...patchesPresent ? [{
|
|
2386
2601
|
model: "bundlePatches",
|
|
2387
2602
|
operation: "delete",
|
|
2388
2603
|
where: { bundleId }
|
|
@@ -2455,7 +2670,7 @@ const createDatabaseClient = (plugin) => {
|
|
|
2455
2670
|
};
|
|
2456
2671
|
};
|
|
2457
2672
|
//#endregion
|
|
2458
|
-
//#region ../plugin-core/dist/node_modules/.pnpm/verkit@0.
|
|
2673
|
+
//#region ../plugin-core/dist/node_modules/.pnpm/verkit@0.4.0/node_modules/verkit/dist/comparison-DenM3wCn.mjs
|
|
2459
2674
|
const LETTER_DASH_NUMBER = "[a-zA-Z0-9-]";
|
|
2460
2675
|
const NUMERIC_IDENTIFIER = String.raw`0|[1-9]\d*`;
|
|
2461
2676
|
const NUMERIC_IDENTIFIER_LOOSE = String.raw`\d+`;
|
|
@@ -2492,22 +2707,9 @@ function makeSafeRegexSource(source) {
|
|
|
2492
2707
|
function safeRegex(source, flags) {
|
|
2493
2708
|
return new RegExp(makeSafeRegexSource(source), flags);
|
|
2494
2709
|
}
|
|
2495
|
-
const NUMERIC$1 = /^\d+$/;
|
|
2496
|
-
function compareIdentifiers(left, right) {
|
|
2497
|
-
if (typeof left === "number" && typeof right === "number") return left === right ? 0 : left < right ? -1 : 1;
|
|
2498
|
-
const leftNumeric = NUMERIC$1.test(String(left));
|
|
2499
|
-
const rightNumeric = NUMERIC$1.test(String(right));
|
|
2500
|
-
const normalizedLeft = leftNumeric ? Number(left) : left;
|
|
2501
|
-
const normalizedRight = rightNumeric ? Number(right) : right;
|
|
2502
|
-
return normalizedLeft === normalizedRight ? 0 : leftNumeric && !rightNumeric ? -1 : rightNumeric && !leftNumeric ? 1 : normalizedLeft < normalizedRight ? -1 : 1;
|
|
2503
|
-
}
|
|
2504
2710
|
const FULL = safeRegex(`^${FULL_PLAIN}$`);
|
|
2505
2711
|
const LOOSE = safeRegex(`^${LOOSE_PLAIN}$`);
|
|
2506
|
-
|
|
2507
|
-
safeRegex(`^${PRERELEASE_LOOSE}$`);
|
|
2508
|
-
const COERCE_EXACT = safeRegex(COERCE);
|
|
2509
|
-
const COERCE_FULL_EXACT = safeRegex(COERCE_FULL);
|
|
2510
|
-
const NUMERIC = /^\d+$/;
|
|
2712
|
+
const NUMERIC$1 = /^\d+$/;
|
|
2511
2713
|
function formatComparableVersion(version) {
|
|
2512
2714
|
const base = `${version.major}.${version.minor}.${version.patch}`;
|
|
2513
2715
|
return version.prerelease?.length ? `${base}-${version.prerelease.join(".")}` : base;
|
|
@@ -2528,7 +2730,7 @@ function parse(version, options = {}) {
|
|
|
2528
2730
|
if (minor > Number.MAX_SAFE_INTEGER || minor < 0) throw new TypeError(`Invalid minor version: ${match[2]}`);
|
|
2529
2731
|
if (patch > Number.MAX_SAFE_INTEGER || patch < 0) throw new TypeError(`Invalid patch version: ${match[3]}`);
|
|
2530
2732
|
const prerelease = match[4] ? match[4].split(".").map((identifier) => {
|
|
2531
|
-
if (NUMERIC.test(identifier)) {
|
|
2733
|
+
if (NUMERIC$1.test(identifier)) {
|
|
2532
2734
|
const numeric = Number(identifier);
|
|
2533
2735
|
if (numeric >= 0 && numeric < Number.MAX_SAFE_INTEGER) return numeric;
|
|
2534
2736
|
}
|
|
@@ -2549,6 +2751,15 @@ function tryParse(version, options = {}) {
|
|
|
2549
2751
|
return null;
|
|
2550
2752
|
}
|
|
2551
2753
|
}
|
|
2754
|
+
const NUMERIC = /^\d+$/;
|
|
2755
|
+
function compareIdentifiers(left, right) {
|
|
2756
|
+
if (typeof left === "number" && typeof right === "number") return left === right ? 0 : left < right ? -1 : 1;
|
|
2757
|
+
const leftNumeric = NUMERIC.test(String(left));
|
|
2758
|
+
const rightNumeric = NUMERIC.test(String(right));
|
|
2759
|
+
const normalizedLeft = leftNumeric ? Number(left) : left;
|
|
2760
|
+
const normalizedRight = rightNumeric ? Number(right) : right;
|
|
2761
|
+
return normalizedLeft === normalizedRight ? 0 : leftNumeric && !rightNumeric ? -1 : rightNumeric && !leftNumeric ? 1 : normalizedLeft < normalizedRight ? -1 : 1;
|
|
2762
|
+
}
|
|
2552
2763
|
function compareMainParsed(left, right) {
|
|
2553
2764
|
return left.major === right.major ? left.minor === right.minor ? left.patch === right.patch ? 0 : left.patch < right.patch ? -1 : 1 : left.minor < right.minor ? -1 : 1 : left.major < right.major ? -1 : 1;
|
|
2554
2765
|
}
|
|
@@ -2570,26 +2781,15 @@ function comparePrereleaseParsed(left, right) {
|
|
|
2570
2781
|
function compareParsed(left, right) {
|
|
2571
2782
|
return compareMainParsed(left, right) || comparePrereleaseParsed(left, right);
|
|
2572
2783
|
}
|
|
2573
|
-
function
|
|
2574
|
-
|
|
2575
|
-
const input = typeof value === "number" ? String(value) : value;
|
|
2576
|
-
if (typeof input !== "string") return null;
|
|
2577
|
-
let match = null;
|
|
2578
|
-
if (options.rtl) {
|
|
2579
|
-
const expression = safeRegex(options.includePrerelease ? COERCE_FULL : COERCE, "g");
|
|
2580
|
-
let next;
|
|
2581
|
-
while ((next = expression.exec(input)) && (!match || match.index + match[0].length !== input.length)) {
|
|
2582
|
-
if (!match || next.index + next[0].length !== match.index + match[0].length) match = next;
|
|
2583
|
-
expression.lastIndex = next.index + next[1].length + next[2].length;
|
|
2584
|
-
}
|
|
2585
|
-
} else match = (options.includePrerelease ? COERCE_FULL_EXACT : COERCE_EXACT).exec(input);
|
|
2586
|
-
if (!match) return null;
|
|
2587
|
-
const major = match[2];
|
|
2588
|
-
return tryParse(`${major}.${match[3] || "0"}.${match[4] || "0"}${options.includePrerelease && match[5] ? `-${match[5]}` : ""}${options.includePrerelease && match[6] ? `+${match[6]}` : ""}`, options);
|
|
2784
|
+
function compare$1(left, right, options = {}) {
|
|
2785
|
+
return compareParsed(parse(left, options), parse(right, options));
|
|
2589
2786
|
}
|
|
2787
|
+
//#endregion
|
|
2788
|
+
//#region ../plugin-core/dist/node_modules/.pnpm/verkit@0.4.0/node_modules/verkit/dist/set-CC5YeoYX.mjs
|
|
2590
2789
|
const STRICT_COMPARATOR = safeRegex(String.raw`^${GREATER_LESS_THAN}\s*(${FULL_PLAIN})$|^$`);
|
|
2591
2790
|
const LOOSE_COMPARATOR$1 = safeRegex(String.raw`^${GREATER_LESS_THAN}\s*(${LOOSE_PLAIN})$|^$`);
|
|
2592
2791
|
function parseComparator(comparator, options = {}) {
|
|
2792
|
+
if (typeof comparator !== "string") return comparator;
|
|
2593
2793
|
const normalized = comparator.trim().replaceAll(/\s+/g, " ");
|
|
2594
2794
|
const match = normalized.match(options.loose ? LOOSE_COMPARATOR$1 : STRICT_COMPARATOR);
|
|
2595
2795
|
if (!match) throw new TypeError(`Invalid comparator: ${normalized}`);
|
|
@@ -2602,9 +2802,8 @@ function parseComparator(comparator, options = {}) {
|
|
|
2602
2802
|
version
|
|
2603
2803
|
};
|
|
2604
2804
|
}
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
}
|
|
2805
|
+
//#endregion
|
|
2806
|
+
//#region ../plugin-core/dist/node_modules/.pnpm/verkit@0.4.0/node_modules/verkit/dist/range-DvX-Y6iv.mjs
|
|
2608
2807
|
const BUILD_STRIP = new RegExp(BUILD, "g");
|
|
2609
2808
|
const BUILD_SAFE = safeRegex(BUILD);
|
|
2610
2809
|
const STRICT_HYPHEN = safeRegex(String.raw`^\s*(${XRANGE_PLAIN})\s+-\s+(${XRANGE_PLAIN})\s*$`);
|
|
@@ -2730,9 +2929,9 @@ function parseSimpleRange(input, options) {
|
|
|
2730
2929
|
function parseRange(range, options = {}) {
|
|
2731
2930
|
if (typeof range !== "string") return range;
|
|
2732
2931
|
const parsedOptions = { ...options };
|
|
2733
|
-
const
|
|
2734
|
-
let sets =
|
|
2735
|
-
if (!sets.length) throw new TypeError(`Range contains no valid comparator sets: ${
|
|
2932
|
+
const normalizedRange = range.trim().replaceAll(/\s+/g, " ");
|
|
2933
|
+
let sets = normalizedRange.split("||").map((part) => parseSimpleRange(part.trim(), parsedOptions)).filter((set) => set.length);
|
|
2934
|
+
if (!sets.length) throw new TypeError(`Range contains no valid comparator sets: ${normalizedRange}`);
|
|
2736
2935
|
if (sets.length > 1) {
|
|
2737
2936
|
const first = sets[0];
|
|
2738
2937
|
sets = sets.filter((set) => set[0]?.value !== "<0.0.0-0");
|
|
@@ -2743,19 +2942,39 @@ function parseRange(range, options = {}) {
|
|
|
2743
2942
|
}
|
|
2744
2943
|
}
|
|
2745
2944
|
return {
|
|
2746
|
-
normalized: sets.map((set) => set.map((comparator) => comparator.value).join(" ")).join("||"),
|
|
2747
2945
|
options: parsedOptions,
|
|
2748
|
-
raw,
|
|
2749
2946
|
sets
|
|
2750
2947
|
};
|
|
2751
2948
|
}
|
|
2949
|
+
//#endregion
|
|
2950
|
+
//#region ../plugin-core/dist/node_modules/.pnpm/verkit@0.4.0/node_modules/verkit/dist/version-CQ98ZBpL.mjs
|
|
2951
|
+
const COERCE_EXACT = safeRegex(COERCE);
|
|
2952
|
+
const COERCE_FULL_EXACT = safeRegex(COERCE_FULL);
|
|
2953
|
+
safeRegex(`^${PRERELEASE}$`);
|
|
2954
|
+
safeRegex(`^${PRERELEASE_LOOSE}$`);
|
|
2955
|
+
function normalizeFull(version, options = {}) {
|
|
2956
|
+
const parsed = tryParse(version, options);
|
|
2957
|
+
return parsed ? formatFullVersion(parsed) : null;
|
|
2958
|
+
}
|
|
2752
2959
|
function normalize(version, options = {}) {
|
|
2753
2960
|
const parsed = tryParse(version, options);
|
|
2754
2961
|
return parsed ? formatComparableVersion(parsed) : null;
|
|
2755
2962
|
}
|
|
2756
2963
|
function coerce(value, options = {}) {
|
|
2757
|
-
|
|
2758
|
-
|
|
2964
|
+
if (typeof value === "object") return value;
|
|
2965
|
+
const input = typeof value === "number" ? String(value) : value;
|
|
2966
|
+
let match = null;
|
|
2967
|
+
if (options.rtl) {
|
|
2968
|
+
const expression = safeRegex(options.includePrerelease ? COERCE_FULL : COERCE, "g");
|
|
2969
|
+
let next;
|
|
2970
|
+
while ((next = expression.exec(input)) && (!match || match.index + match[0].length !== input.length)) {
|
|
2971
|
+
if (!match || next.index + next[0].length !== match.index + match[0].length) match = next;
|
|
2972
|
+
expression.lastIndex = next.index + next[1].length + next[2].length;
|
|
2973
|
+
}
|
|
2974
|
+
} else match = (options.includePrerelease ? COERCE_FULL_EXACT : COERCE_EXACT).exec(input);
|
|
2975
|
+
if (!match) return null;
|
|
2976
|
+
const major = match[2];
|
|
2977
|
+
return tryParse(`${major}.${match[3] || "0"}.${match[4] || "0"}${options.includePrerelease && match[5] ? `-${match[5]}` : ""}${options.includePrerelease && match[6] ? `+${match[6]}` : ""}`, options);
|
|
2759
2978
|
}
|
|
2760
2979
|
//#endregion
|
|
2761
2980
|
//#region ../plugin-core/dist/releaseCatalogCompiler.mjs
|
|
@@ -3035,15 +3254,16 @@ function compileAppVersion(releases) {
|
|
|
3035
3254
|
const retainedIds = new Set(segmentReleases.flatMap(({ retainedIds, rollbackIds }) => [...retainedIds, ...rollbackIds]));
|
|
3036
3255
|
const retainedReleases = releases.filter((release) => retainedIds.has(release.id));
|
|
3037
3256
|
const descriptorIndex = new Map(retainedReleases.map((release, index) => [release.id, index]));
|
|
3257
|
+
const segments = mergeSegments(segmentReleases.map(({ segment, retainedIds, rollbackIds }) => ({
|
|
3258
|
+
...segment,
|
|
3259
|
+
releaseIndexes: releases.filter((release) => retainedIds.has(release.id)).map((release) => descriptorIndex.get(release.id)).filter((index) => index !== void 0),
|
|
3260
|
+
rollbackReleaseIndexes: releases.filter((release) => rollbackIds.has(release.id)).map((release) => descriptorIndex.get(release.id)).filter((index) => index !== void 0)
|
|
3261
|
+
})).filter((segment) => segment.releaseIndexes.length > 0 || segment.rollbackReleaseIndexes.length > 0));
|
|
3038
3262
|
return {
|
|
3039
3263
|
envelope: {
|
|
3040
3264
|
fallbackPolicy: RELEASE_CATALOG_FALLBACK_POLICY,
|
|
3041
3265
|
schemaVersion: 1,
|
|
3042
|
-
segments
|
|
3043
|
-
...segment,
|
|
3044
|
-
releaseIndexes: releases.filter((release) => retainedIds.has(release.id)).map((release) => descriptorIndex.get(release.id)).filter((index) => index !== void 0),
|
|
3045
|
-
rollbackReleaseIndexes: releases.filter((release) => rollbackIds.has(release.id)).map((release) => descriptorIndex.get(release.id)).filter((index) => index !== void 0)
|
|
3046
|
-
})).filter((segment) => segment.releaseIndexes.length > 0 || segment.rollbackReleaseIndexes.length > 0)),
|
|
3266
|
+
segments,
|
|
3047
3267
|
strategy: "APP_VERSION"
|
|
3048
3268
|
},
|
|
3049
3269
|
retainedReleases
|
|
@@ -3111,7 +3331,8 @@ function versionInSegment(version, segment) {
|
|
|
3111
3331
|
return true;
|
|
3112
3332
|
}
|
|
3113
3333
|
function canonicalizeAppVersion(appVersion) {
|
|
3114
|
-
|
|
3334
|
+
const version = coerce(appVersion);
|
|
3335
|
+
return version ? normalizeFull(version) : null;
|
|
3115
3336
|
}
|
|
3116
3337
|
function projectCompiledCatalog(catalog, appVersion) {
|
|
3117
3338
|
let indexes;
|
|
@@ -3139,6 +3360,7 @@ function projectCompiledRollbackCatalog(catalog, appVersion) {
|
|
|
3139
3360
|
const RELEASE_PAGE_SIZE = 1e3;
|
|
3140
3361
|
const DEFAULT_MAX_ATTEMPTS = 3;
|
|
3141
3362
|
var ReleaseCatalogMutationError = class extends Error {
|
|
3363
|
+
code;
|
|
3142
3364
|
name = "ReleaseCatalogMutationError";
|
|
3143
3365
|
constructor(code, message) {
|
|
3144
3366
|
super(message);
|
|
@@ -3398,6 +3620,7 @@ async function rebuildReleaseCatalog(input) {
|
|
|
3398
3620
|
//#endregion
|
|
3399
3621
|
//#region ../plugin-core/dist/releaseManagement.mjs
|
|
3400
3622
|
var ReleaseManagementError = class extends Error {
|
|
3623
|
+
code;
|
|
3401
3624
|
name = "ReleaseManagementError";
|
|
3402
3625
|
constructor(code, message) {
|
|
3403
3626
|
super(message);
|
|
@@ -3477,7 +3700,7 @@ async function deleteRelease(input) {
|
|
|
3477
3700
|
}
|
|
3478
3701
|
//#endregion
|
|
3479
3702
|
//#region ../plugin-core/dist/storageDownloadPath.mjs
|
|
3480
|
-
const decodeBase64Url = (value) => {
|
|
3703
|
+
const decodeBase64Url$1 = (value) => {
|
|
3481
3704
|
try {
|
|
3482
3705
|
const base64 = value.replaceAll("-", "+").replaceAll("_", "/");
|
|
3483
3706
|
const binary = atob(base64.padEnd(Math.ceil(base64.length / 4) * 4, "="));
|
|
@@ -3489,7 +3712,7 @@ const decodeBase64Url = (value) => {
|
|
|
3489
3712
|
const parseStorageDownloadPath = (path) => {
|
|
3490
3713
|
const match = /^\/storage\/([^/]+)\/([^/]+)$/.exec(path);
|
|
3491
3714
|
if (!match) return null;
|
|
3492
|
-
const storageUri = decodeBase64Url(match[1]);
|
|
3715
|
+
const storageUri = decodeBase64Url$1(match[1]);
|
|
3493
3716
|
if (storageUri === null) return null;
|
|
3494
3717
|
try {
|
|
3495
3718
|
return {
|
|
@@ -3610,7 +3833,7 @@ const createReleaseCatalogRouteHandlers = (clientAccessHeaderName = "x-api-key")
|
|
|
3610
3833
|
//#endregion
|
|
3611
3834
|
//#region ../../packages/server/dist/handlerReleaseManagementRoutes.mjs
|
|
3612
3835
|
const unavailable = () => Response.json({ error: "Not found" }, { status: 404 });
|
|
3613
|
-
const isRecord$
|
|
3836
|
+
const isRecord$2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3614
3837
|
const parseRevision = (value) => {
|
|
3615
3838
|
if (value === void 0 || value === null || value === "") return void 0;
|
|
3616
3839
|
const revision = typeof value === "number" ? value : Number(value);
|
|
@@ -3619,7 +3842,7 @@ const parseRevision = (value) => {
|
|
|
3619
3842
|
};
|
|
3620
3843
|
const parsePolicyInput = async (request) => {
|
|
3621
3844
|
const body = await request.json();
|
|
3622
|
-
if (!isRecord$
|
|
3845
|
+
if (!isRecord$2(body) || !isRecord$2(body.patch)) throw new HandlerBadRequestError("Invalid Release policy mutation");
|
|
3623
3846
|
return {
|
|
3624
3847
|
expectedRevision: parseRevision(body.expectedRevision),
|
|
3625
3848
|
patch: body.patch
|
|
@@ -3729,17 +3952,11 @@ const createReleaseManagementRouteHandlers = () => ({
|
|
|
3729
3952
|
});
|
|
3730
3953
|
//#endregion
|
|
3731
3954
|
//#region ../../packages/server/dist/insights/errors.mjs
|
|
3732
|
-
var InsightsScanLimitExceededError = class extends Error {
|
|
3733
|
-
constructor(limit) {
|
|
3734
|
-
super(`Insights event scan exceeded ${limit} rows.`);
|
|
3735
|
-
this.limit = limit;
|
|
3736
|
-
this.name = "InsightsScanLimitExceededError";
|
|
3737
|
-
}
|
|
3738
|
-
};
|
|
3739
3955
|
var InsightsBadRequestError = class extends Error {
|
|
3740
3956
|
name = "InsightsBadRequestError";
|
|
3741
3957
|
};
|
|
3742
3958
|
var InsightsPayloadTooLargeError = class extends Error {
|
|
3959
|
+
maximumBytes;
|
|
3743
3960
|
name = "InsightsPayloadTooLargeError";
|
|
3744
3961
|
constructor(maximumBytes) {
|
|
3745
3962
|
super(`Event payload exceeds ${maximumBytes} bytes`);
|
|
@@ -3749,8 +3966,9 @@ var InsightsPayloadTooLargeError = class extends Error {
|
|
|
3749
3966
|
//#endregion
|
|
3750
3967
|
//#region ../../packages/server/dist/insights/eventInput.mjs
|
|
3751
3968
|
const MAX_EVENT_STRING_LENGTH = 1024;
|
|
3752
|
-
const
|
|
3753
|
-
const
|
|
3969
|
+
const MAX_IDENTITY_LENGTH$2 = 255;
|
|
3970
|
+
const EVENT_BODY_MAX_BYTES = 16384;
|
|
3971
|
+
const eventKeys = /* @__PURE__ */ new Set([
|
|
3754
3972
|
"type",
|
|
3755
3973
|
"installId",
|
|
3756
3974
|
"toBundleId",
|
|
@@ -3767,18 +3985,23 @@ const eventKeys = new Set([
|
|
|
3767
3985
|
"updateStrategy",
|
|
3768
3986
|
"sdkVersion"
|
|
3769
3987
|
]);
|
|
3770
|
-
function isRecord(value) {
|
|
3988
|
+
function isRecord$1(value) {
|
|
3771
3989
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3772
3990
|
}
|
|
3773
3991
|
function requireStringField(payload, key) {
|
|
3774
3992
|
const value = payload[key];
|
|
3775
|
-
if (typeof value !== "string" || value.length === 0 || value.length > MAX_EVENT_STRING_LENGTH) throw new InsightsBadRequestError(`Invalid event field: ${key}`);
|
|
3993
|
+
if (typeof value !== "string" || value.length === 0 || value.length > MAX_EVENT_STRING_LENGTH || new TextDecoder("utf-8", { ignoreBOM: true }).decode(new TextEncoder().encode(value)) !== value) throw new InsightsBadRequestError(`Invalid event field: ${key}`);
|
|
3776
3994
|
return value;
|
|
3777
3995
|
}
|
|
3778
3996
|
function requireNullableStringField(payload, key) {
|
|
3779
3997
|
if (payload[key] === null) return null;
|
|
3780
3998
|
return requireStringField(payload, key);
|
|
3781
3999
|
}
|
|
4000
|
+
function requireIdentityField(payload, key) {
|
|
4001
|
+
const value = requireStringField(payload, key);
|
|
4002
|
+
if (value.length > MAX_IDENTITY_LENGTH$2) throw new InsightsBadRequestError(`Invalid event field: ${key}`);
|
|
4003
|
+
return value;
|
|
4004
|
+
}
|
|
3782
4005
|
async function readBoundedText(request) {
|
|
3783
4006
|
const contentLength = request.headers.get("content-length");
|
|
3784
4007
|
const declaredByteLength = Number(contentLength);
|
|
@@ -3810,13 +4033,13 @@ async function parseJson(request) {
|
|
|
3810
4033
|
}
|
|
3811
4034
|
}
|
|
3812
4035
|
function requireEvent(payload) {
|
|
3813
|
-
if (!isRecord(payload) || Object.keys(payload).some((key) => !eventKeys.has(key))) throw new InsightsBadRequestError("Invalid event payload");
|
|
4036
|
+
if (!isRecord$1(payload) || Object.keys(payload).some((key) => !eventKeys.has(key))) throw new InsightsBadRequestError("Invalid event payload");
|
|
3814
4037
|
const platform = requireStringField(payload, "platform");
|
|
3815
4038
|
if (platform !== "ios" && platform !== "android") throw new InsightsBadRequestError("Invalid event field: platform");
|
|
3816
4039
|
const base = {
|
|
3817
|
-
installId:
|
|
4040
|
+
installId: requireIdentityField(payload, "installId"),
|
|
3818
4041
|
toBundleId: requireStringField(payload, "toBundleId"),
|
|
3819
|
-
...payload.userId === void 0 ? {} : { userId:
|
|
4042
|
+
...payload.userId === void 0 ? {} : { userId: requireIdentityField(payload, "userId") },
|
|
3820
4043
|
...payload.username === void 0 ? {} : { username: requireStringField(payload, "username") },
|
|
3821
4044
|
platform,
|
|
3822
4045
|
appVersion: requireStringField(payload, "appVersion"),
|
|
@@ -3829,9 +4052,9 @@ function requireEvent(payload) {
|
|
|
3829
4052
|
};
|
|
3830
4053
|
const type = requireStringField(payload, "type");
|
|
3831
4054
|
switch (type) {
|
|
4055
|
+
case "UPDATE_DOWNLOADED":
|
|
3832
4056
|
case "UPDATE_APPLIED":
|
|
3833
|
-
case "RECOVERED":
|
|
3834
|
-
case "RELEASE_ADOPTED": {
|
|
4057
|
+
case "RECOVERED": {
|
|
3835
4058
|
const updateStrategy = requireStringField(payload, "updateStrategy");
|
|
3836
4059
|
if (updateStrategy !== "fingerprint" && updateStrategy !== "appVersion") throw new InsightsBadRequestError("Invalid event field: updateStrategy");
|
|
3837
4060
|
return {
|
|
@@ -3855,59 +4078,131 @@ function requireEvent(payload) {
|
|
|
3855
4078
|
async function parseBundleEventRequest(request) {
|
|
3856
4079
|
return requireEvent(await parseJson(request));
|
|
3857
4080
|
}
|
|
4081
|
+
function createBundleEventRow(input) {
|
|
4082
|
+
input = requireEvent(input);
|
|
4083
|
+
const base = {
|
|
4084
|
+
app_version: input.appVersion,
|
|
4085
|
+
channel: input.channel,
|
|
4086
|
+
from_release_id: input.fromReleaseId,
|
|
4087
|
+
id: createUUIDv7(),
|
|
4088
|
+
install_id: input.installId,
|
|
4089
|
+
platform: input.platform,
|
|
4090
|
+
received_at_ms: Date.now(),
|
|
4091
|
+
to_bundle_id: input.toBundleId,
|
|
4092
|
+
to_release_id: input.toReleaseId,
|
|
4093
|
+
user_id: input.userId ?? null,
|
|
4094
|
+
metadata: {
|
|
4095
|
+
cohort: input.cohort,
|
|
4096
|
+
fingerprint_hash: input.fingerprintHash,
|
|
4097
|
+
sdk_version: input.sdkVersion ?? null,
|
|
4098
|
+
username: input.username ?? null,
|
|
4099
|
+
update_strategy: input.updateStrategy
|
|
4100
|
+
}
|
|
4101
|
+
};
|
|
4102
|
+
switch (input.type) {
|
|
4103
|
+
case "UPDATE_DOWNLOADED":
|
|
4104
|
+
case "UPDATE_APPLIED":
|
|
4105
|
+
case "RECOVERED": return {
|
|
4106
|
+
...base,
|
|
4107
|
+
from_bundle_id: input.fromBundleId,
|
|
4108
|
+
type: input.type
|
|
4109
|
+
};
|
|
4110
|
+
case "UNCHANGED": return {
|
|
4111
|
+
...base,
|
|
4112
|
+
from_bundle_id: null,
|
|
4113
|
+
type: input.type
|
|
4114
|
+
};
|
|
4115
|
+
}
|
|
4116
|
+
}
|
|
3858
4117
|
//#endregion
|
|
3859
4118
|
//#region ../../packages/server/dist/insights/queryInput.mjs
|
|
3860
|
-
const
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
4119
|
+
const MAX_PAGE_LIMIT$1 = 100;
|
|
4120
|
+
const MAX_IDENTITY_LENGTH$1 = 255;
|
|
4121
|
+
const MAX_CURSOR_LENGTH$1 = 8192;
|
|
4122
|
+
const readSingle = (url, key) => {
|
|
4123
|
+
const values = url.searchParams.getAll(key);
|
|
4124
|
+
if (values.length > 1) throw new InsightsBadRequestError(`Duplicate '${key}' query parameter.`);
|
|
4125
|
+
return values[0];
|
|
3864
4126
|
};
|
|
3865
|
-
const
|
|
3866
|
-
|
|
3867
|
-
|
|
4127
|
+
const readPageLimit = (url) => {
|
|
4128
|
+
const value = readSingle(url, "limit");
|
|
4129
|
+
if (value === void 0) return void 0;
|
|
4130
|
+
const limit = Number(value);
|
|
4131
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > MAX_PAGE_LIMIT$1) throw new InsightsBadRequestError("Invalid 'limit' query parameter.");
|
|
4132
|
+
return limit;
|
|
4133
|
+
};
|
|
4134
|
+
const readCursor = (url) => {
|
|
4135
|
+
const cursor = readSingle(url, "cursor");
|
|
4136
|
+
if (cursor !== void 0 && (cursor.length === 0 || cursor.length > MAX_CURSOR_LENGTH$1)) throw new InsightsBadRequestError("Invalid 'cursor' query parameter.");
|
|
4137
|
+
return cursor;
|
|
4138
|
+
};
|
|
4139
|
+
const readId = (url, key, maximumLength = MAX_IDENTITY_LENGTH$1) => {
|
|
4140
|
+
const value = readSingle(url, key);
|
|
4141
|
+
if (value === void 0 || value.length === 0 || value.length > maximumLength) throw new InsightsBadRequestError(`Invalid '${key}' query parameter.`);
|
|
4142
|
+
return value;
|
|
3868
4143
|
};
|
|
3869
|
-
const
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
const
|
|
4144
|
+
const readTimestamp = (url, key) => {
|
|
4145
|
+
const raw = readSingle(url, key);
|
|
4146
|
+
if (raw === void 0) return void 0;
|
|
4147
|
+
const value = Number(raw);
|
|
4148
|
+
if (!raw.length || !Number.isSafeInteger(value) || value < 0) throw new InsightsBadRequestError(`Invalid '${key}' query parameter.`);
|
|
4149
|
+
return value;
|
|
4150
|
+
};
|
|
4151
|
+
const readScope$1 = (url) => {
|
|
4152
|
+
const platform = readSingle(url, "platform");
|
|
4153
|
+
if (platform !== "ios" && platform !== "android") throw new InsightsBadRequestError("Invalid 'platform' query parameter.");
|
|
4154
|
+
return {
|
|
4155
|
+
platform,
|
|
4156
|
+
channel: readId(url, "channel", 1024)
|
|
4157
|
+
};
|
|
4158
|
+
};
|
|
4159
|
+
const parseEventPageInput = (request) => {
|
|
3878
4160
|
const url = new URL(request.url);
|
|
4161
|
+
const bundleFields = [
|
|
4162
|
+
"bundleId",
|
|
4163
|
+
"outcome",
|
|
4164
|
+
"platform",
|
|
4165
|
+
"channel"
|
|
4166
|
+
];
|
|
4167
|
+
let bundle;
|
|
4168
|
+
if (bundleFields.some((key) => url.searchParams.has(key))) {
|
|
4169
|
+
const outcome = readSingle(url, "outcome");
|
|
4170
|
+
if (outcome !== "downloaded" && outcome !== "applied" && outcome !== "recovered" && outcome !== "unchanged") throw new InsightsBadRequestError("Invalid 'outcome' query parameter.");
|
|
4171
|
+
bundle = {
|
|
4172
|
+
...readScope$1(url),
|
|
4173
|
+
bundleId: readId(url, "bundleId", 1024),
|
|
4174
|
+
outcome
|
|
4175
|
+
};
|
|
4176
|
+
}
|
|
3879
4177
|
return {
|
|
3880
|
-
|
|
3881
|
-
|
|
4178
|
+
beforeReceivedAtMs: readTimestamp(url, "beforeReceivedAtMs"),
|
|
4179
|
+
sinceMs: readTimestamp(url, "sinceMs"),
|
|
4180
|
+
cursor: readCursor(url),
|
|
4181
|
+
limit: readPageLimit(url),
|
|
4182
|
+
...bundle === void 0 ? {} : { bundle }
|
|
3882
4183
|
};
|
|
3883
4184
|
};
|
|
3884
|
-
const
|
|
3885
|
-
const
|
|
3886
|
-
|
|
4185
|
+
const parseUserInstallationPageInput = (request) => {
|
|
4186
|
+
const url = new URL(request.url);
|
|
4187
|
+
const cursor = readCursor(url);
|
|
4188
|
+
const limit = readPageLimit(url);
|
|
3887
4189
|
return {
|
|
3888
|
-
|
|
3889
|
-
|
|
4190
|
+
userId: readId(url, "userId"),
|
|
4191
|
+
...cursor === void 0 ? {} : { cursor },
|
|
4192
|
+
...limit === void 0 ? {} : { limit }
|
|
3890
4193
|
};
|
|
3891
4194
|
};
|
|
3892
|
-
const
|
|
4195
|
+
const parseReportingOverviewInput = (request) => {
|
|
3893
4196
|
const url = new URL(request.url);
|
|
3894
|
-
const
|
|
3895
|
-
if (windows.length > 1) throw new InsightsBadRequestError("Duplicate 'window' query parameter.");
|
|
3896
|
-
const window = windows[0] ?? "30d";
|
|
4197
|
+
const window = readSingle(url, "window") ?? "30d";
|
|
3897
4198
|
if (window !== "24h" && window !== "7d" && window !== "30d") throw new InsightsBadRequestError("Invalid 'window' query parameter.");
|
|
3898
|
-
const
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
if (userId !== void 0 && (userId.length === 0 || userId.length > MAX_USER_ID_LENGTH)) throw new InsightsBadRequestError("Invalid 'userId' query parameter.");
|
|
3902
|
-
return userId === void 0 ? { window } : {
|
|
4199
|
+
const bundleId = readSingle(url, "bundleId");
|
|
4200
|
+
return {
|
|
4201
|
+
...readScope$1(url),
|
|
3903
4202
|
window,
|
|
3904
|
-
|
|
4203
|
+
...bundleId === void 0 ? {} : { bundleId: readId(url, "bundleId", 1024) }
|
|
3905
4204
|
};
|
|
3906
4205
|
};
|
|
3907
|
-
const parseSearchInput = (request) => ({
|
|
3908
|
-
...parsePagination(request),
|
|
3909
|
-
query: new URL(request.url).searchParams.get("query")?.trim() ?? ""
|
|
3910
|
-
});
|
|
3911
4206
|
//#endregion
|
|
3912
4207
|
//#region ../../packages/server/dist/insights/routes.mjs
|
|
3913
4208
|
const json = (body, status) => Response.json(body, {
|
|
@@ -3917,7 +4212,11 @@ const json = (body, status) => Response.json(body, {
|
|
|
3917
4212
|
const requireParam = (params, key) => {
|
|
3918
4213
|
const value = params[key];
|
|
3919
4214
|
if (value === void 0 || value.length === 0) throw new InsightsBadRequestError(`Missing route parameter: ${key}`);
|
|
3920
|
-
|
|
4215
|
+
try {
|
|
4216
|
+
return decodeURIComponent(value);
|
|
4217
|
+
} catch {
|
|
4218
|
+
throw new InsightsBadRequestError(`Invalid route parameter: ${key}`);
|
|
4219
|
+
}
|
|
3921
4220
|
};
|
|
3922
4221
|
const run = async (operation) => {
|
|
3923
4222
|
try {
|
|
@@ -3925,10 +4224,6 @@ const run = async (operation) => {
|
|
|
3925
4224
|
} catch (error) {
|
|
3926
4225
|
if (error instanceof InsightsBadRequestError) return json({ error: error.message }, 400);
|
|
3927
4226
|
if (error instanceof InsightsPayloadTooLargeError) return json({ error: error.message }, 413);
|
|
3928
|
-
if (error instanceof InsightsScanLimitExceededError) return json({ error: {
|
|
3929
|
-
code: "INSIGHTS_SCAN_LIMIT_EXCEEDED",
|
|
3930
|
-
limit: error.limit
|
|
3931
|
-
} }, 503);
|
|
3932
4227
|
throw error;
|
|
3933
4228
|
}
|
|
3934
4229
|
};
|
|
@@ -3938,32 +4233,27 @@ const createInsightsRouteHandlers = (provider) => ({
|
|
|
3938
4233
|
await provider.appendBundleEvent(await parseBundleEventRequest(request));
|
|
3939
4234
|
return new Response(null, { status: 204 });
|
|
3940
4235
|
}),
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
const
|
|
3944
|
-
return
|
|
4236
|
+
getReportingOverview: (_params, request) => query(() => provider.getReportingOverview(parseReportingOverviewInput(request))),
|
|
4237
|
+
getInstallation: (params) => run(async () => {
|
|
4238
|
+
const installation = await provider.getInstallation({ installId: requireParam(params, "installId") });
|
|
4239
|
+
return installation === null ? json({ error: "Installation not found" }, 404) : json(installation, 200);
|
|
3945
4240
|
}),
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
getInstallationHistory: (params, request) => query(() => {
|
|
3953
|
-
const input = parsePagination(request);
|
|
3954
|
-
return provider.getInstallationHistory(requireParam(params, "installId"), input.limit, input.offset);
|
|
3955
|
-
})
|
|
4241
|
+
listEvents: (_params, request) => query(() => provider.listEvents(parseEventPageInput(request))),
|
|
4242
|
+
listInstallationEvents: (params, request) => query(() => provider.listInstallationEvents({
|
|
4243
|
+
...parseEventPageInput(request),
|
|
4244
|
+
installId: requireParam(params, "installId")
|
|
4245
|
+
})),
|
|
4246
|
+
pageInstallationsByCurrentUserId: (_params, request) => query(() => provider.pageInstallationsByCurrentUserId(parseUserInstallationPageInput(request)))
|
|
3956
4247
|
});
|
|
3957
4248
|
const registerInsightsClientRoutes = (add) => {
|
|
3958
4249
|
add("POST", "/events", "appendBundleEvent");
|
|
3959
4250
|
};
|
|
3960
4251
|
const registerInsightsAdminRoutes = (add) => {
|
|
3961
|
-
add("GET", "/
|
|
3962
|
-
add("GET", "/
|
|
3963
|
-
add("GET", "/installations
|
|
3964
|
-
add("GET", "/installations/
|
|
3965
|
-
add("GET", "/installations", "
|
|
3966
|
-
add("GET", "/installations/:installId/events", "getInstallationHistory");
|
|
4252
|
+
add("GET", "/events", "listEvents");
|
|
4253
|
+
add("GET", "/overview", "getReportingOverview");
|
|
4254
|
+
add("GET", "/installations", "pageInstallationsByCurrentUserId");
|
|
4255
|
+
add("GET", "/installations/:installId/events", "listInstallationEvents");
|
|
4256
|
+
add("GET", "/installations/:installId", "getInstallation");
|
|
3967
4257
|
};
|
|
3968
4258
|
//#endregion
|
|
3969
4259
|
//#region ../../packages/server/dist/internalRouter.mjs
|
|
@@ -4125,461 +4415,329 @@ function createHotUpdaterHandlers(api, insights, apiKeyAuth, downloadStorageObje
|
|
|
4125
4415
|
});
|
|
4126
4416
|
}
|
|
4127
4417
|
//#endregion
|
|
4128
|
-
//#region ../../packages/server/dist/insights/
|
|
4129
|
-
const
|
|
4130
|
-
const
|
|
4131
|
-
const
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
"
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
}
|
|
4142
|
-
|
|
4143
|
-
bucketCount: 7,
|
|
4144
|
-
bucketSizeMs: DAY_MS
|
|
4145
|
-
},
|
|
4146
|
-
"30d": {
|
|
4147
|
-
bucketCount: 30,
|
|
4148
|
-
bucketSizeMs: DAY_MS
|
|
4149
|
-
}
|
|
4418
|
+
//#region ../../packages/server/dist/insights/provider.mjs
|
|
4419
|
+
const DEFAULT_PAGE_LIMIT = 50;
|
|
4420
|
+
const MAX_PAGE_LIMIT = 100;
|
|
4421
|
+
const MAX_EVENT_ID_LENGTH = 1024;
|
|
4422
|
+
const MAX_IDENTITY_LENGTH = 255;
|
|
4423
|
+
const MAX_CURSOR_LENGTH = 8192;
|
|
4424
|
+
const WINDOW_MS = {
|
|
4425
|
+
"24h": 864e5,
|
|
4426
|
+
"7d": 6048e5,
|
|
4427
|
+
"30d": 2592e6
|
|
4428
|
+
};
|
|
4429
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4430
|
+
const requireString = (value, label, maximumLength) => {
|
|
4431
|
+
if (typeof value !== "string" || value.length === 0 || value.length > maximumLength || new TextDecoder("utf-8", { ignoreBOM: true }).decode(new TextEncoder().encode(value)) !== value) throw new InsightsBadRequestError(`Invalid ${label}.`);
|
|
4432
|
+
return value;
|
|
4150
4433
|
};
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
const
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4434
|
+
const requireTimestamp = (value, label) => {
|
|
4435
|
+
if (!Number.isSafeInteger(value) || Number(value) < 0) throw new InsightsBadRequestError(`Invalid ${label}.`);
|
|
4436
|
+
return Number(value);
|
|
4437
|
+
};
|
|
4438
|
+
const readLimit = (value) => {
|
|
4439
|
+
const limit = value ?? DEFAULT_PAGE_LIMIT;
|
|
4440
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > MAX_PAGE_LIMIT) throw new InsightsBadRequestError("Invalid page limit.");
|
|
4441
|
+
return limit;
|
|
4442
|
+
};
|
|
4443
|
+
const encodeBase64Url = (value) => {
|
|
4444
|
+
const bytes = new TextEncoder().encode(value);
|
|
4445
|
+
let binary = "";
|
|
4446
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
4447
|
+
return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/u, "");
|
|
4448
|
+
};
|
|
4449
|
+
const decodeBase64Url = (value) => {
|
|
4450
|
+
if (value.length === 0 || value.length > MAX_CURSOR_LENGTH) throw new InsightsBadRequestError("Invalid Insights cursor.");
|
|
4451
|
+
try {
|
|
4452
|
+
const base64 = value.replaceAll("-", "+").replaceAll("_", "/");
|
|
4453
|
+
const padded = base64.padEnd(Math.ceil(base64.length / 4) * 4, "=");
|
|
4454
|
+
const binary = atob(padded);
|
|
4455
|
+
const bytes = Uint8Array.from(binary, (character) => character.charCodeAt(0));
|
|
4456
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
4457
|
+
} catch {
|
|
4458
|
+
throw new InsightsBadRequestError("Invalid Insights cursor.");
|
|
4170
4459
|
}
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
if (
|
|
4178
|
-
|
|
4179
|
-
if (bucket === void 0) continue;
|
|
4180
|
-
const current = bucket.get(row.install_id);
|
|
4181
|
-
if (current === void 0 || isNewer(row, current)) bucket.set(row.install_id, row);
|
|
4460
|
+
};
|
|
4461
|
+
const encodeCursor = (value) => encodeBase64Url(JSON.stringify(value));
|
|
4462
|
+
const decodeCursor = (value) => {
|
|
4463
|
+
try {
|
|
4464
|
+
return JSON.parse(decodeBase64Url(value));
|
|
4465
|
+
} catch (error) {
|
|
4466
|
+
if (error instanceof InsightsBadRequestError) throw error;
|
|
4467
|
+
throw new InsightsBadRequestError("Invalid Insights cursor.");
|
|
4182
4468
|
}
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
return counts;
|
|
4187
|
-
});
|
|
4188
|
-
const bundleObservationTotals = /* @__PURE__ */ new Map();
|
|
4189
|
-
for (const counts of bundleCountsByBucket) for (const [bundleId, count] of counts) bundleObservationTotals.set(bundleId, (bundleObservationTotals.get(bundleId) ?? 0) + count);
|
|
4469
|
+
};
|
|
4470
|
+
const readScope = (input) => {
|
|
4471
|
+
if (input.platform !== "ios" && input.platform !== "android") throw new InsightsBadRequestError("Invalid Insights platform.");
|
|
4190
4472
|
return {
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
activeInstallations: selectedInstallIds.size,
|
|
4194
|
-
series: latestByBucket.map((bucket, index) => ({
|
|
4195
|
-
bucketStartMs: windowStartMs + index * definition.bucketSizeMs,
|
|
4196
|
-
value: bucket.size
|
|
4197
|
-
})),
|
|
4198
|
-
bundleSeries: [...bundleObservationTotals].sort(([leftId, leftTotal], [rightId, rightTotal]) => rightTotal - leftTotal || compareCodePoints$1(leftId, rightId)).map(([bundleId]) => ({
|
|
4199
|
-
bundleId,
|
|
4200
|
-
series: bundleCountsByBucket.map((counts, index) => ({
|
|
4201
|
-
bucketStartMs: windowStartMs + index * definition.bucketSizeMs,
|
|
4202
|
-
value: counts.get(bundleId) ?? 0
|
|
4203
|
-
}))
|
|
4204
|
-
})),
|
|
4205
|
-
bundles: [...bundleCounts].map(([bundleId, installations]) => ({
|
|
4206
|
-
bundleId,
|
|
4207
|
-
installations
|
|
4208
|
-
})).sort((left, right) => right.installations - left.installations || compareCodePoints$1(left.bundleId, right.bundleId))
|
|
4209
|
-
};
|
|
4210
|
-
}
|
|
4211
|
-
//#endregion
|
|
4212
|
-
//#region ../../packages/server/dist/insights/bounded/scan.mjs
|
|
4213
|
-
const INSIGHTS_SCAN_MAX_ROWS = 5e4;
|
|
4214
|
-
const INSIGHTS_MATERIALIZATION_LIMIT = INSIGHTS_SCAN_MAX_ROWS + 1;
|
|
4215
|
-
const INSIGHTS_SCAN_PAGE_SIZE = 1e3;
|
|
4216
|
-
const INSIGHTS_LOWER_BOUND_ID = "00000000-0000-0000-0000-000000000000";
|
|
4217
|
-
const compareCodePoints = (left, right) => {
|
|
4218
|
-
if (left < right) return -1;
|
|
4219
|
-
if (left > right) return 1;
|
|
4220
|
-
return 0;
|
|
4221
|
-
};
|
|
4222
|
-
const compareEventNewest = (left, right) => right.received_at_ms - left.received_at_ms || compareCodePoints(right.id, left.id);
|
|
4223
|
-
const compareEventOldest = (left, right) => left.received_at_ms - right.received_at_ms || compareCodePoints(left.id, right.id);
|
|
4224
|
-
const materializeEventRows = async (scope) => {
|
|
4225
|
-
const rows = [];
|
|
4226
|
-
const seenIds = /* @__PURE__ */ new Set();
|
|
4227
|
-
let after = scope.lowerBoundMs === void 0 ? void 0 : {
|
|
4228
|
-
receivedAtMs: scope.lowerBoundMs,
|
|
4229
|
-
id: INSIGHTS_LOWER_BOUND_ID
|
|
4473
|
+
platform: input.platform,
|
|
4474
|
+
channel: requireString(input.channel, "channel", MAX_EVENT_ID_LENGTH)
|
|
4230
4475
|
};
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
let previous = after === void 0 ? void 0 : {
|
|
4241
|
-
received_at_ms: after.receivedAtMs,
|
|
4242
|
-
id: after.id
|
|
4476
|
+
};
|
|
4477
|
+
const bundleFilter = (input) => {
|
|
4478
|
+
const scope = readScope(input);
|
|
4479
|
+
const bundleId = requireString(input.bundleId, "bundle ID", MAX_EVENT_ID_LENGTH);
|
|
4480
|
+
switch (input.outcome) {
|
|
4481
|
+
case "downloaded": return {
|
|
4482
|
+
...scope,
|
|
4483
|
+
type: "UPDATE_DOWNLOADED",
|
|
4484
|
+
toBundleId: bundleId
|
|
4243
4485
|
};
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
id: last.id
|
|
4486
|
+
case "applied": return {
|
|
4487
|
+
...scope,
|
|
4488
|
+
type: "UPDATE_APPLIED",
|
|
4489
|
+
toBundleId: bundleId
|
|
4490
|
+
};
|
|
4491
|
+
case "recovered": return {
|
|
4492
|
+
...scope,
|
|
4493
|
+
type: "RECOVERED",
|
|
4494
|
+
fromBundleId: bundleId
|
|
4254
4495
|
};
|
|
4255
|
-
|
|
4496
|
+
case "unchanged": return {
|
|
4497
|
+
...scope,
|
|
4498
|
+
type: "UNCHANGED",
|
|
4499
|
+
toBundleId: bundleId
|
|
4500
|
+
};
|
|
4501
|
+
default: throw new InsightsBadRequestError("Invalid Insights outcome.");
|
|
4256
4502
|
}
|
|
4257
|
-
return rows;
|
|
4258
4503
|
};
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4504
|
+
const sameFilter = (left, right) => {
|
|
4505
|
+
if (!isRecord(left) || left.kind !== right.kind) return false;
|
|
4506
|
+
switch (right.kind) {
|
|
4507
|
+
case "all": return true;
|
|
4508
|
+
case "installationMovement": return left.installId === right.installId;
|
|
4509
|
+
case "bundle": return left.platform === right.platform && left.channel === right.channel && left.type === right.type && (right.type === "RECOVERED" ? left.fromBundleId === right.fromBundleId : left.toBundleId === right.toBundleId);
|
|
4263
4510
|
}
|
|
4264
4511
|
};
|
|
4265
|
-
const
|
|
4266
|
-
const
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
lowerBoundMs: scope.cutoffMs - durationMs
|
|
4271
|
-
})).filter((row) => row.received_at_ms >= scope.cutoffMs - durationMs && ACTIVE_BUNDLE_EVENT_TYPES.includes(row.type));
|
|
4272
|
-
};
|
|
4273
|
-
const startOfUtcHour = (value) => {
|
|
4274
|
-
const date = new Date(value);
|
|
4275
|
-
return Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours());
|
|
4276
|
-
};
|
|
4277
|
-
const startOfUtcDay = (value) => {
|
|
4278
|
-
const date = new Date(value);
|
|
4279
|
-
return Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate());
|
|
4280
|
-
};
|
|
4281
|
-
const getWindowRange = (window, now) => {
|
|
4282
|
-
if (window === "24h") return {
|
|
4283
|
-
sizeMs: 3600 * 1e3,
|
|
4284
|
-
rangeStart: startOfUtcHour(now) - 1380 * 60 * 1e3
|
|
4285
|
-
};
|
|
4286
|
-
const days = window === "7d" ? 7 : 30;
|
|
4512
|
+
const readEventCursor = (value, filter) => {
|
|
4513
|
+
const cursor = decodeCursor(value);
|
|
4514
|
+
if (!isRecord(cursor) || cursor.version !== 2 || cursor.kind !== "events" || !isRecord(cursor.filter) || !isRecord(cursor.after)) throw new InsightsBadRequestError("Invalid Insights cursor.");
|
|
4515
|
+
if (!sameFilter(cursor.filter, filter)) throw new InsightsBadRequestError("Insights cursor does not match the requested events.");
|
|
4516
|
+
if (typeof cursor.after.id !== "string" || !isUUIDv7(cursor.after.id)) throw new InsightsBadRequestError("Invalid Insights event cursor ID.");
|
|
4287
4517
|
return {
|
|
4288
|
-
|
|
4289
|
-
|
|
4518
|
+
after: {
|
|
4519
|
+
id: cursor.after.id,
|
|
4520
|
+
receivedAtMs: requireTimestamp(cursor.after.receivedAtMs, "event cursor")
|
|
4521
|
+
},
|
|
4522
|
+
beforeReceivedAtMs: requireTimestamp(cursor.beforeReceivedAtMs, "event cutoff"),
|
|
4523
|
+
kind: "events",
|
|
4524
|
+
sinceMs: requireTimestamp(cursor.sinceMs, "event start"),
|
|
4525
|
+
filter,
|
|
4526
|
+
version: 2
|
|
4290
4527
|
};
|
|
4291
4528
|
};
|
|
4292
|
-
const
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
return (await materializeEventRows({
|
|
4296
|
-
...scope,
|
|
4297
|
-
lowerBoundMs: range.rangeStart
|
|
4298
|
-
})).filter(({ received_at_ms }) => received_at_ms >= range.rangeStart);
|
|
4299
|
-
};
|
|
4300
|
-
const bucketStart = (receivedAtMs, sizeMs) => sizeMs === 3600 * 1e3 ? startOfUtcHour(receivedAtMs) : startOfUtcDay(receivedAtMs);
|
|
4301
|
-
const createSeries = (request) => {
|
|
4302
|
-
const range = request.window === "all" ? void 0 : getWindowRange(request.window, request.cutoffMs);
|
|
4303
|
-
const sizeMs = range?.sizeMs ?? 1440 * 60 * 1e3;
|
|
4304
|
-
const installIdsByBucket = /* @__PURE__ */ new Map();
|
|
4305
|
-
let oldestMs = request.cutoffMs;
|
|
4306
|
-
for (const row of request.rows) {
|
|
4307
|
-
oldestMs = Math.min(oldestMs, row.received_at_ms);
|
|
4308
|
-
const start = bucketStart(row.received_at_ms, sizeMs);
|
|
4309
|
-
const installIds = installIdsByBucket.get(start) ?? /* @__PURE__ */ new Set();
|
|
4310
|
-
installIds.add(row.install_id);
|
|
4311
|
-
installIdsByBucket.set(start, installIds);
|
|
4312
|
-
}
|
|
4313
|
-
const first = range?.rangeStart ?? startOfUtcDay(oldestMs);
|
|
4314
|
-
const last = bucketStart(request.cutoffMs, sizeMs);
|
|
4315
|
-
return Array.from({ length: Math.floor((last - first) / sizeMs) + 1 }, (_, index) => {
|
|
4316
|
-
const start = first + index * sizeMs;
|
|
4317
|
-
return {
|
|
4318
|
-
bucketStartMs: start,
|
|
4319
|
-
value: installIdsByBucket.get(start)?.size ?? 0
|
|
4320
|
-
};
|
|
4321
|
-
});
|
|
4322
|
-
};
|
|
4323
|
-
const collectEventActivity = (request) => {
|
|
4324
|
-
const range = request.window === "all" ? void 0 : getWindowRange(request.window, request.cutoffMs);
|
|
4325
|
-
const rows = range === void 0 ? request.rows : request.rows.filter(({ received_at_ms }) => received_at_ms >= range.rangeStart && received_at_ms < request.cutoffMs);
|
|
4326
|
-
const installs = /* @__PURE__ */ new Set();
|
|
4327
|
-
const installsByCohort = /* @__PURE__ */ new Map();
|
|
4328
|
-
for (const row of rows) {
|
|
4329
|
-
installs.add(row.install_id);
|
|
4330
|
-
const cohort = installsByCohort.get(row.cohort) ?? /* @__PURE__ */ new Set();
|
|
4331
|
-
cohort.add(row.install_id);
|
|
4332
|
-
installsByCohort.set(row.cohort, cohort);
|
|
4333
|
-
}
|
|
4529
|
+
const readUserInstallationCursor = (value, userId) => {
|
|
4530
|
+
const cursor = decodeCursor(value);
|
|
4531
|
+
if (!isRecord(cursor) || cursor.version !== 1 || cursor.kind !== "user-installations" || cursor.userId !== userId) throw new InsightsBadRequestError("Insights cursor does not match the requested user ID.");
|
|
4334
4532
|
return {
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
})),
|
|
4340
|
-
series: createSeries({
|
|
4341
|
-
...request,
|
|
4342
|
-
rows
|
|
4343
|
-
})
|
|
4533
|
+
afterInstallId: requireString(cursor.afterInstallId, "installation cursor", MAX_IDENTITY_LENGTH),
|
|
4534
|
+
kind: "user-installations",
|
|
4535
|
+
userId,
|
|
4536
|
+
version: 1
|
|
4344
4537
|
};
|
|
4345
4538
|
};
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
latestStatus: row.type,
|
|
4355
|
-
platform: row.platform,
|
|
4356
|
-
appVersion: row.app_version,
|
|
4357
|
-
channel: row.channel,
|
|
4358
|
-
cohort: row.cohort,
|
|
4359
|
-
receivedAtMs: row.received_at_ms
|
|
4360
|
-
};
|
|
4361
|
-
}
|
|
4362
|
-
function matchesIdentity(row, query) {
|
|
4363
|
-
return row.install_id.toLowerCase().includes(query) || row.user_id?.toLowerCase().includes(query) === true || row.username?.toLowerCase().includes(query) === true;
|
|
4364
|
-
}
|
|
4365
|
-
function searchEventInstallations(request) {
|
|
4366
|
-
const query = request.query.toLowerCase();
|
|
4367
|
-
const matchingInstallIds = /* @__PURE__ */ new Set();
|
|
4368
|
-
for (const row of request.rows) if (query.length === 0 || matchesIdentity(row, query)) matchingInstallIds.add(row.install_id);
|
|
4369
|
-
const latestByInstall = /* @__PURE__ */ new Map();
|
|
4370
|
-
for (const row of request.rows) {
|
|
4371
|
-
if (!matchingInstallIds.has(row.install_id)) continue;
|
|
4372
|
-
const current = latestByInstall.get(row.install_id);
|
|
4373
|
-
if (current === void 0 || compareEventNewest(row, current) < 0) latestByInstall.set(row.install_id, row);
|
|
4539
|
+
const compareEventNewest = (left, right) => right.received_at_ms - left.received_at_ms || compareInsightsText(right.id, left.id);
|
|
4540
|
+
const isAfterEventCursor = (row, after) => row.received_at_ms < after.receivedAtMs || row.received_at_ms === after.receivedAtMs && compareInsightsText(row.id, after.id) < 0;
|
|
4541
|
+
const assertEventRows = (rows, input) => {
|
|
4542
|
+
if (rows.length > input.limit) throw new Error("Insights database returned too many event rows.");
|
|
4543
|
+
for (let index = 0; index < rows.length; index += 1) {
|
|
4544
|
+
const row = rows[index];
|
|
4545
|
+
const previous = rows[index - 1];
|
|
4546
|
+
if (!row || typeof row.id !== "string" || !isUUIDv7(row.id) || !Number.isSafeInteger(row.received_at_ms) || row.received_at_ms >= input.beforeReceivedAtMs || row.received_at_ms < input.sinceMs || previous !== void 0 && compareEventNewest(previous, row) >= 0 || input.after !== void 0 && !isAfterEventCursor(row, input.after) || input.filter.kind === "installationMovement" && (row.install_id !== input.filter.installId || !isInsightsMovementEvent(row)) || input.filter.kind === "bundle" && (row.platform !== input.filter.platform || row.channel !== input.filter.channel || row.type !== input.filter.type || (input.filter.type === "RECOVERED" ? row.from_bundle_id !== input.filter.fromBundleId : row.to_bundle_id !== input.filter.toBundleId))) throw new Error("Insights database returned invalid event rows.");
|
|
4374
4547
|
}
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
total: matchingRows.length,
|
|
4381
|
-
limit: request.limit,
|
|
4382
|
-
offset: request.offset
|
|
4383
|
-
}
|
|
4384
|
-
};
|
|
4385
|
-
}
|
|
4386
|
-
//#endregion
|
|
4387
|
-
//#region ../../packages/server/dist/insights/bounded/persistence.mjs
|
|
4388
|
-
function createBundleEventRow(input) {
|
|
4389
|
-
const base = {
|
|
4390
|
-
id: createUUIDv7(),
|
|
4391
|
-
install_id: input.installId,
|
|
4392
|
-
user_id: input.userId ?? null,
|
|
4393
|
-
username: input.username ?? null,
|
|
4394
|
-
from_release_id: input.fromReleaseId,
|
|
4395
|
-
to_release_id: input.toReleaseId,
|
|
4396
|
-
to_bundle_id: input.toBundleId,
|
|
4397
|
-
platform: input.platform,
|
|
4398
|
-
app_version: input.appVersion,
|
|
4399
|
-
channel: input.channel,
|
|
4400
|
-
cohort: input.cohort,
|
|
4401
|
-
fingerprint_hash: input.fingerprintHash,
|
|
4402
|
-
sdk_version: input.sdkVersion ?? null,
|
|
4403
|
-
received_at_ms: Date.now()
|
|
4404
|
-
};
|
|
4405
|
-
switch (input.type) {
|
|
4406
|
-
case "UPDATE_APPLIED":
|
|
4407
|
-
case "RECOVERED":
|
|
4408
|
-
case "RELEASE_ADOPTED": return {
|
|
4409
|
-
...base,
|
|
4410
|
-
type: input.type,
|
|
4411
|
-
from_bundle_id: input.fromBundleId,
|
|
4412
|
-
update_strategy: input.updateStrategy
|
|
4413
|
-
};
|
|
4414
|
-
case "UNCHANGED": return {
|
|
4415
|
-
...base,
|
|
4416
|
-
type: input.type,
|
|
4417
|
-
from_bundle_id: null,
|
|
4418
|
-
update_strategy: null
|
|
4419
|
-
};
|
|
4420
|
-
}
|
|
4421
|
-
}
|
|
4422
|
-
//#endregion
|
|
4423
|
-
//#region ../../packages/server/dist/insights/bounded/provider.mjs
|
|
4424
|
-
const isTransitionEventRow = (row) => row.type === "UPDATE_APPLIED" || row.type === "RECOVERED";
|
|
4425
|
-
const toHistoryRow = (row) => ({
|
|
4426
|
-
id: row.id,
|
|
4427
|
-
type: row.type,
|
|
4548
|
+
};
|
|
4549
|
+
const toEventHistoryRow = (row) => ({
|
|
4550
|
+
appVersion: row.app_version,
|
|
4551
|
+
channel: row.channel,
|
|
4552
|
+
cohort: row.metadata.cohort,
|
|
4428
4553
|
fromBundleId: row.from_bundle_id,
|
|
4554
|
+
id: row.id,
|
|
4555
|
+
installId: row.install_id,
|
|
4556
|
+
platform: row.platform,
|
|
4557
|
+
receivedAtMs: row.received_at_ms,
|
|
4429
4558
|
toBundleId: row.to_bundle_id,
|
|
4430
|
-
|
|
4559
|
+
type: row.type,
|
|
4431
4560
|
userId: row.user_id,
|
|
4432
|
-
|
|
4561
|
+
username: row.metadata.username
|
|
4562
|
+
});
|
|
4563
|
+
const toInstallationRow = (row) => ({
|
|
4433
4564
|
appVersion: row.app_version,
|
|
4434
4565
|
channel: row.channel,
|
|
4435
|
-
cohort: row.cohort,
|
|
4436
|
-
|
|
4566
|
+
cohort: row.metadata.cohort,
|
|
4567
|
+
installId: row.install_id,
|
|
4568
|
+
lastKnownBundleId: row.type === "UPDATE_DOWNLOADED" ? row.from_bundle_id : row.to_bundle_id,
|
|
4569
|
+
pendingBundleId: row.type === "UPDATE_DOWNLOADED" ? row.to_bundle_id : null,
|
|
4570
|
+
pendingReleaseId: row.type === "UPDATE_DOWNLOADED" ? row.to_release_id : null,
|
|
4571
|
+
latestStatus: row.type,
|
|
4572
|
+
platform: row.platform,
|
|
4573
|
+
receivedAtMs: row.received_at_ms,
|
|
4574
|
+
userId: row.user_id,
|
|
4575
|
+
username: row.metadata.username
|
|
4437
4576
|
});
|
|
4438
|
-
const
|
|
4439
|
-
const
|
|
4440
|
-
const
|
|
4441
|
-
const
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4577
|
+
const pageEventRows = async (model, input, filter, map) => {
|
|
4578
|
+
const limit = readLimit(input.limit);
|
|
4579
|
+
const cursor = input.cursor === void 0 ? void 0 : readEventCursor(input.cursor, filter);
|
|
4580
|
+
const beforeReceivedAtMs = cursor?.beforeReceivedAtMs ?? (input.beforeReceivedAtMs === void 0 ? Date.now() : requireTimestamp(input.beforeReceivedAtMs, "event cutoff"));
|
|
4581
|
+
if (cursor !== void 0 && input.beforeReceivedAtMs !== void 0 && input.beforeReceivedAtMs !== beforeReceivedAtMs) throw new InsightsBadRequestError("Insights cursor does not match the requested event cutoff.");
|
|
4582
|
+
const sinceMs = cursor?.sinceMs ?? (input.sinceMs === void 0 ? 0 : requireTimestamp(input.sinceMs, "event start"));
|
|
4583
|
+
if (sinceMs > beforeReceivedAtMs || input.sinceMs !== void 0 && input.sinceMs !== sinceMs || cursor !== void 0 && (cursor.after.receivedAtMs < sinceMs || cursor.after.receivedAtMs >= beforeReceivedAtMs)) throw new InsightsBadRequestError("Insights cursor or range does not match the requested event start.");
|
|
4584
|
+
const databaseInput = {
|
|
4585
|
+
filter,
|
|
4586
|
+
sinceMs,
|
|
4587
|
+
beforeReceivedAtMs,
|
|
4588
|
+
...cursor === void 0 ? {} : { after: cursor.after },
|
|
4589
|
+
limit: limit + 1
|
|
4445
4590
|
};
|
|
4446
|
-
const rows =
|
|
4447
|
-
|
|
4448
|
-
const
|
|
4449
|
-
const
|
|
4450
|
-
rows: installedRows,
|
|
4451
|
-
window,
|
|
4452
|
-
cutoffMs: scope.cutoffMs
|
|
4453
|
-
});
|
|
4454
|
-
const recovered = collectEventActivity({
|
|
4455
|
-
rows: recoveredRows,
|
|
4456
|
-
window,
|
|
4457
|
-
cutoffMs: scope.cutoffMs
|
|
4458
|
-
});
|
|
4459
|
-
const recentRows = [...installedRows, ...recoveredRows].sort(compareEventNewest);
|
|
4591
|
+
const rows = await model.listEvents(databaseInput);
|
|
4592
|
+
assertEventRows(rows, databaseInput);
|
|
4593
|
+
const pageRows = rows.slice(0, limit);
|
|
4594
|
+
const last = pageRows.at(-1);
|
|
4460
4595
|
return {
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
data: recentRows.slice(offset, offset + limit).map(toHistoryRow),
|
|
4475
|
-
pagination: {
|
|
4476
|
-
total: recentRows.length,
|
|
4477
|
-
limit,
|
|
4478
|
-
offset
|
|
4479
|
-
}
|
|
4480
|
-
}
|
|
4596
|
+
beforeReceivedAtMs,
|
|
4597
|
+
data: pageRows.map(map),
|
|
4598
|
+
nextCursor: rows.length > limit && last ? encodeCursor({
|
|
4599
|
+
after: {
|
|
4600
|
+
id: last.id,
|
|
4601
|
+
receivedAtMs: last.received_at_ms
|
|
4602
|
+
},
|
|
4603
|
+
beforeReceivedAtMs,
|
|
4604
|
+
kind: "events",
|
|
4605
|
+
filter,
|
|
4606
|
+
sinceMs,
|
|
4607
|
+
version: 2
|
|
4608
|
+
}) : null
|
|
4481
4609
|
};
|
|
4482
4610
|
};
|
|
4483
|
-
const
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
const rows = (await materializeRowsForWindow({
|
|
4490
|
-
persistence,
|
|
4491
|
-
cutoffMs: Date.now()
|
|
4492
|
-
}, window)).filter(isTransitionEventRow);
|
|
4493
|
-
for (const row of rows) {
|
|
4494
|
-
const bundleId = row.type === "UPDATE_APPLIED" ? row.to_bundle_id : row.from_bundle_id;
|
|
4495
|
-
if (!requestedBundleIds.has(bundleId)) continue;
|
|
4496
|
-
const counts = row.type === "UPDATE_APPLIED" ? installedByBundleId : recoveredByBundleId;
|
|
4497
|
-
const installIds = counts.get(bundleId) ?? /* @__PURE__ */ new Set();
|
|
4498
|
-
installIds.add(row.install_id);
|
|
4499
|
-
counts.set(bundleId, installIds);
|
|
4611
|
+
const assertInstallationRows = (rows, input) => {
|
|
4612
|
+
if (rows.length > input.limit) throw new Error("Insights database returned too many installation rows.");
|
|
4613
|
+
for (let index = 0; index < rows.length; index += 1) {
|
|
4614
|
+
const row = rows[index];
|
|
4615
|
+
const previous = rows[index - 1];
|
|
4616
|
+
if (!row || row.user_id !== input.userId || input.afterInstallId !== void 0 && compareInsightsText(row.install_id, input.afterInstallId) <= 0 || previous !== void 0 && compareInsightsText(previous.install_id, row.install_id) >= 0) throw new Error("Insights database returned invalid installation rows.");
|
|
4500
4617
|
}
|
|
4501
|
-
return normalizedBundleIds.map((bundleId) => ({
|
|
4502
|
-
bundleId,
|
|
4503
|
-
installed: installedByBundleId.get(bundleId)?.size ?? 0,
|
|
4504
|
-
recovered: recoveredByBundleId.get(bundleId)?.size ?? 0
|
|
4505
|
-
}));
|
|
4506
4618
|
};
|
|
4507
|
-
const createInsightsProvider = (
|
|
4508
|
-
mode: "bounded",
|
|
4509
|
-
maxMatchingRows: INSIGHTS_SCAN_MAX_ROWS,
|
|
4619
|
+
const createInsightsProvider = (model) => Object.freeze({
|
|
4510
4620
|
async appendBundleEvent(input) {
|
|
4511
|
-
|
|
4621
|
+
const event = createBundleEventRow(input);
|
|
4622
|
+
await model.recordEvent({ event });
|
|
4512
4623
|
},
|
|
4513
|
-
|
|
4514
|
-
const
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
})).filter(isTransitionEventRow);
|
|
4518
|
-
return {
|
|
4519
|
-
installed: countDistinctInstallations(rows.filter((row) => isInstalledForBundle(row, bundleId))),
|
|
4520
|
-
recovered: countDistinctInstallations(rows.filter((row) => isRecoveredFromBundle(row, bundleId)))
|
|
4624
|
+
listEvents(input) {
|
|
4625
|
+
const filter = input.bundle === void 0 ? { kind: "all" } : {
|
|
4626
|
+
kind: "bundle",
|
|
4627
|
+
...bundleFilter(input.bundle)
|
|
4521
4628
|
};
|
|
4629
|
+
return pageEventRows(model, input, filter, toEventHistoryRow);
|
|
4522
4630
|
},
|
|
4523
|
-
|
|
4524
|
-
|
|
4631
|
+
async listInstallationEvents(input) {
|
|
4632
|
+
if ("bundle" in input && input.bundle !== void 0) throw new InsightsBadRequestError("Installation movement queries cannot include a bundle filter.");
|
|
4633
|
+
const installId = requireString(input.installId, "install ID", MAX_IDENTITY_LENGTH);
|
|
4634
|
+
return pageEventRows(model, input, {
|
|
4635
|
+
kind: "installationMovement",
|
|
4636
|
+
installId
|
|
4637
|
+
}, (row) => toEventHistoryRow(row));
|
|
4525
4638
|
},
|
|
4526
|
-
|
|
4527
|
-
|
|
4639
|
+
async getInstallation({ installId }) {
|
|
4640
|
+
const normalizedInstallId = requireString(installId, "install ID", MAX_IDENTITY_LENGTH);
|
|
4641
|
+
const rows = await model.findLatestEvents({ installId: normalizedInstallId });
|
|
4642
|
+
const row = rows[0] ?? null;
|
|
4643
|
+
if (rows.length > 1 || row !== null && row.install_id !== normalizedInstallId) throw new Error("Insights database returned an invalid installation.");
|
|
4644
|
+
return row === null ? null : toInstallationRow(row);
|
|
4528
4645
|
},
|
|
4529
|
-
async
|
|
4530
|
-
const
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4646
|
+
async pageInstallationsByCurrentUserId(input) {
|
|
4647
|
+
const userId = requireString(input.userId, "user ID", MAX_IDENTITY_LENGTH);
|
|
4648
|
+
const limit = readLimit(input.limit);
|
|
4649
|
+
const cursor = input.cursor === void 0 ? void 0 : readUserInstallationCursor(input.cursor, userId);
|
|
4650
|
+
const databaseInput = {
|
|
4651
|
+
userId,
|
|
4652
|
+
...cursor === void 0 ? {} : { afterInstallId: cursor.afterInstallId },
|
|
4653
|
+
limit: limit + 1
|
|
4654
|
+
};
|
|
4655
|
+
const rows = await model.findLatestEvents(databaseInput);
|
|
4656
|
+
assertInstallationRows(rows, databaseInput);
|
|
4657
|
+
const pageRows = rows.slice(0, limit);
|
|
4658
|
+
const last = pageRows.at(-1);
|
|
4541
4659
|
return {
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
installations
|
|
4546
|
-
|
|
4660
|
+
data: pageRows.map(toInstallationRow),
|
|
4661
|
+
nextCursor: rows.length > limit && last ? encodeCursor({
|
|
4662
|
+
afterInstallId: last.install_id,
|
|
4663
|
+
kind: "user-installations",
|
|
4664
|
+
userId,
|
|
4665
|
+
version: 1
|
|
4666
|
+
}) : null
|
|
4547
4667
|
};
|
|
4548
4668
|
},
|
|
4549
|
-
async
|
|
4550
|
-
const
|
|
4551
|
-
|
|
4552
|
-
|
|
4553
|
-
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4669
|
+
async getReportingOverview(input) {
|
|
4670
|
+
const scope = readScope(input);
|
|
4671
|
+
const { window } = input;
|
|
4672
|
+
if (!Object.hasOwn(WINDOW_MS, window)) throw new InsightsBadRequestError("Invalid reporting installation window.");
|
|
4673
|
+
const beforeReceivedAtMs = Date.now();
|
|
4674
|
+
const sinceMs = Math.max(0, beforeReceivedAtMs - WINDOW_MS[window]);
|
|
4675
|
+
const measure = async (count) => {
|
|
4676
|
+
const value = await count;
|
|
4677
|
+
if (!Number.isSafeInteger(value) || value < 0) throw new Error("Insights database returned an invalid count.");
|
|
4678
|
+
return {
|
|
4679
|
+
count: value,
|
|
4680
|
+
measuredAtMs: Date.now()
|
|
4681
|
+
};
|
|
4682
|
+
};
|
|
4683
|
+
const bundleId = input.bundleId === void 0 ? void 0 : requireString(input.bundleId, "bundle ID", MAX_EVENT_ID_LENGTH);
|
|
4684
|
+
const reporting = measure(model.countLatestEvents({
|
|
4685
|
+
...scope,
|
|
4686
|
+
sinceMs
|
|
4687
|
+
}));
|
|
4688
|
+
if (bundleId === void 0) return {
|
|
4689
|
+
...scope,
|
|
4690
|
+
window,
|
|
4691
|
+
sinceMs,
|
|
4692
|
+
beforeReceivedAtMs,
|
|
4693
|
+
reportingInstallations: await reporting
|
|
4694
|
+
};
|
|
4695
|
+
const countOutcome = (outcome) => measure(model.countEvents({
|
|
4696
|
+
filter: bundleFilter({
|
|
4697
|
+
...scope,
|
|
4698
|
+
bundleId,
|
|
4699
|
+
outcome
|
|
4566
4700
|
}),
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
|
|
4575
|
-
|
|
4576
|
-
|
|
4701
|
+
sinceMs,
|
|
4702
|
+
beforeReceivedAtMs
|
|
4703
|
+
}));
|
|
4704
|
+
const [reportingInstallations, bundleInstallations, downloadedReports, appliedReports, recoveredReports, unchangedReports] = await Promise.all([
|
|
4705
|
+
reporting,
|
|
4706
|
+
measure(model.countLatestEvents({
|
|
4707
|
+
...scope,
|
|
4708
|
+
sinceMs,
|
|
4709
|
+
bundle: [{
|
|
4710
|
+
field: "from_bundle_id",
|
|
4711
|
+
value: bundleId,
|
|
4712
|
+
types: ["UPDATE_DOWNLOADED"]
|
|
4713
|
+
}, {
|
|
4714
|
+
field: "to_bundle_id",
|
|
4715
|
+
value: bundleId,
|
|
4716
|
+
types: [
|
|
4717
|
+
"UNCHANGED",
|
|
4718
|
+
"UPDATE_APPLIED",
|
|
4719
|
+
"RECOVERED"
|
|
4720
|
+
]
|
|
4721
|
+
}]
|
|
4722
|
+
})),
|
|
4723
|
+
countOutcome("downloaded"),
|
|
4724
|
+
countOutcome("applied"),
|
|
4725
|
+
countOutcome("recovered"),
|
|
4726
|
+
countOutcome("unchanged")
|
|
4727
|
+
]);
|
|
4577
4728
|
return {
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4729
|
+
...scope,
|
|
4730
|
+
window,
|
|
4731
|
+
sinceMs,
|
|
4732
|
+
beforeReceivedAtMs,
|
|
4733
|
+
reportingInstallations,
|
|
4734
|
+
bundle: {
|
|
4735
|
+
bundleId,
|
|
4736
|
+
reportingInstallations: bundleInstallations,
|
|
4737
|
+
downloadedReports,
|
|
4738
|
+
appliedReports,
|
|
4739
|
+
recoveredReports,
|
|
4740
|
+
unchangedReports
|
|
4583
4741
|
}
|
|
4584
4742
|
};
|
|
4585
4743
|
}
|
|
@@ -4620,7 +4778,7 @@ const hashApiKey = async (apiKey) => {
|
|
|
4620
4778
|
return bytesToBase64Url(new Uint8Array(digest));
|
|
4621
4779
|
};
|
|
4622
4780
|
const apiKeyId = () => {
|
|
4623
|
-
const bytes = new Uint8Array(16);
|
|
4781
|
+
const bytes = /* @__PURE__ */ new Uint8Array(16);
|
|
4624
4782
|
crypto.getRandomValues(bytes);
|
|
4625
4783
|
return `api-${bytesToBase64Url(bytes)}`;
|
|
4626
4784
|
};
|
|
@@ -4657,7 +4815,7 @@ const registerApiKey = async (input) => {
|
|
|
4657
4815
|
});
|
|
4658
4816
|
};
|
|
4659
4817
|
const createApiKey = (input) => {
|
|
4660
|
-
const bytes = new Uint8Array(32);
|
|
4818
|
+
const bytes = /* @__PURE__ */ new Uint8Array(32);
|
|
4661
4819
|
crypto.getRandomValues(bytes);
|
|
4662
4820
|
return registerApiKey({
|
|
4663
4821
|
apiKey: bytesToBase64Url(bytes),
|
|
@@ -5077,6 +5235,8 @@ const unsupportedSchemaUpgradeMessage = (version) => `Hot Updater v1 cannot migr
|
|
|
5077
5235
|
//#endregion
|
|
5078
5236
|
//#region ../../packages/server/dist/db/schemaReadiness.mjs
|
|
5079
5237
|
var HotUpdaterSchemaMigrationRequiredError = class extends Error {
|
|
5238
|
+
adapterName;
|
|
5239
|
+
currentVersion;
|
|
5080
5240
|
constructor(adapterName, currentVersion) {
|
|
5081
5241
|
super(currentVersion === void 0 ? `Hot Updater database schema is not initialized for ${adapterName}. Run \`hot-updater db migrate\` before using this adapter.` : unsupportedSchemaUpgradeMessage(currentVersion));
|
|
5082
5242
|
this.adapterName = adapterName;
|
|
@@ -5106,7 +5266,7 @@ const sqlProviders = [
|
|
|
5106
5266
|
const noSqlProviders = ["mongodb"];
|
|
5107
5267
|
[...sqlProviders, ...noSqlProviders];
|
|
5108
5268
|
function isDatabasePlugin(plugin) {
|
|
5109
|
-
return typeof plugin === "object" && plugin !== null && "name" in plugin && typeof plugin.name === "string" && "models" in plugin && typeof plugin.models === "object" && plugin.models !== null && "bundles" in plugin.models && typeof plugin.models.bundles === "object" && plugin.models.bundles !== null && "findById" in plugin.models.bundles && typeof plugin.models.bundles.findById === "function" && "findMany" in plugin.models.bundles && typeof plugin.models.bundles.findMany === "function" && "count" in plugin.models.bundles && typeof plugin.models.bundles.count === "function" && "bundlePatches" in plugin.models && typeof plugin.models.bundlePatches === "object" && plugin.models.bundlePatches !== null && "findByBundleIds" in plugin.models.bundlePatches && typeof plugin.models.bundlePatches.findByBundleIds === "function" && "channels" in plugin.models && typeof plugin.models.channels === "object" && plugin.models.channels !== null && "insert" in plugin.models.channels && typeof plugin.models.channels.insert === "function" && "list" in plugin.models.channels && typeof plugin.models.channels.list === "function" && "delete" in plugin.models.channels && typeof plugin.models.channels.delete === "function" && "insights" in plugin.models && typeof plugin.models.insights === "object" && plugin.models.insights !== null && "
|
|
5269
|
+
return typeof plugin === "object" && plugin !== null && "name" in plugin && typeof plugin.name === "string" && "models" in plugin && typeof plugin.models === "object" && plugin.models !== null && "bundles" in plugin.models && typeof plugin.models.bundles === "object" && plugin.models.bundles !== null && "findById" in plugin.models.bundles && typeof plugin.models.bundles.findById === "function" && "findMany" in plugin.models.bundles && typeof plugin.models.bundles.findMany === "function" && "count" in plugin.models.bundles && typeof plugin.models.bundles.count === "function" && "bundlePatches" in plugin.models && typeof plugin.models.bundlePatches === "object" && plugin.models.bundlePatches !== null && "findByBundleIds" in plugin.models.bundlePatches && typeof plugin.models.bundlePatches.findByBundleIds === "function" && "channels" in plugin.models && typeof plugin.models.channels === "object" && plugin.models.channels !== null && "insert" in plugin.models.channels && typeof plugin.models.channels.insert === "function" && "list" in plugin.models.channels && typeof plugin.models.channels.list === "function" && "delete" in plugin.models.channels && typeof plugin.models.channels.delete === "function" && "insights" in plugin.models && typeof plugin.models.insights === "object" && plugin.models.insights !== null && "recordEvent" in plugin.models.insights && typeof plugin.models.insights.recordEvent === "function" && "listEvents" in plugin.models.insights && typeof plugin.models.insights.listEvents === "function" && "findLatestEvents" in plugin.models.insights && typeof plugin.models.insights.findLatestEvents === "function" && "countEvents" in plugin.models.insights && typeof plugin.models.insights.countEvents === "function" && "countLatestEvents" in plugin.models.insights && typeof plugin.models.insights.countLatestEvents === "function" && "apiKeys" in plugin.models && typeof plugin.models.apiKeys === "object" && plugin.models.apiKeys !== null && "create" in plugin.models.apiKeys && typeof plugin.models.apiKeys.create === "function" && "findByHash" in plugin.models.apiKeys && typeof plugin.models.apiKeys.findByHash === "function" && "list" in plugin.models.apiKeys && typeof plugin.models.apiKeys.list === "function" && "revoke" in plugin.models.apiKeys && typeof plugin.models.apiKeys.revoke === "function" && "commit" in plugin && typeof plugin.commit === "function" && (!("dispose" in plugin) || plugin.dispose === void 0 || typeof plugin.dispose === "function");
|
|
5110
5270
|
}
|
|
5111
5271
|
//#endregion
|
|
5112
5272
|
//#region ../../packages/server/dist/storageAccess.mjs
|
|
@@ -5209,27 +5369,41 @@ const hotUpdaterCoreMetadata = Symbol.for("@hot-updater/server/core-metadata");
|
|
|
5209
5369
|
function createHotUpdaterCore(options) {
|
|
5210
5370
|
for (const key of ["authorityId", "catalogId"]) if (Object.hasOwn(options, key)) throw new TypeError(`Remove ${key} from createHotUpdater options. Catalog identity is managed internally.`);
|
|
5211
5371
|
const database = options.database;
|
|
5212
|
-
const
|
|
5372
|
+
const storagePlugins = (options.storage ?? []).map((storage) => {
|
|
5213
5373
|
assertStorageOperations(storage, ["get", "getDownloadUrl"]);
|
|
5214
5374
|
return storage;
|
|
5215
|
-
})
|
|
5375
|
+
});
|
|
5376
|
+
const { downloadStorageObject, readStorageText, resolveFileUrl } = createStorageAccess(storagePlugins);
|
|
5216
5377
|
const adapterCapabilities = database;
|
|
5217
5378
|
if (!isDatabasePlugin(database)) throw new Error("@hot-updater/server only supports database plugins.");
|
|
5218
5379
|
const plugin = database;
|
|
5219
|
-
const
|
|
5380
|
+
const adapterName = adapterCapabilities.adapterName ?? plugin.name;
|
|
5381
|
+
const assertSchemaReady = createSchemaReadinessChecker(adapterName, adapterCapabilities.createMigrator);
|
|
5220
5382
|
const core = createDatabasePluginCore(plugin, resolveFileUrl, {
|
|
5221
5383
|
beforeOperation: assertSchemaReady,
|
|
5222
5384
|
readStorageText
|
|
5223
5385
|
});
|
|
5224
5386
|
const clientAccess = normalizeClientAccess(options.clientAccess);
|
|
5225
5387
|
const insights = createInsightsProvider({
|
|
5226
|
-
async
|
|
5388
|
+
async recordEvent(input) {
|
|
5389
|
+
await assertSchemaReady();
|
|
5390
|
+
return plugin.models.insights.recordEvent(input);
|
|
5391
|
+
},
|
|
5392
|
+
async listEvents(input) {
|
|
5227
5393
|
await assertSchemaReady();
|
|
5228
|
-
return plugin.models.insights.
|
|
5394
|
+
return plugin.models.insights.listEvents(input);
|
|
5229
5395
|
},
|
|
5230
|
-
async
|
|
5396
|
+
async findLatestEvents(input) {
|
|
5231
5397
|
await assertSchemaReady();
|
|
5232
|
-
return plugin.models.insights.
|
|
5398
|
+
return plugin.models.insights.findLatestEvents(input);
|
|
5399
|
+
},
|
|
5400
|
+
async countLatestEvents(input) {
|
|
5401
|
+
await assertSchemaReady();
|
|
5402
|
+
return plugin.models.insights.countLatestEvents(input);
|
|
5403
|
+
},
|
|
5404
|
+
async countEvents(input) {
|
|
5405
|
+
await assertSchemaReady();
|
|
5406
|
+
return plugin.models.insights.countEvents(input);
|
|
5233
5407
|
}
|
|
5234
5408
|
});
|
|
5235
5409
|
const apiKeys = createApiKeyManagement({
|
|
@@ -5268,8 +5442,60 @@ function createHotUpdater(options) {
|
|
|
5268
5442
|
return createHotUpdaterCore(options).api;
|
|
5269
5443
|
}
|
|
5270
5444
|
//#endregion
|
|
5445
|
+
//#region ../plugin-core/dist/insightsLatestQueries.mjs
|
|
5446
|
+
/** Internal query translation for the bundled CRUD adapters. */
|
|
5447
|
+
const latestInsightsWhere = (input) => {
|
|
5448
|
+
if ("installId" in input) return [{
|
|
5449
|
+
field: "install_id",
|
|
5450
|
+
value: input.installId
|
|
5451
|
+
}];
|
|
5452
|
+
if ("userId" in input) return [{
|
|
5453
|
+
field: "user_id",
|
|
5454
|
+
value: input.userId
|
|
5455
|
+
}, ...input.afterInstallId === void 0 ? [] : [{
|
|
5456
|
+
field: "install_id",
|
|
5457
|
+
operator: "gt",
|
|
5458
|
+
value: input.afterInstallId
|
|
5459
|
+
}]];
|
|
5460
|
+
return [
|
|
5461
|
+
{
|
|
5462
|
+
field: "platform",
|
|
5463
|
+
value: input.platform
|
|
5464
|
+
},
|
|
5465
|
+
{
|
|
5466
|
+
field: "channel",
|
|
5467
|
+
value: input.channel
|
|
5468
|
+
},
|
|
5469
|
+
{
|
|
5470
|
+
field: "received_at_ms",
|
|
5471
|
+
operator: "gte",
|
|
5472
|
+
value: input.sinceMs
|
|
5473
|
+
},
|
|
5474
|
+
...input.bundle === void 0 ? [] : [input.bundle.field === "from_bundle_id" ? {
|
|
5475
|
+
field: "from_bundle_id",
|
|
5476
|
+
value: input.bundle.value
|
|
5477
|
+
} : {
|
|
5478
|
+
field: "to_bundle_id",
|
|
5479
|
+
value: input.bundle.value
|
|
5480
|
+
}, {
|
|
5481
|
+
field: "type",
|
|
5482
|
+
operator: "in",
|
|
5483
|
+
value: input.bundle.types
|
|
5484
|
+
}]
|
|
5485
|
+
];
|
|
5486
|
+
};
|
|
5487
|
+
/** OR groups for one latest-event count; an installation is counted once. */
|
|
5488
|
+
const latestInsightsCountGroups = (input) => input.bundle === void 0 ? [latestInsightsWhere({
|
|
5489
|
+
...input,
|
|
5490
|
+
bundle: void 0
|
|
5491
|
+
})] : input.bundle.map((bundle) => latestInsightsWhere({
|
|
5492
|
+
...input,
|
|
5493
|
+
bundle
|
|
5494
|
+
}));
|
|
5495
|
+
//#endregion
|
|
5271
5496
|
//#region src/firebaseDatabaseParserShared.ts
|
|
5272
5497
|
var FirebaseDatabaseDataError = class extends Error {
|
|
5498
|
+
source;
|
|
5273
5499
|
name = "FirebaseDatabaseDataError";
|
|
5274
5500
|
constructor(source) {
|
|
5275
5501
|
super(`Invalid Firebase database data at "${source}".`);
|
|
@@ -5359,14 +5585,15 @@ const parseFirebaseBundleEventRow = (value, source) => {
|
|
|
5359
5585
|
const input = record(value, source);
|
|
5360
5586
|
const type = string(property(input, "type"), source);
|
|
5361
5587
|
const fromBundleId = requiredNullableString(input, "from_bundle_id", source);
|
|
5362
|
-
const
|
|
5363
|
-
if (!(
|
|
5588
|
+
const metadata = property(input, "metadata");
|
|
5589
|
+
if (!isDatabaseBundleEventMetadata(metadata)) throw new FirebaseDatabaseDataError(source);
|
|
5590
|
+
const updateStrategy = metadata.update_strategy;
|
|
5591
|
+
if (!((type === "UPDATE_DOWNLOADED" || type === "UPDATE_APPLIED" || type === "RECOVERED") && typeof fromBundleId === "string" && (updateStrategy === "fingerprint" || updateStrategy === "appVersion") || type === "UNCHANGED" && fromBundleId === null && updateStrategy === null)) throw new FirebaseDatabaseDataError(source);
|
|
5364
5592
|
return {
|
|
5365
5593
|
id: string(property(input, "id"), source),
|
|
5366
5594
|
type,
|
|
5367
5595
|
install_id: string(property(input, "install_id"), source),
|
|
5368
5596
|
user_id: nullableString(property(input, "user_id"), source),
|
|
5369
|
-
username: nullableString(property(input, "username"), source),
|
|
5370
5597
|
from_release_id: requiredNullableString(input, "from_release_id", source),
|
|
5371
5598
|
from_bundle_id: fromBundleId,
|
|
5372
5599
|
to_release_id: requiredNullableString(input, "to_release_id", source),
|
|
@@ -5374,10 +5601,7 @@ const parseFirebaseBundleEventRow = (value, source) => {
|
|
|
5374
5601
|
platform: platform(property(input, "platform"), source),
|
|
5375
5602
|
app_version: string(property(input, "app_version"), source),
|
|
5376
5603
|
channel: string(property(input, "channel"), source),
|
|
5377
|
-
|
|
5378
|
-
update_strategy: updateStrategy,
|
|
5379
|
-
fingerprint_hash: nullableString(property(input, "fingerprint_hash"), source),
|
|
5380
|
-
sdk_version: nullableString(property(input, "sdk_version"), source),
|
|
5604
|
+
metadata,
|
|
5381
5605
|
received_at_ms: number(property(input, "received_at_ms"), source)
|
|
5382
5606
|
};
|
|
5383
5607
|
};
|
|
@@ -5461,13 +5685,15 @@ const matchesCondition = (row, condition) => {
|
|
|
5461
5685
|
switch (condition.operator ?? "eq") {
|
|
5462
5686
|
case "eq": {
|
|
5463
5687
|
if (typeof expected !== "string") return actual === expected;
|
|
5464
|
-
const
|
|
5688
|
+
const mode = "mode" in condition ? condition.mode : void 0;
|
|
5689
|
+
const comparison = normalizeStringComparison(actual, expected, mode);
|
|
5465
5690
|
return comparison !== null && comparison[0] === comparison[1];
|
|
5466
5691
|
}
|
|
5467
5692
|
case "ne": {
|
|
5468
5693
|
if (actual === null || actual === void 0) return false;
|
|
5469
5694
|
if (typeof expected !== "string") return actual !== expected;
|
|
5470
|
-
const
|
|
5695
|
+
const mode = "mode" in condition ? condition.mode : void 0;
|
|
5696
|
+
const comparison = normalizeStringComparison(actual, expected, mode);
|
|
5471
5697
|
return comparison === null || comparison[0] !== comparison[1];
|
|
5472
5698
|
}
|
|
5473
5699
|
case "gt":
|
|
@@ -5492,17 +5718,20 @@ const matchesCondition = (row, condition) => {
|
|
|
5492
5718
|
}
|
|
5493
5719
|
case "contains": {
|
|
5494
5720
|
if (typeof expected !== "string") return false;
|
|
5495
|
-
const
|
|
5721
|
+
const mode = "mode" in condition ? condition.mode : void 0;
|
|
5722
|
+
const comparison = normalizeStringComparison(actual, expected, mode);
|
|
5496
5723
|
return comparison?.[0].includes(comparison[1]) ?? false;
|
|
5497
5724
|
}
|
|
5498
5725
|
case "starts_with": {
|
|
5499
5726
|
if (typeof expected !== "string") return false;
|
|
5500
|
-
const
|
|
5727
|
+
const mode = "mode" in condition ? condition.mode : void 0;
|
|
5728
|
+
const comparison = normalizeStringComparison(actual, expected, mode);
|
|
5501
5729
|
return comparison?.[0].startsWith(comparison[1]) ?? false;
|
|
5502
5730
|
}
|
|
5503
5731
|
case "ends_with": {
|
|
5504
5732
|
if (typeof expected !== "string") return false;
|
|
5505
|
-
const
|
|
5733
|
+
const mode = "mode" in condition ? condition.mode : void 0;
|
|
5734
|
+
const comparison = normalizeStringComparison(actual, expected, mode);
|
|
5506
5735
|
return comparison?.[0].endsWith(comparison[1]) ?? false;
|
|
5507
5736
|
}
|
|
5508
5737
|
}
|
|
@@ -5547,6 +5776,7 @@ const queryFirebaseDatabaseRows = (rows, input) => {
|
|
|
5547
5776
|
//#endregion
|
|
5548
5777
|
//#region src/firebaseDatabaseState.ts
|
|
5549
5778
|
var FirebaseDatabaseConstraintError = class extends Error {
|
|
5779
|
+
constraint;
|
|
5550
5780
|
name = "FirebaseDatabaseConstraintError";
|
|
5551
5781
|
constructor(constraint) {
|
|
5552
5782
|
super(`Firebase database constraint failed: ${constraint}`);
|
|
@@ -5676,6 +5906,7 @@ const createFirebaseDatabaseState = (snapshot) => ({
|
|
|
5676
5906
|
case "bundles": return distinctCount([...snapshot.bundles.values()].filter((row) => matchesFirebaseDatabaseWhere(row, input.where)), input.distinct);
|
|
5677
5907
|
case "bundle_patches": return distinctCount([...snapshot.bundlePatches.values()].filter((row) => matchesFirebaseDatabaseWhere(row, input.where)), input.distinct);
|
|
5678
5908
|
case "releases": return distinctCount([...snapshot.releases.values()].filter((row) => matchesFirebaseDatabaseWhere(row, input.where)), input.distinct);
|
|
5909
|
+
case "bundle_events": return distinctCount([...snapshot.bundleEvents.values()].filter((row) => matchesFirebaseDatabaseWhere(row, input.where)), input.distinct);
|
|
5679
5910
|
}
|
|
5680
5911
|
},
|
|
5681
5912
|
async findOne(input) {
|
|
@@ -5705,6 +5936,7 @@ const createFirebaseDatabaseState = (snapshot) => ({
|
|
|
5705
5936
|
const FIREBASE_V1_COLLECTION_NAMES = {
|
|
5706
5937
|
apiKeys: "hot_updater_v1_api_keys",
|
|
5707
5938
|
bundleEvents: "hot_updater_v1_bundle_events",
|
|
5939
|
+
insightsLatest: "hot_updater_v1_insights_latest",
|
|
5708
5940
|
bundlePatches: "hot_updater_v1_bundle_patches",
|
|
5709
5941
|
bundles: "hot_updater_v1_bundles",
|
|
5710
5942
|
channels: "hot_updater_v1_channels",
|
|
@@ -5715,6 +5947,7 @@ const FIREBASE_V1_COLLECTION_NAMES = {
|
|
|
5715
5947
|
//#endregion
|
|
5716
5948
|
//#region src/firebaseDatabasePersistence.ts
|
|
5717
5949
|
var FirebaseDatabaseAdapterVersionError = class extends Error {
|
|
5950
|
+
version;
|
|
5718
5951
|
name = "FirebaseDatabaseAdapterVersionError";
|
|
5719
5952
|
constructor(version) {
|
|
5720
5953
|
super(`Unsupported Firebase database adapter version: ${String(version)}`);
|
|
@@ -5725,6 +5958,7 @@ const createFirebaseDatabaseCollections = (db) => ({
|
|
|
5725
5958
|
bundles: db.collection(FIREBASE_V1_COLLECTION_NAMES.bundles),
|
|
5726
5959
|
bundlePatches: db.collection(FIREBASE_V1_COLLECTION_NAMES.bundlePatches),
|
|
5727
5960
|
bundleEvents: db.collection(FIREBASE_V1_COLLECTION_NAMES.bundleEvents),
|
|
5961
|
+
insightsLatest: db.collection(FIREBASE_V1_COLLECTION_NAMES.insightsLatest),
|
|
5728
5962
|
channels: db.collection(FIREBASE_V1_COLLECTION_NAMES.channels),
|
|
5729
5963
|
apiKeys: db.collection(FIREBASE_V1_COLLECTION_NAMES.apiKeys),
|
|
5730
5964
|
releaseCatalogs: db.collection(FIREBASE_V1_COLLECTION_NAMES.releaseCatalogs),
|
|
@@ -5733,14 +5967,15 @@ const createFirebaseDatabaseCollections = (db) => ({
|
|
|
5733
5967
|
});
|
|
5734
5968
|
const firebaseChannelDocumentId = (name) => `name_${Buffer.from(name, "utf8").toString("base64url")}`;
|
|
5735
5969
|
const firebaseChannelIdDocumentId = (id) => `channel_id_${Buffer.from(id, "utf8").toString("base64url")}`;
|
|
5970
|
+
const firebaseInstallationDocumentId = (id) => `install_${Buffer.from(id, "utf8").toString("base64url")}`;
|
|
5736
5971
|
const requireFirebaseDocumentKey = (model, documentId, row) => {
|
|
5737
|
-
if (documentId !== ("id" in row ? row.id : row.scope_key)) throw new FirebaseDatabaseConstraintError(`${model}.id.document-key`);
|
|
5972
|
+
if (documentId !== (model === "insights_latest" ? firebaseInstallationDocumentId(row.install_id) : "id" in row ? row.id : row.scope_key)) throw new FirebaseDatabaseConstraintError(`${model}.id.document-key`);
|
|
5738
5973
|
return row;
|
|
5739
5974
|
};
|
|
5740
5975
|
const documentMap = (model, documents) => {
|
|
5741
5976
|
const rows = /* @__PURE__ */ new Map();
|
|
5742
5977
|
for (const { row } of documents) {
|
|
5743
|
-
const key = "id" in row ? row.id : row.scope_key;
|
|
5978
|
+
const key = model === "insights_latest" ? row.install_id : "id" in row ? row.id : row.scope_key;
|
|
5744
5979
|
if (rows.has(key)) throw new FirebaseDatabaseConstraintError(`${model}.id.unique`);
|
|
5745
5980
|
rows.set(key, row);
|
|
5746
5981
|
}
|
|
@@ -5755,10 +5990,6 @@ const patchMap = (snapshot) => documentMap("bundle_patches", snapshot.docs.map((
|
|
|
5755
5990
|
document,
|
|
5756
5991
|
row: parseFirebasePatchRow(document.data(), `bundle_patches/${document.id}`)
|
|
5757
5992
|
})));
|
|
5758
|
-
const eventMap = (snapshot) => documentMap("bundle_events", snapshot.docs.map((document) => ({
|
|
5759
|
-
document,
|
|
5760
|
-
row: parseFirebaseBundleEventRow(document.data(), `bundle_events/${document.id}`)
|
|
5761
|
-
})));
|
|
5762
5993
|
const channelMap = (snapshot) => {
|
|
5763
5994
|
const rows = /* @__PURE__ */ new Map();
|
|
5764
5995
|
const names = /* @__PURE__ */ new Set();
|
|
@@ -5789,18 +6020,17 @@ const toSnapshot = (documents) => {
|
|
|
5789
6020
|
return {
|
|
5790
6021
|
bundles: bundleMap(documents[0]),
|
|
5791
6022
|
bundlePatches: patchMap(documents[1]),
|
|
5792
|
-
bundleEvents:
|
|
5793
|
-
channels: channelMap(documents[
|
|
5794
|
-
apiKeys: apiKeyMap(documents[
|
|
5795
|
-
releases: releaseMap(documents[
|
|
5796
|
-
releaseCatalogs: releaseCatalogMap(documents[
|
|
6023
|
+
bundleEvents: /* @__PURE__ */ new Map(),
|
|
6024
|
+
channels: channelMap(documents[2]),
|
|
6025
|
+
apiKeys: apiKeyMap(documents[3]),
|
|
6026
|
+
releases: releaseMap(documents[4]),
|
|
6027
|
+
releaseCatalogs: releaseCatalogMap(documents[5])
|
|
5797
6028
|
};
|
|
5798
6029
|
};
|
|
5799
6030
|
const loadFirebaseDatabaseSnapshot = async (collections) => {
|
|
5800
|
-
const [bundles, patches,
|
|
6031
|
+
const [bundles, patches, channels, apiKeys, releases, releaseCatalogs] = await Promise.all([
|
|
5801
6032
|
collections.bundles.get(),
|
|
5802
6033
|
collections.bundlePatches.get(),
|
|
5803
|
-
collections.bundleEvents.get(),
|
|
5804
6034
|
collections.channels.get(),
|
|
5805
6035
|
collections.apiKeys.get(),
|
|
5806
6036
|
collections.releases.get(),
|
|
@@ -5809,7 +6039,6 @@ const loadFirebaseDatabaseSnapshot = async (collections) => {
|
|
|
5809
6039
|
return toSnapshot([
|
|
5810
6040
|
bundles,
|
|
5811
6041
|
patches,
|
|
5812
|
-
events,
|
|
5813
6042
|
channels,
|
|
5814
6043
|
apiKeys,
|
|
5815
6044
|
releases,
|
|
@@ -5817,10 +6046,9 @@ const loadFirebaseDatabaseSnapshot = async (collections) => {
|
|
|
5817
6046
|
]);
|
|
5818
6047
|
};
|
|
5819
6048
|
const loadFirebaseTransactionSnapshot = async (transaction, collections) => {
|
|
5820
|
-
const [bundles, patches,
|
|
6049
|
+
const [bundles, patches, channels, apiKeys, releases, releaseCatalogs] = await Promise.all([
|
|
5821
6050
|
transaction.get(collections.bundles),
|
|
5822
6051
|
transaction.get(collections.bundlePatches),
|
|
5823
|
-
transaction.get(collections.bundleEvents),
|
|
5824
6052
|
transaction.get(collections.channels),
|
|
5825
6053
|
transaction.get(collections.apiKeys),
|
|
5826
6054
|
transaction.get(collections.releases),
|
|
@@ -5829,7 +6057,6 @@ const loadFirebaseTransactionSnapshot = async (transaction, collections) => {
|
|
|
5829
6057
|
return toSnapshot([
|
|
5830
6058
|
bundles,
|
|
5831
6059
|
patches,
|
|
5832
|
-
events,
|
|
5833
6060
|
channels,
|
|
5834
6061
|
apiKeys,
|
|
5835
6062
|
releases,
|
|
@@ -5855,13 +6082,6 @@ const persistFirebaseDatabaseSnapshot = ({ transaction, collections, before, aft
|
|
|
5855
6082
|
after: after.bundlePatches,
|
|
5856
6083
|
documentId: (row) => row.id
|
|
5857
6084
|
});
|
|
5858
|
-
persistCollection({
|
|
5859
|
-
transaction,
|
|
5860
|
-
collection: collections.bundleEvents,
|
|
5861
|
-
before: before.bundleEvents,
|
|
5862
|
-
after: after.bundleEvents,
|
|
5863
|
-
documentId: (row) => row.id
|
|
5864
|
-
});
|
|
5865
6085
|
persistCollection({
|
|
5866
6086
|
transaction,
|
|
5867
6087
|
collection: collections.channels,
|
|
@@ -5893,7 +6113,7 @@ const persistFirebaseDatabaseSnapshot = ({ transaction, collections, before, aft
|
|
|
5893
6113
|
documentId: (row) => row.scope_key
|
|
5894
6114
|
});
|
|
5895
6115
|
};
|
|
5896
|
-
const migrateFirebaseDatabase = async (
|
|
6116
|
+
const migrateFirebaseDatabase = async (collections) => {
|
|
5897
6117
|
const versionDocument = collections.settings.doc("database_adapter_version");
|
|
5898
6118
|
const version = await versionDocument.get();
|
|
5899
6119
|
const adapterVersion = version.data()?.version;
|
|
@@ -5904,7 +6124,9 @@ const migrateFirebaseDatabase = async (_db, collections) => {
|
|
|
5904
6124
|
collections.bundlePatches.limit(1).get(),
|
|
5905
6125
|
collections.channels.limit(1).get(),
|
|
5906
6126
|
collections.releases.limit(1).get(),
|
|
5907
|
-
collections.releaseCatalogs.limit(1).get()
|
|
6127
|
+
collections.releaseCatalogs.limit(1).get(),
|
|
6128
|
+
collections.insightsLatest.limit(1).get(),
|
|
6129
|
+
collections.bundleEvents.limit(1).get()
|
|
5908
6130
|
])).some((snapshot) => !snapshot.empty)) throw new FirebaseDatabaseAdapterVersionError("v0");
|
|
5909
6131
|
try {
|
|
5910
6132
|
await versionDocument.create({ version: 4 });
|
|
@@ -5919,13 +6141,36 @@ const exactId = (input) => {
|
|
|
5919
6141
|
const [condition] = input.where;
|
|
5920
6142
|
return condition.field === "id" && (condition.operator === void 0 || condition.operator === "eq") && typeof condition.value === "string" ? condition.value : void 0;
|
|
5921
6143
|
};
|
|
6144
|
+
const firestoreOperator = (operator) => {
|
|
6145
|
+
switch (operator ?? "eq") {
|
|
6146
|
+
case "eq": return "==";
|
|
6147
|
+
case "ne": return "!=";
|
|
6148
|
+
case "gt": return ">";
|
|
6149
|
+
case "gte": return ">=";
|
|
6150
|
+
case "lt": return "<";
|
|
6151
|
+
case "lte": return "<=";
|
|
6152
|
+
case "in": return "in";
|
|
6153
|
+
case "not_in": return "not-in";
|
|
6154
|
+
default: return;
|
|
6155
|
+
}
|
|
6156
|
+
};
|
|
6157
|
+
const applyFirebaseWhere = (initial, where) => {
|
|
6158
|
+
let query = initial;
|
|
6159
|
+
for (const condition of where) {
|
|
6160
|
+
const operator = firestoreOperator(condition.operator);
|
|
6161
|
+
if (condition.connector === "OR" || operator === void 0) throw new FirebaseDatabaseConstraintError("query.unsupported");
|
|
6162
|
+
query = query.where(condition.field, operator, condition.value);
|
|
6163
|
+
}
|
|
6164
|
+
return query;
|
|
6165
|
+
};
|
|
5922
6166
|
const firebaseDatabase = (config) => {
|
|
5923
|
-
const
|
|
5924
|
-
const
|
|
6167
|
+
const implementation = (() => {
|
|
6168
|
+
const app = (0, firebase_admin_app.getApps)().length ? (0, firebase_admin_app.getApp)() : (0, firebase_admin_app.initializeApp)(config);
|
|
6169
|
+
const db = (0, firebase_admin_firestore.getFirestore)(app);
|
|
5925
6170
|
const collections = createFirebaseDatabaseCollections(db);
|
|
5926
6171
|
let migration;
|
|
5927
6172
|
const ensureMigrated = () => {
|
|
5928
|
-
migration ??= migrateFirebaseDatabase(
|
|
6173
|
+
migration ??= migrateFirebaseDatabase(collections).catch((error) => {
|
|
5929
6174
|
migration = void 0;
|
|
5930
6175
|
throw error;
|
|
5931
6176
|
});
|
|
@@ -5948,13 +6193,52 @@ const firebaseDatabase = (config) => {
|
|
|
5948
6193
|
};
|
|
5949
6194
|
const read = async (operation) => {
|
|
5950
6195
|
await ensureMigrated();
|
|
5951
|
-
|
|
6196
|
+
const snapshot = await loadFirebaseDatabaseSnapshot(collections);
|
|
6197
|
+
return operation(createFirebaseDatabaseState(snapshot));
|
|
5952
6198
|
};
|
|
5953
6199
|
return {
|
|
5954
|
-
|
|
6200
|
+
recordInsights: async ({ event }) => {
|
|
6201
|
+
await ensureMigrated();
|
|
6202
|
+
await db.runTransaction(async (transaction) => {
|
|
6203
|
+
const eventReference = collections.bundleEvents.doc(event.id);
|
|
6204
|
+
const installationReference = collections.insightsLatest.doc(firebaseInstallationDocumentId(event.install_id));
|
|
6205
|
+
const [storedEvent, storedInstallation] = await transaction.getAll(eventReference, installationReference);
|
|
6206
|
+
if (storedEvent.exists) return;
|
|
6207
|
+
const current = storedInstallation.exists ? requireFirebaseDocumentKey("insights_latest", storedInstallation.id, parseFirebaseBundleEventRow(storedInstallation.data(), `insights_latest/${storedInstallation.id}`)) : null;
|
|
6208
|
+
transaction.create(eventReference, event);
|
|
6209
|
+
if (current === null || event.received_at_ms > current.received_at_ms || event.received_at_ms === current.received_at_ms && compareInsightsText(event.id, current.id) > 0) transaction.set(installationReference, event);
|
|
6210
|
+
});
|
|
6211
|
+
},
|
|
6212
|
+
findLatestInsightsEvents: async (input) => {
|
|
6213
|
+
await ensureMigrated();
|
|
6214
|
+
if ("installId" in input) {
|
|
6215
|
+
const document = await collections.insightsLatest.doc(firebaseInstallationDocumentId(input.installId)).get();
|
|
6216
|
+
return document.exists ? [requireFirebaseDocumentKey("insights_latest", document.id, parseFirebaseBundleEventRow(document.data(), `insights_latest/${document.id}`))] : [];
|
|
6217
|
+
}
|
|
6218
|
+
return (await applyFirebaseWhere(collections.insightsLatest, latestInsightsWhere(input)).orderBy("install_id", "asc").limit(input.limit).get()).docs.map((document) => requireFirebaseDocumentKey("insights_latest", document.id, parseFirebaseBundleEventRow(document.data(), `insights_latest/${document.id}`)));
|
|
6219
|
+
},
|
|
6220
|
+
countLatestInsightsEvents: async (input) => {
|
|
6221
|
+
await ensureMigrated();
|
|
6222
|
+
const filter = firebase_admin_firestore.Filter.or(...latestInsightsCountGroups(input).map((where) => firebase_admin_firestore.Filter.and(...where.map((condition) => {
|
|
6223
|
+
const operator = firestoreOperator(condition.operator);
|
|
6224
|
+
if (operator === void 0) throw new FirebaseDatabaseConstraintError("query.unsupported");
|
|
6225
|
+
return firebase_admin_firestore.Filter.where(condition.field, operator, condition.value);
|
|
6226
|
+
}))));
|
|
6227
|
+
return (await collections.insightsLatest.where(filter).count().get()).data().count;
|
|
6228
|
+
},
|
|
6229
|
+
create: async (input) => {
|
|
6230
|
+
if (input.model !== "bundle_events") return mutate((database) => database.create(input));
|
|
6231
|
+
await ensureMigrated();
|
|
6232
|
+
await collections.bundleEvents.doc(input.data.id).create(input.data);
|
|
6233
|
+
return input.data;
|
|
6234
|
+
},
|
|
5955
6235
|
update: (input) => mutate((database) => database.update(input)),
|
|
5956
6236
|
delete: (input) => mutate((database) => database.delete(input)),
|
|
5957
|
-
count: (input) =>
|
|
6237
|
+
count: async (input) => {
|
|
6238
|
+
if (input.model !== "bundle_events") return read((database) => database.count(input));
|
|
6239
|
+
await ensureMigrated();
|
|
6240
|
+
return (await applyFirebaseWhere(collections.bundleEvents, input.where ?? []).orderBy("received_at_ms", "desc").orderBy("id", "desc").count().get()).data().count;
|
|
6241
|
+
},
|
|
5958
6242
|
findOne: async (input) => {
|
|
5959
6243
|
const id = exactId(input);
|
|
5960
6244
|
if (id === void 0) return read((database) => database.findOne(input));
|
|
@@ -5976,9 +6260,20 @@ const firebaseDatabase = (config) => {
|
|
|
5976
6260
|
}
|
|
5977
6261
|
},
|
|
5978
6262
|
findMany: async (input) => {
|
|
5979
|
-
if (input.model
|
|
5980
|
-
|
|
5981
|
-
|
|
6263
|
+
if (input.model === "bundle_events") {
|
|
6264
|
+
await ensureMigrated();
|
|
6265
|
+
let query = applyFirebaseWhere(collections.bundleEvents, input.where ?? []);
|
|
6266
|
+
for (const order of input.orderBy ?? []) {
|
|
6267
|
+
if (order.nulls !== void 0) throw new FirebaseDatabaseConstraintError("query.unsupported");
|
|
6268
|
+
query = query.orderBy(order.field, order.direction);
|
|
6269
|
+
}
|
|
6270
|
+
return (await query.offset(input.offset).limit(input.limit).get()).docs.map((document) => requireFirebaseDocumentKey("bundle_events", document.id, parseFirebaseBundleEventRow(document.data(), `bundle_events/${document.id}`)));
|
|
6271
|
+
}
|
|
6272
|
+
if (input.model === "channels") {
|
|
6273
|
+
await ensureMigrated();
|
|
6274
|
+
return queryFirebaseDatabaseRows(await loadFirebaseChannels(collections), input);
|
|
6275
|
+
}
|
|
6276
|
+
return read((database) => database.findMany(input));
|
|
5982
6277
|
},
|
|
5983
6278
|
insertChannel: async (input) => {
|
|
5984
6279
|
await ensureMigrated();
|
|
@@ -6030,7 +6325,8 @@ const firebaseDatabase = (config) => {
|
|
|
6030
6325
|
},
|
|
6031
6326
|
transaction: (callback) => mutate(callback)
|
|
6032
6327
|
};
|
|
6033
|
-
})()
|
|
6328
|
+
})();
|
|
6329
|
+
const adapter = createDatabasePluginAdapter("firebaseDatabase", implementation);
|
|
6034
6330
|
return createDatabasePlugin({
|
|
6035
6331
|
name: "firebaseDatabase",
|
|
6036
6332
|
models: adapter.models,
|
|
@@ -6040,7 +6336,8 @@ const firebaseDatabase = (config) => {
|
|
|
6040
6336
|
//#endregion
|
|
6041
6337
|
//#region src/firebaseStorage.ts
|
|
6042
6338
|
const firebaseStorage = (config) => {
|
|
6043
|
-
const
|
|
6339
|
+
const app = (0, firebase_admin_app.getApps)().length ? (0, firebase_admin_app.getApp)() : (0, firebase_admin_app.initializeApp)(config);
|
|
6340
|
+
const bucket = (0, firebase_admin_storage.getStorage)(app).bucket(config.storageBucket);
|
|
6044
6341
|
const getStorageKey = createStorageKeyBuilder(config.basePath);
|
|
6045
6342
|
const parseAndValidate = (storageUri) => {
|
|
6046
6343
|
const parsed = parseStorageUri(storageUri, "gs");
|
|
@@ -6134,7 +6431,7 @@ const hot = { updater: { v1: (0, firebase_functions_v2_https.onRequest)({ region
|
|
|
6134
6431
|
const request = new Request(fullUrl, {
|
|
6135
6432
|
method: req.method,
|
|
6136
6433
|
headers: req.headers,
|
|
6137
|
-
body: req.method !== "GET" && req.method !== "HEAD" ? req.
|
|
6434
|
+
body: req.method !== "GET" && req.method !== "HEAD" ? new Uint8Array(req.rawBody) : void 0
|
|
6138
6435
|
});
|
|
6139
6436
|
const honoResponse = await app.fetch(request);
|
|
6140
6437
|
res.status(honoResponse.status);
|