@hot-updater/firebase 1.0.0-rc.46 → 1.0.0-rc.47

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.
@@ -1129,29 +1129,23 @@ function validateReleases(releases, strategy) {
1129
1129
  if (seenIds.has(release.id)) throw new ReleaseCatalogCompilationError("INVALID_RELEASE", `Duplicate Release ID: ${release.id}`);
1130
1130
  seenIds.add(release.id);
1131
1131
  if (release.strategy !== strategy) throw new ReleaseCatalogCompilationError("INVALID_RELEASE", `Release ${release.id} has the wrong strategy`);
1132
- if (release.kind === "BUNDLE" && release.bundleId === null || release.kind === "EMBEDDED" && release.bundleId !== null) throw new ReleaseCatalogCompilationError("INVALID_RELEASE", `Release ${release.id} has an invalid kind/Bundle combination`);
1132
+ if (release.bundleId === null) throw new ReleaseCatalogCompilationError("INVALID_RELEASE", `Release ${release.id} has no Bundle`);
1133
1133
  if (release.targetCohorts.length > 100) throw new ReleaseCatalogCompilationError("TARGET_COHORT_LIMIT", `Release ${release.id} exceeds 100 target cohorts`);
1134
1134
  if (strategy === "APP_VERSION" && (release.targetAppVersion === null || release.fingerprintHash !== null)) throw new ReleaseCatalogCompilationError("INVALID_RELEASE", `Release ${release.id} has an invalid app-version target`);
1135
1135
  if (strategy === "FINGERPRINT" && (release.fingerprintHash === null || release.targetAppVersion !== null)) throw new ReleaseCatalogCompilationError("INVALID_RELEASE", `Release ${release.id} has an invalid fingerprint target`);
1136
1136
  }
1137
- return enabled.filter((release) => release.kind === "BUNDLE");
1137
+ return enabled;
1138
1138
  }
1139
1139
  function collectFrontierReleaseIds(releases) {
1140
1140
  if (releases.every((release) => normalizeRolloutCohortCount(release.rolloutCohortCount) >= 1e3)) {
1141
1141
  const retained = /* @__PURE__ */ new Set();
1142
1142
  const bundleIds = /* @__PURE__ */ new Set();
1143
- let embedded = false;
1144
1143
  for (const release of releases) {
1145
- if (release.kind === "EMBEDDED") {
1146
- if (!embedded) {
1147
- embedded = true;
1148
- retained.add(release.id);
1149
- }
1150
- } else if (release.bundleId !== null && bundleIds.size < 11 && !bundleIds.has(release.bundleId)) {
1144
+ if (release.bundleId !== null && !bundleIds.has(release.bundleId)) {
1151
1145
  bundleIds.add(release.bundleId);
1152
1146
  retained.add(release.id);
1153
1147
  }
1154
- if (embedded && bundleIds.size >= 11) break;
1148
+ if (bundleIds.size >= 11) break;
1155
1149
  }
1156
1150
  return retained;
1157
1151
  }
@@ -1168,7 +1162,6 @@ function collectFrontierReleaseIds(releases) {
1168
1162
  normalized,
1169
1163
  numeric: normalized === void 0 ? null : getNumericCohortValue(normalized),
1170
1164
  bundleIds: /* @__PURE__ */ new Set(),
1171
- embedded: false,
1172
1165
  complete: false
1173
1166
  };
1174
1167
  });
