@hot-updater/cloudflare 1.0.0-rc.2 → 1.0.0-rc.3
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/iac/index.cjs +281 -161
- package/dist/iac/index.d.cts +1 -5
- package/dist/iac/index.d.mts +1 -5
- package/dist/iac/index.mjs +277 -157
- package/dist/index.cjs +69 -14
- package/dist/index.d.cts +1 -3
- package/dist/index.d.mts +1 -3
- package/dist/index.mjs +67 -12
- package/dist/worker/index.cjs +71 -15
- package/dist/worker/index.d.cts +2 -3
- package/dist/worker/index.d.mts +2 -3
- package/dist/worker/index.mjs +71 -15
- package/package.json +6 -6
- package/sql/bundles.sql +29 -7
- package/src/d1Database.spec.ts +99 -0
- package/src/d1Database.ts +7 -1
- package/src/d1Implementation.spec.ts +90 -0
- package/src/d1Implementation.ts +42 -3
- package/src/d1Query.ts +3 -0
- package/src/d1Rows.spec.ts +32 -0
- package/src/d1Rows.ts +35 -0
- package/src/worker/d1Database.spec.ts +36 -0
- package/src/worker/d1Database.ts +9 -1
- package/worker/dist/README.md +1 -1
- package/worker/dist/index.js +997 -659
- package/worker/dist/index.js.map +4 -4
- package/worker/migrations/0001_hot-updater_1.0.0.sql +30 -13
- package/worker/src/databaseMigration.integration.spec.ts +62 -1
- package/worker/src/databasePlugins.integration.spec.ts +35 -1
- package/worker/src/insightsMovement.integration.spec.ts +73 -0
package/worker/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@ var __defProp = Object.defineProperty;
|
|
|
2
2
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
3
|
|
|
4
4
|
// ../../packages/server/dist/package.mjs
|
|
5
|
-
var version = "1.0.0-rc.
|
|
5
|
+
var version = "1.0.0-rc.2";
|
|
6
6
|
|
|
7
7
|
// ../../packages/server/dist/version.mjs
|
|
8
8
|
var HOT_UPDATER_SERVER_VERSION = version;
|
|
@@ -303,7 +303,8 @@ __name(parseStorageUri, "parseStorageUri");
|
|
|
303
303
|
var isBrotliManifestAssetPath = /* @__PURE__ */ __name((assetPath) => /(^|\/)index\.[^/]+\.bundle$/.test(assetPath.replace(/\\/g, "/")), "isBrotliManifestAssetPath");
|
|
304
304
|
var getManifestAssetDownloadPath = /* @__PURE__ */ __name((assetPath) => isBrotliManifestAssetPath(assetPath) ? `${assetPath}.br` : assetPath, "getManifestAssetDownloadPath");
|
|
305
305
|
var createStorageUriWithRelativePath = /* @__PURE__ */ __name(({ baseStorageUri, relativePath }) => {
|
|
306
|
-
const
|
|
306
|
+
const protocol = new URL(baseStorageUri).protocol.replace(":", "");
|
|
307
|
+
const parsed = parseStorageUri(baseStorageUri, protocol);
|
|
307
308
|
const childKey = createStorageKeyBuilder(void 0)(relativePath);
|
|
308
309
|
if (!childKey) throw new Error("Relative storage key must not be empty.");
|
|
309
310
|
return createStorageUri({
|
|
@@ -334,6 +335,7 @@ var DatabasePluginInputError = class extends Error {
|
|
|
334
335
|
static {
|
|
335
336
|
__name(this, "DatabasePluginInputError");
|
|
336
337
|
}
|
|
338
|
+
code;
|
|
337
339
|
name = "DatabasePluginInputError";
|
|
338
340
|
constructor(code) {
|
|
339
341
|
super(`Invalid database plugin input: ${code}`);
|
|
@@ -502,7 +504,7 @@ var RELEASE_CATALOG_SCHEMA_VERSION = 1;
|
|
|
502
504
|
var RELEASE_CATALOG_FALLBACK_POLICY = "BUILTIN_IF_ACTIVE_INELIGIBLE";
|
|
503
505
|
var MAX_TARGET_COHORTS_PER_RELEASE = 100;
|
|
504
506
|
var MAX_DISTINCT_TARGET_COHORTS_PER_SCOPE = 512;
|
|
505
|
-
var MAX_COMPILED_CATALOG_BYTES =
|
|
507
|
+
var MAX_COMPILED_CATALOG_BYTES = 262144;
|
|
506
508
|
var BASE64URL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
507
509
|
var MAX_CATALOG_SEGMENT_LENGTH = 255;
|
|
508
510
|
function assertCatalogSegment(value, name) {
|
|
@@ -620,7 +622,7 @@ var isUUIDv7 = /* @__PURE__ */ __name((value) => typeof value === "string" && UU
|
|
|
620
622
|
|
|
621
623
|
// ../plugin-core/dist/uuidv7.mjs
|
|
622
624
|
function createUUIDv7FromTimestampHex(timestampHex) {
|
|
623
|
-
const randomBytes = new Uint8Array(10);
|
|
625
|
+
const randomBytes = /* @__PURE__ */ new Uint8Array(10);
|
|
624
626
|
crypto.getRandomValues(randomBytes);
|
|
625
627
|
const randomHex = Array.from(randomBytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
626
628
|
const randA = randomHex.slice(0, 3);
|
|
@@ -718,6 +720,19 @@ var databaseFields = {
|
|
|
718
720
|
"sdk_version",
|
|
719
721
|
"received_at_ms"
|
|
720
722
|
],
|
|
723
|
+
bundle_installations: [
|
|
724
|
+
"id",
|
|
725
|
+
"install_id",
|
|
726
|
+
"user_id",
|
|
727
|
+
"username",
|
|
728
|
+
"to_bundle_id",
|
|
729
|
+
"type",
|
|
730
|
+
"platform",
|
|
731
|
+
"app_version",
|
|
732
|
+
"channel",
|
|
733
|
+
"cohort",
|
|
734
|
+
"received_at_ms"
|
|
735
|
+
],
|
|
721
736
|
api_keys: [
|
|
722
737
|
"id",
|
|
723
738
|
"hash",
|
|
@@ -740,6 +755,8 @@ var isChannelText2 = /* @__PURE__ */ __name((value) => {
|
|
|
740
755
|
}
|
|
741
756
|
return true;
|
|
742
757
|
}, "isChannelText");
|
|
758
|
+
var isInsightsIdentityText = /* @__PURE__ */ __name((value) => typeof value === "string" && value.length > 0 && value.length <= 255, "isInsightsIdentityText");
|
|
759
|
+
var isNullableInsightsIdentityText = /* @__PURE__ */ __name((value) => value === null || isInsightsIdentityText(value), "isNullableInsightsIdentityText");
|
|
743
760
|
var modelValidators = {
|
|
744
761
|
bundles: {
|
|
745
762
|
id: /* @__PURE__ */ __name((value) => typeof value === "string", "id"),
|
|
@@ -815,8 +832,8 @@ var modelValidators = {
|
|
|
815
832
|
bundle_events: {
|
|
816
833
|
id: /* @__PURE__ */ __name((value) => typeof value === "string", "id"),
|
|
817
834
|
type: /* @__PURE__ */ __name((value) => value === "UPDATE_APPLIED" || value === "RECOVERED" || value === "RELEASE_ADOPTED" || value === "UNCHANGED", "type"),
|
|
818
|
-
install_id:
|
|
819
|
-
user_id:
|
|
835
|
+
install_id: isInsightsIdentityText,
|
|
836
|
+
user_id: isNullableInsightsIdentityText,
|
|
820
837
|
username: /* @__PURE__ */ __name((value) => value === null || typeof value === "string", "username"),
|
|
821
838
|
from_bundle_id: /* @__PURE__ */ __name((value) => value === null || typeof value === "string", "from_bundle_id"),
|
|
822
839
|
from_release_id: /* @__PURE__ */ __name((value) => value === null || typeof value === "string", "from_release_id"),
|
|
@@ -831,6 +848,19 @@ var modelValidators = {
|
|
|
831
848
|
sdk_version: /* @__PURE__ */ __name((value) => value === null || typeof value === "string", "sdk_version"),
|
|
832
849
|
received_at_ms: /* @__PURE__ */ __name((value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0, "received_at_ms")
|
|
833
850
|
},
|
|
851
|
+
bundle_installations: {
|
|
852
|
+
id: /* @__PURE__ */ __name((value) => typeof value === "string", "id"),
|
|
853
|
+
install_id: isInsightsIdentityText,
|
|
854
|
+
user_id: isNullableInsightsIdentityText,
|
|
855
|
+
username: /* @__PURE__ */ __name((value) => value === null || typeof value === "string", "username"),
|
|
856
|
+
to_bundle_id: /* @__PURE__ */ __name((value) => typeof value === "string", "to_bundle_id"),
|
|
857
|
+
type: /* @__PURE__ */ __name((value) => value === "UPDATE_APPLIED" || value === "RECOVERED" || value === "RELEASE_ADOPTED" || value === "UNCHANGED", "type"),
|
|
858
|
+
platform: /* @__PURE__ */ __name((value) => value === "ios" || value === "android", "platform"),
|
|
859
|
+
app_version: /* @__PURE__ */ __name((value) => typeof value === "string", "app_version"),
|
|
860
|
+
channel: /* @__PURE__ */ __name((value) => typeof value === "string", "channel"),
|
|
861
|
+
cohort: /* @__PURE__ */ __name((value) => typeof value === "string", "cohort"),
|
|
862
|
+
received_at_ms: /* @__PURE__ */ __name((value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0, "received_at_ms")
|
|
863
|
+
},
|
|
834
864
|
api_keys: {
|
|
835
865
|
id: /* @__PURE__ */ __name((value) => typeof value === "string", "id"),
|
|
836
866
|
hash: /* @__PURE__ */ __name((value) => typeof value === "string", "hash"),
|
|
@@ -982,6 +1012,19 @@ var sortableFields = {
|
|
|
982
1012
|
"sdk_version",
|
|
983
1013
|
"received_at_ms"
|
|
984
1014
|
]),
|
|
1015
|
+
bundle_installations: /* @__PURE__ */ new Set([
|
|
1016
|
+
"id",
|
|
1017
|
+
"install_id",
|
|
1018
|
+
"user_id",
|
|
1019
|
+
"username",
|
|
1020
|
+
"to_bundle_id",
|
|
1021
|
+
"type",
|
|
1022
|
+
"platform",
|
|
1023
|
+
"app_version",
|
|
1024
|
+
"channel",
|
|
1025
|
+
"cohort",
|
|
1026
|
+
"received_at_ms"
|
|
1027
|
+
]),
|
|
985
1028
|
api_keys: /* @__PURE__ */ new Set([
|
|
986
1029
|
"id",
|
|
987
1030
|
"hash",
|
|
@@ -1054,15 +1097,176 @@ var validateResult = /* @__PURE__ */ __name((model, row, select) => {
|
|
|
1054
1097
|
].every((field) => Object.hasOwn(row, field)) && !hasValidBundleEventInvariants(row)) throw new DatabasePluginInputError("invalid-result");
|
|
1055
1098
|
}, "validateResult");
|
|
1056
1099
|
|
|
1100
|
+
// ../plugin-core/dist/insightsContract.mjs
|
|
1101
|
+
var encoder = new TextEncoder();
|
|
1102
|
+
var compareInsightsText = /* @__PURE__ */ __name((left, right) => {
|
|
1103
|
+
const a = encoder.encode(left);
|
|
1104
|
+
const b = encoder.encode(right);
|
|
1105
|
+
for (let index = 0; index < Math.min(a.length, b.length); index += 1) if (a[index] !== b[index]) return a[index] - b[index];
|
|
1106
|
+
return a.length - b.length;
|
|
1107
|
+
}, "compareInsightsText");
|
|
1108
|
+
var isWellFormedText = /* @__PURE__ */ __name((value) => {
|
|
1109
|
+
for (const character of value) {
|
|
1110
|
+
const point = character.codePointAt(0);
|
|
1111
|
+
if (point >= 55296 && point <= 57343) return false;
|
|
1112
|
+
}
|
|
1113
|
+
return true;
|
|
1114
|
+
}, "isWellFormedText");
|
|
1115
|
+
var isIdentity = /* @__PURE__ */ __name((value) => typeof value === "string" && value.length > 0 && value.length <= 255 && isWellFormedText(value), "isIdentity");
|
|
1116
|
+
var isText = /* @__PURE__ */ __name((value) => typeof value === "string" && value.length > 0 && isWellFormedText(value), "isText");
|
|
1117
|
+
var isTimestamp = /* @__PURE__ */ __name((value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0, "isTimestamp");
|
|
1118
|
+
var isLimit = /* @__PURE__ */ __name((value) => isTimestamp(value) && value >= 1 && value <= 101, "isLimit");
|
|
1119
|
+
var hasOnlyKeys = /* @__PURE__ */ __name((value, keys) => Object.keys(value).every((key) => keys.includes(key)), "hasOnlyKeys");
|
|
1120
|
+
var hasScope = /* @__PURE__ */ __name((value) => (value.platform === "ios" || value.platform === "android") && isText(value.channel), "hasScope");
|
|
1121
|
+
var isBundleFilter = /* @__PURE__ */ __name((value, withKind = false) => {
|
|
1122
|
+
if (!isRecord(value) || !hasScope(value)) return false;
|
|
1123
|
+
const keys = [
|
|
1124
|
+
"platform",
|
|
1125
|
+
"channel",
|
|
1126
|
+
"type",
|
|
1127
|
+
...withKind ? ["kind"] : []
|
|
1128
|
+
];
|
|
1129
|
+
return value.type === "RECOVERED" ? isText(value.fromBundleId) && hasOnlyKeys(value, [...keys, "fromBundleId"]) : (value.type === "UPDATE_APPLIED" || value.type === "RELEASE_ADOPTED") && isText(value.toBundleId) && hasOnlyKeys(value, [...keys, "toBundleId"]);
|
|
1130
|
+
}, "isBundleFilter");
|
|
1131
|
+
var isEventFilter = /* @__PURE__ */ __name((value) => {
|
|
1132
|
+
if (!isRecord(value)) return false;
|
|
1133
|
+
if (value.kind === "all") return hasOnlyKeys(value, ["kind"]);
|
|
1134
|
+
if (value.kind === "installationMovement") return isIdentity(value.installId) && hasOnlyKeys(value, ["kind", "installId"]);
|
|
1135
|
+
return value.kind === "bundle" && isBundleFilter(value, true);
|
|
1136
|
+
}, "isEventFilter");
|
|
1137
|
+
var validateRow = /* @__PURE__ */ __name((model, row, result = false) => {
|
|
1138
|
+
try {
|
|
1139
|
+
validateCreateData(model, row);
|
|
1140
|
+
if (!isRecord(row) || typeof row.id !== "string" || !isUUIDv7(row.id) || Object.values(row).some((value) => typeof value === "string" && !isWellFormedText(value))) throw new DatabasePluginInputError("invalid-data");
|
|
1141
|
+
} catch (error) {
|
|
1142
|
+
if (result) throw new DatabasePluginInputError("invalid-result");
|
|
1143
|
+
throw error;
|
|
1144
|
+
}
|
|
1145
|
+
}, "validateRow");
|
|
1146
|
+
var toInsightsInstallationRow = /* @__PURE__ */ __name((event) => {
|
|
1147
|
+
validateRow("bundle_events", event);
|
|
1148
|
+
return {
|
|
1149
|
+
id: event.id,
|
|
1150
|
+
install_id: event.install_id,
|
|
1151
|
+
user_id: event.user_id,
|
|
1152
|
+
username: event.username,
|
|
1153
|
+
to_bundle_id: event.to_bundle_id,
|
|
1154
|
+
type: event.type,
|
|
1155
|
+
platform: event.platform,
|
|
1156
|
+
app_version: event.app_version,
|
|
1157
|
+
channel: event.channel,
|
|
1158
|
+
cohort: event.cohort,
|
|
1159
|
+
received_at_ms: event.received_at_ms
|
|
1160
|
+
};
|
|
1161
|
+
}, "toInsightsInstallationRow");
|
|
1162
|
+
var isInsightsMovementEvent = /* @__PURE__ */ __name((event) => event.type === "UPDATE_APPLIED" || event.type === "RECOVERED", "isInsightsMovementEvent");
|
|
1163
|
+
var matchesInsightsEventFilter = /* @__PURE__ */ __name((event, filter) => {
|
|
1164
|
+
if (filter.kind === "all") return true;
|
|
1165
|
+
if (filter.kind === "installationMovement") return event.install_id === filter.installId && isInsightsMovementEvent(event);
|
|
1166
|
+
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);
|
|
1167
|
+
}, "matchesInsightsEventFilter");
|
|
1168
|
+
var invalidQuery = /* @__PURE__ */ __name(() => {
|
|
1169
|
+
throw new DatabasePluginInputError("invalid-query");
|
|
1170
|
+
}, "invalidQuery");
|
|
1171
|
+
var invalidResult = /* @__PURE__ */ __name(() => {
|
|
1172
|
+
throw new DatabasePluginInputError("invalid-result");
|
|
1173
|
+
}, "invalidResult");
|
|
1174
|
+
var validateCount = /* @__PURE__ */ __name((count) => isTimestamp(count) ? count : invalidResult(), "validateCount");
|
|
1175
|
+
var createValidatedInsightsModel = /* @__PURE__ */ __name((model) => ({
|
|
1176
|
+
async record(input) {
|
|
1177
|
+
if (!isRecord(input) || !hasOnlyKeys(input, ["event", "installation"])) throw new DatabasePluginInputError("invalid-data");
|
|
1178
|
+
const expected = toInsightsInstallationRow(input.event);
|
|
1179
|
+
validateRow("bundle_installations", input.installation);
|
|
1180
|
+
if (Object.entries(expected).some(([field, value]) => Reflect.get(input.installation, field) !== value)) throw new DatabasePluginInputError("invalid-data");
|
|
1181
|
+
await model.record(input);
|
|
1182
|
+
},
|
|
1183
|
+
async listEvents(input) {
|
|
1184
|
+
if (!isRecord(input) || !hasOnlyKeys(input, [
|
|
1185
|
+
"filter",
|
|
1186
|
+
"sinceMs",
|
|
1187
|
+
"beforeReceivedAtMs",
|
|
1188
|
+
"after",
|
|
1189
|
+
"limit"
|
|
1190
|
+
]) || !isEventFilter(input.filter) || !isLimit(input.limit) || !isTimestamp(input.beforeReceivedAtMs) || input.sinceMs !== void 0 && !isTimestamp(input.sinceMs)) invalidQuery();
|
|
1191
|
+
const sinceMs = input.sinceMs ?? 0;
|
|
1192
|
+
if (sinceMs > input.beforeReceivedAtMs || input.after !== void 0 && (!isRecord(input.after) || !hasOnlyKeys(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();
|
|
1193
|
+
const rows = await model.listEvents(input);
|
|
1194
|
+
if (!Array.isArray(rows) || rows.length > input.limit) invalidResult();
|
|
1195
|
+
let previous = input.after;
|
|
1196
|
+
for (const row of rows) {
|
|
1197
|
+
validateRow("bundle_events", row, true);
|
|
1198
|
+
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();
|
|
1199
|
+
previous = {
|
|
1200
|
+
receivedAtMs: row.received_at_ms,
|
|
1201
|
+
id: row.id
|
|
1202
|
+
};
|
|
1203
|
+
}
|
|
1204
|
+
return rows;
|
|
1205
|
+
},
|
|
1206
|
+
async findInstallations(input) {
|
|
1207
|
+
if (!isRecord(input)) invalidQuery();
|
|
1208
|
+
if ("installId" in input) {
|
|
1209
|
+
if (!hasOnlyKeys(input, ["installId"]) || !isIdentity(input.installId)) invalidQuery();
|
|
1210
|
+
} else if (!hasOnlyKeys(input, [
|
|
1211
|
+
"userId",
|
|
1212
|
+
"afterInstallId",
|
|
1213
|
+
"limit"
|
|
1214
|
+
]) || !isIdentity(input.userId) || !isLimit(input.limit) || input.afterInstallId !== void 0 && !isIdentity(input.afterInstallId)) invalidQuery();
|
|
1215
|
+
const rows = await model.findInstallations(input);
|
|
1216
|
+
if (!Array.isArray(rows) || rows.length > ("installId" in input ? 1 : input.limit)) invalidResult();
|
|
1217
|
+
let previous = "installId" in input ? void 0 : input.afterInstallId;
|
|
1218
|
+
for (const row of rows) {
|
|
1219
|
+
validateRow("bundle_installations", row, true);
|
|
1220
|
+
if ("installId" in input ? row.install_id !== input.installId : row.user_id !== input.userId || previous !== void 0 && compareInsightsText(row.install_id, previous) <= 0) invalidResult();
|
|
1221
|
+
previous = row.install_id;
|
|
1222
|
+
}
|
|
1223
|
+
return rows;
|
|
1224
|
+
},
|
|
1225
|
+
async countInstallations(input) {
|
|
1226
|
+
if (!isRecord(input) || !hasOnlyKeys(input, [
|
|
1227
|
+
"platform",
|
|
1228
|
+
"channel",
|
|
1229
|
+
"sinceMs",
|
|
1230
|
+
"bundleId"
|
|
1231
|
+
]) || !hasScope(input) || !isTimestamp(input.sinceMs) || input.bundleId !== void 0 && !isText(input.bundleId)) invalidQuery();
|
|
1232
|
+
return validateCount(await model.countInstallations(input));
|
|
1233
|
+
},
|
|
1234
|
+
async countEvents(input) {
|
|
1235
|
+
if (!isRecord(input) || !hasOnlyKeys(input, [
|
|
1236
|
+
"filter",
|
|
1237
|
+
"sinceMs",
|
|
1238
|
+
"beforeReceivedAtMs"
|
|
1239
|
+
]) || !isBundleFilter(input.filter) || !isTimestamp(input.sinceMs) || !isTimestamp(input.beforeReceivedAtMs) || input.sinceMs > input.beforeReceivedAtMs) invalidQuery();
|
|
1240
|
+
return validateCount(await model.countEvents(input));
|
|
1241
|
+
}
|
|
1242
|
+
}), "createValidatedInsightsModel");
|
|
1243
|
+
|
|
1057
1244
|
// ../plugin-core/dist/databasePluginCrudValidationMutations.mjs
|
|
1058
1245
|
var validateMutationWhere = /* @__PURE__ */ __name((where) => {
|
|
1059
1246
|
if (where.length === 0) throw new DatabasePluginInputError("empty-mutation-where");
|
|
1060
1247
|
}, "validateMutationWhere");
|
|
1061
1248
|
var validateUpdateWhere = /* @__PURE__ */ __name((model, where) => {
|
|
1249
|
+
if (model === "bundle_installations") {
|
|
1250
|
+
const [install, receivedAt, id] = where;
|
|
1251
|
+
const exactInstall = isRecord(install) && install.field === "install_id" && (install.operator === void 0 || install.operator === "eq") && typeof install.value === "string" && install.connector === void 0 && install.mode === void 0;
|
|
1252
|
+
const receivedBefore = isRecord(receivedAt) && receivedAt.field === "received_at_ms" && (receivedAt.operator === "lt" || receivedAt.operator === "eq") && typeof receivedAt.value === "number" && Number.isSafeInteger(receivedAt.value) && receivedAt.value >= 0 && receivedAt.connector === void 0 && receivedAt.mode === void 0;
|
|
1253
|
+
const idBefore = isRecord(id) && id.field === "id" && id.operator === "lt" && typeof id.value === "string" && id.connector === void 0 && id.mode === void 0;
|
|
1254
|
+
if (!exactInstall || !receivedBefore || !(where.length === 2 && receivedAt.operator === "lt" || where.length === 3 && receivedAt.operator === "eq" && idBefore)) throw new DatabasePluginInputError("invalid-update-selector");
|
|
1255
|
+
return;
|
|
1256
|
+
}
|
|
1062
1257
|
const selector = where[0];
|
|
1063
1258
|
const primaryField = model === "release_catalogs" ? "scope_key" : "id";
|
|
1064
1259
|
if (where.length !== 1 || !isRecord(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");
|
|
1065
1260
|
}, "validateUpdateWhere");
|
|
1261
|
+
var validateInsightsInstallationUpdateData = /* @__PURE__ */ __name((update) => {
|
|
1262
|
+
if (!isRecord(update)) throw new DatabasePluginInputError("invalid-data");
|
|
1263
|
+
const fields = databaseFields.bundle_installations.filter((field) => field !== "install_id");
|
|
1264
|
+
if (Reflect.ownKeys(update).length !== fields.length || fields.some((field) => !Object.hasOwn(update, field))) throw new DatabasePluginInputError("invalid-data");
|
|
1265
|
+
for (const field of fields) {
|
|
1266
|
+
const validator = modelValidators.bundle_installations[field];
|
|
1267
|
+
if (!validator?.(Reflect.get(update, field))) throw new DatabasePluginInputError("invalid-data");
|
|
1268
|
+
}
|
|
1269
|
+
}, "validateInsightsInstallationUpdateData");
|
|
1066
1270
|
var validateBundleUpdateData = /* @__PURE__ */ __name((update) => {
|
|
1067
1271
|
if (!isRecord(update)) throw new DatabasePluginInputError("invalid-data");
|
|
1068
1272
|
for (const [field, value] of Object.entries(update)) {
|
|
@@ -1232,7 +1436,7 @@ var createDatabasePluginCrud = /* @__PURE__ */ __name((implementation) => {
|
|
|
1232
1436
|
async function create(input) {
|
|
1233
1437
|
validateModel(input.model);
|
|
1234
1438
|
validateCreateData(input.model, input.data);
|
|
1235
|
-
if (input.onConflict !== void 0 && !(input.onConflict === "ignore" && (input.model === "channels" || input.model === "api_keys"))) throw new DatabasePluginInputError("invalid-operation");
|
|
1439
|
+
if (input.onConflict !== void 0 && !(input.onConflict === "ignore" && (input.model === "channels" || input.model === "api_keys" || input.model === "bundle_installations"))) throw new DatabasePluginInputError("invalid-operation");
|
|
1236
1440
|
validateSelect(input.model, input.select);
|
|
1237
1441
|
const row = await implementation.create(input);
|
|
1238
1442
|
validateResult(input.model, row, input.select);
|
|
@@ -1247,6 +1451,7 @@ var createDatabasePluginCrud = /* @__PURE__ */ __name((implementation) => {
|
|
|
1247
1451
|
if (input.model === "bundles") validateBundleUpdateData(input.update);
|
|
1248
1452
|
else if (input.model === "releases") validateReleaseUpdateData(input.update);
|
|
1249
1453
|
else if (input.model === "release_catalogs") validateReleaseCatalogUpdateData(input.update);
|
|
1454
|
+
else if (input.model === "bundle_installations") validateInsightsInstallationUpdateData(input.update);
|
|
1250
1455
|
else if (input.model === "api_keys") validateApiKeyUpdateData(input.update);
|
|
1251
1456
|
else throw new DatabasePluginInputError("invalid-operation");
|
|
1252
1457
|
validateSelect(input.model, input.select);
|
|
@@ -1322,10 +1527,90 @@ var createTransactionDatabasePlugin = /* @__PURE__ */ __name((implementation) =>
|
|
|
1322
1527
|
// ../plugin-core/dist/createDatabasePlugin.mjs
|
|
1323
1528
|
var PAGE_SIZE = 100;
|
|
1324
1529
|
var compareChannelRows = /* @__PURE__ */ __name((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0, "compareChannelRows");
|
|
1530
|
+
var toInsightsBundleWhere = /* @__PURE__ */ __name((filter) => [
|
|
1531
|
+
{
|
|
1532
|
+
field: "platform",
|
|
1533
|
+
value: filter.platform
|
|
1534
|
+
},
|
|
1535
|
+
{
|
|
1536
|
+
field: "channel",
|
|
1537
|
+
value: filter.channel
|
|
1538
|
+
},
|
|
1539
|
+
{
|
|
1540
|
+
field: "type",
|
|
1541
|
+
value: filter.type
|
|
1542
|
+
},
|
|
1543
|
+
filter.type === "RECOVERED" ? {
|
|
1544
|
+
field: "from_bundle_id",
|
|
1545
|
+
value: filter.fromBundleId
|
|
1546
|
+
} : {
|
|
1547
|
+
field: "to_bundle_id",
|
|
1548
|
+
value: filter.toBundleId
|
|
1549
|
+
}
|
|
1550
|
+
], "toInsightsBundleWhere");
|
|
1551
|
+
var toInsightsEventRanges = /* @__PURE__ */ __name((filter) => {
|
|
1552
|
+
if (filter.kind === "all") return [[]];
|
|
1553
|
+
if (filter.kind === "bundle") return [toInsightsBundleWhere(filter)];
|
|
1554
|
+
return ["UPDATE_APPLIED", "RECOVERED"].map((type) => [{
|
|
1555
|
+
field: "install_id",
|
|
1556
|
+
value: filter.installId
|
|
1557
|
+
}, {
|
|
1558
|
+
field: "type",
|
|
1559
|
+
value: type
|
|
1560
|
+
}]);
|
|
1561
|
+
}, "toInsightsEventRanges");
|
|
1562
|
+
var listInsightsEventRange = /* @__PURE__ */ __name(async (crud, input, filterWhere) => {
|
|
1563
|
+
const where = [...filterWhere, {
|
|
1564
|
+
field: "received_at_ms",
|
|
1565
|
+
operator: "gte",
|
|
1566
|
+
value: input.sinceMs ?? 0
|
|
1567
|
+
}];
|
|
1568
|
+
const sameTimestamp = input.after === void 0 ? [] : await crud.findMany({
|
|
1569
|
+
model: "bundle_events",
|
|
1570
|
+
where: [
|
|
1571
|
+
...filterWhere,
|
|
1572
|
+
{
|
|
1573
|
+
field: "received_at_ms",
|
|
1574
|
+
value: input.after.receivedAtMs
|
|
1575
|
+
},
|
|
1576
|
+
{
|
|
1577
|
+
field: "id",
|
|
1578
|
+
operator: "lt",
|
|
1579
|
+
value: input.after.id
|
|
1580
|
+
}
|
|
1581
|
+
],
|
|
1582
|
+
orderBy: [{
|
|
1583
|
+
field: "id",
|
|
1584
|
+
direction: "desc"
|
|
1585
|
+
}],
|
|
1586
|
+
limit: input.limit,
|
|
1587
|
+
offset: 0
|
|
1588
|
+
});
|
|
1589
|
+
if (sameTimestamp.length === input.limit) return sameTimestamp;
|
|
1590
|
+
const older = await crud.findMany({
|
|
1591
|
+
model: "bundle_events",
|
|
1592
|
+
where: [...where, {
|
|
1593
|
+
field: "received_at_ms",
|
|
1594
|
+
operator: "lt",
|
|
1595
|
+
value: input.after?.receivedAtMs ?? input.beforeReceivedAtMs
|
|
1596
|
+
}],
|
|
1597
|
+
orderBy: [{
|
|
1598
|
+
field: "received_at_ms",
|
|
1599
|
+
direction: "desc"
|
|
1600
|
+
}, {
|
|
1601
|
+
field: "id",
|
|
1602
|
+
direction: "desc"
|
|
1603
|
+
}],
|
|
1604
|
+
limit: input.limit - sameTimestamp.length,
|
|
1605
|
+
offset: 0
|
|
1606
|
+
});
|
|
1607
|
+
return [...sameTimestamp, ...older];
|
|
1608
|
+
}, "listInsightsEventRange");
|
|
1325
1609
|
var DatabaseAtomicCommitUnsupportedError = class extends Error {
|
|
1326
1610
|
static {
|
|
1327
1611
|
__name(this, "DatabaseAtomicCommitUnsupportedError");
|
|
1328
1612
|
}
|
|
1613
|
+
pluginName;
|
|
1329
1614
|
name = "DatabaseAtomicCommitUnsupportedError";
|
|
1330
1615
|
constructor(pluginName) {
|
|
1331
1616
|
super(`Database plugin "${pluginName}" cannot atomically commit changes across models.`);
|
|
@@ -1345,6 +1630,7 @@ var DatabaseCommitConflictError = class extends Error {
|
|
|
1345
1630
|
static {
|
|
1346
1631
|
__name(this, "DatabaseCommitConflictError");
|
|
1347
1632
|
}
|
|
1633
|
+
result;
|
|
1348
1634
|
name = "DatabaseCommitConflictError";
|
|
1349
1635
|
constructor(result) {
|
|
1350
1636
|
super("Database commit precondition failed.");
|
|
@@ -1576,12 +1862,6 @@ var applyChange = /* @__PURE__ */ __name(async (database, change, changeIndex) =
|
|
|
1576
1862
|
}
|
|
1577
1863
|
return;
|
|
1578
1864
|
}
|
|
1579
|
-
case "insights":
|
|
1580
|
-
await database.create({
|
|
1581
|
-
model: "bundle_events",
|
|
1582
|
-
data: change.row
|
|
1583
|
-
});
|
|
1584
|
-
return;
|
|
1585
1865
|
case "apiKeys":
|
|
1586
1866
|
switch (change.operation) {
|
|
1587
1867
|
case "insert":
|
|
@@ -1650,11 +1930,11 @@ var applyChanges = /* @__PURE__ */ __name(async (database, input) => {
|
|
|
1650
1930
|
for (const [changeIndex, change] of input.changes.entries()) await applyChange(database, change, changeIndex);
|
|
1651
1931
|
return { committed: true };
|
|
1652
1932
|
}, "applyChanges");
|
|
1653
|
-
var
|
|
1933
|
+
var hasOnlyKeys2 = /* @__PURE__ */ __name((value, keys) => {
|
|
1654
1934
|
return Reflect.ownKeys(value).length === keys.length && keys.every((key) => Object.hasOwn(value, key));
|
|
1655
1935
|
}, "hasOnlyKeys");
|
|
1656
1936
|
var validateWhere2 = /* @__PURE__ */ __name((where, field, validateValue = (value) => typeof value === "string") => {
|
|
1657
|
-
if (!isRecord(where) || !
|
|
1937
|
+
if (!isRecord(where) || !hasOnlyKeys2(where, [field]) || !validateValue(Reflect.get(where, field))) throw new DatabasePluginInputError("invalid-data");
|
|
1658
1938
|
}, "validateWhere");
|
|
1659
1939
|
var validateDatabaseChange = /* @__PURE__ */ __name((change) => {
|
|
1660
1940
|
if (!isRecord(change)) throw new DatabasePluginInputError("invalid-data");
|
|
@@ -1662,7 +1942,7 @@ var validateDatabaseChange = /* @__PURE__ */ __name((change) => {
|
|
|
1662
1942
|
case "bundles":
|
|
1663
1943
|
switch (change.operation) {
|
|
1664
1944
|
case "insert":
|
|
1665
|
-
if (!
|
|
1945
|
+
if (!hasOnlyKeys2(change, [
|
|
1666
1946
|
"model",
|
|
1667
1947
|
"operation",
|
|
1668
1948
|
"row"
|
|
@@ -1670,7 +1950,7 @@ var validateDatabaseChange = /* @__PURE__ */ __name((change) => {
|
|
|
1670
1950
|
validateCreateData("bundles", change.row);
|
|
1671
1951
|
return;
|
|
1672
1952
|
case "update":
|
|
1673
|
-
if (!
|
|
1953
|
+
if (!hasOnlyKeys2(change, [
|
|
1674
1954
|
"model",
|
|
1675
1955
|
"operation",
|
|
1676
1956
|
"where",
|
|
@@ -1680,7 +1960,7 @@ var validateDatabaseChange = /* @__PURE__ */ __name((change) => {
|
|
|
1680
1960
|
validateBundleUpdateData(change.update);
|
|
1681
1961
|
return;
|
|
1682
1962
|
case "delete":
|
|
1683
|
-
if (!
|
|
1963
|
+
if (!hasOnlyKeys2(change, [
|
|
1684
1964
|
"model",
|
|
1685
1965
|
"operation",
|
|
1686
1966
|
"where"
|
|
@@ -1693,7 +1973,7 @@ var validateDatabaseChange = /* @__PURE__ */ __name((change) => {
|
|
|
1693
1973
|
case "bundlePatches":
|
|
1694
1974
|
switch (change.operation) {
|
|
1695
1975
|
case "insert":
|
|
1696
|
-
if (!
|
|
1976
|
+
if (!hasOnlyKeys2(change, [
|
|
1697
1977
|
"model",
|
|
1698
1978
|
"operation",
|
|
1699
1979
|
"row"
|
|
@@ -1701,7 +1981,7 @@ var validateDatabaseChange = /* @__PURE__ */ __name((change) => {
|
|
|
1701
1981
|
validateCreateData("bundle_patches", change.row);
|
|
1702
1982
|
return;
|
|
1703
1983
|
case "delete":
|
|
1704
|
-
if (!
|
|
1984
|
+
if (!hasOnlyKeys2(change, [
|
|
1705
1985
|
"model",
|
|
1706
1986
|
"operation",
|
|
1707
1987
|
"where"
|
|
@@ -1714,7 +1994,7 @@ var validateDatabaseChange = /* @__PURE__ */ __name((change) => {
|
|
|
1714
1994
|
case "releases":
|
|
1715
1995
|
switch (change.operation) {
|
|
1716
1996
|
case "insert":
|
|
1717
|
-
if (!
|
|
1997
|
+
if (!hasOnlyKeys2(change, [
|
|
1718
1998
|
"model",
|
|
1719
1999
|
"operation",
|
|
1720
2000
|
"row"
|
|
@@ -1722,7 +2002,7 @@ var validateDatabaseChange = /* @__PURE__ */ __name((change) => {
|
|
|
1722
2002
|
validateCreateData("releases", change.row);
|
|
1723
2003
|
return;
|
|
1724
2004
|
case "update":
|
|
1725
|
-
if (!
|
|
2005
|
+
if (!hasOnlyKeys2(change, [
|
|
1726
2006
|
"model",
|
|
1727
2007
|
"operation",
|
|
1728
2008
|
"where",
|
|
@@ -1732,7 +2012,7 @@ var validateDatabaseChange = /* @__PURE__ */ __name((change) => {
|
|
|
1732
2012
|
validateReleaseUpdateData(change.update);
|
|
1733
2013
|
return;
|
|
1734
2014
|
case "delete":
|
|
1735
|
-
if (!
|
|
2015
|
+
if (!hasOnlyKeys2(change, [
|
|
1736
2016
|
"model",
|
|
1737
2017
|
"operation",
|
|
1738
2018
|
"where"
|
|
@@ -1743,7 +2023,7 @@ var validateDatabaseChange = /* @__PURE__ */ __name((change) => {
|
|
|
1743
2023
|
throw new DatabasePluginInputError("invalid-operation");
|
|
1744
2024
|
}
|
|
1745
2025
|
case "releaseCatalogs":
|
|
1746
|
-
if (change.operation !== "put" || !
|
|
2026
|
+
if (change.operation !== "put" || !hasOnlyKeys2(change, [
|
|
1747
2027
|
"model",
|
|
1748
2028
|
"operation",
|
|
1749
2029
|
"row"
|
|
@@ -1753,7 +2033,7 @@ var validateDatabaseChange = /* @__PURE__ */ __name((change) => {
|
|
|
1753
2033
|
case "channels":
|
|
1754
2034
|
switch (change.operation) {
|
|
1755
2035
|
case "insert":
|
|
1756
|
-
if (!
|
|
2036
|
+
if (!hasOnlyKeys2(change, [
|
|
1757
2037
|
"model",
|
|
1758
2038
|
"operation",
|
|
1759
2039
|
"row",
|
|
@@ -1762,7 +2042,7 @@ var validateDatabaseChange = /* @__PURE__ */ __name((change) => {
|
|
|
1762
2042
|
validateCreateData("channels", change.row);
|
|
1763
2043
|
return;
|
|
1764
2044
|
case "delete":
|
|
1765
|
-
if (!
|
|
2045
|
+
if (!hasOnlyKeys2(change, [
|
|
1766
2046
|
"model",
|
|
1767
2047
|
"operation",
|
|
1768
2048
|
"where"
|
|
@@ -1772,18 +2052,10 @@ var validateDatabaseChange = /* @__PURE__ */ __name((change) => {
|
|
|
1772
2052
|
default:
|
|
1773
2053
|
throw new DatabasePluginInputError("invalid-operation");
|
|
1774
2054
|
}
|
|
1775
|
-
case "insights":
|
|
1776
|
-
if (change.operation !== "insert" || !hasOnlyKeys(change, [
|
|
1777
|
-
"model",
|
|
1778
|
-
"operation",
|
|
1779
|
-
"row"
|
|
1780
|
-
])) throw new DatabasePluginInputError("invalid-operation");
|
|
1781
|
-
validateCreateData("bundle_events", change.row);
|
|
1782
|
-
return;
|
|
1783
2055
|
case "apiKeys":
|
|
1784
2056
|
switch (change.operation) {
|
|
1785
2057
|
case "insert":
|
|
1786
|
-
if (!
|
|
2058
|
+
if (!hasOnlyKeys2(change, [
|
|
1787
2059
|
"model",
|
|
1788
2060
|
"operation",
|
|
1789
2061
|
"row",
|
|
@@ -1792,7 +2064,7 @@ var validateDatabaseChange = /* @__PURE__ */ __name((change) => {
|
|
|
1792
2064
|
validateCreateData("api_keys", change.row);
|
|
1793
2065
|
return;
|
|
1794
2066
|
case "update":
|
|
1795
|
-
if (!
|
|
2067
|
+
if (!hasOnlyKeys2(change, [
|
|
1796
2068
|
"model",
|
|
1797
2069
|
"operation",
|
|
1798
2070
|
"where",
|
|
@@ -1800,7 +2072,7 @@ var validateDatabaseChange = /* @__PURE__ */ __name((change) => {
|
|
|
1800
2072
|
])) throw new DatabasePluginInputError("invalid-data");
|
|
1801
2073
|
validateWhere2(change.where, "id");
|
|
1802
2074
|
if (!isRecord(change.update)) throw new DatabasePluginInputError("invalid-data");
|
|
1803
|
-
if (!
|
|
2075
|
+
if (!hasOnlyKeys2(change.update, ["revokedAtMs"])) throw new DatabasePluginInputError("invalid-data");
|
|
1804
2076
|
validateApiKeyUpdateData({ revoked_at_ms: change.update.revokedAtMs });
|
|
1805
2077
|
return;
|
|
1806
2078
|
default:
|
|
@@ -1813,7 +2085,7 @@ var validateDatabaseChange = /* @__PURE__ */ __name((change) => {
|
|
|
1813
2085
|
var validateDatabaseCommitExpectation = /* @__PURE__ */ __name((expectation) => {
|
|
1814
2086
|
if (!isRecord(expectation)) throw new DatabasePluginInputError("invalid-data");
|
|
1815
2087
|
if (expectation.model === "releases") {
|
|
1816
|
-
if (!
|
|
2088
|
+
if (!hasOnlyKeys2(expectation, [
|
|
1817
2089
|
"model",
|
|
1818
2090
|
"id",
|
|
1819
2091
|
"revision"
|
|
@@ -1821,7 +2093,7 @@ var validateDatabaseCommitExpectation = /* @__PURE__ */ __name((expectation) =>
|
|
|
1821
2093
|
return;
|
|
1822
2094
|
}
|
|
1823
2095
|
if (expectation.model === "releaseCatalogs") {
|
|
1824
|
-
if (!
|
|
2096
|
+
if (!hasOnlyKeys2(expectation, [
|
|
1825
2097
|
"model",
|
|
1826
2098
|
"scopeKey",
|
|
1827
2099
|
"generation"
|
|
@@ -1831,7 +2103,7 @@ var validateDatabaseCommitExpectation = /* @__PURE__ */ __name((expectation) =>
|
|
|
1831
2103
|
throw new DatabasePluginInputError("invalid-model");
|
|
1832
2104
|
}, "validateDatabaseCommitExpectation");
|
|
1833
2105
|
function validateDatabaseCommit(input) {
|
|
1834
|
-
if (!isRecord(input) || !Array.isArray(input.changes) || (Object.hasOwn(input, "expectations") ? !
|
|
2106
|
+
if (!isRecord(input) || !Array.isArray(input.changes) || (Object.hasOwn(input, "expectations") ? !hasOnlyKeys2(input, ["changes", "expectations"]) || !Array.isArray(input.expectations) : !hasOnlyKeys2(input, ["changes"]))) throw new DatabasePluginInputError("invalid-data");
|
|
1835
2107
|
input.changes.forEach(validateDatabaseChange);
|
|
1836
2108
|
if (Array.isArray(input.expectations)) input.expectations.forEach(validateDatabaseCommitExpectation);
|
|
1837
2109
|
}
|
|
@@ -2052,36 +2324,82 @@ var createDatabasePluginAdapter = /* @__PURE__ */ __name((name, implementation)
|
|
|
2052
2324
|
}
|
|
2053
2325
|
},
|
|
2054
2326
|
insights: {
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2327
|
+
record: /* @__PURE__ */ __name((input) => implementation.recordInsights(input), "record"),
|
|
2328
|
+
async listEvents(input) {
|
|
2329
|
+
const ranges = await Promise.all(toInsightsEventRanges(input.filter).map((where) => listInsightsEventRange(crud, input, where)));
|
|
2330
|
+
if (ranges.length === 1) return ranges[0];
|
|
2331
|
+
return ranges.flat().sort((left, right) => right.received_at_ms - left.received_at_ms || compareInsightsText(right.id, left.id)).slice(0, input.limit);
|
|
2060
2332
|
},
|
|
2061
|
-
async
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
model: "bundle_events",
|
|
2333
|
+
async findInstallations(input) {
|
|
2334
|
+
if ("installId" in input) {
|
|
2335
|
+
const row = await crud.findOne({
|
|
2336
|
+
model: "bundle_installations",
|
|
2066
2337
|
where: [{
|
|
2338
|
+
field: "install_id",
|
|
2339
|
+
value: input.installId
|
|
2340
|
+
}]
|
|
2341
|
+
});
|
|
2342
|
+
return row === null ? [] : [row];
|
|
2343
|
+
}
|
|
2344
|
+
return crud.findMany({
|
|
2345
|
+
model: "bundle_installations",
|
|
2346
|
+
where: [{
|
|
2347
|
+
field: "user_id",
|
|
2348
|
+
value: input.userId
|
|
2349
|
+
}, ...input.afterInstallId === void 0 ? [] : [{
|
|
2350
|
+
field: "install_id",
|
|
2351
|
+
operator: "gt",
|
|
2352
|
+
value: input.afterInstallId
|
|
2353
|
+
}]],
|
|
2354
|
+
orderBy: [{
|
|
2355
|
+
field: "install_id",
|
|
2356
|
+
direction: "asc"
|
|
2357
|
+
}],
|
|
2358
|
+
limit: input.limit,
|
|
2359
|
+
offset: 0
|
|
2360
|
+
});
|
|
2361
|
+
},
|
|
2362
|
+
countInstallations(input) {
|
|
2363
|
+
return crud.count({
|
|
2364
|
+
model: "bundle_installations",
|
|
2365
|
+
where: [
|
|
2366
|
+
{
|
|
2367
|
+
field: "platform",
|
|
2368
|
+
value: input.platform
|
|
2369
|
+
},
|
|
2370
|
+
{
|
|
2371
|
+
field: "channel",
|
|
2372
|
+
value: input.channel
|
|
2373
|
+
},
|
|
2374
|
+
{
|
|
2375
|
+
field: "received_at_ms",
|
|
2376
|
+
operator: "gte",
|
|
2377
|
+
value: input.sinceMs
|
|
2378
|
+
},
|
|
2379
|
+
...input.bundleId === void 0 ? [] : [{
|
|
2380
|
+
field: "to_bundle_id",
|
|
2381
|
+
value: input.bundleId
|
|
2382
|
+
}]
|
|
2383
|
+
]
|
|
2384
|
+
});
|
|
2385
|
+
},
|
|
2386
|
+
countEvents(input) {
|
|
2387
|
+
return crud.count({
|
|
2388
|
+
model: "bundle_events",
|
|
2389
|
+
where: [
|
|
2390
|
+
...toInsightsBundleWhere(input.filter),
|
|
2391
|
+
{
|
|
2392
|
+
field: "received_at_ms",
|
|
2393
|
+
operator: "gte",
|
|
2394
|
+
value: input.sinceMs
|
|
2395
|
+
},
|
|
2396
|
+
{
|
|
2067
2397
|
field: "received_at_ms",
|
|
2068
2398
|
operator: "lt",
|
|
2069
2399
|
value: input.beforeReceivedAtMs
|
|
2070
|
-
}
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
direction: "asc"
|
|
2074
|
-
}, {
|
|
2075
|
-
field: "id",
|
|
2076
|
-
direction: "asc"
|
|
2077
|
-
}],
|
|
2078
|
-
limit: PAGE_SIZE,
|
|
2079
|
-
offset
|
|
2080
|
-
});
|
|
2081
|
-
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));
|
|
2082
|
-
if (page.length < PAGE_SIZE) break;
|
|
2083
|
-
}
|
|
2084
|
-
return rows.slice(0, input.limit);
|
|
2400
|
+
}
|
|
2401
|
+
]
|
|
2402
|
+
});
|
|
2085
2403
|
}
|
|
2086
2404
|
},
|
|
2087
2405
|
apiKeys: {
|
|
@@ -2125,7 +2443,13 @@ var createDatabasePluginAdapter = /* @__PURE__ */ __name((name, implementation)
|
|
|
2125
2443
|
...implementation.dispose ? { dispose: implementation.dispose } : {}
|
|
2126
2444
|
};
|
|
2127
2445
|
}, "createDatabasePluginAdapter");
|
|
2128
|
-
var createDatabasePlugin = /* @__PURE__ */ __name((options) => ({
|
|
2446
|
+
var createDatabasePlugin = /* @__PURE__ */ __name((options) => ({
|
|
2447
|
+
...options,
|
|
2448
|
+
models: {
|
|
2449
|
+
...options.models,
|
|
2450
|
+
insights: createValidatedInsightsModel(options.models.insights)
|
|
2451
|
+
}
|
|
2452
|
+
}), "createDatabasePlugin");
|
|
2129
2453
|
|
|
2130
2454
|
// ../plugin-core/dist/createStoragePlugin.mjs
|
|
2131
2455
|
var createStoragePlugin = /* @__PURE__ */ __name((options) => ({ ...options }), "createStoragePlugin");
|
|
@@ -2267,6 +2591,8 @@ var DatabasePatchUpdateUnsupportedError = class extends Error {
|
|
|
2267
2591
|
static {
|
|
2268
2592
|
__name(this, "DatabasePatchUpdateUnsupportedError");
|
|
2269
2593
|
}
|
|
2594
|
+
bundleId;
|
|
2595
|
+
pluginName;
|
|
2270
2596
|
name = "DatabasePatchUpdateUnsupportedError";
|
|
2271
2597
|
constructor(bundleId, pluginName) {
|
|
2272
2598
|
super(`Database plugin "${pluginName}" cannot atomically replace patches for bundle "${bundleId}".`);
|
|
@@ -2355,7 +2681,8 @@ var hydrateRows = /* @__PURE__ */ __name(async (database, ownerRows) => {
|
|
|
2355
2681
|
const patchRows = await database.models.bundlePatches.findByBundleIds(ownerRows.map(({ id }) => id));
|
|
2356
2682
|
const ownerIds = new Set(ownerRows.map(({ id }) => id));
|
|
2357
2683
|
const referencedIds = [...new Set(patchRows.map(({ base_bundle_id }) => base_bundle_id).filter((id) => !ownerIds.has(id)))];
|
|
2358
|
-
|
|
2684
|
+
const referencedRows = referencedIds.length === 0 ? [] : await loadBundleRows(database, { id: { in: referencedIds } });
|
|
2685
|
+
return rowsToBundles(ownerRows, patchRows, referencedRows);
|
|
2359
2686
|
}, "hydrateRows");
|
|
2360
2687
|
var cursorIdFilter = /* @__PURE__ */ __name((cursor, direction) => {
|
|
2361
2688
|
if (cursor?.after) return { [direction === "desc" ? "lt" : "gt"]: cursor.after };
|
|
@@ -2410,6 +2737,7 @@ var DatabaseBundleNotFoundError = class extends Error {
|
|
|
2410
2737
|
static {
|
|
2411
2738
|
__name(this, "DatabaseBundleNotFoundError");
|
|
2412
2739
|
}
|
|
2740
|
+
bundleId;
|
|
2413
2741
|
name = "DatabaseBundleNotFoundError";
|
|
2414
2742
|
constructor(bundleId) {
|
|
2415
2743
|
super(`Bundle "${bundleId}" was not found.`);
|
|
@@ -2420,6 +2748,8 @@ var DatabasePatchInsertUnsupportedError = class extends Error {
|
|
|
2420
2748
|
static {
|
|
2421
2749
|
__name(this, "DatabasePatchInsertUnsupportedError");
|
|
2422
2750
|
}
|
|
2751
|
+
bundleId;
|
|
2752
|
+
pluginName;
|
|
2423
2753
|
name = "DatabasePatchInsertUnsupportedError";
|
|
2424
2754
|
constructor(bundleId, pluginName) {
|
|
2425
2755
|
super(`Database plugin "${pluginName}" cannot atomically insert patches for bundle "${bundleId}".`);
|
|
@@ -2438,12 +2768,13 @@ var insertChanges = /* @__PURE__ */ __name((bundle) => [{
|
|
|
2438
2768
|
}))], "insertChanges");
|
|
2439
2769
|
var updateChanges = /* @__PURE__ */ __name((bundleId, update) => {
|
|
2440
2770
|
const rowUpdate = bundleUpdateToRow(update);
|
|
2771
|
+
const patchesPresent = Object.hasOwn(update, "patches");
|
|
2441
2772
|
return [{
|
|
2442
2773
|
model: "bundles",
|
|
2443
2774
|
operation: "update",
|
|
2444
2775
|
where: { id: bundleId },
|
|
2445
2776
|
update: rowUpdate
|
|
2446
|
-
}, ...
|
|
2777
|
+
}, ...patchesPresent ? [{
|
|
2447
2778
|
model: "bundlePatches",
|
|
2448
2779
|
operation: "delete",
|
|
2449
2780
|
where: { bundleId }
|
|
@@ -2516,7 +2847,7 @@ var createDatabaseClient = /* @__PURE__ */ __name((plugin) => {
|
|
|
2516
2847
|
};
|
|
2517
2848
|
}, "createDatabaseClient");
|
|
2518
2849
|
|
|
2519
|
-
// ../plugin-core/dist/node_modules/.pnpm/verkit@0.
|
|
2850
|
+
// ../plugin-core/dist/node_modules/.pnpm/verkit@0.4.0/node_modules/verkit/dist/comparison-DenM3wCn.mjs
|
|
2520
2851
|
var LETTER_DASH_NUMBER = "[a-zA-Z0-9-]";
|
|
2521
2852
|
var NUMERIC_IDENTIFIER = String.raw`0|[1-9]\d*`;
|
|
2522
2853
|
var NUMERIC_IDENTIFIER_LOOSE = String.raw`\d+`;
|
|
@@ -2555,23 +2886,9 @@ function safeRegex(source, flags) {
|
|
|
2555
2886
|
return new RegExp(makeSafeRegexSource(source), flags);
|
|
2556
2887
|
}
|
|
2557
2888
|
__name(safeRegex, "safeRegex");
|
|
2558
|
-
var NUMERIC$1 = /^\d+$/;
|
|
2559
|
-
function compareIdentifiers(left, right) {
|
|
2560
|
-
if (typeof left === "number" && typeof right === "number") return left === right ? 0 : left < right ? -1 : 1;
|
|
2561
|
-
const leftNumeric = NUMERIC$1.test(String(left));
|
|
2562
|
-
const rightNumeric = NUMERIC$1.test(String(right));
|
|
2563
|
-
const normalizedLeft = leftNumeric ? Number(left) : left;
|
|
2564
|
-
const normalizedRight = rightNumeric ? Number(right) : right;
|
|
2565
|
-
return normalizedLeft === normalizedRight ? 0 : leftNumeric && !rightNumeric ? -1 : rightNumeric && !leftNumeric ? 1 : normalizedLeft < normalizedRight ? -1 : 1;
|
|
2566
|
-
}
|
|
2567
|
-
__name(compareIdentifiers, "compareIdentifiers");
|
|
2568
2889
|
var FULL = safeRegex(`^${FULL_PLAIN}$`);
|
|
2569
2890
|
var LOOSE = safeRegex(`^${LOOSE_PLAIN}$`);
|
|
2570
|
-
|
|
2571
|
-
safeRegex(`^${PRERELEASE_LOOSE}$`);
|
|
2572
|
-
var COERCE_EXACT = safeRegex(COERCE);
|
|
2573
|
-
var COERCE_FULL_EXACT = safeRegex(COERCE_FULL);
|
|
2574
|
-
var NUMERIC = /^\d+$/;
|
|
2891
|
+
var NUMERIC$1 = /^\d+$/;
|
|
2575
2892
|
function formatComparableVersion(version2) {
|
|
2576
2893
|
const base = `${version2.major}.${version2.minor}.${version2.patch}`;
|
|
2577
2894
|
return version2.prerelease?.length ? `${base}-${version2.prerelease.join(".")}` : base;
|
|
@@ -2594,7 +2911,7 @@ function parse(version2, options = {}) {
|
|
|
2594
2911
|
if (minor > Number.MAX_SAFE_INTEGER || minor < 0) throw new TypeError(`Invalid minor version: ${match2[2]}`);
|
|
2595
2912
|
if (patch > Number.MAX_SAFE_INTEGER || patch < 0) throw new TypeError(`Invalid patch version: ${match2[3]}`);
|
|
2596
2913
|
const prerelease = match2[4] ? match2[4].split(".").map((identifier) => {
|
|
2597
|
-
if (NUMERIC.test(identifier)) {
|
|
2914
|
+
if (NUMERIC$1.test(identifier)) {
|
|
2598
2915
|
const numeric = Number(identifier);
|
|
2599
2916
|
if (numeric >= 0 && numeric < Number.MAX_SAFE_INTEGER) return numeric;
|
|
2600
2917
|
}
|
|
@@ -2617,6 +2934,16 @@ function tryParse(version2, options = {}) {
|
|
|
2617
2934
|
}
|
|
2618
2935
|
}
|
|
2619
2936
|
__name(tryParse, "tryParse");
|
|
2937
|
+
var NUMERIC = /^\d+$/;
|
|
2938
|
+
function compareIdentifiers(left, right) {
|
|
2939
|
+
if (typeof left === "number" && typeof right === "number") return left === right ? 0 : left < right ? -1 : 1;
|
|
2940
|
+
const leftNumeric = NUMERIC.test(String(left));
|
|
2941
|
+
const rightNumeric = NUMERIC.test(String(right));
|
|
2942
|
+
const normalizedLeft = leftNumeric ? Number(left) : left;
|
|
2943
|
+
const normalizedRight = rightNumeric ? Number(right) : right;
|
|
2944
|
+
return normalizedLeft === normalizedRight ? 0 : leftNumeric && !rightNumeric ? -1 : rightNumeric && !leftNumeric ? 1 : normalizedLeft < normalizedRight ? -1 : 1;
|
|
2945
|
+
}
|
|
2946
|
+
__name(compareIdentifiers, "compareIdentifiers");
|
|
2620
2947
|
function compareMainParsed(left, right) {
|
|
2621
2948
|
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;
|
|
2622
2949
|
}
|
|
@@ -2641,29 +2968,18 @@ function compareParsed(left, right) {
|
|
|
2641
2968
|
return compareMainParsed(left, right) || comparePrereleaseParsed(left, right);
|
|
2642
2969
|
}
|
|
2643
2970
|
__name(compareParsed, "compareParsed");
|
|
2644
|
-
function
|
|
2645
|
-
|
|
2646
|
-
const input = typeof value === "number" ? String(value) : value;
|
|
2647
|
-
if (typeof input !== "string") return null;
|
|
2648
|
-
let match2 = null;
|
|
2649
|
-
if (options.rtl) {
|
|
2650
|
-
const expression = safeRegex(options.includePrerelease ? COERCE_FULL : COERCE, "g");
|
|
2651
|
-
let next;
|
|
2652
|
-
while ((next = expression.exec(input)) && (!match2 || match2.index + match2[0].length !== input.length)) {
|
|
2653
|
-
if (!match2 || next.index + next[0].length !== match2.index + match2[0].length) match2 = next;
|
|
2654
|
-
expression.lastIndex = next.index + next[1].length + next[2].length;
|
|
2655
|
-
}
|
|
2656
|
-
} else match2 = (options.includePrerelease ? COERCE_FULL_EXACT : COERCE_EXACT).exec(input);
|
|
2657
|
-
if (!match2) return null;
|
|
2658
|
-
const major = match2[2];
|
|
2659
|
-
return tryParse(`${major}.${match2[3] || "0"}.${match2[4] || "0"}${options.includePrerelease && match2[5] ? `-${match2[5]}` : ""}${options.includePrerelease && match2[6] ? `+${match2[6]}` : ""}`, options);
|
|
2971
|
+
function compare(left, right, options = {}) {
|
|
2972
|
+
return compareParsed(parse(left, options), parse(right, options));
|
|
2660
2973
|
}
|
|
2661
|
-
__name(
|
|
2974
|
+
__name(compare, "compare");
|
|
2975
|
+
|
|
2976
|
+
// ../plugin-core/dist/node_modules/.pnpm/verkit@0.4.0/node_modules/verkit/dist/set-CC5YeoYX.mjs
|
|
2662
2977
|
var STRICT_COMPARATOR = safeRegex(String.raw`^${GREATER_LESS_THAN}\s*(${FULL_PLAIN})$|^$`);
|
|
2663
|
-
var LOOSE_COMPARATOR
|
|
2978
|
+
var LOOSE_COMPARATOR = safeRegex(String.raw`^${GREATER_LESS_THAN}\s*(${LOOSE_PLAIN})$|^$`);
|
|
2664
2979
|
function parseComparator(comparator, options = {}) {
|
|
2980
|
+
if (typeof comparator !== "string") return comparator;
|
|
2665
2981
|
const normalized = comparator.trim().replaceAll(/\s+/g, " ");
|
|
2666
|
-
const match2 = normalized.match(options.loose ? LOOSE_COMPARATOR
|
|
2982
|
+
const match2 = normalized.match(options.loose ? LOOSE_COMPARATOR : STRICT_COMPARATOR);
|
|
2667
2983
|
if (!match2) throw new TypeError(`Invalid comparator: ${normalized}`);
|
|
2668
2984
|
const operator = match2[1] === "=" ? "" : match2[1] || "";
|
|
2669
2985
|
const version2 = match2[2] ? parse(match2[2], options) : null;
|
|
@@ -2675,10 +2991,8 @@ function parseComparator(comparator, options = {}) {
|
|
|
2675
2991
|
};
|
|
2676
2992
|
}
|
|
2677
2993
|
__name(parseComparator, "parseComparator");
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
}
|
|
2681
|
-
__name(compare, "compare");
|
|
2994
|
+
|
|
2995
|
+
// ../plugin-core/dist/node_modules/.pnpm/verkit@0.4.0/node_modules/verkit/dist/range-DvX-Y6iv.mjs
|
|
2682
2996
|
var BUILD_STRIP = new RegExp(BUILD, "g");
|
|
2683
2997
|
var BUILD_SAFE = safeRegex(BUILD);
|
|
2684
2998
|
var STRICT_HYPHEN = safeRegex(String.raw`^\s*(${XRANGE_PLAIN})\s+-\s+(${XRANGE_PLAIN})\s*$`);
|
|
@@ -2695,7 +3009,7 @@ var LOOSE_XRANGE = safeRegex(String.raw`^${GREATER_LESS_THAN}\s*${XRANGE_PLAIN_L
|
|
|
2695
3009
|
var STAR = safeRegex(String.raw`(<|>)?=?\s*\*`);
|
|
2696
3010
|
var GTE_ZERO = /^\s*>=\s*0\.0\.0\s*$/;
|
|
2697
3011
|
var GTE_ZERO_PRERELEASE = /^\s*>=\s*0\.0\.0-0\s*$/;
|
|
2698
|
-
var
|
|
3012
|
+
var LOOSE_COMPARATOR2 = safeRegex(String.raw`^${GREATER_LESS_THAN}\s*(${LOOSE_PLAIN})$|^$`);
|
|
2699
3013
|
function isWildcard(value) {
|
|
2700
3014
|
return !value || String(value).toLowerCase() === "x" || String(value) === "*";
|
|
2701
3015
|
}
|
|
@@ -2802,7 +3116,7 @@ function expandComparator(comparator, options) {
|
|
|
2802
3116
|
__name(expandComparator, "expandComparator");
|
|
2803
3117
|
function parseSimpleRange(input, options) {
|
|
2804
3118
|
let parts = replaceHyphenRange(input.replace(BUILD_STRIP, ""), options).replace(COMPARATOR_TRIM, "$1$2$3").replace(TILDE_TRIM, "$1~").replace(CARET_TRIM, "$1^").split(" ").map((part) => expandComparator(part, options)).join(" ").split(/\s+/).map((part) => part.trim().replace(options.includePrerelease ? GTE_ZERO_PRERELEASE : GTE_ZERO, ""));
|
|
2805
|
-
if (options.loose) parts = parts.filter((part) =>
|
|
3119
|
+
if (options.loose) parts = parts.filter((part) => LOOSE_COMPARATOR2.test(part));
|
|
2806
3120
|
const unique = /* @__PURE__ */ new Map();
|
|
2807
3121
|
for (const comparator of parts.map((part) => parseComparator(part, options))) {
|
|
2808
3122
|
if (comparator.value === "<0.0.0-0") return [comparator];
|
|
@@ -2815,9 +3129,9 @@ __name(parseSimpleRange, "parseSimpleRange");
|
|
|
2815
3129
|
function parseRange(range, options = {}) {
|
|
2816
3130
|
if (typeof range !== "string") return range;
|
|
2817
3131
|
const parsedOptions = { ...options };
|
|
2818
|
-
const
|
|
2819
|
-
let sets =
|
|
2820
|
-
if (!sets.length) throw new TypeError(`Range contains no valid comparator sets: ${
|
|
3132
|
+
const normalizedRange = range.trim().replaceAll(/\s+/g, " ");
|
|
3133
|
+
let sets = normalizedRange.split("||").map((part) => parseSimpleRange(part.trim(), parsedOptions)).filter((set) => set.length);
|
|
3134
|
+
if (!sets.length) throw new TypeError(`Range contains no valid comparator sets: ${normalizedRange}`);
|
|
2821
3135
|
if (sets.length > 1) {
|
|
2822
3136
|
const first = sets[0];
|
|
2823
3137
|
sets = sets.filter((set) => set[0]?.value !== "<0.0.0-0");
|
|
@@ -2828,21 +3142,47 @@ function parseRange(range, options = {}) {
|
|
|
2828
3142
|
}
|
|
2829
3143
|
}
|
|
2830
3144
|
return {
|
|
2831
|
-
normalized: sets.map((set) => set.map((comparator) => comparator.value).join(" ")).join("||"),
|
|
2832
3145
|
options: parsedOptions,
|
|
2833
|
-
raw: raw2,
|
|
2834
3146
|
sets
|
|
2835
3147
|
};
|
|
2836
3148
|
}
|
|
2837
3149
|
__name(parseRange, "parseRange");
|
|
3150
|
+
|
|
3151
|
+
// ../plugin-core/dist/node_modules/.pnpm/verkit@0.4.0/node_modules/verkit/dist/version-CQ98ZBpL.mjs
|
|
3152
|
+
var COERCE_EXACT = safeRegex(COERCE);
|
|
3153
|
+
var COERCE_FULL_EXACT = safeRegex(COERCE_FULL);
|
|
3154
|
+
safeRegex(`^${PRERELEASE}$`);
|
|
3155
|
+
safeRegex(`^${PRERELEASE_LOOSE}$`);
|
|
3156
|
+
function normalizeFull(version2, options = {}) {
|
|
3157
|
+
const parsed = tryParse(version2, options);
|
|
3158
|
+
return parsed ? formatFullVersion(parsed) : null;
|
|
3159
|
+
}
|
|
3160
|
+
__name(normalizeFull, "normalizeFull");
|
|
2838
3161
|
function normalize(version2, options = {}) {
|
|
2839
3162
|
const parsed = tryParse(version2, options);
|
|
2840
3163
|
return parsed ? formatComparableVersion(parsed) : null;
|
|
2841
3164
|
}
|
|
2842
3165
|
__name(normalize, "normalize");
|
|
2843
3166
|
function coerce(value, options = {}) {
|
|
2844
|
-
|
|
2845
|
-
|
|
3167
|
+
if (typeof value === "object") return value;
|
|
3168
|
+
const input = typeof value === "number" ? String(value) : value;
|
|
3169
|
+
let match2 = null;
|
|
3170
|
+
if (options.rtl) {
|
|
3171
|
+
const source = options.includePrerelease ? COERCE_FULL : COERCE;
|
|
3172
|
+
const expression = safeRegex(source, "g");
|
|
3173
|
+
let next;
|
|
3174
|
+
while ((next = expression.exec(input)) && (!match2 || match2.index + match2[0].length !== input.length)) {
|
|
3175
|
+
if (!match2 || next.index + next[0].length !== match2.index + match2[0].length) match2 = next;
|
|
3176
|
+
expression.lastIndex = next.index + next[1].length + next[2].length;
|
|
3177
|
+
}
|
|
3178
|
+
} else match2 = (options.includePrerelease ? COERCE_FULL_EXACT : COERCE_EXACT).exec(input);
|
|
3179
|
+
if (!match2) return null;
|
|
3180
|
+
const major = match2[2];
|
|
3181
|
+
const minor = match2[3] || "0";
|
|
3182
|
+
const patch = match2[4] || "0";
|
|
3183
|
+
const prerelease = options.includePrerelease && match2[5] ? `-${match2[5]}` : "";
|
|
3184
|
+
const build = options.includePrerelease && match2[6] ? `+${match2[6]}` : "";
|
|
3185
|
+
return tryParse(`${major}.${minor}.${patch}${prerelease}${build}`, options);
|
|
2846
3186
|
}
|
|
2847
3187
|
__name(coerce, "coerce");
|
|
2848
3188
|
|
|
@@ -3139,15 +3479,16 @@ function compileAppVersion(releases) {
|
|
|
3139
3479
|
const retainedIds = new Set(segmentReleases.flatMap(({ retainedIds: retainedIds2, rollbackIds }) => [...retainedIds2, ...rollbackIds]));
|
|
3140
3480
|
const retainedReleases = releases.filter((release) => retainedIds.has(release.id));
|
|
3141
3481
|
const descriptorIndex = new Map(retainedReleases.map((release, index) => [release.id, index]));
|
|
3482
|
+
const segments = mergeSegments(segmentReleases.map(({ segment, retainedIds: retainedIds2, rollbackIds }) => ({
|
|
3483
|
+
...segment,
|
|
3484
|
+
releaseIndexes: releases.filter((release) => retainedIds2.has(release.id)).map((release) => descriptorIndex.get(release.id)).filter((index) => index !== void 0),
|
|
3485
|
+
rollbackReleaseIndexes: releases.filter((release) => rollbackIds.has(release.id)).map((release) => descriptorIndex.get(release.id)).filter((index) => index !== void 0)
|
|
3486
|
+
})).filter((segment) => segment.releaseIndexes.length > 0 || segment.rollbackReleaseIndexes.length > 0));
|
|
3142
3487
|
return {
|
|
3143
3488
|
envelope: {
|
|
3144
3489
|
fallbackPolicy: RELEASE_CATALOG_FALLBACK_POLICY,
|
|
3145
3490
|
schemaVersion: RELEASE_CATALOG_SCHEMA_VERSION,
|
|
3146
|
-
segments
|
|
3147
|
-
...segment,
|
|
3148
|
-
releaseIndexes: releases.filter((release) => retainedIds2.has(release.id)).map((release) => descriptorIndex.get(release.id)).filter((index) => index !== void 0),
|
|
3149
|
-
rollbackReleaseIndexes: releases.filter((release) => rollbackIds.has(release.id)).map((release) => descriptorIndex.get(release.id)).filter((index) => index !== void 0)
|
|
3150
|
-
})).filter((segment) => segment.releaseIndexes.length > 0 || segment.rollbackReleaseIndexes.length > 0)),
|
|
3491
|
+
segments,
|
|
3151
3492
|
strategy: "APP_VERSION"
|
|
3152
3493
|
},
|
|
3153
3494
|
retainedReleases
|
|
@@ -3221,7 +3562,8 @@ function versionInSegment(version2, segment) {
|
|
|
3221
3562
|
}
|
|
3222
3563
|
__name(versionInSegment, "versionInSegment");
|
|
3223
3564
|
function canonicalizeAppVersion(appVersion) {
|
|
3224
|
-
|
|
3565
|
+
const version2 = coerce(appVersion);
|
|
3566
|
+
return version2 ? normalizeFull(version2) : null;
|
|
3225
3567
|
}
|
|
3226
3568
|
__name(canonicalizeAppVersion, "canonicalizeAppVersion");
|
|
3227
3569
|
function projectCompiledCatalog(catalog, appVersion) {
|
|
@@ -3255,6 +3597,7 @@ var ReleaseCatalogMutationError = class extends Error {
|
|
|
3255
3597
|
static {
|
|
3256
3598
|
__name(this, "ReleaseCatalogMutationError");
|
|
3257
3599
|
}
|
|
3600
|
+
code;
|
|
3258
3601
|
name = "ReleaseCatalogMutationError";
|
|
3259
3602
|
constructor(code, message) {
|
|
3260
3603
|
super(message);
|
|
@@ -3521,6 +3864,7 @@ var ReleaseManagementError = class extends Error {
|
|
|
3521
3864
|
static {
|
|
3522
3865
|
__name(this, "ReleaseManagementError");
|
|
3523
3866
|
}
|
|
3867
|
+
code;
|
|
3524
3868
|
name = "ReleaseManagementError";
|
|
3525
3869
|
constructor(code, message) {
|
|
3526
3870
|
super(message);
|
|
@@ -3643,7 +3987,8 @@ var createStorageDownloadUrl = /* @__PURE__ */ __name((signingKey) => {
|
|
|
3643
3987
|
name: "HMAC"
|
|
3644
3988
|
}, false, ["sign"]);
|
|
3645
3989
|
return async ({ storageUri }) => {
|
|
3646
|
-
|
|
3990
|
+
const signature = encodeBytes(new Uint8Array(await crypto.subtle.sign("HMAC", await key, new TextEncoder().encode(storageUri))));
|
|
3991
|
+
return { url: createStorageDownloadPath(storageUri, signature) };
|
|
3647
3992
|
};
|
|
3648
3993
|
}, "createStorageDownloadUrl");
|
|
3649
3994
|
|
|
@@ -3875,16 +4220,6 @@ var createReleaseManagementRouteHandlers = /* @__PURE__ */ __name(() => ({
|
|
|
3875
4220
|
}), "createReleaseManagementRouteHandlers");
|
|
3876
4221
|
|
|
3877
4222
|
// ../../packages/server/dist/insights/errors.mjs
|
|
3878
|
-
var InsightsScanLimitExceededError = class extends Error {
|
|
3879
|
-
static {
|
|
3880
|
-
__name(this, "InsightsScanLimitExceededError");
|
|
3881
|
-
}
|
|
3882
|
-
constructor(limit) {
|
|
3883
|
-
super(`Insights event scan exceeded ${limit} rows.`);
|
|
3884
|
-
this.limit = limit;
|
|
3885
|
-
this.name = "InsightsScanLimitExceededError";
|
|
3886
|
-
}
|
|
3887
|
-
};
|
|
3888
4223
|
var InsightsBadRequestError = class extends Error {
|
|
3889
4224
|
static {
|
|
3890
4225
|
__name(this, "InsightsBadRequestError");
|
|
@@ -3895,6 +4230,7 @@ var InsightsPayloadTooLargeError = class extends Error {
|
|
|
3895
4230
|
static {
|
|
3896
4231
|
__name(this, "InsightsPayloadTooLargeError");
|
|
3897
4232
|
}
|
|
4233
|
+
maximumBytes;
|
|
3898
4234
|
name = "InsightsPayloadTooLargeError";
|
|
3899
4235
|
constructor(maximumBytes) {
|
|
3900
4236
|
super(`Event payload exceeds ${maximumBytes} bytes`);
|
|
@@ -3904,7 +4240,8 @@ var InsightsPayloadTooLargeError = class extends Error {
|
|
|
3904
4240
|
|
|
3905
4241
|
// ../../packages/server/dist/insights/eventInput.mjs
|
|
3906
4242
|
var MAX_EVENT_STRING_LENGTH = 1024;
|
|
3907
|
-
var
|
|
4243
|
+
var MAX_IDENTITY_LENGTH = 255;
|
|
4244
|
+
var EVENT_BODY_MAX_BYTES = 16384;
|
|
3908
4245
|
var eventKeys = /* @__PURE__ */ new Set([
|
|
3909
4246
|
"type",
|
|
3910
4247
|
"installId",
|
|
@@ -3928,7 +4265,7 @@ function isRecord3(value) {
|
|
|
3928
4265
|
__name(isRecord3, "isRecord");
|
|
3929
4266
|
function requireStringField(payload, key) {
|
|
3930
4267
|
const value = payload[key];
|
|
3931
|
-
if (typeof value !== "string" || value.length === 0 || value.length > MAX_EVENT_STRING_LENGTH) throw new InsightsBadRequestError(`Invalid event field: ${key}`);
|
|
4268
|
+
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}`);
|
|
3932
4269
|
return value;
|
|
3933
4270
|
}
|
|
3934
4271
|
__name(requireStringField, "requireStringField");
|
|
@@ -3937,6 +4274,12 @@ function requireNullableStringField(payload, key) {
|
|
|
3937
4274
|
return requireStringField(payload, key);
|
|
3938
4275
|
}
|
|
3939
4276
|
__name(requireNullableStringField, "requireNullableStringField");
|
|
4277
|
+
function requireIdentityField(payload, key) {
|
|
4278
|
+
const value = requireStringField(payload, key);
|
|
4279
|
+
if (value.length > MAX_IDENTITY_LENGTH) throw new InsightsBadRequestError(`Invalid event field: ${key}`);
|
|
4280
|
+
return value;
|
|
4281
|
+
}
|
|
4282
|
+
__name(requireIdentityField, "requireIdentityField");
|
|
3940
4283
|
async function readBoundedText(request) {
|
|
3941
4284
|
const contentLength = request.headers.get("content-length");
|
|
3942
4285
|
const declaredByteLength = Number(contentLength);
|
|
@@ -3974,9 +4317,9 @@ function requireEvent(payload) {
|
|
|
3974
4317
|
const platform = requireStringField(payload, "platform");
|
|
3975
4318
|
if (platform !== "ios" && platform !== "android") throw new InsightsBadRequestError("Invalid event field: platform");
|
|
3976
4319
|
const base = {
|
|
3977
|
-
installId:
|
|
4320
|
+
installId: requireIdentityField(payload, "installId"),
|
|
3978
4321
|
toBundleId: requireStringField(payload, "toBundleId"),
|
|
3979
|
-
...payload.userId === void 0 ? {} : { userId:
|
|
4322
|
+
...payload.userId === void 0 ? {} : { userId: requireIdentityField(payload, "userId") },
|
|
3980
4323
|
...payload.username === void 0 ? {} : { username: requireStringField(payload, "username") },
|
|
3981
4324
|
platform,
|
|
3982
4325
|
appVersion: requireStringField(payload, "appVersion"),
|
|
@@ -4018,60 +4361,133 @@ async function parseBundleEventRequest(request) {
|
|
|
4018
4361
|
return requireEvent(await parseJson(request));
|
|
4019
4362
|
}
|
|
4020
4363
|
__name(parseBundleEventRequest, "parseBundleEventRequest");
|
|
4364
|
+
function createBundleEventRow(input) {
|
|
4365
|
+
input = requireEvent(input);
|
|
4366
|
+
const base = {
|
|
4367
|
+
app_version: input.appVersion,
|
|
4368
|
+
channel: input.channel,
|
|
4369
|
+
cohort: input.cohort,
|
|
4370
|
+
fingerprint_hash: input.fingerprintHash,
|
|
4371
|
+
from_release_id: input.fromReleaseId,
|
|
4372
|
+
id: createUUIDv7(),
|
|
4373
|
+
install_id: input.installId,
|
|
4374
|
+
platform: input.platform,
|
|
4375
|
+
received_at_ms: Date.now(),
|
|
4376
|
+
sdk_version: input.sdkVersion ?? null,
|
|
4377
|
+
to_bundle_id: input.toBundleId,
|
|
4378
|
+
to_release_id: input.toReleaseId,
|
|
4379
|
+
user_id: input.userId ?? null,
|
|
4380
|
+
username: input.username ?? null
|
|
4381
|
+
};
|
|
4382
|
+
switch (input.type) {
|
|
4383
|
+
case "UPDATE_APPLIED":
|
|
4384
|
+
case "RECOVERED":
|
|
4385
|
+
case "RELEASE_ADOPTED":
|
|
4386
|
+
return {
|
|
4387
|
+
...base,
|
|
4388
|
+
from_bundle_id: input.fromBundleId,
|
|
4389
|
+
type: input.type,
|
|
4390
|
+
update_strategy: input.updateStrategy
|
|
4391
|
+
};
|
|
4392
|
+
case "UNCHANGED":
|
|
4393
|
+
return {
|
|
4394
|
+
...base,
|
|
4395
|
+
from_bundle_id: null,
|
|
4396
|
+
type: input.type,
|
|
4397
|
+
update_strategy: null
|
|
4398
|
+
};
|
|
4399
|
+
}
|
|
4400
|
+
}
|
|
4401
|
+
__name(createBundleEventRow, "createBundleEventRow");
|
|
4021
4402
|
|
|
4022
4403
|
// ../../packages/server/dist/insights/queryInput.mjs
|
|
4023
|
-
var
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
const
|
|
4035
|
-
if (
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4404
|
+
var MAX_PAGE_LIMIT = 100;
|
|
4405
|
+
var MAX_IDENTITY_LENGTH2 = 255;
|
|
4406
|
+
var MAX_CURSOR_LENGTH = 8192;
|
|
4407
|
+
var readSingle = /* @__PURE__ */ __name((url, key) => {
|
|
4408
|
+
const values = url.searchParams.getAll(key);
|
|
4409
|
+
if (values.length > 1) throw new InsightsBadRequestError(`Duplicate '${key}' query parameter.`);
|
|
4410
|
+
return values[0];
|
|
4411
|
+
}, "readSingle");
|
|
4412
|
+
var readPageLimit = /* @__PURE__ */ __name((url) => {
|
|
4413
|
+
const value = readSingle(url, "limit");
|
|
4414
|
+
if (value === void 0) return void 0;
|
|
4415
|
+
const limit = Number(value);
|
|
4416
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > MAX_PAGE_LIMIT) throw new InsightsBadRequestError("Invalid 'limit' query parameter.");
|
|
4417
|
+
return limit;
|
|
4418
|
+
}, "readPageLimit");
|
|
4419
|
+
var readCursor = /* @__PURE__ */ __name((url) => {
|
|
4420
|
+
const cursor = readSingle(url, "cursor");
|
|
4421
|
+
if (cursor !== void 0 && (cursor.length === 0 || cursor.length > MAX_CURSOR_LENGTH)) throw new InsightsBadRequestError("Invalid 'cursor' query parameter.");
|
|
4422
|
+
return cursor;
|
|
4423
|
+
}, "readCursor");
|
|
4424
|
+
var readId = /* @__PURE__ */ __name((url, key, maximumLength = MAX_IDENTITY_LENGTH2) => {
|
|
4425
|
+
const value = readSingle(url, key);
|
|
4426
|
+
if (value === void 0 || value.length === 0 || value.length > maximumLength) throw new InsightsBadRequestError(`Invalid '${key}' query parameter.`);
|
|
4427
|
+
return value;
|
|
4428
|
+
}, "readId");
|
|
4429
|
+
var readTimestamp = /* @__PURE__ */ __name((url, key) => {
|
|
4430
|
+
const raw2 = readSingle(url, key);
|
|
4431
|
+
if (raw2 === void 0) return void 0;
|
|
4432
|
+
const value = Number(raw2);
|
|
4433
|
+
if (!raw2.length || !Number.isSafeInteger(value) || value < 0) throw new InsightsBadRequestError(`Invalid '${key}' query parameter.`);
|
|
4434
|
+
return value;
|
|
4435
|
+
}, "readTimestamp");
|
|
4436
|
+
var readScope = /* @__PURE__ */ __name((url) => {
|
|
4437
|
+
const platform = readSingle(url, "platform");
|
|
4438
|
+
if (platform !== "ios" && platform !== "android") throw new InsightsBadRequestError("Invalid 'platform' query parameter.");
|
|
4439
|
+
return {
|
|
4440
|
+
platform,
|
|
4441
|
+
channel: readId(url, "channel", 1024)
|
|
4442
|
+
};
|
|
4443
|
+
}, "readScope");
|
|
4444
|
+
var parseEventPageInput = /* @__PURE__ */ __name((request) => {
|
|
4042
4445
|
const url = new URL(request.url);
|
|
4446
|
+
const bundleFields = [
|
|
4447
|
+
"bundleId",
|
|
4448
|
+
"outcome",
|
|
4449
|
+
"platform",
|
|
4450
|
+
"channel"
|
|
4451
|
+
];
|
|
4452
|
+
let bundle;
|
|
4453
|
+
if (bundleFields.some((key) => url.searchParams.has(key))) {
|
|
4454
|
+
const outcome = readSingle(url, "outcome");
|
|
4455
|
+
if (outcome !== "applied" && outcome !== "recovered" && outcome !== "adopted") throw new InsightsBadRequestError("Invalid 'outcome' query parameter.");
|
|
4456
|
+
bundle = {
|
|
4457
|
+
...readScope(url),
|
|
4458
|
+
bundleId: readId(url, "bundleId", 1024),
|
|
4459
|
+
outcome
|
|
4460
|
+
};
|
|
4461
|
+
}
|
|
4043
4462
|
return {
|
|
4044
|
-
|
|
4045
|
-
|
|
4463
|
+
beforeReceivedAtMs: readTimestamp(url, "beforeReceivedAtMs"),
|
|
4464
|
+
sinceMs: readTimestamp(url, "sinceMs"),
|
|
4465
|
+
cursor: readCursor(url),
|
|
4466
|
+
limit: readPageLimit(url),
|
|
4467
|
+
...bundle === void 0 ? {} : { bundle }
|
|
4046
4468
|
};
|
|
4047
|
-
}, "
|
|
4048
|
-
var
|
|
4049
|
-
const
|
|
4050
|
-
|
|
4469
|
+
}, "parseEventPageInput");
|
|
4470
|
+
var parseUserInstallationPageInput = /* @__PURE__ */ __name((request) => {
|
|
4471
|
+
const url = new URL(request.url);
|
|
4472
|
+
const cursor = readCursor(url);
|
|
4473
|
+
const limit = readPageLimit(url);
|
|
4051
4474
|
return {
|
|
4052
|
-
|
|
4053
|
-
|
|
4475
|
+
userId: readId(url, "userId"),
|
|
4476
|
+
...cursor === void 0 ? {} : { cursor },
|
|
4477
|
+
...limit === void 0 ? {} : { limit }
|
|
4054
4478
|
};
|
|
4055
|
-
}, "
|
|
4056
|
-
var
|
|
4479
|
+
}, "parseUserInstallationPageInput");
|
|
4480
|
+
var parseReportingOverviewInput = /* @__PURE__ */ __name((request) => {
|
|
4057
4481
|
const url = new URL(request.url);
|
|
4058
|
-
const
|
|
4059
|
-
if (windows.length > 1) throw new InsightsBadRequestError("Duplicate 'window' query parameter.");
|
|
4060
|
-
const window = windows[0] ?? "30d";
|
|
4482
|
+
const window = readSingle(url, "window") ?? "30d";
|
|
4061
4483
|
if (window !== "24h" && window !== "7d" && window !== "30d") throw new InsightsBadRequestError("Invalid 'window' query parameter.");
|
|
4062
|
-
const
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
if (userId !== void 0 && (userId.length === 0 || userId.length > MAX_USER_ID_LENGTH)) throw new InsightsBadRequestError("Invalid 'userId' query parameter.");
|
|
4066
|
-
return userId === void 0 ? { window } : {
|
|
4484
|
+
const bundleId = readSingle(url, "bundleId");
|
|
4485
|
+
return {
|
|
4486
|
+
...readScope(url),
|
|
4067
4487
|
window,
|
|
4068
|
-
|
|
4488
|
+
...bundleId === void 0 ? {} : { bundleId: readId(url, "bundleId", 1024) }
|
|
4069
4489
|
};
|
|
4070
|
-
}, "
|
|
4071
|
-
var parseSearchInput = /* @__PURE__ */ __name((request) => ({
|
|
4072
|
-
...parsePagination(request),
|
|
4073
|
-
query: new URL(request.url).searchParams.get("query")?.trim() ?? ""
|
|
4074
|
-
}), "parseSearchInput");
|
|
4490
|
+
}, "parseReportingOverviewInput");
|
|
4075
4491
|
|
|
4076
4492
|
// ../../packages/server/dist/insights/routes.mjs
|
|
4077
4493
|
var json = /* @__PURE__ */ __name((body, status) => Response.json(body, {
|
|
@@ -4081,7 +4497,11 @@ var json = /* @__PURE__ */ __name((body, status) => Response.json(body, {
|
|
|
4081
4497
|
var requireParam = /* @__PURE__ */ __name((params, key) => {
|
|
4082
4498
|
const value = params[key];
|
|
4083
4499
|
if (value === void 0 || value.length === 0) throw new InsightsBadRequestError(`Missing route parameter: ${key}`);
|
|
4084
|
-
|
|
4500
|
+
try {
|
|
4501
|
+
return decodeURIComponent(value);
|
|
4502
|
+
} catch {
|
|
4503
|
+
throw new InsightsBadRequestError(`Invalid route parameter: ${key}`);
|
|
4504
|
+
}
|
|
4085
4505
|
}, "requireParam");
|
|
4086
4506
|
var run = /* @__PURE__ */ __name(async (operation) => {
|
|
4087
4507
|
try {
|
|
@@ -4089,10 +4509,6 @@ var run = /* @__PURE__ */ __name(async (operation) => {
|
|
|
4089
4509
|
} catch (error) {
|
|
4090
4510
|
if (error instanceof InsightsBadRequestError) return json({ error: error.message }, 400);
|
|
4091
4511
|
if (error instanceof InsightsPayloadTooLargeError) return json({ error: error.message }, 413);
|
|
4092
|
-
if (error instanceof InsightsScanLimitExceededError) return json({ error: {
|
|
4093
|
-
code: "INSIGHTS_SCAN_LIMIT_EXCEEDED",
|
|
4094
|
-
limit: error.limit
|
|
4095
|
-
} }, 503);
|
|
4096
4512
|
throw error;
|
|
4097
4513
|
}
|
|
4098
4514
|
}, "run");
|
|
@@ -4102,32 +4518,27 @@ var createInsightsRouteHandlers = /* @__PURE__ */ __name((provider) => ({
|
|
|
4102
4518
|
await provider.appendBundleEvent(await parseBundleEventRequest(request));
|
|
4103
4519
|
return new Response(null, { status: 204 });
|
|
4104
4520
|
}), "appendBundleEvent"),
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
const
|
|
4108
|
-
return
|
|
4109
|
-
}), "
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
getInstallationHistory: /* @__PURE__ */ __name((params, request) => query(() => {
|
|
4117
|
-
const input = parsePagination(request);
|
|
4118
|
-
return provider.getInstallationHistory(requireParam(params, "installId"), input.limit, input.offset);
|
|
4119
|
-
}), "getInstallationHistory")
|
|
4521
|
+
getReportingOverview: /* @__PURE__ */ __name((_params, request) => query(() => provider.getReportingOverview(parseReportingOverviewInput(request))), "getReportingOverview"),
|
|
4522
|
+
getInstallation: /* @__PURE__ */ __name((params) => run(async () => {
|
|
4523
|
+
const installation = await provider.getInstallation({ installId: requireParam(params, "installId") });
|
|
4524
|
+
return installation === null ? json({ error: "Installation not found" }, 404) : json(installation, 200);
|
|
4525
|
+
}), "getInstallation"),
|
|
4526
|
+
listEvents: /* @__PURE__ */ __name((_params, request) => query(() => provider.listEvents(parseEventPageInput(request))), "listEvents"),
|
|
4527
|
+
listInstallationEvents: /* @__PURE__ */ __name((params, request) => query(() => provider.listInstallationEvents({
|
|
4528
|
+
...parseEventPageInput(request),
|
|
4529
|
+
installId: requireParam(params, "installId")
|
|
4530
|
+
})), "listInstallationEvents"),
|
|
4531
|
+
pageInstallationsByCurrentUserId: /* @__PURE__ */ __name((_params, request) => query(() => provider.pageInstallationsByCurrentUserId(parseUserInstallationPageInput(request))), "pageInstallationsByCurrentUserId")
|
|
4120
4532
|
}), "createInsightsRouteHandlers");
|
|
4121
4533
|
var registerInsightsClientRoutes = /* @__PURE__ */ __name((add) => {
|
|
4122
4534
|
add("POST", "/events", "appendBundleEvent");
|
|
4123
4535
|
}, "registerInsightsClientRoutes");
|
|
4124
4536
|
var registerInsightsAdminRoutes = /* @__PURE__ */ __name((add) => {
|
|
4125
|
-
add("GET", "/
|
|
4126
|
-
add("GET", "/
|
|
4127
|
-
add("GET", "/installations
|
|
4128
|
-
add("GET", "/installations/
|
|
4129
|
-
add("GET", "/installations", "
|
|
4130
|
-
add("GET", "/installations/:installId/events", "getInstallationHistory");
|
|
4537
|
+
add("GET", "/events", "listEvents");
|
|
4538
|
+
add("GET", "/overview", "getReportingOverview");
|
|
4539
|
+
add("GET", "/installations", "pageInstallationsByCurrentUserId");
|
|
4540
|
+
add("GET", "/installations/:installId/events", "listInstallationEvents");
|
|
4541
|
+
add("GET", "/installations/:installId", "getInstallation");
|
|
4131
4542
|
}, "registerInsightsAdminRoutes");
|
|
4132
4543
|
|
|
4133
4544
|
// ../../packages/server/dist/internalRouter.mjs
|
|
@@ -4293,474 +4704,318 @@ function createHotUpdaterHandlers(api, insights, apiKeyAuth, downloadStorageObje
|
|
|
4293
4704
|
}
|
|
4294
4705
|
__name(createHotUpdaterHandlers, "createHotUpdaterHandlers");
|
|
4295
4706
|
|
|
4296
|
-
// ../../packages/server/dist/insights/
|
|
4297
|
-
var
|
|
4298
|
-
var
|
|
4299
|
-
var
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
"
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
"24h": {
|
|
4307
|
-
bucketCount: 24,
|
|
4308
|
-
bucketSizeMs: HOUR_MS
|
|
4309
|
-
},
|
|
4310
|
-
"7d": {
|
|
4311
|
-
bucketCount: 7,
|
|
4312
|
-
bucketSizeMs: DAY_MS
|
|
4313
|
-
},
|
|
4314
|
-
"30d": {
|
|
4315
|
-
bucketCount: 30,
|
|
4316
|
-
bucketSizeMs: DAY_MS
|
|
4317
|
-
}
|
|
4707
|
+
// ../../packages/server/dist/insights/provider.mjs
|
|
4708
|
+
var DEFAULT_PAGE_LIMIT = 50;
|
|
4709
|
+
var MAX_PAGE_LIMIT2 = 100;
|
|
4710
|
+
var MAX_EVENT_ID_LENGTH = 1024;
|
|
4711
|
+
var MAX_IDENTITY_LENGTH3 = 255;
|
|
4712
|
+
var MAX_CURSOR_LENGTH2 = 8192;
|
|
4713
|
+
var WINDOW_MS = {
|
|
4714
|
+
"24h": 864e5,
|
|
4715
|
+
"7d": 6048e5,
|
|
4716
|
+
"30d": 2592e6
|
|
4318
4717
|
};
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
}
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
if (
|
|
4326
|
-
return
|
|
4327
|
-
}
|
|
4328
|
-
__name(
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
const
|
|
4335
|
-
|
|
4336
|
-
const
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
const bucket = latestByBucket[Math.floor((row.received_at_ms - windowStartMs) / definition.bucketSizeMs)];
|
|
4350
|
-
if (bucket === void 0) continue;
|
|
4351
|
-
const current = bucket.get(row.install_id);
|
|
4352
|
-
if (current === void 0 || isNewer(row, current)) bucket.set(row.install_id, row);
|
|
4353
|
-
}
|
|
4354
|
-
const bundleCountsByBucket = latestByBucket.map((bucket) => {
|
|
4355
|
-
const counts = /* @__PURE__ */ new Map();
|
|
4356
|
-
for (const row of bucket.values()) counts.set(row.to_bundle_id, (counts.get(row.to_bundle_id) ?? 0) + 1);
|
|
4357
|
-
return counts;
|
|
4358
|
-
});
|
|
4359
|
-
const bundleObservationTotals = /* @__PURE__ */ new Map();
|
|
4360
|
-
for (const counts of bundleCountsByBucket) for (const [bundleId, count] of counts) bundleObservationTotals.set(bundleId, (bundleObservationTotals.get(bundleId) ?? 0) + count);
|
|
4361
|
-
return {
|
|
4362
|
-
asOfMs: request.asOfMs,
|
|
4363
|
-
window: request.window,
|
|
4364
|
-
activeInstallations: selectedInstallIds.size,
|
|
4365
|
-
series: latestByBucket.map((bucket, index) => ({
|
|
4366
|
-
bucketStartMs: windowStartMs + index * definition.bucketSizeMs,
|
|
4367
|
-
value: bucket.size
|
|
4368
|
-
})),
|
|
4369
|
-
bundleSeries: [...bundleObservationTotals].sort(([leftId, leftTotal], [rightId, rightTotal]) => rightTotal - leftTotal || compareCodePoints(leftId, rightId)).map(([bundleId]) => ({
|
|
4370
|
-
bundleId,
|
|
4371
|
-
series: bundleCountsByBucket.map((counts, index) => ({
|
|
4372
|
-
bucketStartMs: windowStartMs + index * definition.bucketSizeMs,
|
|
4373
|
-
value: counts.get(bundleId) ?? 0
|
|
4374
|
-
}))
|
|
4375
|
-
})),
|
|
4376
|
-
bundles: [...bundleCounts].map(([bundleId, installations]) => ({
|
|
4377
|
-
bundleId,
|
|
4378
|
-
installations
|
|
4379
|
-
})).sort((left, right) => right.installations - left.installations || compareCodePoints(left.bundleId, right.bundleId))
|
|
4380
|
-
};
|
|
4381
|
-
}
|
|
4382
|
-
__name(collectActiveInstallationOverview, "collectActiveInstallationOverview");
|
|
4383
|
-
|
|
4384
|
-
// ../../packages/server/dist/insights/bounded/scan.mjs
|
|
4385
|
-
var INSIGHTS_SCAN_MAX_ROWS = 5e4;
|
|
4386
|
-
var INSIGHTS_MATERIALIZATION_LIMIT = INSIGHTS_SCAN_MAX_ROWS + 1;
|
|
4387
|
-
var INSIGHTS_SCAN_PAGE_SIZE = 1e3;
|
|
4388
|
-
var INSIGHTS_LOWER_BOUND_ID = "00000000-0000-0000-0000-000000000000";
|
|
4389
|
-
var compareCodePoints2 = /* @__PURE__ */ __name((left, right) => {
|
|
4390
|
-
if (left < right) return -1;
|
|
4391
|
-
if (left > right) return 1;
|
|
4392
|
-
return 0;
|
|
4393
|
-
}, "compareCodePoints");
|
|
4394
|
-
var compareEventNewest = /* @__PURE__ */ __name((left, right) => right.received_at_ms - left.received_at_ms || compareCodePoints2(right.id, left.id), "compareEventNewest");
|
|
4395
|
-
var compareEventOldest = /* @__PURE__ */ __name((left, right) => left.received_at_ms - right.received_at_ms || compareCodePoints2(left.id, right.id), "compareEventOldest");
|
|
4396
|
-
var materializeEventRows = /* @__PURE__ */ __name(async (scope) => {
|
|
4397
|
-
const rows = [];
|
|
4398
|
-
const seenIds = /* @__PURE__ */ new Set();
|
|
4399
|
-
let after = scope.lowerBoundMs === void 0 ? void 0 : {
|
|
4400
|
-
receivedAtMs: scope.lowerBoundMs,
|
|
4401
|
-
id: INSIGHTS_LOWER_BOUND_ID
|
|
4402
|
-
};
|
|
4403
|
-
while (rows.length < INSIGHTS_MATERIALIZATION_LIMIT) {
|
|
4404
|
-
const limit = Math.min(INSIGHTS_SCAN_PAGE_SIZE, INSIGHTS_MATERIALIZATION_LIMIT - rows.length);
|
|
4405
|
-
const page = await scope.persistence.scan({
|
|
4406
|
-
beforeReceivedAtMs: scope.cutoffMs,
|
|
4407
|
-
...after === void 0 ? {} : { after },
|
|
4408
|
-
limit
|
|
4409
|
-
});
|
|
4410
|
-
if (page.length === 0) break;
|
|
4411
|
-
if (page.length > limit) throw new InsightsPersistenceOrderError();
|
|
4412
|
-
let previous = after === void 0 ? void 0 : {
|
|
4413
|
-
received_at_ms: after.receivedAtMs,
|
|
4414
|
-
id: after.id
|
|
4415
|
-
};
|
|
4416
|
-
for (const row of page) {
|
|
4417
|
-
if (row.received_at_ms >= scope.cutoffMs || previous !== void 0 && compareEventOldest(previous, row) >= 0 || seenIds.has(row.id)) throw new InsightsPersistenceOrderError();
|
|
4418
|
-
seenIds.add(row.id);
|
|
4419
|
-
rows.push(row);
|
|
4420
|
-
previous = row;
|
|
4421
|
-
}
|
|
4422
|
-
const last = page.at(-1);
|
|
4423
|
-
if (last !== void 0) after = {
|
|
4424
|
-
receivedAtMs: last.received_at_ms,
|
|
4425
|
-
id: last.id
|
|
4426
|
-
};
|
|
4427
|
-
if (rows.length > 5e4) throw new InsightsScanLimitExceededError(INSIGHTS_SCAN_MAX_ROWS);
|
|
4428
|
-
}
|
|
4429
|
-
return rows;
|
|
4430
|
-
}, "materializeEventRows");
|
|
4431
|
-
var InsightsPersistenceOrderError = class extends Error {
|
|
4432
|
-
static {
|
|
4433
|
-
__name(this, "InsightsPersistenceOrderError");
|
|
4434
|
-
}
|
|
4435
|
-
name = "InsightsPersistenceOrderError";
|
|
4436
|
-
constructor() {
|
|
4437
|
-
super("Insights persistence did not advance its scan cursor.");
|
|
4718
|
+
var isRecord4 = /* @__PURE__ */ __name((value) => typeof value === "object" && value !== null && !Array.isArray(value), "isRecord");
|
|
4719
|
+
var requireString = /* @__PURE__ */ __name((value, label, maximumLength) => {
|
|
4720
|
+
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}.`);
|
|
4721
|
+
return value;
|
|
4722
|
+
}, "requireString");
|
|
4723
|
+
var requireTimestamp = /* @__PURE__ */ __name((value, label) => {
|
|
4724
|
+
if (!Number.isSafeInteger(value) || Number(value) < 0) throw new InsightsBadRequestError(`Invalid ${label}.`);
|
|
4725
|
+
return Number(value);
|
|
4726
|
+
}, "requireTimestamp");
|
|
4727
|
+
var readLimit = /* @__PURE__ */ __name((value) => {
|
|
4728
|
+
const limit = value ?? DEFAULT_PAGE_LIMIT;
|
|
4729
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > MAX_PAGE_LIMIT2) throw new InsightsBadRequestError("Invalid page limit.");
|
|
4730
|
+
return limit;
|
|
4731
|
+
}, "readLimit");
|
|
4732
|
+
var encodeBase64Url2 = /* @__PURE__ */ __name((value) => {
|
|
4733
|
+
const bytes = new TextEncoder().encode(value);
|
|
4734
|
+
let binary = "";
|
|
4735
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
4736
|
+
return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/u, "");
|
|
4737
|
+
}, "encodeBase64Url");
|
|
4738
|
+
var decodeBase64Url2 = /* @__PURE__ */ __name((value) => {
|
|
4739
|
+
if (value.length === 0 || value.length > MAX_CURSOR_LENGTH2) throw new InsightsBadRequestError("Invalid Insights cursor.");
|
|
4740
|
+
try {
|
|
4741
|
+
const base64 = value.replaceAll("-", "+").replaceAll("_", "/");
|
|
4742
|
+
const padded = base64.padEnd(Math.ceil(base64.length / 4) * 4, "=");
|
|
4743
|
+
const binary = atob(padded);
|
|
4744
|
+
const bytes = Uint8Array.from(binary, (character) => character.charCodeAt(0));
|
|
4745
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
4746
|
+
} catch {
|
|
4747
|
+
throw new InsightsBadRequestError("Invalid Insights cursor.");
|
|
4438
4748
|
}
|
|
4439
|
-
};
|
|
4440
|
-
var
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
}, "materializeActiveRows");
|
|
4448
|
-
var startOfUtcHour = /* @__PURE__ */ __name((value) => {
|
|
4449
|
-
const date = new Date(value);
|
|
4450
|
-
return Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours());
|
|
4451
|
-
}, "startOfUtcHour");
|
|
4452
|
-
var startOfUtcDay = /* @__PURE__ */ __name((value) => {
|
|
4453
|
-
const date = new Date(value);
|
|
4454
|
-
return Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate());
|
|
4455
|
-
}, "startOfUtcDay");
|
|
4456
|
-
var getWindowRange = /* @__PURE__ */ __name((window, now) => {
|
|
4457
|
-
if (window === "24h") return {
|
|
4458
|
-
sizeMs: 3600 * 1e3,
|
|
4459
|
-
rangeStart: startOfUtcHour(now) - 1380 * 60 * 1e3
|
|
4460
|
-
};
|
|
4461
|
-
const days = window === "7d" ? 7 : 30;
|
|
4462
|
-
return {
|
|
4463
|
-
sizeMs: 1440 * 60 * 1e3,
|
|
4464
|
-
rangeStart: startOfUtcDay(now) - (days - 1) * 24 * 60 * 60 * 1e3
|
|
4465
|
-
};
|
|
4466
|
-
}, "getWindowRange");
|
|
4467
|
-
var materializeRowsForWindow = /* @__PURE__ */ __name(async (scope, window) => {
|
|
4468
|
-
if (window === "all") return materializeEventRows(scope);
|
|
4469
|
-
const range = getWindowRange(window, scope.cutoffMs);
|
|
4470
|
-
return (await materializeEventRows({
|
|
4471
|
-
...scope,
|
|
4472
|
-
lowerBoundMs: range.rangeStart
|
|
4473
|
-
})).filter(({ received_at_ms }) => received_at_ms >= range.rangeStart);
|
|
4474
|
-
}, "materializeRowsForWindow");
|
|
4475
|
-
var bucketStart = /* @__PURE__ */ __name((receivedAtMs, sizeMs) => sizeMs === 3600 * 1e3 ? startOfUtcHour(receivedAtMs) : startOfUtcDay(receivedAtMs), "bucketStart");
|
|
4476
|
-
var createSeries = /* @__PURE__ */ __name((request) => {
|
|
4477
|
-
const range = request.window === "all" ? void 0 : getWindowRange(request.window, request.cutoffMs);
|
|
4478
|
-
const sizeMs = range?.sizeMs ?? 1440 * 60 * 1e3;
|
|
4479
|
-
const installIdsByBucket = /* @__PURE__ */ new Map();
|
|
4480
|
-
let oldestMs = request.cutoffMs;
|
|
4481
|
-
for (const row of request.rows) {
|
|
4482
|
-
oldestMs = Math.min(oldestMs, row.received_at_ms);
|
|
4483
|
-
const start = bucketStart(row.received_at_ms, sizeMs);
|
|
4484
|
-
const installIds = installIdsByBucket.get(start) ?? /* @__PURE__ */ new Set();
|
|
4485
|
-
installIds.add(row.install_id);
|
|
4486
|
-
installIdsByBucket.set(start, installIds);
|
|
4487
|
-
}
|
|
4488
|
-
const first = range?.rangeStart ?? startOfUtcDay(oldestMs);
|
|
4489
|
-
const last = bucketStart(request.cutoffMs, sizeMs);
|
|
4490
|
-
return Array.from({ length: Math.floor((last - first) / sizeMs) + 1 }, (_, index) => {
|
|
4491
|
-
const start = first + index * sizeMs;
|
|
4492
|
-
return {
|
|
4493
|
-
bucketStartMs: start,
|
|
4494
|
-
value: installIdsByBucket.get(start)?.size ?? 0
|
|
4495
|
-
};
|
|
4496
|
-
});
|
|
4497
|
-
}, "createSeries");
|
|
4498
|
-
var collectEventActivity = /* @__PURE__ */ __name((request) => {
|
|
4499
|
-
const range = request.window === "all" ? void 0 : getWindowRange(request.window, request.cutoffMs);
|
|
4500
|
-
const rows = range === void 0 ? request.rows : request.rows.filter(({ received_at_ms }) => received_at_ms >= range.rangeStart && received_at_ms < request.cutoffMs);
|
|
4501
|
-
const installs = /* @__PURE__ */ new Set();
|
|
4502
|
-
const installsByCohort = /* @__PURE__ */ new Map();
|
|
4503
|
-
for (const row of rows) {
|
|
4504
|
-
installs.add(row.install_id);
|
|
4505
|
-
const cohort = installsByCohort.get(row.cohort) ?? /* @__PURE__ */ new Set();
|
|
4506
|
-
cohort.add(row.install_id);
|
|
4507
|
-
installsByCohort.set(row.cohort, cohort);
|
|
4749
|
+
}, "decodeBase64Url");
|
|
4750
|
+
var encodeCursor = /* @__PURE__ */ __name((value) => encodeBase64Url2(JSON.stringify(value)), "encodeCursor");
|
|
4751
|
+
var decodeCursor = /* @__PURE__ */ __name((value) => {
|
|
4752
|
+
try {
|
|
4753
|
+
return JSON.parse(decodeBase64Url2(value));
|
|
4754
|
+
} catch (error) {
|
|
4755
|
+
if (error instanceof InsightsBadRequestError) throw error;
|
|
4756
|
+
throw new InsightsBadRequestError("Invalid Insights cursor.");
|
|
4508
4757
|
}
|
|
4758
|
+
}, "decodeCursor");
|
|
4759
|
+
var readScope2 = /* @__PURE__ */ __name((input) => {
|
|
4760
|
+
if (input.platform !== "ios" && input.platform !== "android") throw new InsightsBadRequestError("Invalid Insights platform.");
|
|
4509
4761
|
return {
|
|
4510
|
-
summary: installs.size,
|
|
4511
|
-
cohorts: [...installsByCohort].sort(([left], [right]) => compareCodePoints2(left, right)).map(([cohort, installIds]) => ({
|
|
4512
|
-
cohort,
|
|
4513
|
-
value: installIds.size
|
|
4514
|
-
})),
|
|
4515
|
-
series: createSeries({
|
|
4516
|
-
...request,
|
|
4517
|
-
rows
|
|
4518
|
-
})
|
|
4519
|
-
};
|
|
4520
|
-
}, "collectEventActivity");
|
|
4521
|
-
|
|
4522
|
-
// ../../packages/server/dist/insights/bounded/installationSearch.mjs
|
|
4523
|
-
function toSearchRow(row) {
|
|
4524
|
-
return {
|
|
4525
|
-
installId: row.install_id,
|
|
4526
|
-
username: row.username,
|
|
4527
|
-
userId: row.user_id,
|
|
4528
|
-
lastKnownBundleId: row.to_bundle_id,
|
|
4529
|
-
latestStatus: row.type,
|
|
4530
|
-
platform: row.platform,
|
|
4531
|
-
appVersion: row.app_version,
|
|
4532
|
-
channel: row.channel,
|
|
4533
|
-
cohort: row.cohort,
|
|
4534
|
-
receivedAtMs: row.received_at_ms
|
|
4535
|
-
};
|
|
4536
|
-
}
|
|
4537
|
-
__name(toSearchRow, "toSearchRow");
|
|
4538
|
-
function matchesIdentity(row, query2) {
|
|
4539
|
-
return row.install_id.toLowerCase().includes(query2) || row.user_id?.toLowerCase().includes(query2) === true || row.username?.toLowerCase().includes(query2) === true;
|
|
4540
|
-
}
|
|
4541
|
-
__name(matchesIdentity, "matchesIdentity");
|
|
4542
|
-
function searchEventInstallations(request) {
|
|
4543
|
-
const query2 = request.query.toLowerCase();
|
|
4544
|
-
const matchingInstallIds = /* @__PURE__ */ new Set();
|
|
4545
|
-
for (const row of request.rows) if (query2.length === 0 || matchesIdentity(row, query2)) matchingInstallIds.add(row.install_id);
|
|
4546
|
-
const latestByInstall = /* @__PURE__ */ new Map();
|
|
4547
|
-
for (const row of request.rows) {
|
|
4548
|
-
if (!matchingInstallIds.has(row.install_id)) continue;
|
|
4549
|
-
const current = latestByInstall.get(row.install_id);
|
|
4550
|
-
if (current === void 0 || compareEventNewest(row, current) < 0) latestByInstall.set(row.install_id, row);
|
|
4551
|
-
}
|
|
4552
|
-
const matchingRows = [...latestByInstall.values()].sort((left, right) => compareCodePoints2(left.install_id, right.install_id));
|
|
4553
|
-
const pageSize = Math.min(Math.max(request.limit, 0), 100);
|
|
4554
|
-
return {
|
|
4555
|
-
data: matchingRows.slice(request.offset, request.offset + pageSize).map(toSearchRow),
|
|
4556
|
-
pagination: {
|
|
4557
|
-
total: matchingRows.length,
|
|
4558
|
-
limit: request.limit,
|
|
4559
|
-
offset: request.offset
|
|
4560
|
-
}
|
|
4561
|
-
};
|
|
4562
|
-
}
|
|
4563
|
-
__name(searchEventInstallations, "searchEventInstallations");
|
|
4564
|
-
|
|
4565
|
-
// ../../packages/server/dist/insights/bounded/persistence.mjs
|
|
4566
|
-
function createBundleEventRow(input) {
|
|
4567
|
-
const base = {
|
|
4568
|
-
id: createUUIDv7(),
|
|
4569
|
-
install_id: input.installId,
|
|
4570
|
-
user_id: input.userId ?? null,
|
|
4571
|
-
username: input.username ?? null,
|
|
4572
|
-
from_release_id: input.fromReleaseId,
|
|
4573
|
-
to_release_id: input.toReleaseId,
|
|
4574
|
-
to_bundle_id: input.toBundleId,
|
|
4575
4762
|
platform: input.platform,
|
|
4576
|
-
|
|
4577
|
-
channel: input.channel,
|
|
4578
|
-
cohort: input.cohort,
|
|
4579
|
-
fingerprint_hash: input.fingerprintHash,
|
|
4580
|
-
sdk_version: input.sdkVersion ?? null,
|
|
4581
|
-
received_at_ms: Date.now()
|
|
4763
|
+
channel: requireString(input.channel, "channel", MAX_EVENT_ID_LENGTH)
|
|
4582
4764
|
};
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
4765
|
+
}, "readScope");
|
|
4766
|
+
var bundleFilter = /* @__PURE__ */ __name((input) => {
|
|
4767
|
+
const scope = readScope2(input);
|
|
4768
|
+
const bundleId = requireString(input.bundleId, "bundle ID", MAX_EVENT_ID_LENGTH);
|
|
4769
|
+
switch (input.outcome) {
|
|
4770
|
+
case "applied":
|
|
4587
4771
|
return {
|
|
4588
|
-
...
|
|
4589
|
-
type:
|
|
4590
|
-
|
|
4591
|
-
update_strategy: input.updateStrategy
|
|
4772
|
+
...scope,
|
|
4773
|
+
type: "UPDATE_APPLIED",
|
|
4774
|
+
toBundleId: bundleId
|
|
4592
4775
|
};
|
|
4593
|
-
case "
|
|
4776
|
+
case "recovered":
|
|
4594
4777
|
return {
|
|
4595
|
-
...
|
|
4596
|
-
type:
|
|
4597
|
-
|
|
4598
|
-
update_strategy: null
|
|
4778
|
+
...scope,
|
|
4779
|
+
type: "RECOVERED",
|
|
4780
|
+
fromBundleId: bundleId
|
|
4599
4781
|
};
|
|
4782
|
+
case "adopted":
|
|
4783
|
+
return {
|
|
4784
|
+
...scope,
|
|
4785
|
+
type: "RELEASE_ADOPTED",
|
|
4786
|
+
toBundleId: bundleId
|
|
4787
|
+
};
|
|
4788
|
+
default:
|
|
4789
|
+
throw new InsightsBadRequestError("Invalid Insights outcome.");
|
|
4600
4790
|
}
|
|
4601
|
-
}
|
|
4602
|
-
__name(
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
|
|
4791
|
+
}, "bundleFilter");
|
|
4792
|
+
var sameFilter = /* @__PURE__ */ __name((left, right) => {
|
|
4793
|
+
if (!isRecord4(left) || left.kind !== right.kind) return false;
|
|
4794
|
+
switch (right.kind) {
|
|
4795
|
+
case "all":
|
|
4796
|
+
return true;
|
|
4797
|
+
case "installationMovement":
|
|
4798
|
+
return left.installId === right.installId;
|
|
4799
|
+
case "bundle":
|
|
4800
|
+
return left.platform === right.platform && left.channel === right.channel && left.type === right.type && (right.type === "RECOVERED" ? left.fromBundleId === right.fromBundleId : left.toBundleId === right.toBundleId);
|
|
4801
|
+
}
|
|
4802
|
+
}, "sameFilter");
|
|
4803
|
+
var readEventCursor = /* @__PURE__ */ __name((value, filter) => {
|
|
4804
|
+
const cursor = decodeCursor(value);
|
|
4805
|
+
if (!isRecord4(cursor) || cursor.version !== 2 || cursor.kind !== "events" || !isRecord4(cursor.filter) || !isRecord4(cursor.after)) throw new InsightsBadRequestError("Invalid Insights cursor.");
|
|
4806
|
+
if (!sameFilter(cursor.filter, filter)) throw new InsightsBadRequestError("Insights cursor does not match the requested events.");
|
|
4807
|
+
if (typeof cursor.after.id !== "string" || !isUUIDv7(cursor.after.id)) throw new InsightsBadRequestError("Invalid Insights event cursor ID.");
|
|
4808
|
+
return {
|
|
4809
|
+
after: {
|
|
4810
|
+
id: cursor.after.id,
|
|
4811
|
+
receivedAtMs: requireTimestamp(cursor.after.receivedAtMs, "event cursor")
|
|
4812
|
+
},
|
|
4813
|
+
beforeReceivedAtMs: requireTimestamp(cursor.beforeReceivedAtMs, "event cutoff"),
|
|
4814
|
+
kind: "events",
|
|
4815
|
+
sinceMs: requireTimestamp(cursor.sinceMs, "event start"),
|
|
4816
|
+
filter,
|
|
4817
|
+
version: 2
|
|
4818
|
+
};
|
|
4819
|
+
}, "readEventCursor");
|
|
4820
|
+
var readUserInstallationCursor = /* @__PURE__ */ __name((value, userId) => {
|
|
4821
|
+
const cursor = decodeCursor(value);
|
|
4822
|
+
if (!isRecord4(cursor) || cursor.version !== 1 || cursor.kind !== "user-installations" || cursor.userId !== userId) throw new InsightsBadRequestError("Insights cursor does not match the requested user ID.");
|
|
4823
|
+
return {
|
|
4824
|
+
afterInstallId: requireString(cursor.afterInstallId, "installation cursor", MAX_IDENTITY_LENGTH3),
|
|
4825
|
+
kind: "user-installations",
|
|
4826
|
+
userId,
|
|
4827
|
+
version: 1
|
|
4828
|
+
};
|
|
4829
|
+
}, "readUserInstallationCursor");
|
|
4830
|
+
var compareEventNewest = /* @__PURE__ */ __name((left, right) => right.received_at_ms - left.received_at_ms || compareInsightsText(right.id, left.id), "compareEventNewest");
|
|
4831
|
+
var isAfterEventCursor = /* @__PURE__ */ __name((row, after) => row.received_at_ms < after.receivedAtMs || row.received_at_ms === after.receivedAtMs && compareInsightsText(row.id, after.id) < 0, "isAfterEventCursor");
|
|
4832
|
+
var assertEventRows = /* @__PURE__ */ __name((rows, input) => {
|
|
4833
|
+
if (rows.length > input.limit) throw new Error("Insights database returned too many event rows.");
|
|
4834
|
+
for (let index = 0; index < rows.length; index += 1) {
|
|
4835
|
+
const row = rows[index];
|
|
4836
|
+
const previous = rows[index - 1];
|
|
4837
|
+
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.");
|
|
4838
|
+
}
|
|
4839
|
+
}, "assertEventRows");
|
|
4840
|
+
var toEventHistoryRow = /* @__PURE__ */ __name((row) => ({
|
|
4841
|
+
appVersion: row.app_version,
|
|
4842
|
+
channel: row.channel,
|
|
4843
|
+
cohort: row.cohort,
|
|
4609
4844
|
fromBundleId: row.from_bundle_id,
|
|
4845
|
+
id: row.id,
|
|
4846
|
+
installId: row.install_id,
|
|
4847
|
+
platform: row.platform,
|
|
4848
|
+
receivedAtMs: row.received_at_ms,
|
|
4610
4849
|
toBundleId: row.to_bundle_id,
|
|
4611
|
-
|
|
4850
|
+
type: row.type,
|
|
4612
4851
|
userId: row.user_id,
|
|
4613
|
-
|
|
4852
|
+
username: row.username
|
|
4853
|
+
}), "toEventHistoryRow");
|
|
4854
|
+
var toInstallationRow = /* @__PURE__ */ __name((row) => ({
|
|
4614
4855
|
appVersion: row.app_version,
|
|
4615
4856
|
channel: row.channel,
|
|
4616
4857
|
cohort: row.cohort,
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
|
|
4858
|
+
installId: row.install_id,
|
|
4859
|
+
lastKnownBundleId: row.to_bundle_id,
|
|
4860
|
+
latestStatus: row.type,
|
|
4861
|
+
platform: row.platform,
|
|
4862
|
+
receivedAtMs: row.received_at_ms,
|
|
4863
|
+
userId: row.user_id,
|
|
4864
|
+
username: row.username
|
|
4865
|
+
}), "toInstallationRow");
|
|
4866
|
+
var pageEventRows = /* @__PURE__ */ __name(async (model, input, filter, map) => {
|
|
4867
|
+
const limit = readLimit(input.limit);
|
|
4868
|
+
const cursor = input.cursor === void 0 ? void 0 : readEventCursor(input.cursor, filter);
|
|
4869
|
+
const beforeReceivedAtMs = cursor?.beforeReceivedAtMs ?? (input.beforeReceivedAtMs === void 0 ? Date.now() : requireTimestamp(input.beforeReceivedAtMs, "event cutoff"));
|
|
4870
|
+
if (cursor !== void 0 && input.beforeReceivedAtMs !== void 0 && input.beforeReceivedAtMs !== beforeReceivedAtMs) throw new InsightsBadRequestError("Insights cursor does not match the requested event cutoff.");
|
|
4871
|
+
const sinceMs = cursor?.sinceMs ?? (input.sinceMs === void 0 ? 0 : requireTimestamp(input.sinceMs, "event start"));
|
|
4872
|
+
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.");
|
|
4873
|
+
const databaseInput = {
|
|
4874
|
+
filter,
|
|
4875
|
+
sinceMs,
|
|
4876
|
+
beforeReceivedAtMs,
|
|
4877
|
+
...cursor === void 0 ? {} : { after: cursor.after },
|
|
4878
|
+
limit: limit + 1
|
|
4626
4879
|
};
|
|
4627
|
-
const rows =
|
|
4628
|
-
|
|
4629
|
-
const
|
|
4630
|
-
const
|
|
4631
|
-
rows: installedRows,
|
|
4632
|
-
window,
|
|
4633
|
-
cutoffMs: scope.cutoffMs
|
|
4634
|
-
});
|
|
4635
|
-
const recovered = collectEventActivity({
|
|
4636
|
-
rows: recoveredRows,
|
|
4637
|
-
window,
|
|
4638
|
-
cutoffMs: scope.cutoffMs
|
|
4639
|
-
});
|
|
4640
|
-
const recentRows = [...installedRows, ...recoveredRows].sort(compareEventNewest);
|
|
4880
|
+
const rows = await model.listEvents(databaseInput);
|
|
4881
|
+
assertEventRows(rows, databaseInput);
|
|
4882
|
+
const pageRows = rows.slice(0, limit);
|
|
4883
|
+
const last = pageRows.at(-1);
|
|
4641
4884
|
return {
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
data: recentRows.slice(offset, offset + limit).map(toHistoryRow),
|
|
4656
|
-
pagination: {
|
|
4657
|
-
total: recentRows.length,
|
|
4658
|
-
limit,
|
|
4659
|
-
offset
|
|
4660
|
-
}
|
|
4661
|
-
}
|
|
4885
|
+
beforeReceivedAtMs,
|
|
4886
|
+
data: pageRows.map(map),
|
|
4887
|
+
nextCursor: rows.length > limit && last ? encodeCursor({
|
|
4888
|
+
after: {
|
|
4889
|
+
id: last.id,
|
|
4890
|
+
receivedAtMs: last.received_at_ms
|
|
4891
|
+
},
|
|
4892
|
+
beforeReceivedAtMs,
|
|
4893
|
+
kind: "events",
|
|
4894
|
+
filter,
|
|
4895
|
+
sinceMs,
|
|
4896
|
+
version: 2
|
|
4897
|
+
}) : null
|
|
4662
4898
|
};
|
|
4663
|
-
}, "
|
|
4664
|
-
var
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
}, window)).filter(isTransitionEventRow);
|
|
4674
|
-
for (const row of rows) {
|
|
4675
|
-
const bundleId = row.type === "UPDATE_APPLIED" ? row.to_bundle_id : row.from_bundle_id;
|
|
4676
|
-
if (!requestedBundleIds.has(bundleId)) continue;
|
|
4677
|
-
const counts = row.type === "UPDATE_APPLIED" ? installedByBundleId : recoveredByBundleId;
|
|
4678
|
-
const installIds = counts.get(bundleId) ?? /* @__PURE__ */ new Set();
|
|
4679
|
-
installIds.add(row.install_id);
|
|
4680
|
-
counts.set(bundleId, installIds);
|
|
4681
|
-
}
|
|
4682
|
-
return normalizedBundleIds.map((bundleId) => ({
|
|
4683
|
-
bundleId,
|
|
4684
|
-
installed: installedByBundleId.get(bundleId)?.size ?? 0,
|
|
4685
|
-
recovered: recoveredByBundleId.get(bundleId)?.size ?? 0
|
|
4686
|
-
}));
|
|
4687
|
-
}, "getInsightsSummaries");
|
|
4688
|
-
var createInsightsProvider = /* @__PURE__ */ __name((persistence) => Object.freeze({
|
|
4689
|
-
mode: "bounded",
|
|
4690
|
-
maxMatchingRows: INSIGHTS_SCAN_MAX_ROWS,
|
|
4899
|
+
}, "pageEventRows");
|
|
4900
|
+
var assertInstallationRows = /* @__PURE__ */ __name((rows, input) => {
|
|
4901
|
+
if (rows.length > input.limit) throw new Error("Insights database returned too many installation rows.");
|
|
4902
|
+
for (let index = 0; index < rows.length; index += 1) {
|
|
4903
|
+
const row = rows[index];
|
|
4904
|
+
const previous = rows[index - 1];
|
|
4905
|
+
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.");
|
|
4906
|
+
}
|
|
4907
|
+
}, "assertInstallationRows");
|
|
4908
|
+
var createInsightsProvider = /* @__PURE__ */ __name((model) => Object.freeze({
|
|
4691
4909
|
async appendBundleEvent(input) {
|
|
4692
|
-
|
|
4910
|
+
const event = createBundleEventRow(input);
|
|
4911
|
+
await model.record({
|
|
4912
|
+
event,
|
|
4913
|
+
installation: toInsightsInstallationRow(event)
|
|
4914
|
+
});
|
|
4693
4915
|
},
|
|
4694
|
-
|
|
4695
|
-
const
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
})).filter(isTransitionEventRow);
|
|
4699
|
-
return {
|
|
4700
|
-
installed: countDistinctInstallations(rows.filter((row) => isInstalledForBundle(row, bundleId))),
|
|
4701
|
-
recovered: countDistinctInstallations(rows.filter((row) => isRecoveredFromBundle(row, bundleId)))
|
|
4916
|
+
listEvents(input) {
|
|
4917
|
+
const filter = input.bundle === void 0 ? { kind: "all" } : {
|
|
4918
|
+
kind: "bundle",
|
|
4919
|
+
...bundleFilter(input.bundle)
|
|
4702
4920
|
};
|
|
4921
|
+
return pageEventRows(model, input, filter, toEventHistoryRow);
|
|
4703
4922
|
},
|
|
4704
|
-
|
|
4705
|
-
|
|
4923
|
+
async listInstallationEvents(input) {
|
|
4924
|
+
if ("bundle" in input && input.bundle !== void 0) throw new InsightsBadRequestError("Installation movement queries cannot include a bundle filter.");
|
|
4925
|
+
const installId = requireString(input.installId, "install ID", MAX_IDENTITY_LENGTH3);
|
|
4926
|
+
return pageEventRows(model, input, {
|
|
4927
|
+
kind: "installationMovement",
|
|
4928
|
+
installId
|
|
4929
|
+
}, (row) => toEventHistoryRow(row));
|
|
4706
4930
|
},
|
|
4707
|
-
|
|
4708
|
-
|
|
4931
|
+
async getInstallation({ installId }) {
|
|
4932
|
+
const normalizedInstallId = requireString(installId, "install ID", MAX_IDENTITY_LENGTH3);
|
|
4933
|
+
const rows = await model.findInstallations({ installId: normalizedInstallId });
|
|
4934
|
+
const row = rows[0] ?? null;
|
|
4935
|
+
if (rows.length > 1 || row !== null && row.install_id !== normalizedInstallId) throw new Error("Insights database returned an invalid installation.");
|
|
4936
|
+
return row === null ? null : toInstallationRow(row);
|
|
4709
4937
|
},
|
|
4710
|
-
async
|
|
4711
|
-
const
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
4938
|
+
async pageInstallationsByCurrentUserId(input) {
|
|
4939
|
+
const userId = requireString(input.userId, "user ID", MAX_IDENTITY_LENGTH3);
|
|
4940
|
+
const limit = readLimit(input.limit);
|
|
4941
|
+
const cursor = input.cursor === void 0 ? void 0 : readUserInstallationCursor(input.cursor, userId);
|
|
4942
|
+
const databaseInput = {
|
|
4943
|
+
userId,
|
|
4944
|
+
...cursor === void 0 ? {} : { afterInstallId: cursor.afterInstallId },
|
|
4945
|
+
limit: limit + 1
|
|
4946
|
+
};
|
|
4947
|
+
const rows = await model.findInstallations(databaseInput);
|
|
4948
|
+
assertInstallationRows(rows, databaseInput);
|
|
4949
|
+
const pageRows = rows.slice(0, limit);
|
|
4950
|
+
const last = pageRows.at(-1);
|
|
4722
4951
|
return {
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
installations
|
|
4727
|
-
|
|
4952
|
+
data: pageRows.map(toInstallationRow),
|
|
4953
|
+
nextCursor: rows.length > limit && last ? encodeCursor({
|
|
4954
|
+
afterInstallId: last.install_id,
|
|
4955
|
+
kind: "user-installations",
|
|
4956
|
+
userId,
|
|
4957
|
+
version: 1
|
|
4958
|
+
}) : null
|
|
4728
4959
|
};
|
|
4729
4960
|
},
|
|
4730
|
-
async
|
|
4731
|
-
const
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
|
|
4746
|
-
|
|
4961
|
+
async getReportingOverview(input) {
|
|
4962
|
+
const scope = readScope2(input);
|
|
4963
|
+
const { window } = input;
|
|
4964
|
+
if (!Object.hasOwn(WINDOW_MS, window)) throw new InsightsBadRequestError("Invalid reporting installation window.");
|
|
4965
|
+
const beforeReceivedAtMs = Date.now();
|
|
4966
|
+
const sinceMs = Math.max(0, beforeReceivedAtMs - WINDOW_MS[window]);
|
|
4967
|
+
const measure = /* @__PURE__ */ __name(async (count) => {
|
|
4968
|
+
const value = await count;
|
|
4969
|
+
if (!Number.isSafeInteger(value) || value < 0) throw new Error("Insights database returned an invalid count.");
|
|
4970
|
+
return {
|
|
4971
|
+
count: value,
|
|
4972
|
+
measuredAtMs: Date.now()
|
|
4973
|
+
};
|
|
4974
|
+
}, "measure");
|
|
4975
|
+
const bundleId = input.bundleId === void 0 ? void 0 : requireString(input.bundleId, "bundle ID", MAX_EVENT_ID_LENGTH);
|
|
4976
|
+
const reporting = measure(model.countInstallations({
|
|
4977
|
+
...scope,
|
|
4978
|
+
sinceMs
|
|
4979
|
+
}));
|
|
4980
|
+
if (bundleId === void 0) return {
|
|
4981
|
+
...scope,
|
|
4982
|
+
window,
|
|
4983
|
+
sinceMs,
|
|
4984
|
+
beforeReceivedAtMs,
|
|
4985
|
+
reportingInstallations: await reporting
|
|
4986
|
+
};
|
|
4987
|
+
const countOutcome = /* @__PURE__ */ __name((outcome) => measure(model.countEvents({
|
|
4988
|
+
filter: bundleFilter({
|
|
4989
|
+
...scope,
|
|
4990
|
+
bundleId,
|
|
4991
|
+
outcome
|
|
4747
4992
|
}),
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
|
|
4993
|
+
sinceMs,
|
|
4994
|
+
beforeReceivedAtMs
|
|
4995
|
+
})), "countOutcome");
|
|
4996
|
+
const [reportingInstallations, bundleInstallations, appliedReports, recoveredReports, adoptedReports] = await Promise.all([
|
|
4997
|
+
reporting,
|
|
4998
|
+
measure(model.countInstallations({
|
|
4999
|
+
...scope,
|
|
5000
|
+
sinceMs,
|
|
5001
|
+
bundleId
|
|
5002
|
+
})),
|
|
5003
|
+
countOutcome("applied"),
|
|
5004
|
+
countOutcome("recovered"),
|
|
5005
|
+
countOutcome("adopted")
|
|
5006
|
+
]);
|
|
4758
5007
|
return {
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
5008
|
+
...scope,
|
|
5009
|
+
window,
|
|
5010
|
+
sinceMs,
|
|
5011
|
+
beforeReceivedAtMs,
|
|
5012
|
+
reportingInstallations,
|
|
5013
|
+
bundle: {
|
|
5014
|
+
bundleId,
|
|
5015
|
+
reportingInstallations: bundleInstallations,
|
|
5016
|
+
appliedReports,
|
|
5017
|
+
recoveredReports,
|
|
5018
|
+
adoptedReports
|
|
4764
5019
|
}
|
|
4765
5020
|
};
|
|
4766
5021
|
}
|
|
@@ -4801,7 +5056,7 @@ var hashApiKey = /* @__PURE__ */ __name(async (apiKey) => {
|
|
|
4801
5056
|
return bytesToBase64Url(new Uint8Array(digest));
|
|
4802
5057
|
}, "hashApiKey");
|
|
4803
5058
|
var apiKeyId = /* @__PURE__ */ __name(() => {
|
|
4804
|
-
const bytes = new Uint8Array(16);
|
|
5059
|
+
const bytes = /* @__PURE__ */ new Uint8Array(16);
|
|
4805
5060
|
crypto.getRandomValues(bytes);
|
|
4806
5061
|
return `api-${bytesToBase64Url(bytes)}`;
|
|
4807
5062
|
}, "apiKeyId");
|
|
@@ -4838,7 +5093,7 @@ var registerApiKey = /* @__PURE__ */ __name(async (input) => {
|
|
|
4838
5093
|
});
|
|
4839
5094
|
}, "registerApiKey");
|
|
4840
5095
|
var createApiKey = /* @__PURE__ */ __name((input) => {
|
|
4841
|
-
const bytes = new Uint8Array(32);
|
|
5096
|
+
const bytes = /* @__PURE__ */ new Uint8Array(32);
|
|
4842
5097
|
crypto.getRandomValues(bytes);
|
|
4843
5098
|
return registerApiKey({
|
|
4844
5099
|
apiKey: bytesToBase64Url(bytes),
|
|
@@ -5267,6 +5522,8 @@ var HotUpdaterSchemaMigrationRequiredError = class extends Error {
|
|
|
5267
5522
|
static {
|
|
5268
5523
|
__name(this, "HotUpdaterSchemaMigrationRequiredError");
|
|
5269
5524
|
}
|
|
5525
|
+
adapterName;
|
|
5526
|
+
currentVersion;
|
|
5270
5527
|
constructor(adapterName, currentVersion) {
|
|
5271
5528
|
super(currentVersion === void 0 ? `Hot Updater database schema is not initialized for ${adapterName}. Run \`hot-updater db migrate\` before using this adapter.` : unsupportedSchemaUpgradeMessage(currentVersion));
|
|
5272
5529
|
this.adapterName = adapterName;
|
|
@@ -5297,7 +5554,7 @@ var sqlProviders = [
|
|
|
5297
5554
|
var noSqlProviders = ["mongodb"];
|
|
5298
5555
|
[...sqlProviders, ...noSqlProviders];
|
|
5299
5556
|
function isDatabasePlugin(plugin) {
|
|
5300
|
-
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 && "
|
|
5557
|
+
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 && "record" in plugin.models.insights && typeof plugin.models.insights.record === "function" && "listEvents" in plugin.models.insights && typeof plugin.models.insights.listEvents === "function" && "findInstallations" in plugin.models.insights && typeof plugin.models.insights.findInstallations === "function" && "countEvents" in plugin.models.insights && typeof plugin.models.insights.countEvents === "function" && "countInstallations" in plugin.models.insights && typeof plugin.models.insights.countInstallations === "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");
|
|
5301
5558
|
}
|
|
5302
5559
|
__name(isDatabasePlugin, "isDatabasePlugin");
|
|
5303
5560
|
|
|
@@ -5401,27 +5658,41 @@ var hotUpdaterCoreMetadata = /* @__PURE__ */ Symbol.for("@hot-updater/server/cor
|
|
|
5401
5658
|
function createHotUpdaterCore(options) {
|
|
5402
5659
|
for (const key of ["authorityId", "catalogId"]) if (Object.hasOwn(options, key)) throw new TypeError(`Remove ${key} from createHotUpdater options. Catalog identity is managed internally.`);
|
|
5403
5660
|
const database = options.database;
|
|
5404
|
-
const
|
|
5661
|
+
const storagePlugins = (options.storage ?? []).map((storage) => {
|
|
5405
5662
|
assertStorageOperations(storage, ["get", "getDownloadUrl"]);
|
|
5406
5663
|
return storage;
|
|
5407
|
-
})
|
|
5664
|
+
});
|
|
5665
|
+
const { downloadStorageObject, readStorageText, resolveFileUrl } = createStorageAccess(storagePlugins);
|
|
5408
5666
|
const adapterCapabilities = database;
|
|
5409
5667
|
if (!isDatabasePlugin(database)) throw new Error("@hot-updater/server only supports database plugins.");
|
|
5410
5668
|
const plugin = database;
|
|
5411
|
-
const
|
|
5669
|
+
const adapterName = adapterCapabilities.adapterName ?? plugin.name;
|
|
5670
|
+
const assertSchemaReady = createSchemaReadinessChecker(adapterName, adapterCapabilities.createMigrator);
|
|
5412
5671
|
const core = createDatabasePluginCore(plugin, resolveFileUrl, {
|
|
5413
5672
|
beforeOperation: assertSchemaReady,
|
|
5414
5673
|
readStorageText
|
|
5415
5674
|
});
|
|
5416
5675
|
const clientAccess = normalizeClientAccess(options.clientAccess);
|
|
5417
5676
|
const insights = createInsightsProvider({
|
|
5418
|
-
async
|
|
5677
|
+
async record(input) {
|
|
5678
|
+
await assertSchemaReady();
|
|
5679
|
+
return plugin.models.insights.record(input);
|
|
5680
|
+
},
|
|
5681
|
+
async listEvents(input) {
|
|
5419
5682
|
await assertSchemaReady();
|
|
5420
|
-
return plugin.models.insights.
|
|
5683
|
+
return plugin.models.insights.listEvents(input);
|
|
5421
5684
|
},
|
|
5422
|
-
async
|
|
5685
|
+
async findInstallations(input) {
|
|
5423
5686
|
await assertSchemaReady();
|
|
5424
|
-
return plugin.models.insights.
|
|
5687
|
+
return plugin.models.insights.findInstallations(input);
|
|
5688
|
+
},
|
|
5689
|
+
async countInstallations(input) {
|
|
5690
|
+
await assertSchemaReady();
|
|
5691
|
+
return plugin.models.insights.countInstallations(input);
|
|
5692
|
+
},
|
|
5693
|
+
async countEvents(input) {
|
|
5694
|
+
await assertSchemaReady();
|
|
5695
|
+
return plugin.models.insights.countEvents(input);
|
|
5425
5696
|
}
|
|
5426
5697
|
});
|
|
5427
5698
|
const apiKeys = createApiKeyManagement({
|
|
@@ -7626,7 +7897,7 @@ var InvalidD1RowError = class extends Error {
|
|
|
7626
7897
|
this.model = model;
|
|
7627
7898
|
}
|
|
7628
7899
|
};
|
|
7629
|
-
var
|
|
7900
|
+
var isRecord5 = /* @__PURE__ */ __name((value) => typeof value === "object" && value !== null && !Array.isArray(value), "isRecord");
|
|
7630
7901
|
var stringValue = /* @__PURE__ */ __name((row, field, model) => {
|
|
7631
7902
|
const value = row[field];
|
|
7632
7903
|
if (typeof value !== "string") throw new InvalidD1RowError(model);
|
|
@@ -7744,6 +8015,28 @@ var eventRow = /* @__PURE__ */ __name((row) => {
|
|
|
7744
8015
|
received_at_ms: numberValue(row, "received_at_ms", "bundle_events")
|
|
7745
8016
|
};
|
|
7746
8017
|
}, "eventRow");
|
|
8018
|
+
var installationRow = /* @__PURE__ */ __name((row) => {
|
|
8019
|
+
const type = stringValue(row, "type", "bundle_installations");
|
|
8020
|
+
const platform = stringValue(row, "platform", "bundle_installations");
|
|
8021
|
+
if (!["UPDATE_APPLIED", "RECOVERED", "RELEASE_ADOPTED", "UNCHANGED"].includes(
|
|
8022
|
+
type
|
|
8023
|
+
) || platform !== "ios" && platform !== "android") {
|
|
8024
|
+
throw new InvalidD1RowError("bundle_installations");
|
|
8025
|
+
}
|
|
8026
|
+
return {
|
|
8027
|
+
id: stringValue(row, "id", "bundle_installations"),
|
|
8028
|
+
install_id: stringValue(row, "install_id", "bundle_installations"),
|
|
8029
|
+
user_id: nullableString(row, "user_id", "bundle_installations"),
|
|
8030
|
+
username: nullableString(row, "username", "bundle_installations"),
|
|
8031
|
+
to_bundle_id: stringValue(row, "to_bundle_id", "bundle_installations"),
|
|
8032
|
+
type,
|
|
8033
|
+
platform,
|
|
8034
|
+
app_version: stringValue(row, "app_version", "bundle_installations"),
|
|
8035
|
+
channel: stringValue(row, "channel", "bundle_installations"),
|
|
8036
|
+
cohort: stringValue(row, "cohort", "bundle_installations"),
|
|
8037
|
+
received_at_ms: numberValue(row, "received_at_ms", "bundle_installations")
|
|
8038
|
+
};
|
|
8039
|
+
}, "installationRow");
|
|
7747
8040
|
var apiKeyRow = /* @__PURE__ */ __name((row) => {
|
|
7748
8041
|
const role = stringValue(row, "role", "api_keys");
|
|
7749
8042
|
if (role !== "client") throw new InvalidD1RowError("api_keys");
|
|
@@ -7822,7 +8115,7 @@ var releaseCatalogRow = /* @__PURE__ */ __name((row) => {
|
|
|
7822
8115
|
};
|
|
7823
8116
|
}, "releaseCatalogRow");
|
|
7824
8117
|
function parseD1Row(model, value) {
|
|
7825
|
-
if (!
|
|
8118
|
+
if (!isRecord5(value)) throw new InvalidD1RowError(model);
|
|
7826
8119
|
switch (model) {
|
|
7827
8120
|
case "bundles":
|
|
7828
8121
|
return bundleRow(value);
|
|
@@ -7832,6 +8125,8 @@ function parseD1Row(model, value) {
|
|
|
7832
8125
|
return channelRow(value);
|
|
7833
8126
|
case "bundle_events":
|
|
7834
8127
|
return eventRow(value);
|
|
8128
|
+
case "bundle_installations":
|
|
8129
|
+
return installationRow(value);
|
|
7835
8130
|
case "api_keys":
|
|
7836
8131
|
return apiKeyRow(value);
|
|
7837
8132
|
case "releases":
|
|
@@ -7968,6 +8263,7 @@ var d1TableNames = {
|
|
|
7968
8263
|
bundle_patches: "bundle_patches",
|
|
7969
8264
|
channels: "channels",
|
|
7970
8265
|
bundle_events: "bundle_events",
|
|
8266
|
+
bundle_installations: "bundle_installations",
|
|
7971
8267
|
api_keys: "api_keys",
|
|
7972
8268
|
release_catalogs: "release_catalogs",
|
|
7973
8269
|
releases: "releases"
|
|
@@ -8021,6 +8317,8 @@ var findManyD1Rows = /* @__PURE__ */ __name(async (executor, input) => {
|
|
|
8021
8317
|
return rows.map((row) => parseD1Row("channels", row));
|
|
8022
8318
|
case "bundle_events":
|
|
8023
8319
|
return rows.map((row) => parseD1Row("bundle_events", row));
|
|
8320
|
+
case "bundle_installations":
|
|
8321
|
+
return rows.map((row) => parseD1Row("bundle_installations", row));
|
|
8024
8322
|
case "api_keys":
|
|
8025
8323
|
return rows.map((row) => parseD1Row("api_keys", row));
|
|
8026
8324
|
case "releases":
|
|
@@ -8031,6 +8329,15 @@ var findManyD1Rows = /* @__PURE__ */ __name(async (executor, input) => {
|
|
|
8031
8329
|
}, "findManyD1Rows");
|
|
8032
8330
|
|
|
8033
8331
|
// src/d1Implementation.ts
|
|
8332
|
+
var D1ExecutionError = class extends Error {
|
|
8333
|
+
static {
|
|
8334
|
+
__name(this, "D1ExecutionError");
|
|
8335
|
+
}
|
|
8336
|
+
name = "D1ExecutionError";
|
|
8337
|
+
constructor() {
|
|
8338
|
+
super("D1 did not successfully execute every requested statement");
|
|
8339
|
+
}
|
|
8340
|
+
};
|
|
8034
8341
|
var EXPECTATION_CONFLICT_MARKER = "HOT_UPDATER_COMMIT_EXPECTATION_CONFLICT";
|
|
8035
8342
|
var expectationGuard = /* @__PURE__ */ __name((expectations) => {
|
|
8036
8343
|
const clauses = [];
|
|
@@ -8157,6 +8464,7 @@ var insertQuery = /* @__PURE__ */ __name((input, guard, conflictMode = "returnEx
|
|
|
8157
8464
|
values = channelValues(input.data);
|
|
8158
8465
|
break;
|
|
8159
8466
|
case "bundle_events":
|
|
8467
|
+
case "bundle_installations":
|
|
8160
8468
|
case "api_keys":
|
|
8161
8469
|
case "release_catalogs":
|
|
8162
8470
|
case "releases":
|
|
@@ -8164,7 +8472,7 @@ var insertQuery = /* @__PURE__ */ __name((input, guard, conflictMode = "returnEx
|
|
|
8164
8472
|
values = Object.values(input.data);
|
|
8165
8473
|
break;
|
|
8166
8474
|
}
|
|
8167
|
-
const conflict = input.onConflict !== "ignore" ? "" : input.model === "channels" ? conflictMode === "ignore" ? " ON CONFLICT(name) DO NOTHING" : " ON CONFLICT(name) DO UPDATE SET name = excluded.name" : input.model === "api_keys" ? conflictMode === "ignore" ? " ON CONFLICT(hash) DO NOTHING" : " ON CONFLICT(hash) DO UPDATE SET hash = excluded.hash" : "";
|
|
8475
|
+
const conflict = input.onConflict !== "ignore" ? "" : input.model === "channels" ? conflictMode === "ignore" ? " ON CONFLICT(name) DO NOTHING" : " ON CONFLICT(name) DO UPDATE SET name = excluded.name" : input.model === "api_keys" ? conflictMode === "ignore" ? " ON CONFLICT(hash) DO NOTHING" : " ON CONFLICT(hash) DO UPDATE SET hash = excluded.hash" : " ON CONFLICT(install_id) DO UPDATE SET install_id = excluded.install_id";
|
|
8168
8476
|
return {
|
|
8169
8477
|
sql: `INSERT INTO ${d1TableNames[input.model]} (${columns.join(", ")}) ${guard === void 0 ? `VALUES (${d1Placeholders(values.length)})` : `SELECT ${d1Placeholders(values.length)} WHERE ${guard.sql}`}${conflict} RETURNING *`,
|
|
8170
8478
|
params: [...encodeD1Values(values), ...guard?.params ?? []]
|
|
@@ -8331,8 +8639,6 @@ var changeQuery = /* @__PURE__ */ __name((change, guard) => {
|
|
|
8331
8639
|
...guard.params
|
|
8332
8640
|
]
|
|
8333
8641
|
};
|
|
8334
|
-
case "insights":
|
|
8335
|
-
return insertQuery({ model: "bundle_events", data: change.row }, guard);
|
|
8336
8642
|
case "apiKeys":
|
|
8337
8643
|
return change.operation === "insert" ? insertQuery(
|
|
8338
8644
|
{
|
|
@@ -8503,6 +8809,28 @@ var deleteChannel = /* @__PURE__ */ __name(async (executor, input) => {
|
|
|
8503
8809
|
return deletedRows.length > 0 ? { deleted: true } : { deleted: false, reason: "not_empty" };
|
|
8504
8810
|
}, "deleteChannel");
|
|
8505
8811
|
var createD1Implementation = /* @__PURE__ */ __name((executor) => ({
|
|
8812
|
+
async recordInsights({ event, installation }) {
|
|
8813
|
+
const columns = Object.keys(installation);
|
|
8814
|
+
const eventInsert = insertQuery({ model: "bundle_events", data: event });
|
|
8815
|
+
await executor.batch([
|
|
8816
|
+
{
|
|
8817
|
+
sql: `INSERT INTO bundle_installations (${columns.join(", ")})
|
|
8818
|
+
SELECT ${d1Placeholders(columns.length)}
|
|
8819
|
+
WHERE NOT EXISTS (SELECT 1 FROM bundle_events WHERE id = json_extract(?, '$'))
|
|
8820
|
+
ON CONFLICT(install_id) DO UPDATE SET ${columns.filter((column) => column !== "install_id").map((column) => `${column} = excluded.${column}`).join(", ")}
|
|
8821
|
+
WHERE (excluded.received_at_ms, excluded.id) >
|
|
8822
|
+
(bundle_installations.received_at_ms, bundle_installations.id)`,
|
|
8823
|
+
params: encodeD1Values([...Object.values(installation), event.id])
|
|
8824
|
+
},
|
|
8825
|
+
{
|
|
8826
|
+
...eventInsert,
|
|
8827
|
+
sql: eventInsert.sql.replace(
|
|
8828
|
+
" RETURNING *",
|
|
8829
|
+
" ON CONFLICT(id) DO NOTHING"
|
|
8830
|
+
)
|
|
8831
|
+
}
|
|
8832
|
+
]);
|
|
8833
|
+
},
|
|
8506
8834
|
async create(input) {
|
|
8507
8835
|
const query2 = insertQuery(input);
|
|
8508
8836
|
const rows = await executor.query(query2.sql, query2.params);
|
|
@@ -8515,6 +8843,8 @@ var createD1Implementation = /* @__PURE__ */ __name((executor) => ({
|
|
|
8515
8843
|
return parseD1Row("channels", rows[0]);
|
|
8516
8844
|
case "bundle_events":
|
|
8517
8845
|
return parseD1Row("bundle_events", rows[0]);
|
|
8846
|
+
case "bundle_installations":
|
|
8847
|
+
return parseD1Row("bundle_installations", rows[0]);
|
|
8518
8848
|
case "api_keys":
|
|
8519
8849
|
return parseD1Row("api_keys", rows[0]);
|
|
8520
8850
|
case "releases":
|
|
@@ -8536,6 +8866,8 @@ var createD1Implementation = /* @__PURE__ */ __name((executor) => ({
|
|
|
8536
8866
|
return parseD1Row("releases", rows[0]);
|
|
8537
8867
|
case "release_catalogs":
|
|
8538
8868
|
return parseD1Row("release_catalogs", rows[0]);
|
|
8869
|
+
case "bundle_installations":
|
|
8870
|
+
return parseD1Row("bundle_installations", rows[0]);
|
|
8539
8871
|
}
|
|
8540
8872
|
},
|
|
8541
8873
|
async delete(input) {
|
|
@@ -8563,6 +8895,8 @@ var createD1Implementation = /* @__PURE__ */ __name((executor) => ({
|
|
|
8563
8895
|
return parseD1Row("releases", rows[0]);
|
|
8564
8896
|
case "release_catalogs":
|
|
8565
8897
|
return parseD1Row("release_catalogs", rows[0]);
|
|
8898
|
+
case "bundle_installations":
|
|
8899
|
+
return parseD1Row("bundle_installations", rows[0]);
|
|
8566
8900
|
}
|
|
8567
8901
|
},
|
|
8568
8902
|
findMany: /* @__PURE__ */ __name((input) => findManyD1Rows(executor, input), "findMany"),
|
|
@@ -8600,6 +8934,7 @@ var d1Database = /* @__PURE__ */ __name((database) => {
|
|
|
8600
8934
|
const implementation = createD1Implementation({
|
|
8601
8935
|
async query(sql, params) {
|
|
8602
8936
|
const result = await database.prepare(sql).bind(...params).all();
|
|
8937
|
+
if (result.success === false) throw new D1ExecutionError();
|
|
8603
8938
|
return result.results ?? [];
|
|
8604
8939
|
},
|
|
8605
8940
|
async batch(statements) {
|
|
@@ -8608,6 +8943,9 @@ var d1Database = /* @__PURE__ */ __name((database) => {
|
|
|
8608
8943
|
({ sql, params }) => database.prepare(sql).bind(...params)
|
|
8609
8944
|
)
|
|
8610
8945
|
);
|
|
8946
|
+
if (results.length !== statements.length || results.some(({ success }) => success === false)) {
|
|
8947
|
+
throw new D1ExecutionError();
|
|
8948
|
+
}
|
|
8611
8949
|
return results.map(({ results: results2 }) => results2 ?? []);
|
|
8612
8950
|
}
|
|
8613
8951
|
});
|