@hot-updater/firebase 1.0.0-rc.35 → 1.0.0-rc.36

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.
@@ -4524,7 +4524,7 @@ const createKvAdapter = ({ store, tablePrefix = "" }) => {
4524
4524
  };
4525
4525
  //#endregion
4526
4526
  //#region ../../packages/server/dist/version.mjs
4527
- const HOT_UPDATER_SERVER_VERSION = "1.0.0-rc.35";
4527
+ const HOT_UPDATER_SERVER_VERSION = "1.0.0-rc.36";
4528
4528
  //#endregion
4529
4529
  //#region ../../packages/server/dist/handlerVersionRoutes.mjs
4530
4530
  /**
@@ -7687,8 +7687,19 @@ const isString = (value) => typeof value === "string";
7687
7687
  const isDatabaseHttpResponse = (value) => isDatabaseJsonObject(value) && (value.resource === "catalog" || value.resource === "artifact") && isString(value.path) && Number.isSafeInteger(value.status) && (value.body === null || isString(value.body)) && typeof value.body_truncated === "boolean" && Number.isSafeInteger(value.received_at_ms);
7688
7688
  /** An `UPDATE_FAILED` report's stored failure: a stage and a reason, and what else the client knew. */
7689
7689
  const isDatabaseBundleEventFailure = (value) => isDatabaseJsonObject(value) && isString(value.stage) && isString(value.reason) && isOptional(value, "resource", isString) && isOptional(value, "http_status", (status) => Number.isSafeInteger(status)) && isOptional(value, "transport", isString) && isOptional(value, "origin_code", isString) && isOptional(value, "error_message", isString) && isOptional(value, "error_stack", isString);
7690
- const isDatabaseBundleEventMetadata = (value) => isDatabaseJsonObject(value) && typeof value.cohort === "string" && (value.update_strategy === null || value.update_strategy === "fingerprint" || value.update_strategy === "appVersion") && (value.fingerprint_hash === null || typeof value.fingerprint_hash === "string") && (value.sdk_version === null || typeof value.sdk_version === "string") && isOptional(value, "failure", isDatabaseBundleEventFailure) && isOptional(value, "http_response", isDatabaseHttpResponse) && isOptional(value, "delivery", isString) && isOptional(value, "patch_fallback", (flag) => typeof flag === "boolean");
7690
+ const isDatabaseBundleEventMetadata = (value) => isDatabaseJsonObject(value) && typeof value.cohort === "string" && (value.update_strategy === null || value.update_strategy === "fingerprint" || value.update_strategy === "appVersion") && (value.fingerprint_hash === null || typeof value.fingerprint_hash === "string") && (value.sdk_version === null || typeof value.sdk_version === "string") && isOptional(value, "min_bundle_id", isString) && isOptional(value, "failure", isDatabaseBundleEventFailure) && isOptional(value, "http_response", isDatabaseHttpResponse) && isOptional(value, "delivery", isString) && isOptional(value, "patch_fallback", (flag) => typeof flag === "boolean");
7691
7691
  const isRecord$2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
7692
+ /**
7693
+ * The built-in bundle a report says its installation runs: the running
7694
+ * bundle, which a download leaves at its source, when it is the native
7695
+ * build's built-in bundle, whatever Release selected it. Null for any other
7696
+ * bundle, and for a report without the build's built-in bundle ID.
7697
+ */
7698
+ const runningBuiltinBundleId = (row) => {
7699
+ const running = row.type === "UPDATE_DOWNLOADED" ? row.from_bundle_id : row.to_bundle_id;
7700
+ const builtin = isRecord$2(row.metadata) ? row.metadata.min_bundle_id : null;
7701
+ return typeof builtin === "string" && running === builtin ? builtin : null;
7702
+ };
7692
7703
  const isIdentity$1 = (value) => typeof value === "string" && value.length > 0 && value.length <= 255;
7693
7704
  const isTextOrNull = (value) => value === null || typeof value === "string";
7694
7705
  /** Each bundle event field and what it holds. */
@@ -7989,6 +8000,11 @@ function readEventId(payload) {
7989
8000
  if (!isUUIDv7(value)) throw new InsightsBadRequestError("Invalid event field: eventId");
7990
8001
  return value;
7991
8002
  }