@@ -1193,16 +1186,11 @@ function collectFrontierReleaseIds(releases) {
1193
1186
  for (const index of eligibleStateIndexes) {
1194
1187
  const state = cohortStates[index];
1195
1188
  if (state.complete) continue;
1196
- if (release.kind === "EMBEDDED") {
1197
- if (!state.embedded) {
1198
- state.embedded = true;
1199
- retained.add(release.id);
1200
- }
1201
- } else if (release.bundleId !== null && state.bundleIds.size < 11 && !state.bundleIds.has(release.bundleId)) {
1189
+ if (release.bundleId !== null && !state.bundleIds.has(release.bundleId)) {
1202
1190
  state.bundleIds.add(release.bundleId);
1203
1191
  retained.add(release.id);
1204
1192
  }
1205
- if (state.embedded && state.bundleIds.size >= 11) {
1193
+ if (state.bundleIds.size >= 11) {
1206
1194
  state.complete = true;
1207
1195
  incompleteStateCount -= 1;
1208
1196
  }
@@ -1387,12 +1375,11 @@ function projectCompiledCatalog(catalog, appVersion) {
1387
1375
  }
1388
1376
  function projectCompiledRollbackCatalog(catalog, appVersion) {
1389
1377
  let indexes;
1390
- if (catalog.strategy === "FINGERPRINT") indexes = catalog.rollbackReleaseIndexes ?? catalog.releaseIndexes;
1378
+ if (catalog.strategy === "FINGERPRINT") indexes = catalog.rollbackReleaseIndexes;
1391
1379
  else {
1392
1380
  const canonicalVersion = appVersion && canonicalizeAppVersion(appVersion);
1393
1381
  if (!canonicalVersion) return [];
1394
- const segment = catalog.segments.find((candidate) => versionInSegment(canonicalVersion, candidate));
1395
- indexes = segment?.rollbackReleaseIndexes ?? segment?.releaseIndexes ?? [];
1382
+ indexes = catalog.segments.find((segment) => versionInSegment(canonicalVersion, segment))?.rollbackReleaseIndexes ?? [];
1396
1383
  }
1397
1384
  return indexes.map((index) => catalog.releaseDescriptors[index]);
1398
1385
  }
@@ -1914,7 +1901,7 @@ const SETTINGS_TABLE = {
1914
1901
  const ENGINE_SCHEMA_KEY = "schema.engine";
1915
1902
  const CORE_SCHEMA_KEY = "schema.core";
1916
1903
  const stored = ({ key, expected, found }) => `"${key}" is ${found === null ? "missing" : `"${found}"`}; expected "${expected}"`;
1917
- const settingMessage = (adapterName, { key, expected, found }) => `Hot Updater schema setting "${key}" for ${adapterName} is ${found === null ? "missing" : `"${found}"`}; expected "${expected}". ${key === "schema.engine" ? "Create a new empty database and run `hot-updater db migrate`; databases from before the storage engine are not converted." : "Run `hot-updater db migrate`."}`;
1904
+ const settingMessage = (adapterName, { key, expected, found }) => `Hot Updater schema setting "${key}" for ${adapterName} is ${found === null ? "missing" : `"${found}"`}; expected "${expected}". Run \`hot-updater db migrate\`.`;
1918
1905
  /** The plugin a `schema.<id>` setting belongs to; undefined for core's and the engine's. */
1919
1906
  const pluginOf = ({ key }) => key.startsWith("schema.") && key !== "schema.engine" && key !== CORE_SCHEMA_KEY ? key.slice(7) : void 0;
1920
1907
  const listed = (items) => items.length < 2 ? items.join("") : `${items.slice(0, -1).join(", ")} and ${items.at(-1)}`;
@@ -2032,26 +2019,13 @@ const METER = Symbol.for("@hot-updater/plugin-core/read-meter");
2032
2019
  const readMeterOf = (database) => typeof database === "object" && database !== null ? database[METER] : void 0;
2033
2020
  //#endregion
2034
2021
  //#region ../plugin-core/dist/engine/db/schemaSettings.mjs
2035
- /** Refuses a database with `schema.core` but no `schema.engine`: it predates the engine. */
2036
- const assertEngineDatabase = (adapterName, stored) => {
2037
- if (stored.get("schema.core") && !stored.get("schema.engine")) throw new HotUpdaterSchemaMigrationRequiredError(adapterName, String(stored.get("schema.core").value), {
2038
- key: ENGINE_SCHEMA_KEY,
2039
- expected: "1",
2040
- found: null
2041
- });
2042
- };
2043
2022
  const storedSettings = async (adapter, keys) => {
2044
2023
  const rows = await readSettings(adapter, keys);
2045
2024
  return new Map(keys.map((key, position) => [key, rows[position] ?? null]));
2046
2025
  };
2047
- /** Writes the settings rows after every table exists, refusing a pre-engine database. */
2026
+ /** Writes the settings rows after every table exists. */
2048
2027
  const writeSchemaSettings = async (adapter, adapterName, settings) => {
2049
- const stored = await storedSettings(adapter, [.../* @__PURE__ */ new Set([
2050
- "schema.core",
2051
- ENGINE_SCHEMA_KEY,
2052
- ...Object.keys(settings)
2053
- ])]);
2054
- assertEngineDatabase(adapterName, stored);
2028
+ const stored = await storedSettings(adapter, Object.keys(settings));
2055
2029
  const ops = Object.entries(settings).flatMap(([key, value]) => {
2056
2030
  const row = stored.get(key);
2057
2031
  if (row === null || row === void 0) return [{
@@ -2074,19 +2048,11 @@ const writeSchemaSettings = async (adapter, adapterName, settings) => {
2074
2048
  }];
2075
2049
  });
2076
2050
  if (ops.length === 0) return;
2077
- if (!(await adapter.write(ops)).ok) throw new Error("The schema settings changed during the migration; run it again.");
2051
+ if (!(await adapter.write(ops)).ok) throw new Error(`${adapterName}: the schema settings changed during the migration; run it again.`);
2078
2052
  };
2079
- /**
2080
- * Creates or updates every table, then writes the settings rows last. A
2081
- * pre-engine database is refused before any table changes.
2082
- */
2053
+ /** Creates or updates every table, then writes the settings rows last. */
2083
2054
  const migrateSchema = async (adapter, adapterName, tables, settings) => {
2084
2055
  if (adapter.migrations === void 0) throw new Error(`${adapterName} creates its tables with its own tooling; write only the settings rows.`);
2085
- const stored = await storedSettings(adapter, ["schema.core", ENGINE_SCHEMA_KEY]).catch((error) => {
2086
- if (!isMissingSchemaError(error)) throw error;
2087
- return /* @__PURE__ */ new Map();
2088
- });
2089
- assertEngineDatabase(adapterName, stored);
2090
2056
  await adapter.migrations.apply([...tables, SETTINGS_TABLE]);
2091
2057
  await writeSchemaSettings(adapter, adapterName, settings);
2092
2058
  };
@@ -2378,15 +2344,14 @@ const storedSchemaVersion = (stored) => {
2378
2344
  const version = stored?.get("schema.core") ?? stored?.get("version");
2379
2345
  return typeof version === "string" ? version : void 0;
2380
2346
  };
2381
- /** A v0 or pre-engine database is recreated, never converted. */
2382
- const refusePreEngineDatabase = (adapterName, stored) => {
2383
- const version = storedSchemaVersion(stored);
2384
- if (version === void 0 || stored.has("schema.engine")) return;
2385
- throw new HotUpdaterSchemaMigrationRequiredError(adapterName, version, stored.has("schema.core") ? {
2386
- key: ENGINE_SCHEMA_KEY,
2387
- expected: "1",
2388
- found: null
2389
- } : void 0);
2347
+ /**
2348
+ * A v0 database, whose settings hold `version` and no `schema.engine`, is
2349
+ * recreated, never converted.
2350
+ */
2351
+ const refuseV0Database = (adapterName, stored) => {
2352
+ const version = stored.get("version");
2353
+ if (typeof version !== "string" || stored.has("schema.engine")) return;
2354
+ throw new HotUpdaterSchemaMigrationRequiredError(adapterName, version);
2390
2355
  };
2391
2356
  //#endregion
2392
2357
  //#region ../plugin-core/dist/engine/database/cursor.mjs
@@ -3449,13 +3414,13 @@ const resolveSchema = (modules) => {
3449
3414
  * `hot-updater db migrate` for a store whose adapter creates its own tables
3450
3415
  * (MongoDB and the key-value stores): the tables and indexes, then the
3451
3416
  * settings rows, applied when a setting is missing or different, and refused
3452
- * on a database from before the engine.
3417
+ * on a v0 database.
3453
3418
  */
3454
3419
  const createEngineMigrator = (options) => {
3455
3420
  const getVersion = async () => storedSchemaVersion(await options.readSettings());
3456
3421
  const makeResult = async (migrate = {}) => {
3457
3422
  const stored = await options.readSettings();
3458
- refusePreEngineDatabase(options.adapterName, stored);
3423
+ refuseV0Database(options.adapterName, stored);
3459
3424
  if (Object.entries(options.settings).every(([key, value]) => stored.get(key) === value)) return getEmptyMigrationResult();
3460
3425
  return {
3461
3426
  operations: [...options.schema.tables.map((table) => ({
@@ -4560,7 +4525,7 @@ const createKvAdapter = ({ store, tablePrefix = "" }) => {
4560
4525
  };
4561
4526
  //#endregion
4562
4527
  //#region ../../packages/server/dist/version.mjs
4563
- const HOT_UPDATER_SERVER_VERSION = "1.0.0-rc.46";
4528
+ const HOT_UPDATER_SERVER_VERSION = "1.0.0-rc.47";
4564
4529
  //#endregion
4565
4530
  //#region ../../packages/server/dist/handlerVersionRoutes.mjs
4566
4531
  /**
@@ -5249,7 +5214,7 @@ const baseBundleIdsOf = (row, range, bundleId, limit) => {
5249
5214
  for (const { descriptor, index } of newestFirst) {
5250
5215
  if (ids.length >= limit) break;
5251
5216
  const id = descriptor.bundleId;
5252
- if (serving.has(index) && descriptor.kind === "BUNDLE" && id !== null && id < bundleId && !ids.includes(id)) ids.push(id);
5217
+ if (serving.has(index) && id !== null && id < bundleId && !ids.includes(id)) ids.push(id);
5253
5218
  }
5254
5219
  return ids;
5255
5220
  };
@@ -6015,7 +5980,7 @@ const createCoreOperations = (db, { now = Date.now } = {}) => {
6015
5980
  }),
6016
5981
  promoteRelease: (input) => withChannels(async () => {
6017
5982
  const checkSource = (source) => {
6018
- if (source.kind !== "BUNDLE" || source.bundle_id === null) throw new ReleaseManagementError("TARGET_RELEASE_INVALID", "Only a Bundle Release can be promoted.");
5983
+ if (source.bundle_id === null) throw new ReleaseManagementError("TARGET_RELEASE_INVALID", "Only a Bundle Release can be promoted.");
6019
5984
  };
6020
5985
  checkSource((await db.transaction((tx) => loadTarget(tx, input))).release);
6021
5986
  const targetChannel = input.targetChannel.trim();
@@ -6214,7 +6179,6 @@ const fail = (message) => {
6214
6179
  const isRecord$6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
6215
6180
  const checkPlugin = (value, at) => {
6216
6181
  if (!isRecord$6(value)) return fail(`${at} is not a plugin.`);
6217
- if ("kind" in value) fail(`${at} has a kind; core is built in and is never passed as a plugin.`);
6218
6182
  const unknown = Object.keys(value).find((key) => !PLUGIN_KEYS.has(key));
6219
6183
  if (unknown !== void 0) fail(`${at} has an unknown key "${unknown}".`);
6220
6184
  const plugin = value;
@@ -6706,17 +6706,6 @@ const FIREBASE_V1_FUNCTION_NAME = "hot-updater-v1";
6706
6706
  * the data, so item partitions are table names with no prefix.
6707
6707
  */
6708
6708
  const FIREBASE_V1_COLLECTION = "hot_updater_v1";
6709
- /**
6710
- * The collections a 1.0 release candidate wrote before the storage engine.
6711
- * Init reads them only to refuse such a database.
6712
- */
6713
- const FIREBASE_PRE_ENGINE_COLLECTIONS = [
6714
- "hot_updater_v1_private_settings",
6715
- "hot_updater_v1_bundles",
6716
- "hot_updater_v1_bundle_patches",
6717
- "hot_updater_v1_channels",
6718
- "hot_updater_v1_release_catalogs"
6719
- ];
6720
6709
  //#endregion
6721
6710
  //#region src/firestoreStore.ts
6722
6711
  /**
@@ -6954,11 +6943,9 @@ const inputFirebaseApplicationCredentials = async ({ applicationCredentials, non
6954
6943
  const ENGINE_SETTING = "schema.engine";
6955
6944
  /**
6956
6945
  * v1 once the storage engine's schema settings are written; incompatible
6957
- * when a 1.0 release candidate's collections hold data, or another engine
6958
- * version is recorded; otherwise fresh.
6946
+ * when another engine version is recorded; otherwise fresh.
6959
6947
  */
6960
- const resolveFirebaseInfrastructureState = ({ engine, preEngineData }) => {
6961
- if (preEngineData) return "incompatible";
6948
+ const resolveFirebaseInfrastructureState = ({ engine }) => {
6962
6949
  if (engine === void 0) return "fresh";
6963
6950
  return engine === _hot_updater_plugin_core.coreSettings[ENGINE_SETTING] ? "v1" : "incompatible";
6964
6951
  };
@@ -6974,13 +6961,8 @@ const assertFirebaseInfrastructureCanInitialize = async ({ applicationCredential
6974
6961
  projectId
6975
6962
  }, `hot-updater-init-${projectId}-${Date.now()}`);
6976
6963
  try {
6977
- const db = (0, firebase_admin_firestore.getFirestore)(app);
6978
- const setting = await db.collection(FIREBASE_V1_COLLECTION).doc(firebaseEngineSettingDocumentId()).get();
6979
- const preEngine = await Promise.all(FIREBASE_PRE_ENGINE_COLLECTIONS.map((name) => db.collection(name).limit(1).get()));
6980
- if (resolveFirebaseInfrastructureState({
6981
- engine: setting.exists ? setting.get("row.value") : void 0,
6982
- preEngineData: preEngine.some((snapshot) => !snapshot.empty)
6983
- }) === "incompatible") throw new _hot_updater_cli_tools.InitError(`Firebase v1 infrastructure in project ${projectId} holds data from a 1.0 release candidate or an unsupported database version. Use a new Firebase project, or delete the hot_updater_v1_* collections, then rerun init.`);
6964
+ const setting = await (0, firebase_admin_firestore.getFirestore)(app).collection(FIREBASE_V1_COLLECTION).doc(firebaseEngineSettingDocumentId()).get();
6965
+ if (resolveFirebaseInfrastructureState({ engine: setting.exists ? setting.get("row.value") : void 0 }) === "incompatible") throw new _hot_updater_cli_tools.InitError(`Firebase v1 infrastructure in project ${projectId} records an unsupported database version. Use a new Firebase project, or delete the ${FIREBASE_V1_COLLECTION} collection, then rerun init.`);
6984
6966
  } catch (error) {
6985
6967
  if (error instanceof _hot_updater_cli_tools.InitError) throw error;
6986
6968
  const code = typeof error === "object" && error !== null ? Reflect.get(error, "code") : void 0;
@@ -6699,17 +6699,6 @@ const FIREBASE_V1_FUNCTION_NAME = "hot-updater-v1";
6699
6699
  * the data, so item partitions are table names with no prefix.
6700
6700
  */
6701
6701
  const FIREBASE_V1_COLLECTION = "hot_updater_v1";
6702
- /**
6703
- * The collections a 1.0 release candidate wrote before the storage engine.
6704
- * Init reads them only to refuse such a database.
6705
- */
6706
- const FIREBASE_PRE_ENGINE_COLLECTIONS = [
6707
- "hot_updater_v1_private_settings",
6708
- "hot_updater_v1_bundles",
6709
- "hot_updater_v1_bundle_patches",
6710
- "hot_updater_v1_channels",
6711
- "hot_updater_v1_release_catalogs"
6712
- ];
6713
6702
  //#endregion
6714
6703
  //#region src/firestoreStore.ts
6715
6704
  /**
@@ -6947,11 +6936,9 @@ const inputFirebaseApplicationCredentials = async ({ applicationCredentials, non
6947
6936
  const ENGINE_SETTING = "schema.engine";
6948
6937
  /**
6949
6938
  * v1 once the storage engine's schema settings are written; incompatible
6950
- * when a 1.0 release candidate's collections hold data, or another engine
6951
- * version is recorded; otherwise fresh.
6939
+ * when another engine version is recorded; otherwise fresh.
6952
6940
  */
6953
- const resolveFirebaseInfrastructureState = ({ engine, preEngineData }) => {
6954
- if (preEngineData) return "incompatible";
6941
+ const resolveFirebaseInfrastructureState = ({ engine }) => {
6955
6942
  if (engine === void 0) return "fresh";
6956
6943
  return engine === coreSettings[ENGINE_SETTING] ? "v1" : "incompatible";
6957
6944
  };
@@ -6967,13 +6954,8 @@ const assertFirebaseInfrastructureCanInitialize = async ({ applicationCredential
6967
6954
  projectId
6968
6955
  }, `hot-updater-init-${projectId}-${Date.now()}`);
6969
6956
  try {
6970
- const db = getFirestore(app);
6971
- const setting = await db.collection(FIREBASE_V1_COLLECTION).doc(firebaseEngineSettingDocumentId()).get();
6972
- const preEngine = await Promise.all(FIREBASE_PRE_ENGINE_COLLECTIONS.map((name) => db.collection(name).limit(1).get()));
6973
- if (resolveFirebaseInfrastructureState({
6974
- engine: setting.exists ? setting.get("row.value") : void 0,
6975
- preEngineData: preEngine.some((snapshot) => !snapshot.empty)
6976
- }) === "incompatible") throw new InitError(`Firebase v1 infrastructure in project ${projectId} holds data from a 1.0 release candidate or an unsupported database version. Use a new Firebase project, or delete the hot_updater_v1_* collections, then rerun init.`);
6957
+ const setting = await getFirestore(app).collection(FIREBASE_V1_COLLECTION).doc(firebaseEngineSettingDocumentId()).get();
6958
+ if (resolveFirebaseInfrastructureState({ engine: setting.exists ? setting.get("row.value") : void 0 }) === "incompatible") throw new InitError(`Firebase v1 infrastructure in project ${projectId} records an unsupported database version. Use a new Firebase project, or delete the ${FIREBASE_V1_COLLECTION} collection, then rerun init.`);
6977
6959
  } catch (error) {
6978
6960
  if (error instanceof InitError) throw error;
6979
6961
  const code = typeof error === "object" && error !== null ? Reflect.get(error, "code") : void 0;
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.46",
4
+ "version": "1.0.0-rc.47",
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.46",
41
- "@hot-updater/protocol": "1.0.0-rc.46",
42
- "@hot-updater/plugin-core": "1.0.0-rc.46",
43
- "@hot-updater/server": "1.0.0-rc.46"
40
+ "@hot-updater/plugin-core": "1.0.0-rc.47",
41
+ "@hot-updater/cli-tools": "1.0.0-rc.47",
42
+ "@hot-updater/protocol": "1.0.0-rc.47",
43
+ "@hot-updater/server": "1.0.0-rc.47"
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.46",
59
- "@hot-updater/test-utils": "1.0.0-rc.46"
58
+ "@hot-updater/mock": "1.0.0-rc.47",
59
+ "@hot-updater/test-utils": "1.0.0-rc.47"
60
60
  },
61
61
  "peerDependencies": {
62
62
  "firebase-admin": "*",