@hot-updater/firebase 1.0.0-rc.45 → 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.
@@ -216,6 +216,13 @@ const isDatabaseJsonValue = (value) => {
216
216
  const isDatabaseJsonObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && isDatabaseJsonValue(value);
217
217
  const isDatabaseMetadataObject = (value) => isDatabaseJsonObject(value) && (!Object.hasOwn(value, "app_version") || typeof value["app_version"] === "string");
218
218
  //#endregion
219
+ //#region ../plugin-core/dist/databaseMetadata.mjs
220
+ const bundleMetadataToRow = (metadata) => {
221
+ if (metadata === void 0) return {};
222
+ if (!isDatabaseMetadataObject(metadata)) throw new DatabaseAdapterInputError("invalid-data");
223
+ return metadata;
224
+ };
225
+ //#endregion
219
226
  //#region ../../packages/protocol/dist/index.mjs
220
227
  const LETTER_DASH_NUMBER$2 = "[a-zA-Z0-9-]";
221
228
  const NUMERIC_IDENTIFIER$2 = String.raw`0|[1-9]\d*`;
@@ -329,7 +336,6 @@ function canonicalizeAppVersion(appVersion) {
329
336
  const version = coerce$1(appVersion);
330
337
  return version ? normalizeFull(version) : null;
331
338
  }
332
- const stripBundleArtifactMetadata = (metadata) => metadata;
333
339
  const getManifestStorageUri = (bundle) => bundle.manifestStorageUri;
334
340
  const getManifestFileHash = (bundle) => bundle.manifestFileHash;
335
341
  const getAssetBaseStorageUri = (bundle) => bundle.assetBaseStorageUri;
@@ -576,14 +582,6 @@ const NIL_UUID = "00000000-0000-0000-0000-000000000000";
576
582
  const UUID_V7_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
577
583
  const isUUIDv7 = (value) => typeof value === "string" && UUID_V7_PATTERN.test(value);
578
584
  //#endregion
579
- //#region ../plugin-core/dist/databaseMetadata.mjs
580
- const bundleMetadataToRow = (metadata) => {
581
- const value = stripBundleArtifactMetadata(metadata);
582
- if (value === void 0) return {};
583
- if (!isDatabaseMetadataObject(value)) throw new DatabaseAdapterInputError("invalid-data");
584
- return value;
585
- };
586
- //#endregion
587
585
  //#region ../plugin-core/dist/databaseRows.mjs
588
586
  const releaseRowToRelease = (row) => ({
589
587
  id: row.id,
@@ -641,7 +639,7 @@ const rowToBundle = (row, patchRows = []) => {
641
639
  id: row.id,
642
640
  platform: row.platform,
643
641
  gitCommitHash: row.git_commit_hash,
644
- metadata: stripBundleArtifactMetadata(row.metadata),
642
+ metadata: row.metadata,
645
643
  manifestStorageUri: row.manifest_storage_uri,
646
644
  manifestFileHash: row.manifest_file_hash,
647
645
  assetBaseStorageUri: row.asset_base_storage_uri,
@@ -649,6 +647,35 @@ const rowToBundle = (row, patchRows = []) => {
649
647
  };
650
648
  };
651
649
  //#endregion
650
+ //#region ../plugin-core/dist/uuidv7.mjs
651
+ const extractTimestampFromUUIDv7 = (uuid) => {
652
+ if (!isUUIDv7(uuid)) throw new TypeError("Expected a canonical lowercase UUIDv7");
653
+ const timestampHex = uuid.split("-").join("").slice(0, 12);
654
+ return Number.parseInt(timestampHex, 16);
655
+ };
656
+ function createUUIDv7FromTimestampHex(timestampHex) {
657
+ const randomBytes = /* @__PURE__ */ new Uint8Array(10);
658
+ crypto.getRandomValues(randomBytes);
659
+ const randomHex = Array.from(randomBytes).map((b) => b.toString(16).padStart(2, "0")).join("");
660
+ const randA = randomHex.slice(0, 3);
661
+ const randBHex = randomHex.slice(3, 19);
662
+ const versionAndRandA = `7${randA}`;
663
+ const variantAndFirstRandB = (128 | parseInt(randBHex.slice(0, 2), 16) & 63).toString(16).padStart(2, "0");
664
+ return [
665
+ timestampHex.slice(0, 8),
666
+ timestampHex.slice(8, 12),
667
+ versionAndRandA,
668
+ variantAndFirstRandB + randBHex.slice(2, 4),
669
+ randBHex.slice(4, 16)
670
+ ].join("-");
671
+ }
672
+ const createUUIDv7 = () => createUUIDv7FromTimestampHex(Date.now().toString(16).padStart(12, "0"));
673
+ const createUUIDv7After = (floor, nowMs = Date.now()) => {
674
+ const timestamp = Math.max(nowMs, floor === null ? 0 : extractTimestampFromUUIDv7(floor) + 1);
675
+ if (!Number.isSafeInteger(timestamp) || timestamp < 0 || timestamp >= 2 ** 48) throw new RangeError("UUIDv7 timestamp is out of range");
676
+ return createUUIDv7FromTimestampHex(timestamp.toString(16).padStart(12, "0"));
677
+ };
678
+ //#endregion
652
679
  //#region ../plugin-core/dist/node_modules/.pnpm/verkit@0.4.0/node_modules/verkit/dist/comparison-DenM3wCn.mjs
653
680
  const LETTER_DASH_NUMBER$1 = "[a-zA-Z0-9-]";
654
681
  const NUMERIC_IDENTIFIER$1 = String.raw`0|[1-9]\d*`;
@@ -995,42 +1022,6 @@ function coerce(value, options = {}) {
995
1022
  return tryParse(`${major}.${match[3] || "0"}.${match[4] || "0"}${options.includePrerelease && match[5] ? `-${match[5]}` : ""}${options.includePrerelease && match[6] ? `+${match[6]}` : ""}`, options);
996
1023
  }
997
1024
  //#endregion
998
- //#region ../plugin-core/dist/semverSatisfies.mjs
999
- const semverSatisfies = (targetAppVersion, currentVersion) => {
1000
- const currentCoerce = coerce(currentVersion);
1001
- if (!currentCoerce) return false;
1002
- return satisfies(currentCoerce, targetAppVersion);
1003
- };
1004
- //#endregion
1005
- //#region ../plugin-core/dist/uuidv7.mjs
1006
- const extractTimestampFromUUIDv7 = (uuid) => {
1007
- if (!isUUIDv7(uuid)) throw new TypeError("Expected a canonical lowercase UUIDv7");
1008
- const timestampHex = uuid.split("-").join("").slice(0, 12);
1009
- return Number.parseInt(timestampHex, 16);
1010
- };
1011
- function createUUIDv7FromTimestampHex(timestampHex) {
1012
- const randomBytes = /* @__PURE__ */ new Uint8Array(10);
1013
- crypto.getRandomValues(randomBytes);
1014
- const randomHex = Array.from(randomBytes).map((b) => b.toString(16).padStart(2, "0")).join("");
1015
- const randA = randomHex.slice(0, 3);
1016
- const randBHex = randomHex.slice(3, 19);
1017
- const versionAndRandA = `7${randA}`;
1018
- const variantAndFirstRandB = (128 | parseInt(randBHex.slice(0, 2), 16) & 63).toString(16).padStart(2, "0");
1019
- return [
1020
- timestampHex.slice(0, 8),
1021
- timestampHex.slice(8, 12),
1022
- versionAndRandA,
1023
- variantAndFirstRandB + randBHex.slice(2, 4),
1024
- randBHex.slice(4, 16)
1025
- ].join("-");
1026
- }
1027
- const createUUIDv7 = () => createUUIDv7FromTimestampHex(Date.now().toString(16).padStart(12, "0"));
1028
- const createUUIDv7After = (floor, nowMs = Date.now()) => {
1029
- const timestamp = Math.max(nowMs, floor === null ? 0 : extractTimestampFromUUIDv7(floor) + 1);
1030
- if (!Number.isSafeInteger(timestamp) || timestamp < 0 || timestamp >= 2 ** 48) throw new RangeError("UUIDv7 timestamp is out of range");
1031
- return createUUIDv7FromTimestampHex(timestamp.toString(16).padStart(12, "0"));
1032
- };
1033
- //#endregion
1034
1025
  //#region ../plugin-core/dist/releaseCatalogCompiler.mjs
1035
1026
  var ReleaseCatalogCompilationError = class extends Error {
1036
1027
  code;
@@ -1138,29 +1129,23 @@ function validateReleases(releases, strategy) {
1138
1129
  if (seenIds.has(release.id)) throw new ReleaseCatalogCompilationError("INVALID_RELEASE", `Duplicate Release ID: ${release.id}`);
1139
1130
  seenIds.add(release.id);
1140
1131
  if (release.strategy !== strategy) throw new ReleaseCatalogCompilationError("INVALID_RELEASE", `Release ${release.id} has the wrong strategy`);
1141
- 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`);
1142
1133
  if (release.targetCohorts.length > 100) throw new ReleaseCatalogCompilationError("TARGET_COHORT_LIMIT", `Release ${release.id} exceeds 100 target cohorts`);
1143
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`);
1144
1135
  if (strategy === "FINGERPRINT" && (release.fingerprintHash === null || release.targetAppVersion !== null)) throw new ReleaseCatalogCompilationError("INVALID_RELEASE", `Release ${release.id} has an invalid fingerprint target`);
1145
1136
  }
1146
- return enabled.filter((release) => release.kind === "BUNDLE");
1137
+ return enabled;
1147
1138
  }
1148
1139
  function collectFrontierReleaseIds(releases) {
1149
1140
  if (releases.every((release) => normalizeRolloutCohortCount(release.rolloutCohortCount) >= 1e3)) {
1150
1141
  const retained = /* @__PURE__ */ new Set();
1151
1142
  const bundleIds = /* @__PURE__ */ new Set();
1152
- let embedded = false;
1153
1143
  for (const release of releases) {
1154
- if (release.kind === "EMBEDDED") {
1155
- if (!embedded) {
1156
- embedded = true;
1157
- retained.add(release.id);
1158
- }
1159
- } else if (release.bundleId !== null && bundleIds.size < 11 && !bundleIds.has(release.bundleId)) {
1144
+ if (release.bundleId !== null && !bundleIds.has(release.bundleId)) {
1160
1145
  bundleIds.add(release.bundleId);
1161
1146
  retained.add(release.id);
1162
1147
  }
1163
- if (embedded && bundleIds.size >= 11) break;
1148
+ if (bundleIds.size >= 11) break;
1164
1149
  }
1165
1150
  return retained;
1166
1151
  }
@@ -1177,7 +1162,6 @@ function collectFrontierReleaseIds(releases) {
1177
1162
  normalized,
1178
1163
  numeric: normalized === void 0 ? null : getNumericCohortValue(normalized),
1179
1164
  bundleIds: /* @__PURE__ */ new Set(),
1180
- embedded: false,
1181
1165
  complete: false
1182
1166
  };
1183
1167
  });
@@ -1202,16 +1186,11 @@ function collectFrontierReleaseIds(releases) {
1202
1186
  for (const index of eligibleStateIndexes) {
1203
1187
  const state = cohortStates[index];
1204
1188
  if (state.complete) continue;
1205
- if (release.kind === "EMBEDDED") {
1206
- if (!state.embedded) {
1207
- state.embedded = true;
1208
- retained.add(release.id);
1209
- }
1210
- } else if (release.bundleId !== null && state.bundleIds.size < 11 && !state.bundleIds.has(release.bundleId)) {
1189
+ if (release.bundleId !== null && !state.bundleIds.has(release.bundleId)) {
1211
1190
  state.bundleIds.add(release.bundleId);
1212
1191
  retained.add(release.id);
1213
1192
  }
1214
- if (state.embedded && state.bundleIds.size >= 11) {
1193
+ if (state.bundleIds.size >= 11) {
1215
1194
  state.complete = true;
1216
1195
  incompleteStateCount -= 1;
1217
1196
  }
@@ -1396,12 +1375,11 @@ function projectCompiledCatalog(catalog, appVersion) {
1396
1375
  }
1397
1376
  function projectCompiledRollbackCatalog(catalog, appVersion) {
1398
1377
  let indexes;
1399
- if (catalog.strategy === "FINGERPRINT") indexes = catalog.rollbackReleaseIndexes ?? catalog.releaseIndexes;
1378
+ if (catalog.strategy === "FINGERPRINT") indexes = catalog.rollbackReleaseIndexes;
1400
1379
  else {
1401
1380
  const canonicalVersion = appVersion && canonicalizeAppVersion(appVersion);
1402
1381
  if (!canonicalVersion) return [];
1403
- const segment = catalog.segments.find((candidate) => versionInSegment(canonicalVersion, candidate));
1404
- indexes = segment?.rollbackReleaseIndexes ?? segment?.releaseIndexes ?? [];
1382
+ indexes = catalog.segments.find((segment) => versionInSegment(canonicalVersion, segment))?.rollbackReleaseIndexes ?? [];
1405
1383
  }
1406
1384
  return indexes.map((index) => catalog.releaseDescriptors[index]);
1407
1385
  }
@@ -1426,6 +1404,13 @@ var ReleaseCatalogMutationError = class extends Error {
1426
1404
  }
1427
1405
  };
1428
1406
  //#endregion
1407
+ //#region ../plugin-core/dist/semverSatisfies.mjs
1408
+ const semverSatisfies = (targetAppVersion, currentVersion) => {
1409
+ const currentCoerce = coerce(currentVersion);
1410
+ if (!currentCoerce) return false;
1411
+ return satisfies(currentCoerce, targetAppVersion);
1412
+ };
1413
+ //#endregion
1429
1414
  //#region ../plugin-core/dist/serverPlugin/definePlugin.mjs
1430
1415
  /** Declares a plugin; Hot Updater's own plugins and third-party ones use the same contract. */
1431
1416
  const definePlugin = (plugin) => plugin;
@@ -1916,7 +1901,7 @@ const SETTINGS_TABLE = {
1916
1901
  const ENGINE_SCHEMA_KEY = "schema.engine";
1917
1902
  const CORE_SCHEMA_KEY = "schema.core";
1918
1903
  const stored = ({ key, expected, found }) => `"${key}" is ${found === null ? "missing" : `"${found}"`}; expected "${expected}"`;
1919
- 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\`.`;
1920
1905
  /** The plugin a `schema.<id>` setting belongs to; undefined for core's and the engine's. */
1921
1906
  const pluginOf = ({ key }) => key.startsWith("schema.") && key !== "schema.engine" && key !== CORE_SCHEMA_KEY ? key.slice(7) : void 0;
1922
1907
  const listed = (items) => items.length < 2 ? items.join("") : `${items.slice(0, -1).join(", ")} and ${items.at(-1)}`;
@@ -2034,26 +2019,13 @@ const METER = Symbol.for("@hot-updater/plugin-core/read-meter");
2034
2019
  const readMeterOf = (database) => typeof database === "object" && database !== null ? database[METER] : void 0;
2035
2020
  //#endregion
2036
2021
  //#region ../plugin-core/dist/engine/db/schemaSettings.mjs
2037
- /** Refuses a database with `schema.core` but no `schema.engine`: it predates the engine. */
2038
- const assertEngineDatabase = (adapterName, stored) => {
2039
- if (stored.get("schema.core") && !stored.get("schema.engine")) throw new HotUpdaterSchemaMigrationRequiredError(adapterName, String(stored.get("schema.core").value), {
2040
- key: ENGINE_SCHEMA_KEY,
2041
- expected: "1",
2042
- found: null
2043
- });
2044
- };
2045
2022
  const storedSettings = async (adapter, keys) => {
2046
2023
  const rows = await readSettings(adapter, keys);
2047
2024
  return new Map(keys.map((key, position) => [key, rows[position] ?? null]));
2048
2025
  };
2049
- /** Writes the settings rows after every table exists, refusing a pre-engine database. */
2026
+ /** Writes the settings rows after every table exists. */
2050
2027
  const writeSchemaSettings = async (adapter, adapterName, settings) => {
2051
- const stored = await storedSettings(adapter, [.../* @__PURE__ */ new Set([
2052
- "schema.core",
2053
- ENGINE_SCHEMA_KEY,
2054
- ...Object.keys(settings)
2055
- ])]);
2056
- assertEngineDatabase(adapterName, stored);
2028
+ const stored = await storedSettings(adapter, Object.keys(settings));
2057
2029
  const ops = Object.entries(settings).flatMap(([key, value]) => {
2058
2030
  const row = stored.get(key);
2059
2031
  if (row === null || row === void 0) return [{
@@ -2076,19 +2048,11 @@ const writeSchemaSettings = async (adapter, adapterName, settings) => {
2076
2048
  }];
2077
2049
  });
2078
2050
  if (ops.length === 0) return;
2079
- 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.`);
2080
2052
  };
2081
- /**
2082
- * Creates or updates every table, then writes the settings rows last. A
2083
- * pre-engine database is refused before any table changes.
2084
- */
2053
+ /** Creates or updates every table, then writes the settings rows last. */
2085
2054
  const migrateSchema = async (adapter, adapterName, tables, settings) => {
2086
2055
  if (adapter.migrations === void 0) throw new Error(`${adapterName} creates its tables with its own tooling; write only the settings rows.`);
2087
- const stored = await storedSettings(adapter, ["schema.core", ENGINE_SCHEMA_KEY]).catch((error) => {
2088
- if (!isMissingSchemaError(error)) throw error;
2089
- return /* @__PURE__ */ new Map();
2090
- });
2091
- assertEngineDatabase(adapterName, stored);
2092
2056
  await adapter.migrations.apply([...tables, SETTINGS_TABLE]);
2093
2057
  await writeSchemaSettings(adapter, adapterName, settings);
2094
2058
  };
@@ -2373,9 +2337,6 @@ const getEmptyMigrationResult = () => ({
2373
2337
  execute: async () => {},
2374
2338
  getSQL: () => ""
2375
2339
  });
2376
- const assertSupportedMigrationMode = (options) => {
2377
- if (options.mode === "from-database") throw new Error("Hot Updater migrations support only mode: 'from-schema'.");
2378
- };
2379
2340
  //#endregion
2380
2341
  //#region ../plugin-core/dist/engine/db/settingsMigrator.mjs
2381
2342
  /** `schema.core`, or a v0 database's `version`. */
@@ -2383,15 +2344,14 @@ const storedSchemaVersion = (stored) => {
2383
2344
  const version = stored?.get("schema.core") ?? stored?.get("version");
2384
2345
  return typeof version === "string" ? version : void 0;
2385
2346
  };
2386
- /** A v0 or pre-engine database is recreated, never converted. */
2387
- const refusePreEngineDatabase = (adapterName, stored) => {
2388
- const version = storedSchemaVersion(stored);
2389
- if (version === void 0 || stored.has("schema.engine")) return;
2390
- throw new HotUpdaterSchemaMigrationRequiredError(adapterName, version, stored.has("schema.core") ? {
2391
- key: ENGINE_SCHEMA_KEY,
2392
- expected: "1",
2393
- found: null
2394
- } : 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);
2395
2355
  };
2396
2356
  //#endregion
2397
2357
  //#region ../plugin-core/dist/engine/database/cursor.mjs
@@ -3454,14 +3414,13 @@ const resolveSchema = (modules) => {
3454
3414
  * `hot-updater db migrate` for a store whose adapter creates its own tables
3455
3415
  * (MongoDB and the key-value stores): the tables and indexes, then the
3456
3416
  * settings rows, applied when a setting is missing or different, and refused
3457
- * on a database from before the engine.
3417
+ * on a v0 database.
3458
3418
  */
3459
3419
  const createEngineMigrator = (options) => {
3460
3420
  const getVersion = async () => storedSchemaVersion(await options.readSettings());
3461
3421
  const makeResult = async (migrate = {}) => {
3462
- assertSupportedMigrationMode(migrate);
3463
3422
  const stored = await options.readSettings();
3464
- refusePreEngineDatabase(options.adapterName, stored);
3423
+ refuseV0Database(options.adapterName, stored);
3465
3424
  if (Object.entries(options.settings).every(([key, value]) => stored.get(key) === value)) return getEmptyMigrationResult();
3466
3425
  return {
3467
3426
  operations: [...options.schema.tables.map((table) => ({
@@ -4566,7 +4525,7 @@ const createKvAdapter = ({ store, tablePrefix = "" }) => {
4566
4525
  };
4567
4526
  //#endregion
4568
4527
  //#region ../../packages/server/dist/version.mjs
4569
- const HOT_UPDATER_SERVER_VERSION = "1.0.0-rc.45";
4528
+ const HOT_UPDATER_SERVER_VERSION = "1.0.0-rc.47";
4570
4529
  //#endregion
4571
4530
  //#region ../../packages/server/dist/handlerVersionRoutes.mjs
4572
4531
  /**
@@ -5255,7 +5214,7 @@ const baseBundleIdsOf = (row, range, bundleId, limit) => {
5255
5214
  for (const { descriptor, index } of newestFirst) {
5256
5215
  if (ids.length >= limit) break;
5257
5216
  const id = descriptor.bundleId;
5258
- 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);
5259
5218
  }
5260
5219
  return ids;
5261
5220
  };
@@ -6021,7 +5980,7 @@ const createCoreOperations = (db, { now = Date.now } = {}) => {
6021
5980
  }),
6022
5981
  promoteRelease: (input) => withChannels(async () => {
6023
5982
  const checkSource = (source) => {
6024
- 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.");
6025
5984
  };
6026
5985
  checkSource((await db.transaction((tx) => loadTarget(tx, input))).release);
6027
5986
  const targetChannel = input.targetChannel.trim();
@@ -6220,7 +6179,6 @@ const fail = (message) => {
6220
6179
  const isRecord$6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
6221
6180
  const checkPlugin = (value, at) => {
6222
6181
  if (!isRecord$6(value)) return fail(`${at} is not a plugin.`);
6223
- if ("kind" in value) fail(`${at} has a kind; core is built in and is never passed as a plugin.`);
6224
6182
  const unknown = Object.keys(value).find((key) => !PLUGIN_KEYS.has(key));
6225
6183
  if (unknown !== void 0) fail(`${at} has an unknown key "${unknown}".`);
6226
6184
  const plugin = value;
@@ -7112,7 +7070,7 @@ const databaseOf = (value) => {
7112
7070
  if (isAdapter(value.adapter)) return value;
7113
7071
  if ("core" in value && "fetchAdmin" in value) throw new HotUpdaterConfigError("standaloneRepository reaches a server's admin API from the CLI and console. createHotUpdater needs the server's own database, such as kyselyAdapter(...).");
7114
7072
  }
7115
- throw new HotUpdaterConfigError("database must be a Hot Updater 1.0 database, such as kyselyAdapter(...) or postgres(...). Upgrade the provider package that created it.");
7073
+ throw new HotUpdaterConfigError("database must be a Hot Updater database adapter, such as kyselyAdapter(...) or postgres(...).");
7116
7074
  };
7117
7075
  /** The configured storage, when it is a storage adapter. */
7118
7076
  const storageOf = (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;
@@ -6995,12 +6977,17 @@ const assertFirebaseFunctionCanInitialize = async ({ fetchImpl, functions }) =>
6995
6977
  for (const existingFunction of existingFunctions) {
6996
6978
  if (!existingFunction.uri) throw new _hot_updater_cli_tools.InitError(`Could not verify the Firebase infrastructure generation at Function ${FIREBASE_V1_FUNCTION_NAME}: endpoint URL was not reported.`);
6997
6979
  const versionUrl = new URL("version", `${existingFunction.uri.replace(/\/$/u, "")}/`).toString();
6998
- await (0, _hot_updater_cli_tools.assertInfrastructureGenerationAtUrl)({
6999
- fetchImpl,
7000
- provider: "Firebase",
7001
- resource: `Function ${FIREBASE_V1_FUNCTION_NAME}`,
7002
- versionUrl
7003
- });
6980
+ try {
6981
+ await (0, _hot_updater_cli_tools.assertInfrastructureGenerationAtUrl)({
6982
+ fetchImpl,
6983
+ provider: "Firebase",
6984
+ resource: `Function ${FIREBASE_V1_FUNCTION_NAME}`,
6985
+ versionUrl
6986
+ });
6987
+ } catch (error) {
6988
+ if (!(error instanceof _hot_updater_cli_tools.LegacyInfrastructureError)) throw error;
6989
+ throw new _hot_updater_cli_tools.InitError(`Function ${FIREBASE_V1_FUNCTION_NAME}, which init deploys, already exists in this Firebase project and is incompatible: its /version does not report Hot Updater infrastructure generation 1. Delete the function or use another Firebase project, then rerun init. The existing function was not changed.`);
6990
+ }
7004
6991
  }
7005
6992
  };
7006
6993
  //#endregion
@@ -1,7 +1,7 @@
1
1
  import { createRequire } from "node:module";
2
2
  import fs from "fs";
3
3
  import path from "path";
4
- import { ConfigBuilder, HOT_UPDATER_SERVER_PACKAGE_VERSION_ENV, InitError, MissingInitInputsError, assembleServer, assertInfrastructureGenerationAtUrl, assertInitProviderInputs, confirmInitInputPersistence, copyDirToTmp, createHotUpdaterConfigScaffoldFromBuilder, getHotUpdaterInitInputEnv, getInitProviderEnvVars, getInitProviderTextPromptValues, link, makeEnv, p, printAppSetup, provisionClientCredential, readHotUpdaterInitEnv, resolveHotUpdaterServerVersion, resolveInitProviderInput, resolvePackageVersion, transformEnv, writeHotUpdaterFiles } from "@hot-updater/cli-tools";
4
+ import { ConfigBuilder, HOT_UPDATER_SERVER_PACKAGE_VERSION_ENV, InitError, LegacyInfrastructureError, MissingInitInputsError, assembleServer, assertInfrastructureGenerationAtUrl, assertInitProviderInputs, confirmInitInputPersistence, copyDirToTmp, createHotUpdaterConfigScaffoldFromBuilder, getHotUpdaterInitInputEnv, getInitProviderEnvVars, getInitProviderTextPromptValues, link, makeEnv, p, printAppSetup, provisionClientCredential, readHotUpdaterInitEnv, resolveHotUpdaterServerVersion, resolveInitProviderInput, resolvePackageVersion, transformEnv, writeHotUpdaterFiles } from "@hot-updater/cli-tools";
5
5
  import { apiKeys, insights, remoteConfig } from "@hot-updater/server/plugins";
6
6
  import { fileURLToPath } from "node:url";
7
7
  import { ChildProcess, execFile, spawn, spawnSync } from "node:child_process";
@@ -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;
@@ -6988,12 +6970,17 @@ const assertFirebaseFunctionCanInitialize = async ({ fetchImpl, functions }) =>
6988
6970
  for (const existingFunction of existingFunctions) {
6989
6971
  if (!existingFunction.uri) throw new InitError(`Could not verify the Firebase infrastructure generation at Function ${FIREBASE_V1_FUNCTION_NAME}: endpoint URL was not reported.`);
6990
6972
  const versionUrl = new URL("version", `${existingFunction.uri.replace(/\/$/u, "")}/`).toString();
6991
- await assertInfrastructureGenerationAtUrl({
6992
- fetchImpl,
6993
- provider: "Firebase",
6994
- resource: `Function ${FIREBASE_V1_FUNCTION_NAME}`,
6995
- versionUrl
6996
- });
6973
+ try {
6974
+ await assertInfrastructureGenerationAtUrl({
6975
+ fetchImpl,
6976
+ provider: "Firebase",
6977
+ resource: `Function ${FIREBASE_V1_FUNCTION_NAME}`,
6978
+ versionUrl
6979
+ });
6980
+ } catch (error) {
6981
+ if (!(error instanceof LegacyInfrastructureError)) throw error;
6982
+ throw new InitError(`Function ${FIREBASE_V1_FUNCTION_NAME}, which init deploys, already exists in this Firebase project and is incompatible: its /version does not report Hot Updater infrastructure generation 1. Delete the function or use another Firebase project, then rerun init. The existing function was not changed.`);
6983
+ }
6997
6984
  }
6998
6985
  };
6999
6986
  //#endregion
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.45",
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.45",
41
- "@hot-updater/protocol": "1.0.0-rc.45",
42
- "@hot-updater/plugin-core": "1.0.0-rc.45",
43
- "@hot-updater/server": "1.0.0-rc.45"
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.45",
59
- "@hot-updater/test-utils": "1.0.0-rc.45"
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": "*",