8003
+ /**
8004
+ * The native build's built-in bundle ID: a bundle ID's length at most, and
8005
+ * left out when it is missing or malformed, so the report still records.
8006
+ */
8007
+ const readMinBundleId = (value) => typeof value === "string" && value.length > 0 && value.length <= 36 ? value : null;
7992
8008
  /** One value of an open set: any other value reads as `unknown`. */
7993
8009
  const oneOf = (values, value) => values.includes(value) ? value : "unknown";
7994
8010
  const STAGES = [
@@ -8114,6 +8130,7 @@ function requireEvent(payload) {
8114
8130
  cohort: requireStringField(payload, "cohort"),
8115
8131
  fingerprintHash: requireNullableStringField(payload, "fingerprintHash"),
8116
8132
  sdkVersion: payload.sdkVersion === void 0 ? null : requireNullableStringField(payload, "sdkVersion"),
8133
+ minBundleId: readMinBundleId(payload.minBundleId),
8117
8134
  fromReleaseId: requireNullableStringField(payload, "fromReleaseId"),
8118
8135
  toReleaseId: requireNullableStringField(payload, "toReleaseId")
8119
8136
  };
@@ -8195,6 +8212,7 @@ function createBundleEventRow(input) {
8195
8212
  cohort: input.cohort,
8196
8213
  fingerprint_hash: input.fingerprintHash,
8197
8214
  sdk_version: input.sdkVersion ?? null,
8215
+ ...input.minBundleId ? { min_bundle_id: input.minBundleId } : {},
8198
8216
  update_strategy: input.updateStrategy
8199
8217
  };
8200
8218
  switch (input.type) {
@@ -8417,6 +8435,7 @@ const eventDetails = ({ type, metadata }) => ({
8417
8435
  const toEventHistoryRow = (row) => ({
8418
8436
  ...row.to_release_id == null ? {} : { toReleaseId: row.to_release_id },
8419
8437
  ...row.metadata.sdk_version == null ? {} : { sdkVersion: row.metadata.sdk_version },
8438
+ ...row.metadata.min_bundle_id === void 0 ? {} : { minBundleId: row.metadata.min_bundle_id },
8420
8439
  appVersion: row.app_version,
8421
8440
  channel: row.channel,
8422
8441
  cohort: row.metadata.cohort,
@@ -8432,6 +8451,7 @@ const toEventHistoryRow = (row) => ({
8432
8451
  });
8433
8452
  const toInstallationRow = (row) => ({
8434
8453
  ...eventDetails(row).httpResponse ? { httpResponse: eventDetails(row).httpResponse } : {},
8454
+ ...row.metadata.min_bundle_id === void 0 ? {} : { minBundleId: row.metadata.min_bundle_id },
8435
8455
  appVersion: row.app_version,
8436
8456
  channel: row.channel,
8437
8457
  cohort: row.metadata.cohort,
@@ -8983,6 +9003,55 @@ const insightsDistribution = (retention) => defineAggregate({
8983
9003
  },
8984
9004
  retention
8985
9005
  });
9006
+ /**
9007
+ * The installations of `insights_distribution` that run their native build's
9008
+ * built-in bundle, by its ID (`builtin_bundle_id`), within the same rows:
9009
+ * reads move them out of the row of their release, or of no release.
9010
+ */
9011
+ const insightsBuiltinDistribution = (retention) => defineAggregate({
9012
+ channel: { type: "string" },
9013
+ platform: {
9014
+ type: "string",
9015
+ maxLength: 16
9016
+ },
9017
+ app_version: { type: "string" },
9018
+ release_id: {
9019
+ type: "string",
9020
+ maxLength: 36
9021
+ },
9022
+ builtin_bundle_id: {
9023
+ type: "string",
9024
+ maxLength: 36
9025
+ },
9026
+ bucket_start_ms: { type: "integer" }
9027
+ }, {
9028
+ key: [
9029
+ "channel",
9030
+ "platform",
9031
+ "app_version",
9032
+ "release_id",
9033
+ "builtin_bundle_id",
9034
+ "bucket_start_ms"
9035
+ ],
9036
+ gauges: ["latest_installations"],
9037
+ shards: GAUGE_SHARDS,
9038
+ batched: true,
9039
+ indexes: {
9040
+ byScope: {
9041
+ eq: ["channel", "platform"],
9042
+ sort: ["bucket_start_ms"]
9043
+ },
9044
+ byVersion: {
9045
+ eq: [
9046
+ "channel",
9047
+ "platform",
9048
+ "app_version"
9049
+ ],
9050
+ sort: ["bucket_start_ms"]
9051
+ }
9052
+ },
9053
+ retention
9054
+ });
8986
9055
  /** Latest events by the bundle they came from or went to, bucketed like the distribution. */
8987
9056
  const insightsLatestByBundle = (retention) => defineAggregate({
8988
9057
  platform: {
@@ -9102,6 +9171,7 @@ const createInsightsSchema = ({ rawDays, dailyDays } = DEFAULT_INSIGHTS_RETENTIO
9102
9171
  insights_overview_lifetime: lifetimeCounters(),
9103
9172
  insights_sketches_lifetime: lifetimeSketches(),
9104
9173
  insights_distribution: insightsDistribution(daily),
9174
+ insights_builtin_distribution: insightsBuiltinDistribution(daily),
9105
9175
  insights_latest_by_bundle: insightsLatestByBundle(daily),
9106
9176
  insights_outcomes: insightsOutcomes(hourly),
9107
9177
  insights_failures: insightsFailures(hourly)
@@ -9220,13 +9290,16 @@ const insightsIdentity = (identity) => insightsOverviewId({
9220
9290
  bucketStartMs: 0
9221
9291
  });
9222
9292
  /**
9223
- * The bucket a head's gauges count it in, the UTC day of its event, and the
9224
- * release its distribution row names: the one it runs, which a download
9225
- * leaves at its source.
9293
+ * The bucket a head's gauges count it in, the UTC day of its event, and what
9294
+ * its distribution row names: the release it runs, which a download leaves
9295
+ * at its source, and the built-in bundle, when it runs the native build's.
9296
+ * A head stored without the built-in bundle ID counts in no built-in row, so
9297
+ * taking it back moves none.
9226
9298
  */
9227
9299
  const gaugeSlot = (head) => ({
9228
9300
  bucket: dayOf(head.received_at_ms),
9229
- releaseId: head.type === "UPDATE_DOWNLOADED" ? head.from_release_id : head.to_release_id
9301
+ releaseId: head.type === "UPDATE_DOWNLOADED" ? head.from_release_id : head.to_release_id,
9302
+ builtinBundleId: runningBuiltinBundleId(head)
9230
9303
  });
9231
9304
  /**
9232
9305
  * The `insights_latest_by_bundle` field of a head's (from, to) pair. A head
@@ -9237,11 +9310,12 @@ const PAIR_FIELD = "from_to";
9237
9310
  /** The pair gauge's `bundle_id`: a hash of the two bundle ids, which fit no single 36-character column. */
9238
9311
  const bundlePairKey = (from, to) => insightsKey(`${from.length}:${from}${to.length}:${to}`);
9239
9312
  /**
9240
- * Moves a head's gauges: the distribution row, one row per bundle it
9241
- * references, and its pair; -1 takes back the head an event replaces.
9313
+ * Moves a head's gauges: the distribution row, and the built-in one when it
9314
+ * runs its build's built-in bundle, one row per bundle it references, and
9315
+ * its pair; -1 takes back the head an event replaces.
9242
9316
  */
9243
9317
  const countHead = (tx, head, delta) => {
9244
- const { bucket, releaseId } = gaugeSlot(head);
9318
+ const { bucket, releaseId, builtinBundleId } = gaugeSlot(head);
9245
9319
  const shardBy = head.install_id;
9246
9320
  tx.aggregate("insights_distribution", {
9247
9321
  channel: head.channel,
@@ -9250,6 +9324,14 @@ const countHead = (tx, head, delta) => {
9250
9324
  release_id: releaseId ?? "",
9251
9325
  bucket_start_ms: bucket
9252
9326
  }, { latest_installations: delta }, { shardBy });
9327
+ if (builtinBundleId !== null) tx.aggregate("insights_builtin_distribution", {
9328
+ channel: head.channel,
9329
+ platform: head.platform,
9330
+ app_version: head.app_version,
9331
+ release_id: releaseId ?? "",
9332
+ builtin_bundle_id: builtinBundleId,
9333
+ bucket_start_ms: bucket
9334
+ }, { latest_installations: delta }, { shardBy });
9253
9335
  for (const field of ["from_bundle_id", "to_bundle_id"]) {
9254
9336
  const bundleId = head[field];
9255
9337
  if (bundleId === null) continue;
@@ -9905,26 +9987,49 @@ const getAppUsage = async (db, input, now, retention) => {
9905
9987
  lt: timeRange.end
9906
9988
  };
9907
9989
  const distribution = [];
9908
- for (const platform of reported) distribution.push(...await drain((page) => input.appVersion === void 0 ? db.findAggregates("insights_distribution", {
9909
- index: "byScope",
9910
- where: {
9911
- channel: input.channel,
9912
- platform
9913
- },
9914
- range,
9915
- limit: PAGE,
9916
- ...page
9917
- }) : db.findAggregates("insights_distribution", {
9918
- index: "byVersion",
9919
- where: {
9920
- channel: input.channel,
9921
- platform,
9922
- app_version: input.appVersion
9923
- },
9924
- range,
9925
- limit: PAGE,
9926
- ...page
9927
- })));
9990
+ const builtinDistribution = [];
9991
+ for (const platform of reported) {
9992
+ distribution.push(...await drain((page) => input.appVersion === void 0 ? db.findAggregates("insights_distribution", {
9993
+ index: "byScope",
9994
+ where: {
9995
+ channel: input.channel,
9996
+ platform
9997
+ },
9998
+ range,
9999
+ limit: PAGE,
10000
+ ...page
10001
+ }) : db.findAggregates("insights_distribution", {
10002
+ index: "byVersion",
10003
+ where: {
10004
+ channel: input.channel,
10005
+ platform,
10006
+ app_version: input.appVersion
10007
+ },
10008
+ range,
10009
+ limit: PAGE,
10010
+ ...page
10011
+ })));
10012
+ builtinDistribution.push(...await drain((page) => input.appVersion === void 0 ? db.findAggregates("insights_builtin_distribution", {
10013
+ index: "byScope",
10014
+ where: {
10015
+ channel: input.channel,
10016
+ platform
10017
+ },
10018
+ range,
10019
+ limit: PAGE,
10020
+ ...page
10021
+ }) : db.findAggregates("insights_builtin_distribution", {
10022
+ index: "byVersion",
10023
+ where: {
10024
+ channel: input.channel,
10025
+ platform,
10026
+ app_version: input.appVersion
10027
+ },
10028
+ range,
10029
+ limit: PAGE,
10030
+ ...page
10031
+ })));
10032
+ }
9928
10033
  const points = [];
9929
10034
  for (let start = timeRange.start; start < timeRange.end; start += intervalMs) points.push({
9930
10035
  startMs: start,
@@ -9933,22 +10038,31 @@ const getAppUsage = async (db, input, now, retention) => {
9933
10038
  const versions = /* @__PURE__ */ new Map();
9934
10039
  const platforms = /* @__PURE__ */ new Map();
9935
10040
  const bundles = /* @__PURE__ */ new Map();
9936
- for (const row of distribution) {
9937
- const count = row.latest_installations;
9938
- versions.set(row.app_version, (versions.get(row.app_version) ?? 0) + count);
9939
- platforms.set(row.platform, (platforms.get(row.platform) ?? 0) + count);
9940
- const releaseId = row.release_id === "" ? null : row.release_id;
10041
+ const countBundle = (row, releaseId, builtinBundleId, count) => {
9941
10042
  const key = JSON.stringify([
9942
10043
  row.app_version,
9943
10044
  row.platform,
9944
- releaseId
10045
+ releaseId,
10046
+ builtinBundleId
9945
10047
  ]);
9946
10048
  bundles.set(key, {
9947
10049
  appVersion: row.app_version,
9948
10050
  platform: row.platform,
9949
10051
  releaseId,
10052
+ builtinBundleId,
9950
10053
  installations: (bundles.get(key)?.installations ?? 0) + count
9951
10054
  });
10055
+ };
10056
+ for (const row of distribution) {
10057
+ const count = row.latest_installations;
10058
+ versions.set(row.app_version, (versions.get(row.app_version) ?? 0) + count);
10059
+ platforms.set(row.platform, (platforms.get(row.platform) ?? 0) + count);
10060
+ countBundle(row, row.release_id || null, null, count);
10061
+ }
10062
+ for (const row of builtinDistribution) {
10063
+ const releaseId = row.release_id || null;
10064
+ countBundle(row, releaseId, null, -row.latest_installations);
10065
+ countBundle(row, releaseId, row.builtin_bundle_id, row.latest_installations);
9952
10066
  }
9953
10067
  const sortedVersions = byInstallations(versions);
9954
10068
  return {
package/dist/index.d.cts CHANGED
@@ -68,6 +68,8 @@ type DatabaseBundleEventMetadata = DatabaseJsonObject & {
68
68
  readonly update_strategy: "fingerprint" | "appVersion" | null;
69
69
  readonly fingerprint_hash: string | null;
70
70
  readonly sdk_version: string | null;
71
+ /** The native build's built-in bundle ID, when the SDK reports it. */
72
+ readonly min_bundle_id?: string;
71
73
  /** `UPDATE_FAILED`: what failed. */
72
74
  readonly failure?: BundleEventFailure;
73
75
  readonly http_response?: DatabaseHttpResponse;
@@ -243,7 +245,10 @@ interface InsightsGetAppUsageResult {
243
245
  readonly bundleDistribution: readonly {
244
246
  readonly appVersion: string;
245
247
  readonly platform: "ios" | "android";
248
+ /** The release the installations run; null for none. */
246
249
  readonly releaseId: string | null;
250
+ /** The native build's built-in bundle, when they run it. */
251
+ readonly builtinBundleId: string | null;
247
252
  readonly installations: number;
248
253
  }[];
249
254
  readonly measuredAtMs: number;
@@ -574,6 +579,38 @@ declare const plugins: readonly [import("@hot-updater/plugin-core").HotUpdaterPl
574
579
  readonly sort: readonly ["bucket_start_ms"];
575
580
  };
576
581
  }, never, readonly ["channel", "platform", "app_version", "release_id", "bucket_start_ms"]>;
582
+ readonly insights_builtin_distribution: import("@hot-updater/plugin-core").AggregateDefinition<{
583
+ readonly channel: {
584
+ readonly type: "string";
585
+ };
586
+ readonly platform: {
587
+ readonly type: "string";
588
+ readonly maxLength: 16;
589
+ };
590
+ readonly app_version: {
591
+ readonly type: "string";
592
+ };
593
+ readonly release_id: {
594
+ readonly type: "string";
595
+ readonly maxLength: 36;
596
+ };
597
+ readonly builtin_bundle_id: {
598
+ readonly type: "string";
599
+ readonly maxLength: 36;
600
+ };
601
+ readonly bucket_start_ms: {
602
+ readonly type: "integer";
603
+ };
604
+ }, "latest_installations", {
605
+ readonly byScope: {
606
+ readonly eq: readonly ["channel", "platform"];
607
+ readonly sort: readonly ["bucket_start_ms"];
608
+ };
609
+ readonly byVersion: {
610
+ readonly eq: readonly ["channel", "platform", "app_version"];
611
+ readonly sort: readonly ["bucket_start_ms"];
612
+ };
613
+ }, never, readonly ["channel", "platform", "app_version", "release_id", "builtin_bundle_id", "bucket_start_ms"]>;
577
614
  readonly insights_latest_by_bundle: import("@hot-updater/plugin-core").AggregateDefinition<{
578
615
  readonly platform: {
579
616
  readonly type: "string";
package/dist/index.d.mts CHANGED
@@ -68,6 +68,8 @@ type DatabaseBundleEventMetadata = DatabaseJsonObject & {
68
68
  readonly update_strategy: "fingerprint" | "appVersion" | null;
69
69
  readonly fingerprint_hash: string | null;
70
70
  readonly sdk_version: string | null;
71
+ /** The native build's built-in bundle ID, when the SDK reports it. */
72
+ readonly min_bundle_id?: string;
71
73
  /** `UPDATE_FAILED`: what failed. */
72
74
  readonly failure?: BundleEventFailure;
73
75
  readonly http_response?: DatabaseHttpResponse;
@@ -243,7 +245,10 @@ interface InsightsGetAppUsageResult {
243
245
  readonly bundleDistribution: readonly {
244
246
  readonly appVersion: string;
245
247
  readonly platform: "ios" | "android";
248
+ /** The release the installations run; null for none. */
246
249
  readonly releaseId: string | null;
250
+ /** The native build's built-in bundle, when they run it. */
251
+ readonly builtinBundleId: string | null;
247
252
  readonly installations: number;
248
253
  }[];
249
254
  readonly measuredAtMs: number;
@@ -574,6 +579,38 @@ declare const plugins: readonly [import("@hot-updater/plugin-core").HotUpdaterPl
574
579
  readonly sort: readonly ["bucket_start_ms"];
575
580
  };
576
581
  }, never, readonly ["channel", "platform", "app_version", "release_id", "bucket_start_ms"]>;
582
+ readonly insights_builtin_distribution: import("@hot-updater/plugin-core").AggregateDefinition<{
583
+ readonly channel: {
584
+ readonly type: "string";
585
+ };
586
+ readonly platform: {
587
+ readonly type: "string";
588
+ readonly maxLength: 16;
589
+ };
590
+ readonly app_version: {
591
+ readonly type: "string";
592
+ };
593
+ readonly release_id: {
594
+ readonly type: "string";
595
+ readonly maxLength: 36;
596
+ };
597
+ readonly builtin_bundle_id: {
598
+ readonly type: "string";
599
+ readonly maxLength: 36;
600
+ };
601
+ readonly bucket_start_ms: {
602
+ readonly type: "integer";
603
+ };
604
+ }, "latest_installations", {
605
+ readonly byScope: {
606
+ readonly eq: readonly ["channel", "platform"];
607
+ readonly sort: readonly ["bucket_start_ms"];
608
+ };
609
+ readonly byVersion: {
610
+ readonly eq: readonly ["channel", "platform", "app_version"];
611
+ readonly sort: readonly ["bucket_start_ms"];
612
+ };
613
+ }, never, readonly ["channel", "platform", "app_version", "release_id", "builtin_bundle_id", "bucket_start_ms"]>;
577
614
  readonly insights_latest_by_bundle: import("@hot-updater/plugin-core").AggregateDefinition<{
578
615
  readonly platform: {
579
616
  readonly type: "string";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hot-updater/firebase",
3
3
  "type": "module",
4
- "version": "1.0.0-rc.35",
4
+ "version": "1.0.0-rc.36",
5
5
  "description": "React Native OTA solution for self-hosted",
6
6
  "main": "dist/index.cjs",
7
7
  "types": "dist/index.d.cts",
@@ -37,10 +37,10 @@
37
37
  ],
38
38
  "dependencies": {
39
39
  "hono": "4.12.34",
40
- "@hot-updater/cli-tools": "1.0.0-rc.35",
41
- "@hot-updater/plugin-core": "1.0.0-rc.35",
42
- "@hot-updater/protocol": "1.0.0-rc.35",
43
- "@hot-updater/server": "1.0.0-rc.35"
40
+ "@hot-updater/cli-tools": "1.0.0-rc.36",
41
+ "@hot-updater/protocol": "1.0.0-rc.36",
42
+ "@hot-updater/plugin-core": "1.0.0-rc.36",
43
+ "@hot-updater/server": "1.0.0-rc.36"
44
44
  },
45
45
  "publishConfig": {
46
46
  "access": "public"
@@ -55,8 +55,8 @@
55
55
  "firebase-tools": "15.29.0",
56
56
  "fkill": "^9.0.0",
57
57
  "mime": "^4.0.4",
58
- "@hot-updater/mock": "1.0.0-rc.35",
59
- "@hot-updater/test-utils": "1.0.0-rc.35"
58
+ "@hot-updater/mock": "1.0.0-rc.36",
59
+ "@hot-updater/test-utils": "1.0.0-rc.36"
60
60
  },
61
61
  "peerDependencies": {
62
62
  "firebase-admin": "*",