@hot-updater/server 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.
@@ -94,7 +94,7 @@ const parsePrismaBundleEventRow = (value) => {
94
94
  const platform = readString(value, "platform");
95
95
  const fromBundleId = readNullableString(value, "from_bundle_id");
96
96
  const updateStrategy = readNullableString(value, "update_strategy");
97
- if (platform !== "ios" && platform !== "android" || !((type === "UPDATE_APPLIED" || type === "RECOVERED" || type === "RELEASE_ADOPTED") && typeof fromBundleId === "string" && (updateStrategy === "fingerprint" || updateStrategy === "appVersion") || type === "UNCHANGED" && fromBundleId === null && updateStrategy === null)) throw new PrismaAdapterError("invalid event shape");
97
+ if (platform !== "ios" && platform !== "android" || !((type === "UPDATE_APPLIED" || type === "RECOVERED") && typeof fromBundleId === "string" && (updateStrategy === "fingerprint" || updateStrategy === "appVersion") || type === "UNCHANGED" && fromBundleId === null && updateStrategy === null)) throw new PrismaAdapterError("invalid event shape");
98
98
  return {
99
99
  id: readString(value, "id"),
100
100
  type,
@@ -119,7 +119,7 @@ const parsePrismaInsightsInstallationRow = (value) => {
119
119
  if (!isRecord(value)) throw new PrismaAdapterError("invalid installation row");
120
120
  const platform = readString(value, "platform");
121
121
  const type = readString(value, "type");
122
- if (platform !== "ios" && platform !== "android" || type !== "UPDATE_APPLIED" && type !== "RECOVERED" && type !== "RELEASE_ADOPTED" && type !== "UNCHANGED") throw new PrismaAdapterError("invalid installation fields");
122
+ if (platform !== "ios" && platform !== "android" || type !== "UPDATE_APPLIED" && type !== "RECOVERED" && type !== "UNCHANGED") throw new PrismaAdapterError("invalid installation fields");
123
123
  return {
124
124
  install_id: readString(value, "install_id"),
125
125
  id: readString(value, "id"),
@@ -94,7 +94,7 @@ const parsePrismaBundleEventRow = (value) => {
94
94
  const platform = readString(value, "platform");
95
95
  const fromBundleId = readNullableString(value, "from_bundle_id");
96
96
  const updateStrategy = readNullableString(value, "update_strategy");
97
- if (platform !== "ios" && platform !== "android" || !((type === "UPDATE_APPLIED" || type === "RECOVERED" || type === "RELEASE_ADOPTED") && typeof fromBundleId === "string" && (updateStrategy === "fingerprint" || updateStrategy === "appVersion") || type === "UNCHANGED" && fromBundleId === null && updateStrategy === null)) throw new PrismaAdapterError("invalid event shape");
97
+ if (platform !== "ios" && platform !== "android" || !((type === "UPDATE_APPLIED" || type === "RECOVERED") && typeof fromBundleId === "string" && (updateStrategy === "fingerprint" || updateStrategy === "appVersion") || type === "UNCHANGED" && fromBundleId === null && updateStrategy === null)) throw new PrismaAdapterError("invalid event shape");
98
98
  return {
99
99
  id: readString(value, "id"),
100
100
  type,
@@ -119,7 +119,7 @@ const parsePrismaInsightsInstallationRow = (value) => {
119
119
  if (!isRecord(value)) throw new PrismaAdapterError("invalid installation row");
120
120
  const platform = readString(value, "platform");
121
121
  const type = readString(value, "type");
122
- if (platform !== "ios" && platform !== "android" || type !== "UPDATE_APPLIED" && type !== "RECOVERED" && type !== "RELEASE_ADOPTED" && type !== "UNCHANGED") throw new PrismaAdapterError("invalid installation fields");
122
+ if (platform !== "ios" && platform !== "android" || type !== "UPDATE_APPLIED" && type !== "RECOVERED" && type !== "UNCHANGED") throw new PrismaAdapterError("invalid installation fields");
123
123
  return {
124
124
  install_id: readString(value, "install_id"),
125
125
  id: readString(value, "id"),
@@ -14,7 +14,7 @@ type CreateBundleEventRequestBase = {
14
14
  readonly toReleaseId: string | null;
15
15
  };
16
16
  type CreateBundleEventRequest = (CreateBundleEventRequestBase & {
17
- readonly type: "UPDATE_APPLIED" | "RECOVERED" | "RELEASE_ADOPTED";
17
+ readonly type: "UPDATE_APPLIED" | "RECOVERED";
18
18
  readonly fromBundleId: string;
19
19
  readonly updateStrategy: "fingerprint" | "appVersion";
20
20
  }) | (CreateBundleEventRequestBase & {
@@ -26,7 +26,7 @@ type ActiveInstallationWindow = "24h" | "7d" | "30d";
26
26
  type EventHistoryRow = {
27
27
  readonly id: string;
28
28
  readonly installId: string;
29
- readonly type: "UPDATE_APPLIED" | "RECOVERED" | "RELEASE_ADOPTED" | "UNCHANGED";
29
+ readonly type: "UPDATE_APPLIED" | "RECOVERED" | "UNCHANGED";
30
30
  readonly fromBundleId: string | null;
31
31
  readonly toBundleId: string;
32
32
  readonly username: string | null;
@@ -66,7 +66,7 @@ type InsightsScope = {
66
66
  };
67
67
  type InsightsBundleSelection = InsightsScope & {
68
68
  readonly bundleId: string;
69
- readonly outcome: "applied" | "recovered" | "adopted";
69
+ readonly outcome: "applied" | "recovered" | "unchanged";
70
70
  };
71
71
  type InsightsCountMeasurement = {
72
72
  readonly count: number;
@@ -82,7 +82,7 @@ type ReportingOverview = InsightsScope & {
82
82
  readonly reportingInstallations: InsightsCountMeasurement;
83
83
  readonly appliedReports: InsightsCountMeasurement;
84
84
  readonly recoveredReports: InsightsCountMeasurement;
85
- readonly adoptedReports: InsightsCountMeasurement;
85
+ readonly unchangedReports: InsightsCountMeasurement;
86
86
  };
87
87
  };
88
88
  //#endregion
@@ -14,7 +14,7 @@ type CreateBundleEventRequestBase = {
14
14
  readonly toReleaseId: string | null;
15
15
  };
16
16
  type CreateBundleEventRequest = (CreateBundleEventRequestBase & {
17
- readonly type: "UPDATE_APPLIED" | "RECOVERED" | "RELEASE_ADOPTED";
17
+ readonly type: "UPDATE_APPLIED" | "RECOVERED";
18
18
  readonly fromBundleId: string;
19
19
  readonly updateStrategy: "fingerprint" | "appVersion";
20
20
  }) | (CreateBundleEventRequestBase & {
@@ -26,7 +26,7 @@ type ActiveInstallationWindow = "24h" | "7d" | "30d";
26
26
  type EventHistoryRow = {
27
27
  readonly id: string;
28
28
  readonly installId: string;
29
- readonly type: "UPDATE_APPLIED" | "RECOVERED" | "RELEASE_ADOPTED" | "UNCHANGED";
29
+ readonly type: "UPDATE_APPLIED" | "RECOVERED" | "UNCHANGED";
30
30
  readonly fromBundleId: string | null;
31
31
  readonly toBundleId: string;
32
32
  readonly username: string | null;
@@ -66,7 +66,7 @@ type InsightsScope = {
66
66
  };
67
67
  type InsightsBundleSelection = InsightsScope & {
68
68
  readonly bundleId: string;
69
- readonly outcome: "applied" | "recovered" | "adopted";
69
+ readonly outcome: "applied" | "recovered" | "unchanged";
70
70
  };
71
71
  type InsightsCountMeasurement = {
72
72
  readonly count: number;
@@ -82,7 +82,7 @@ type ReportingOverview = InsightsScope & {
82
82
  readonly reportingInstallations: InsightsCountMeasurement;
83
83
  readonly appliedReports: InsightsCountMeasurement;
84
84
  readonly recoveredReports: InsightsCountMeasurement;
85
- readonly adoptedReports: InsightsCountMeasurement;
85
+ readonly unchangedReports: InsightsCountMeasurement;
86
86
  };
87
87
  };
88
88
  //#endregion
@@ -89,8 +89,7 @@ function requireEvent(payload) {
89
89
  const type = requireStringField(payload, "type");
90
90
  switch (type) {
91
91
  case "UPDATE_APPLIED":
92
- case "RECOVERED":
93
- case "RELEASE_ADOPTED": {
92
+ case "RECOVERED": {
94
93
  const updateStrategy = requireStringField(payload, "updateStrategy");
95
94
  if (updateStrategy !== "fingerprint" && updateStrategy !== "appVersion") throw new require_errors.InsightsBadRequestError("Invalid event field: updateStrategy");
96
95
  return {
@@ -134,8 +133,7 @@ function createBundleEventRow(input) {
134
133
  };
135
134
  switch (input.type) {
136
135
  case "UPDATE_APPLIED":
137
- case "RECOVERED":
138
- case "RELEASE_ADOPTED": return {
136
+ case "RECOVERED": return {
139
137
  ...base,
140
138
  from_bundle_id: input.fromBundleId,
141
139
  type: input.type,
@@ -89,8 +89,7 @@ function requireEvent(payload) {
89
89
  const type = requireStringField(payload, "type");
90
90
  switch (type) {
91
91
  case "UPDATE_APPLIED":
92
- case "RECOVERED":
93
- case "RELEASE_ADOPTED": {
92
+ case "RECOVERED": {
94
93
  const updateStrategy = requireStringField(payload, "updateStrategy");
95
94
  if (updateStrategy !== "fingerprint" && updateStrategy !== "appVersion") throw new InsightsBadRequestError("Invalid event field: updateStrategy");
96
95
  return {
@@ -134,8 +133,7 @@ function createBundleEventRow(input) {
134
133
  };
135
134
  switch (input.type) {
136
135
  case "UPDATE_APPLIED":
137
- case "RECOVERED":
138
- case "RELEASE_ADOPTED": return {
136
+ case "RECOVERED": return {
139
137
  ...base,
140
138
  from_bundle_id: input.fromBundleId,
141
139
  type: input.type,
@@ -74,9 +74,9 @@ const bundleFilter = (input) => {
74
74
  type: "RECOVERED",
75
75
  fromBundleId: bundleId
76
76
  };
77
- case "adopted": return {
77
+ case "unchanged": return {
78
78
  ...scope,
79
- type: "RELEASE_ADOPTED",
79
+ type: "UNCHANGED",
80
80
  toBundleId: bundleId
81
81
  };
82
82
  default: throw new require_errors.InsightsBadRequestError("Invalid Insights outcome.");
@@ -283,7 +283,7 @@ const createInsightsProvider = (model) => Object.freeze({
283
283
  sinceMs,
284
284
  beforeReceivedAtMs
285
285
  }));
286
- const [reportingInstallations, bundleInstallations, appliedReports, recoveredReports, adoptedReports] = await Promise.all([
286
+ const [reportingInstallations, bundleInstallations, appliedReports, recoveredReports, unchangedReports] = await Promise.all([
287
287
  reporting,
288
288
  measure(model.countInstallations({
289
289
  ...scope,
@@ -292,7 +292,7 @@ const createInsightsProvider = (model) => Object.freeze({
292
292
  })),
293
293
  countOutcome("applied"),
294
294
  countOutcome("recovered"),
295
- countOutcome("adopted")
295
+ countOutcome("unchanged")
296
296
  ]);
297
297
  return {
298
298
  ...scope,
@@ -305,7 +305,7 @@ const createInsightsProvider = (model) => Object.freeze({
305
305
  reportingInstallations: bundleInstallations,
306
306
  appliedReports,
307
307
  recoveredReports,
308
- adoptedReports
308
+ unchangedReports
309
309
  }
310
310
  };
311
311
  }
@@ -74,9 +74,9 @@ const bundleFilter = (input) => {
74
74
  type: "RECOVERED",
75
75
  fromBundleId: bundleId
76
76
  };
77
- case "adopted": return {
77
+ case "unchanged": return {
78
78
  ...scope,
79
- type: "RELEASE_ADOPTED",
79
+ type: "UNCHANGED",
80
80
  toBundleId: bundleId
81
81
  };
82
82
  default: throw new InsightsBadRequestError("Invalid Insights outcome.");
@@ -283,7 +283,7 @@ const createInsightsProvider = (model) => Object.freeze({
283
283
  sinceMs,
284
284
  beforeReceivedAtMs
285
285
  }));
286
- const [reportingInstallations, bundleInstallations, appliedReports, recoveredReports, adoptedReports] = await Promise.all([
286
+ const [reportingInstallations, bundleInstallations, appliedReports, recoveredReports, unchangedReports] = await Promise.all([
287
287
  reporting,
288
288
  measure(model.countInstallations({
289
289
  ...scope,
@@ -292,7 +292,7 @@ const createInsightsProvider = (model) => Object.freeze({
292
292
  })),
293
293
  countOutcome("applied"),
294
294
  countOutcome("recovered"),
295
- countOutcome("adopted")
295
+ countOutcome("unchanged")
296
296
  ]);
297
297
  return {
298
298
  ...scope,
@@ -305,7 +305,7 @@ const createInsightsProvider = (model) => Object.freeze({
305
305
  reportingInstallations: bundleInstallations,
306
306
  appliedReports,
307
307
  recoveredReports,
308
- adoptedReports
308
+ unchangedReports
309
309
  }
310
310
  };
311
311
  }
@@ -51,7 +51,7 @@ const parseEventPageInput = (request) => {
51
51
  let bundle;
52
52
  if (bundleFields.some((key) => url.searchParams.has(key))) {
53
53
  const outcome = readSingle(url, "outcome");
54
- if (outcome !== "applied" && outcome !== "recovered" && outcome !== "adopted") throw new require_errors.InsightsBadRequestError("Invalid 'outcome' query parameter.");
54
+ if (outcome !== "applied" && outcome !== "recovered" && outcome !== "unchanged") throw new require_errors.InsightsBadRequestError("Invalid 'outcome' query parameter.");
55
55
  bundle = {
56
56
  ...readScope(url),
57
57
  bundleId: readId(url, "bundleId", 1024),
@@ -51,7 +51,7 @@ const parseEventPageInput = (request) => {
51
51
  let bundle;
52
52
  if (bundleFields.some((key) => url.searchParams.has(key))) {
53
53
  const outcome = readSingle(url, "outcome");
54
- if (outcome !== "applied" && outcome !== "recovered" && outcome !== "adopted") throw new InsightsBadRequestError("Invalid 'outcome' query parameter.");
54
+ if (outcome !== "applied" && outcome !== "recovered" && outcome !== "unchanged") throw new InsightsBadRequestError("Invalid 'outcome' query parameter.");
55
55
  bundle = {
56
56
  ...readScope(url),
57
57
  bundleId: readId(url, "bundleId", 1024),
package/dist/package.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "1.0.0-rc.2";
2
+ var version = "1.0.0-rc.3";
3
3
  //#endregion
4
4
  Object.defineProperty(exports, "version", {
5
5
  enumerable: true,
package/dist/package.mjs CHANGED
@@ -1,4 +1,4 @@
1
1
  //#region package.json
2
- var version = "1.0.0-rc.2";
2
+ var version = "1.0.0-rc.3";
3
3
  //#endregion
4
4
  export { version };
@@ -313,7 +313,7 @@ const bundleEventsV100 = require_dsl.table("bundle_events", {
313
313
  checks: [
314
314
  require_dsl.check({
315
315
  name: "bundle_events_type_check",
316
- expression: "type in ('UPDATE_APPLIED', 'RECOVERED', 'RELEASE_ADOPTED', 'UNCHANGED')",
316
+ expression: "type in ('UPDATE_APPLIED', 'RECOVERED', 'UNCHANGED')",
317
317
  sqliteInline: true
318
318
  }),
319
319
  require_dsl.check({
@@ -323,7 +323,7 @@ const bundleEventsV100 = require_dsl.table("bundle_events", {
323
323
  }),
324
324
  require_dsl.check({
325
325
  name: "bundle_events_shape_check",
326
- expression: "((type in ('UPDATE_APPLIED', 'RECOVERED', 'RELEASE_ADOPTED')) and from_bundle_id is not null and update_strategy is not null and update_strategy in ('fingerprint', 'appVersion')) or (type = 'UNCHANGED' and from_bundle_id is null and update_strategy is null)",
326
+ expression: "((type in ('UPDATE_APPLIED', 'RECOVERED')) and from_bundle_id is not null and update_strategy is not null and update_strategy in ('fingerprint', 'appVersion')) or (type = 'UNCHANGED' and from_bundle_id is null and update_strategy is null)",
327
327
  sqliteInline: true
328
328
  }),
329
329
  require_dsl.check({
@@ -364,7 +364,7 @@ const bundleInstallationsV100 = require_dsl.table("bundle_installations", {
364
364
  checks: [
365
365
  require_dsl.check({
366
366
  name: "bundle_installations_type_check",
367
- expression: "type in ('UPDATE_APPLIED', 'RECOVERED', 'RELEASE_ADOPTED', 'UNCHANGED')",
367
+ expression: "type in ('UPDATE_APPLIED', 'RECOVERED', 'UNCHANGED')",
368
368
  sqliteInline: true
369
369
  }),
370
370
  require_dsl.check({
@@ -313,7 +313,7 @@ const bundleEventsV100 = table("bundle_events", {
313
313
  checks: [
314
314
  check({
315
315
  name: "bundle_events_type_check",
316
- expression: "type in ('UPDATE_APPLIED', 'RECOVERED', 'RELEASE_ADOPTED', 'UNCHANGED')",
316
+ expression: "type in ('UPDATE_APPLIED', 'RECOVERED', 'UNCHANGED')",
317
317
  sqliteInline: true
318
318
  }),
319
319
  check({
@@ -323,7 +323,7 @@ const bundleEventsV100 = table("bundle_events", {
323
323
  }),
324
324
  check({
325
325
  name: "bundle_events_shape_check",
326
- expression: "((type in ('UPDATE_APPLIED', 'RECOVERED', 'RELEASE_ADOPTED')) and from_bundle_id is not null and update_strategy is not null and update_strategy in ('fingerprint', 'appVersion')) or (type = 'UNCHANGED' and from_bundle_id is null and update_strategy is null)",
326
+ expression: "((type in ('UPDATE_APPLIED', 'RECOVERED')) and from_bundle_id is not null and update_strategy is not null and update_strategy in ('fingerprint', 'appVersion')) or (type = 'UNCHANGED' and from_bundle_id is null and update_strategy is null)",
327
327
  sqliteInline: true
328
328
  }),
329
329
  check({
@@ -364,7 +364,7 @@ const bundleInstallationsV100 = table("bundle_installations", {
364
364
  checks: [
365
365
  check({
366
366
  name: "bundle_installations_type_check",
367
- expression: "type in ('UPDATE_APPLIED', 'RECOVERED', 'RELEASE_ADOPTED', 'UNCHANGED')",
367
+ expression: "type in ('UPDATE_APPLIED', 'RECOVERED', 'UNCHANGED')",
368
368
  sqliteInline: true
369
369
  }),
370
370
  check({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hot-updater/server",
3
- "version": "1.0.0-rc.2",
3
+ "version": "1.0.0-rc.3",
4
4
  "type": "module",
5
5
  "description": "React Native OTA solution for self-hosted",
6
6
  "sideEffects": false,
@@ -56,7 +56,7 @@
56
56
  "rou3": "0.7.9",
57
57
  "verkit": "0.4.0",
58
58
  "@hot-updater/core": "1.0.0-rc.0",
59
- "@hot-updater/plugin-core": "1.0.0-rc.2",
59
+ "@hot-updater/plugin-core": "1.0.0-rc.3",
60
60
  "@hot-updater/bsdiff": "1.0.0-rc.0"
61
61
  },
62
62
  "peerDependencies": {
@@ -91,8 +91,8 @@
91
91
  "mongodb": "6.20.0",
92
92
  "msw": "^2.7.0",
93
93
  "uuidv7": "^1.0.2",
94
- "@hot-updater/standalone": "1.0.0-rc.2",
95
- "@hot-updater/test-utils": "1.0.0-rc.2"
94
+ "@hot-updater/standalone": "1.0.0-rc.3",
95
+ "@hot-updater/test-utils": "1.0.0-rc.3"
96
96
  },
97
97
  "scripts": {
98
98
  "build": "tsdown",
@@ -168,9 +168,7 @@ export const parsePrismaBundleEventRow = (value: unknown): BundleEventRow => {
168
168
  if (
169
169
  (platform !== "ios" && platform !== "android") ||
170
170
  !(
171
- ((type === "UPDATE_APPLIED" ||
172
- type === "RECOVERED" ||
173
- type === "RELEASE_ADOPTED") &&
171
+ ((type === "UPDATE_APPLIED" || type === "RECOVERED") &&
174
172
  typeof fromBundleId === "string" &&
175
173
  (updateStrategy === "fingerprint" ||
176
174
  updateStrategy === "appVersion")) ||
@@ -210,10 +208,7 @@ export const parsePrismaInsightsInstallationRow = (
210
208
  const type = readString(value, "type");
211
209
  if (
212
210
  (platform !== "ios" && platform !== "android") ||
213
- (type !== "UPDATE_APPLIED" &&
214
- type !== "RECOVERED" &&
215
- type !== "RELEASE_ADOPTED" &&
216
- type !== "UNCHANGED")
211
+ (type !== "UPDATE_APPLIED" && type !== "RECOVERED" && type !== "UNCHANGED")
217
212
  ) {
218
213
  throw new PrismaAdapterError("invalid installation fields");
219
214
  }
@@ -15,7 +15,7 @@ export type CreateBundleEventRequestBase = {
15
15
 
16
16
  export type CreateBundleEventRequest =
17
17
  | (CreateBundleEventRequestBase & {
18
- readonly type: "UPDATE_APPLIED" | "RECOVERED" | "RELEASE_ADOPTED";
18
+ readonly type: "UPDATE_APPLIED" | "RECOVERED";
19
19
  readonly fromBundleId: string;
20
20
  readonly updateStrategy: "fingerprint" | "appVersion";
21
21
  })
@@ -30,11 +30,7 @@ export type ActiveInstallationWindow = "24h" | "7d" | "30d";
30
30
  export type EventHistoryRow = {
31
31
  readonly id: string;
32
32
  readonly installId: string;
33
- readonly type:
34
- | "UPDATE_APPLIED"
35
- | "RECOVERED"
36
- | "RELEASE_ADOPTED"
37
- | "UNCHANGED";
33
+ readonly type: "UPDATE_APPLIED" | "RECOVERED" | "UNCHANGED";
38
34
  readonly fromBundleId: string | null;
39
35
  readonly toBundleId: string;
40
36
  readonly username: string | null;
@@ -80,7 +76,7 @@ export type InsightsScope = {
80
76
 
81
77
  export type InsightsBundleSelection = InsightsScope & {
82
78
  readonly bundleId: string;
83
- readonly outcome: "applied" | "recovered" | "adopted";
79
+ readonly outcome: "applied" | "recovered" | "unchanged";
84
80
  };
85
81
 
86
82
  export type InsightsCountMeasurement = {
@@ -98,6 +94,6 @@ export type ReportingOverview = InsightsScope & {
98
94
  readonly reportingInstallations: InsightsCountMeasurement;
99
95
  readonly appliedReports: InsightsCountMeasurement;
100
96
  readonly recoveredReports: InsightsCountMeasurement;
101
- readonly adoptedReports: InsightsCountMeasurement;
97
+ readonly unchangedReports: InsightsCountMeasurement;
102
98
  };
103
99
  };
@@ -147,8 +147,7 @@ function requireEvent(payload: unknown): CreateBundleEventRequest {
147
147
  const type = requireStringField(payload, "type");
148
148
  switch (type) {
149
149
  case "UPDATE_APPLIED":
150
- case "RECOVERED":
151
- case "RELEASE_ADOPTED": {
150
+ case "RECOVERED": {
152
151
  const updateStrategy = requireStringField(payload, "updateStrategy");
153
152
  if (updateStrategy !== "fingerprint" && updateStrategy !== "appVersion") {
154
153
  throw new InsightsBadRequestError(
@@ -207,7 +206,6 @@ export function createBundleEventRow(
207
206
  switch (input.type) {
208
207
  case "UPDATE_APPLIED":
209
208
  case "RECOVERED":
210
- case "RELEASE_ADOPTED":
211
209
  return {
212
210
  ...base,
213
211
  from_bundle_id: input.fromBundleId,
@@ -140,10 +140,10 @@ describe("createHotUpdater Insights", () => {
140
140
  });
141
141
  await report(300, {
142
142
  installId: "install-2",
143
- type: "RELEASE_ADOPTED",
144
- fromBundleId: "B",
143
+ type: "UNCHANGED",
144
+ fromBundleId: null,
145
145
  toBundleId: "B",
146
- updateStrategy: "appVersion",
146
+ updateStrategy: null,
147
147
  });
148
148
  await report(400, {
149
149
  installId: "install-3",
@@ -166,7 +166,7 @@ describe("createHotUpdater Insights", () => {
166
166
  reportingInstallations: { count: 1 },
167
167
  appliedReports: { count: 1 },
168
168
  recoveredReports: { count: 1 },
169
- adoptedReports: { count: 1 },
169
+ unchangedReports: { count: 1 },
170
170
  },
171
171
  });
172
172
  const drilldown = await hotUpdater.handlers.admin(
@@ -255,6 +255,44 @@ describe("createHotUpdater Insights", () => {
255
255
  expect(adminQuery.headers.get("cache-control")).toBe("private, no-store");
256
256
  });
257
257
 
258
+ it("records same-file selection as no change and rejects a fourth event type", async () => {
259
+ const database = createInMemoryDatabasePlugin();
260
+ const record = vi.spyOn(database.models.insights, "record");
261
+ const hotUpdater = createHotUpdater({
262
+ database,
263
+ clientAccess: { type: "public" },
264
+ });
265
+ const selection = {
266
+ ...event,
267
+ fromReleaseId: "previous-release",
268
+ toReleaseId: "selected-release",
269
+ };
270
+ expect(
271
+ (await hotUpdater.handlers.client(eventRequest(selection))).status,
272
+ ).toBe(204);
273
+ expect(record).toHaveBeenCalledWith({
274
+ event: expect.objectContaining({
275
+ type: "UNCHANGED",
276
+ from_bundle_id: null,
277
+ update_strategy: null,
278
+ from_release_id: "previous-release",
279
+ to_release_id: "selected-release",
280
+ to_bundle_id: event.toBundleId,
281
+ }),
282
+ installation: expect.objectContaining({ type: "UNCHANGED" }),
283
+ });
284
+ const response = await hotUpdater.handlers.client(
285
+ eventRequest({
286
+ ...selection,
287
+ type: "RELEASE_ADOPTED",
288
+ fromBundleId: event.toBundleId,
289
+ updateStrategy: "appVersion",
290
+ }),
291
+ );
292
+ expect(response.status).toBe(400);
293
+ expect(record).toHaveBeenCalledTimes(1);
294
+ });
295
+
258
296
  it("returns a stable client error for malformed event payloads", async () => {
259
297
  const hotUpdater = createHotUpdater({
260
298
  database: createInMemoryDatabasePlugin(),
@@ -13,7 +13,7 @@ const eventId = (index: number) =>
13
13
 
14
14
  type TransitionEventRow = Extract<
15
15
  BundleEventRow,
16
- { readonly type: "UPDATE_APPLIED" | "RECOVERED" | "RELEASE_ADOPTED" }
16
+ { readonly type: "UPDATE_APPLIED" | "RECOVERED" }
17
17
  >;
18
18
 
19
19
  const eventRow = (
@@ -269,13 +269,13 @@ describe("createInsightsProvider", () => {
269
269
  expect(result.bundle?.reportingInstallations.count).toBe(2);
270
270
  expect(result.bundle?.appliedReports.count).toBe(5);
271
271
  expect(result.bundle?.recoveredReports.count).toBe(3);
272
- expect(result.bundle?.adoptedReports.count).toBe(1);
272
+ expect(result.bundle?.unchangedReports.count).toBe(1);
273
273
  expect(
274
274
  fixture.countEvents.mock.calls.map(([input]) => input.filter),
275
275
  ).toEqual([
276
276
  { ...scope, type: "UPDATE_APPLIED", toBundleId: "B" },
277
277
  { ...scope, type: "RECOVERED", fromBundleId: "B" },
278
- { ...scope, type: "RELEASE_ADOPTED", toBundleId: "B" },
278
+ { ...scope, type: "UNCHANGED", toBundleId: "B" },
279
279
  ]);
280
280
  await provider.listEvents({
281
281
  bundle: { ...scope, bundleId: "B", outcome: "recovered" },
@@ -157,8 +157,8 @@ const bundleFilter = (
157
157
  return { ...scope, type: "UPDATE_APPLIED", toBundleId: bundleId };
158
158
  case "recovered":
159
159
  return { ...scope, type: "RECOVERED", fromBundleId: bundleId };
160
- case "adopted":
161
- return { ...scope, type: "RELEASE_ADOPTED", toBundleId: bundleId };
160
+ case "unchanged":
161
+ return { ...scope, type: "UNCHANGED", toBundleId: bundleId };
162
162
  default:
163
163
  throw new InsightsBadRequestError("Invalid Insights outcome.");
164
164
  }
@@ -565,13 +565,13 @@ export const createInsightsProvider = (
565
565
  bundleInstallations,
566
566
  appliedReports,
567
567
  recoveredReports,
568
- adoptedReports,
568
+ unchangedReports,
569
569
  ] = await Promise.all([
570
570
  reporting,
571
571
  measure(model.countInstallations({ ...scope, sinceMs, bundleId })),
572
572
  countOutcome("applied"),
573
573
  countOutcome("recovered"),
574
- countOutcome("adopted"),
574
+ countOutcome("unchanged"),
575
575
  ]);
576
576
  return {
577
577
  ...scope,
@@ -584,7 +584,7 @@ export const createInsightsProvider = (
584
584
  reportingInstallations: bundleInstallations,
585
585
  appliedReports,
586
586
  recoveredReports,
587
- adoptedReports,
587
+ unchangedReports,
588
588
  },
589
589
  };
590
590
  },
@@ -87,7 +87,7 @@ export const parseEventPageInput = (
87
87
  if (
88
88
  outcome !== "applied" &&
89
89
  outcome !== "recovered" &&
90
- outcome !== "adopted"
90
+ outcome !== "unchanged"
91
91
  ) {
92
92
  throw new InsightsBadRequestError("Invalid 'outcome' query parameter.");
93
93
  }
@@ -433,8 +433,7 @@ export const bundleEventsV100 = table(
433
433
  checks: [
434
434
  check({
435
435
  name: "bundle_events_type_check",
436
- expression:
437
- "type in ('UPDATE_APPLIED', 'RECOVERED', 'RELEASE_ADOPTED', 'UNCHANGED')",
436
+ expression: "type in ('UPDATE_APPLIED', 'RECOVERED', 'UNCHANGED')",
438
437
  sqliteInline: true,
439
438
  }),
440
439
  check({
@@ -445,7 +444,7 @@ export const bundleEventsV100 = table(
445
444
  check({
446
445
  name: "bundle_events_shape_check",
447
446
  expression:
448
- "((type in ('UPDATE_APPLIED', 'RECOVERED', 'RELEASE_ADOPTED')) and from_bundle_id is not null and update_strategy is not null and update_strategy in ('fingerprint', 'appVersion')) or (type = 'UNCHANGED' and from_bundle_id is null and update_strategy is null)",
447
+ "((type in ('UPDATE_APPLIED', 'RECOVERED')) and from_bundle_id is not null and update_strategy is not null and update_strategy in ('fingerprint', 'appVersion')) or (type = 'UNCHANGED' and from_bundle_id is null and update_strategy is null)",
449
448
  sqliteInline: true,
450
449
  }),
451
450
  check({
@@ -494,8 +493,7 @@ export const bundleInstallationsV100 = table(
494
493
  checks: [
495
494
  check({
496
495
  name: "bundle_installations_type_check",
497
- expression:
498
- "type in ('UPDATE_APPLIED', 'RECOVERED', 'RELEASE_ADOPTED', 'UNCHANGED')",
496
+ expression: "type in ('UPDATE_APPLIED', 'RECOVERED', 'UNCHANGED')",
499
497
  sqliteInline: true,
500
498
  }),
501
499
  check({