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

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.
@@ -4525,7 +4525,7 @@ const createKvAdapter = ({ store, tablePrefix = "" }) => {
4525
4525
  };
4526
4526
  //#endregion
4527
4527
  //#region ../../packages/server/dist/version.mjs
4528
- const HOT_UPDATER_SERVER_VERSION = "1.0.0-rc.47";
4528
+ const HOT_UPDATER_SERVER_VERSION = "1.0.1";
4529
4529
  //#endregion
4530
4530
  //#region ../../packages/server/dist/handlerVersionRoutes.mjs
4531
4531
  /**
@@ -11460,10 +11460,11 @@ const createFirestoreStore = ({ firestore, collection, nativePageSize }) => {
11460
11460
  };
11461
11461
  //#endregion
11462
11462
  //#region src/firebaseDatabase.ts
11463
- const adapterOf = ({ collection = FIREBASE_V1_COLLECTION, aggregateBatching: _, ...appOptions }) => {
11463
+ const adapterOf = ({ databaseId, collection = FIREBASE_V1_COLLECTION, aggregateBatching: _, ...appOptions }) => {
11464
11464
  const app = (0, firebase_admin_app.getApps)().length ? (0, firebase_admin_app.getApp)() : (0, firebase_admin_app.initializeApp)(appOptions);
11465
+ const firestore = databaseId ? (0, firebase_admin_firestore.getFirestore)(app, databaseId) : (0, firebase_admin_firestore.getFirestore)(app);
11465
11466
  return createKvAdapter({ store: createFirestoreStore({
11466
- firestore: (0, firebase_admin_firestore.getFirestore)(app),
11467
+ firestore,
11467
11468
  collection
11468
11469
  }) });
11469
11470
  };
@@ -11551,7 +11552,10 @@ const storageBucket = adminOptions.storageBucket;
11551
11552
  const cdnUrl = process.env.HOT_UPDATER_CDN_URL;
11552
11553
  if (!storageBucket) throw new Error("Firebase runtime requires storageBucket to read bundle manifests.");
11553
11554
  const hotUpdater = createHotUpdater({
11554
- database: firebaseDatabase({ ...adminOptions }),
11555
+ database: firebaseDatabase({
11556
+ ...adminOptions,
11557
+ databaseId: HotUpdater.FIRESTORE_DATABASE_ID
11558
+ }),
11555
11559
  plugins: [
11556
11560
  insights(),
11557
11561
  apiKeys(),
package/dist/index.cjs CHANGED
@@ -168,10 +168,11 @@ const createFirestoreStore = ({ firestore, collection, nativePageSize }) => {
168
168
  };
169
169
  //#endregion
170
170
  //#region src/firebaseDatabase.ts
171
- const adapterOf = ({ collection = FIREBASE_V1_COLLECTION, aggregateBatching: _, ...appOptions }) => {
171
+ const adapterOf = ({ databaseId, collection = FIREBASE_V1_COLLECTION, aggregateBatching: _, ...appOptions }) => {
172
172
  const app = (0, firebase_admin_app.getApps)().length ? (0, firebase_admin_app.getApp)() : (0, firebase_admin_app.initializeApp)(appOptions);
173
+ const firestore = databaseId ? (0, firebase_admin_firestore.getFirestore)(app, databaseId) : (0, firebase_admin_firestore.getFirestore)(app);
173
174
  return (0, _hot_updater_plugin_core.createKvAdapter)({ store: createFirestoreStore({
174
- firestore: (0, firebase_admin_firestore.getFirestore)(app),
175
+ firestore,
175
176
  collection
176
177
  }) });
177
178
  };
package/dist/index.d.cts CHANGED
@@ -2,6 +2,11 @@ import { EngineDatabase, PluginTables, StorageAdapterWith } from "@hot-updater/p
2
2
  import { AppOptions } from "firebase-admin/app";
3
3
  //#region src/firebaseDatabase.d.ts
4
4
  type FirebaseDatabaseConfig = AppOptions & {
5
+ /**
6
+ * The Firestore database the collection lives in, for a project with
7
+ * named databases. Defaults to the project's `(default)` database.
8
+ */
9
+ readonly databaseId?: string;
5
10
  /** The one collection the database keeps its items in. */
6
11
  readonly collection?: string;
7
12
  /**
package/dist/index.d.mts CHANGED
@@ -2,6 +2,11 @@ import { EngineDatabase, PluginTables, StorageAdapterWith } from "@hot-updater/p
2
2
  import { AppOptions } from "firebase-admin/app";
3
3
  //#region src/firebaseDatabase.d.ts
4
4
  type FirebaseDatabaseConfig = AppOptions & {
5
+ /**
6
+ * The Firestore database the collection lives in, for a project with
7
+ * named databases. Defaults to the project's `(default)` database.
8
+ */
9
+ readonly databaseId?: string;
5
10
  /** The one collection the database keeps its items in. */
6
11
  readonly collection?: string;
7
12
  /**
package/dist/index.mjs CHANGED
@@ -167,10 +167,11 @@ const createFirestoreStore = ({ firestore, collection, nativePageSize }) => {
167
167
  };
168
168
  //#endregion
169
169
  //#region src/firebaseDatabase.ts
170
- const adapterOf = ({ collection = FIREBASE_V1_COLLECTION, aggregateBatching: _, ...appOptions }) => {
170
+ const adapterOf = ({ databaseId, collection = FIREBASE_V1_COLLECTION, aggregateBatching: _, ...appOptions }) => {
171
171
  const app = getApps().length ? getApp() : initializeApp(appOptions);
172
+ const firestore = databaseId ? getFirestore(app, databaseId) : getFirestore(app);
172
173
  return createKvAdapter({ store: createFirestoreStore({
173
- firestore: getFirestore(app),
174
+ firestore,
174
175
  collection
175
176
  }) });
176
177
  };
@@ -6864,10 +6864,11 @@ const createFirestoreStore = ({ firestore, collection, nativePageSize }) => {
6864
6864
  };
6865
6865
  //#endregion
6866
6866
  //#region src/firebaseDatabase.ts
6867
- const adapterOf = ({ collection = FIREBASE_V1_COLLECTION, aggregateBatching: _, ...appOptions }) => {
6867
+ const adapterOf = ({ databaseId, collection = FIREBASE_V1_COLLECTION, aggregateBatching: _, ...appOptions }) => {
6868
6868
  const app = (0, firebase_admin_app.getApps)().length ? (0, firebase_admin_app.getApp)() : (0, firebase_admin_app.initializeApp)(appOptions);
6869
+ const firestore = databaseId ? (0, firebase_admin_firestore.getFirestore)(app, databaseId) : (0, firebase_admin_firestore.getFirestore)(app);
6869
6870
  return (0, _hot_updater_plugin_core.createKvAdapter)({ store: createFirestoreStore({
6870
- firestore: (0, firebase_admin_firestore.getFirestore)(app),
6871
+ firestore,
6871
6872
  collection
6872
6873
  }) });
6873
6874
  };
@@ -6888,6 +6889,8 @@ const firebaseDatabase = (config) => (0, _hot_updater_plugin_core.createEngineDa
6888
6889
  //#region iac/init/index.ts
6889
6890
  const isFirebaseRegion = (value) => value !== void 0 && /^[a-z]+(?:-[a-z]+)+[0-9]+$/.test(value);
6890
6891
  const isFirebaseProjectId = (value) => value !== void 0 && /^[a-z][a-z0-9-]{4,28}[a-z0-9]$/.test(value);
6892
+ /** `(default)`, or a named database's ID: 4-63 lowercase letters, digits, or hyphens. */
6893
+ const isFirestoreDatabaseId = (value) => value !== void 0 && (value === "(default)" || /^[a-z][a-z0-9-]{2,61}[a-z0-9]$/.test(value));
6891
6894
  const initProvider = {
6892
6895
  label: "Firebase",
6893
6896
  inputs: {
@@ -6910,6 +6913,16 @@ const initProvider = {
6910
6913
  },
6911
6914
  validate: isFirebaseRegion
6912
6915
  },
6916
+ databaseId: {
6917
+ envKey: "HOT_UPDATER_FIREBASE_DATABASE_ID",
6918
+ help: "Firestore database ID",
6919
+ optional: true,
6920
+ prompt: {
6921
+ message: "Select a Firestore database",
6922
+ type: "select"
6923
+ },
6924
+ validate: isFirestoreDatabaseId
6925
+ },
6913
6926
  applicationCredentials: {
6914
6927
  envKey: "GOOGLE_APPLICATION_CREDENTIALS",
6915
6928
  help: "Service account JSON path",
@@ -6954,15 +6967,15 @@ const firebaseEngineSettingDocumentId = () => firestoreDocumentId({
6954
6967
  pk: _hot_updater_plugin_core.SETTINGS_TABLE.name,
6955
6968
  sk: (0, _hot_updater_plugin_core.encodeKvKey)([ENGINE_SETTING])
6956
6969
  });
6957
- const assertFirebaseInfrastructureCanInitialize = async ({ applicationCredentials, projectId }) => {
6970
+ const assertFirebaseInfrastructureCanInitialize = async ({ applicationCredentials, databaseId, projectId }) => {
6958
6971
  const credential = applicationCredentials ? (0, firebase_admin_app.cert)(JSON.parse(await fs_promises.default.readFile(applicationCredentials, "utf8"))) : (0, firebase_admin_app.applicationDefault)();
6959
6972
  const app = (0, firebase_admin_app.initializeApp)({
6960
6973
  credential,
6961
6974
  projectId
6962
6975
  }, `hot-updater-init-${projectId}-${Date.now()}`);
6963
6976
  try {
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.`);
6977
+ const setting = await (0, firebase_admin_firestore.getFirestore)(app, databaseId).collection(FIREBASE_V1_COLLECTION).doc(firebaseEngineSettingDocumentId()).get();
6978
+ 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} (Firestore database ${databaseId}) records an unsupported database version. Use a new Firebase project, or delete the ${FIREBASE_V1_COLLECTION} collection, then rerun init.`);
6966
6979
  } catch (error) {
6967
6980
  if (error instanceof _hot_updater_cli_tools.InitError) throw error;
6968
6981
  const code = typeof error === "object" && error !== null ? Reflect.get(error, "code") : void 0;
@@ -7006,6 +7019,7 @@ const resolveFirebaseInitInputs = (existingEnv) => {
7006
7019
  const applicationCredentials = (0, _hot_updater_cli_tools.resolveInitProviderInput)(existingEnv, inputs.applicationCredentials);
7007
7020
  return {
7008
7021
  applicationCredentials: applicationCredentials === "your-credentials.json" ? void 0 : applicationCredentials,
7022
+ databaseId: (0, _hot_updater_cli_tools.resolveInitProviderInput)(existingEnv, inputs.databaseId),
7009
7023
  projectId: (0, _hot_updater_cli_tools.resolveInitProviderInput)(existingEnv, inputs.projectId),
7010
7024
  region: (0, _hot_updater_cli_tools.resolveInitProviderInput)(existingEnv, inputs.region)
7011
7025
  };
@@ -7198,6 +7212,7 @@ const getConfigScaffold = (build) => {
7198
7212
  }],
7199
7213
  configString: `firebaseDatabase({
7200
7214
  projectId: process.env.HOT_UPDATER_FIREBASE_PROJECT_ID!,
7215
+ databaseId: process.env.HOT_UPDATER_FIREBASE_DATABASE_ID,
7201
7216
  credential,
7202
7217
  })`
7203
7218
  };
@@ -7228,8 +7243,58 @@ const credential = applicationDefault();`.trim()
7228
7243
  return (0, _hot_updater_cli_tools.createHotUpdaterConfigScaffoldFromBuilder)(builder, { helperStatements });
7229
7244
  };
7230
7245
  //#endregion
7246
+ //#region iac/firestoreDatabase.ts
7247
+ const FIRESTORE_DEFAULT_DATABASE_ID = "(default)";
7248
+ /**
7249
+ * The IDs of a project's databases the Firestore API can use, from
7250
+ * `gcloud firestore databases list --format=json`, whose names are
7251
+ * `projects/<project>/databases/<id>`: not Datastore mode, and not an
7252
+ * Enterprise database with Firestore data access disabled, the default for
7253
+ * MongoDB-compatible ones.
7254
+ */
7255
+ const parseFirestoreDatabaseIds = (databases) => Array.isArray(databases) ? databases.flatMap((database) => {
7256
+ if (typeof database !== "object" || database === null) return [];
7257
+ const name = Reflect.get(database, "name");
7258
+ if (typeof name !== "string" || Reflect.get(database, "type") === "DATASTORE_MODE" || Reflect.get(database, "firestoreDataAccessMode") === "DATA_ACCESS_MODE_DISABLED") return [];
7259
+ return [name.slice(name.lastIndexOf("/") + 1)];
7260
+ }) : [];
7261
+ /**
7262
+ * The database init sets up: the saved one, the only one, or the one the
7263
+ * user selects. Without a saved one, non-interactive init keeps `(default)`
7264
+ * and needs `HOT_UPDATER_FIREBASE_DATABASE_ID` only to choose among named
7265
+ * databases.
7266
+ */
7267
+ const resolveFirestoreDatabaseId = async ({ databaseIds, nonInteractive, savedDatabaseId }) => {
7268
+ const input = initProvider.inputs.databaseId;
7269
+ if (savedDatabaseId !== void 0) {
7270
+ if (databaseIds.includes(savedDatabaseId)) return savedDatabaseId;
7271
+ if (nonInteractive) throw new _hot_updater_cli_tools.MissingInitInputsError([input.envKey]);
7272
+ _hot_updater_cli_tools.p.log.warn(`Saved Firestore database ${savedDatabaseId} was not found. Select a database again.`);
7273
+ }
7274
+ const [onlyDatabaseId] = databaseIds;
7275
+ if (databaseIds.length === 1 && onlyDatabaseId !== void 0) return onlyDatabaseId;
7276
+ const hasDefault = databaseIds.includes(FIRESTORE_DEFAULT_DATABASE_ID);
7277
+ if (nonInteractive) {
7278
+ if (hasDefault) return FIRESTORE_DEFAULT_DATABASE_ID;
7279
+ throw new _hot_updater_cli_tools.MissingInitInputsError([input.envKey]);
7280
+ }
7281
+ const selected = await _hot_updater_cli_tools.p.select({
7282
+ initialValue: hasDefault ? FIRESTORE_DEFAULT_DATABASE_ID : onlyDatabaseId,
7283
+ message: input.prompt.message,
7284
+ options: databaseIds.map((databaseId) => ({
7285
+ value: databaseId,
7286
+ label: databaseId
7287
+ }))
7288
+ });
7289
+ if (_hot_updater_cli_tools.p.isCancel(selected)) {
7290
+ _hot_updater_cli_tools.p.cancel("Operation cancelled.");
7291
+ process.exit(1);
7292
+ }
7293
+ return selected;
7294
+ };
7295
+ //#endregion
7231
7296
  //#region iac/select.ts
7232
- const setEnv = async ({ applicationCredentials, projectId, storageBucket, build, region }) => {
7297
+ const setEnv = async ({ applicationCredentials, databaseId, projectId, storageBucket, build, region }) => {
7233
7298
  await (0, _hot_updater_cli_tools.makeEnv)({
7234
7299
  [initProvider.inputs.applicationCredentials.envKey]: {
7235
7300
  comment: "Project Settings > Service Accounts > New Private Key > Download JSON",
@@ -7237,6 +7302,7 @@ const setEnv = async ({ applicationCredentials, projectId, storageBucket, build,
7237
7302
  },
7238
7303
  [initProvider.inputs.projectId.envKey]: projectId,
7239
7304
  [initProvider.inputs.region.envKey]: region,
7305
+ [initProvider.inputs.databaseId.envKey]: databaseId,
7240
7306
  HOT_UPDATER_FIREBASE_STORAGE_BUCKET: storageBucket
7241
7307
  }, ".env.hotupdater", { preserveKeys: applicationCredentials ? [] : [initProvider.inputs.applicationCredentials.envKey] });
7242
7308
  _hot_updater_cli_tools.p.log.success("Firebase credentials have been successfully configured.");
@@ -7286,7 +7352,7 @@ const listProjects = async (nonInteractive = false, cliEnv) => {
7286
7352
  throw error;
7287
7353
  }
7288
7354
  };
7289
- const initFirebaseUser = async (cwd, preferredProjectId, nonInteractive = false, cliEnv, resolveCliEnv) => {
7355
+ const initFirebaseUser = async (cwd, preferredProjectId, nonInteractive = false, cliEnv, resolveCliEnv, preferredDatabaseId) => {
7290
7356
  if (!cliEnv) try {
7291
7357
  const authList = await execa("gcloud", [
7292
7358
  "auth",
@@ -7376,6 +7442,7 @@ const initFirebaseUser = async (cwd, preferredProjectId, nonInteractive = false,
7376
7442
  }
7377
7443
  }
7378
7444
  }]);
7445
+ let databaseIds = [];
7379
7446
  try {
7380
7447
  const databases = await execa("gcloud", [
7381
7448
  "firestore",
@@ -7391,7 +7458,8 @@ const initFirebaseUser = async (cwd, preferredProjectId, nonInteractive = false,
7391
7458
  */
7392
7459
  input: "N\n"
7393
7460
  });
7394
- if (JSON.parse(databases.stdout).length === 0) {
7461
+ databaseIds = parseFirestoreDatabaseIds(JSON.parse(databases.stdout));
7462
+ if (databaseIds.length === 0) {
7395
7463
  _hot_updater_cli_tools.p.log.warning("Firestore Database not found");
7396
7464
  _hot_updater_cli_tools.p.log.step("Please enable Firestore in the Firebase Console:");
7397
7465
  _hot_updater_cli_tools.p.log.step((0, _hot_updater_cli_tools.link)(`https://console.firebase.google.com/project/${projectId}/firestore`));
@@ -7401,6 +7469,11 @@ const initFirebaseUser = async (cwd, preferredProjectId, nonInteractive = false,
7401
7469
  } catch (err) {
7402
7470
  handleError(err instanceof Error ? err : new Error(String(err)));
7403
7471
  }
7472
+ const databaseId = await resolveFirestoreDatabaseId({
7473
+ databaseIds,
7474
+ nonInteractive,
7475
+ savedDatabaseId: preferredDatabaseId
7476
+ });
7404
7477
  let storageBucket = null;
7405
7478
  await _hot_updater_cli_tools.p.tasks([{
7406
7479
  title: "Getting storage bucket...",
@@ -7440,6 +7513,7 @@ const initFirebaseUser = async (cwd, preferredProjectId, nonInteractive = false,
7440
7513
  }
7441
7514
  return {
7442
7515
  status: "ready",
7516
+ databaseId,
7443
7517
  storageBucket,
7444
7518
  projectNumber,
7445
7519
  projectId
@@ -7551,10 +7625,23 @@ const mergeIndexes = (originalIndexes, newIndexes) => {
7551
7625
  fieldOverrides: [...originalIndexes.fieldOverrides.filter((original) => !replaced(original)), ...newIndexes.fieldOverrides]
7552
7626
  };
7553
7627
  };
7554
- const deployFirestore = async (cwd, nonInteractive = false, cliEnv) => {
7628
+ /** Points the template's `firebase.json` at the database init sets up. */
7629
+ const setFirestoreDatabase = async (cwd, databaseId) => {
7630
+ const configPath = path.default.join(cwd, "firebase.json");
7631
+ const config = JSON.parse(await fs.default.promises.readFile(configPath, "utf-8"));
7632
+ config.firestore = {
7633
+ ...config.firestore,
7634
+ database: databaseId
7635
+ };
7636
+ await fs.default.promises.writeFile(configPath, JSON.stringify(config, null, 2));
7637
+ };
7638
+ const deployFirestore = async (cwd, databaseId, nonInteractive = false, cliEnv) => {
7639
+ await setFirestoreDatabase(cwd, databaseId);
7555
7640
  const original = await execa("npx", [
7556
7641
  "firebase",
7557
7642
  "firestore:indexes",
7643
+ "--database",
7644
+ databaseId,
7558
7645
  ...nonInteractive ? ["--non-interactive"] : []
7559
7646
  ], {
7560
7647
  cwd,
@@ -7674,10 +7761,11 @@ const runInit = async ({ build, envFile }) => {
7674
7761
  projectId
7675
7762
  });
7676
7763
  return cliEnv;
7677
- });
7764
+ }, savedInputs.databaseId);
7678
7765
  if (initializeVariable.status === "ready") {
7679
7766
  await assertFirebaseInfrastructureCanInitialize({
7680
7767
  applicationCredentials,
7768
+ databaseId: initializeVariable.databaseId,
7681
7769
  projectId: initializeVariable.projectId
7682
7770
  });
7683
7771
  await ensureCloudFunctionsApiEnabled(initializeVariable.projectId, cliEnv);
@@ -7693,6 +7781,7 @@ const runInit = async ({ build, envFile }) => {
7693
7781
  const resolvedInputs = {
7694
7782
  ...savedInputs,
7695
7783
  applicationCredentials: applicationCredentials || void 0,
7784
+ databaseId: initializeVariable.status === "ready" ? initializeVariable.databaseId : savedInputs.databaseId,
7696
7785
  projectId: initializeVariable.projectId,
7697
7786
  region: currentRegion
7698
7787
  };
@@ -7716,9 +7805,13 @@ const runInit = async ({ build, envFile }) => {
7716
7805
  await removeTmpDir();
7717
7806
  return;
7718
7807
  }
7719
- const functionsCode = (0, _hot_updater_cli_tools.transformEnv)(functionsIndexPath, { REGION: currentRegion });
7808
+ const functionsCode = (0, _hot_updater_cli_tools.transformEnv)(functionsIndexPath, {
7809
+ REGION: currentRegion,
7810
+ FIRESTORE_DATABASE_ID: initializeVariable.databaseId
7811
+ });
7720
7812
  await fs.default.promises.writeFile(functionsIndexPath, functionsCode);
7721
7813
  await setEnv({
7814
+ databaseId: initializeVariable.databaseId,
7722
7815
  projectId: initializeVariable.projectId,
7723
7816
  storageBucket: initializeVariable.storageBucket,
7724
7817
  build,
@@ -7739,11 +7832,12 @@ const runInit = async ({ build, envFile }) => {
7739
7832
  }
7740
7833
  }
7741
7834
  }]);
7742
- await deployFirestore(tmpDir, nonInteractive, cliEnv);
7835
+ await deployFirestore(tmpDir, initializeVariable.databaseId, nonInteractive, cliEnv);
7743
7836
  const credential = applicationCredentials ? (0, firebase_admin_app.cert)(JSON.parse(await fs.default.promises.readFile(applicationCredentials, "utf-8"))) : (0, firebase_admin_app.applicationDefault)();
7744
7837
  const existingApps = new Set((0, firebase_admin_app.getApps)());
7745
7838
  const databaseConfig = {
7746
7839
  credential,
7840
+ databaseId: initializeVariable.databaseId,
7747
7841
  projectId: initializeVariable.projectId
7748
7842
  };
7749
7843
  const database = firebaseDatabase(databaseConfig);
@@ -7820,3 +7914,4 @@ const runInit = async ({ build, envFile }) => {
7820
7914
  exports.initProvider = initProvider;
7821
7915
  exports.mergeIndexes = mergeIndexes;
7822
7916
  exports.runInit = runInit;
7917
+ exports.setFirestoreDatabase = setFirestoreDatabase;
@@ -22,6 +22,16 @@ declare const initProvider: {
22
22
  };
23
23
  readonly validate: (value: string | undefined) => value is string;
24
24
  };
25
+ readonly databaseId: {
26
+ readonly envKey: "HOT_UPDATER_FIREBASE_DATABASE_ID";
27
+ readonly help: "Firestore database ID";
28
+ readonly optional: true;
29
+ readonly prompt: {
30
+ readonly message: "Select a Firestore database";
31
+ readonly type: "select";
32
+ };
33
+ readonly validate: (value: string | undefined) => value is string;
34
+ };
25
35
  readonly applicationCredentials: {
26
36
  readonly envKey: "GOOGLE_APPLICATION_CREDENTIALS";
27
37
  readonly help: "Service account JSON path";
@@ -74,6 +84,8 @@ declare const mergeIndexes: (originalIndexes: {
74
84
  indexes: FirebaseIndex[];
75
85
  fieldOverrides: FieldOverride[];
76
86
  };
87
+ /** Points the template's `firebase.json` at the database init sets up. */
88
+ declare const setFirestoreDatabase: (cwd: string, databaseId: string) => Promise<void>;
77
89
  declare const runInit: ({ build, envFile }: RunInitOptions) => Promise<void>;
78
90
  //#endregion
79
- export { initProvider, mergeIndexes, runInit };
91
+ export { initProvider, mergeIndexes, runInit, setFirestoreDatabase };
@@ -22,6 +22,16 @@ declare const initProvider: {
22
22
  };
23
23
  readonly validate: (value: string | undefined) => value is string;
24
24
  };
25
+ readonly databaseId: {
26
+ readonly envKey: "HOT_UPDATER_FIREBASE_DATABASE_ID";
27
+ readonly help: "Firestore database ID";
28
+ readonly optional: true;
29
+ readonly prompt: {
30
+ readonly message: "Select a Firestore database";
31
+ readonly type: "select";
32
+ };
33
+ readonly validate: (value: string | undefined) => value is string;
34
+ };
25
35
  readonly applicationCredentials: {
26
36
  readonly envKey: "GOOGLE_APPLICATION_CREDENTIALS";
27
37
  readonly help: "Service account JSON path";
@@ -74,6 +84,8 @@ declare const mergeIndexes: (originalIndexes: {
74
84
  indexes: FirebaseIndex[];
75
85
  fieldOverrides: FieldOverride[];
76
86
  };
87
+ /** Points the template's `firebase.json` at the database init sets up. */
88
+ declare const setFirestoreDatabase: (cwd: string, databaseId: string) => Promise<void>;
77
89
  declare const runInit: ({ build, envFile }: RunInitOptions) => Promise<void>;
78
90
  //#endregion
79
- export { initProvider, mergeIndexes, runInit };
91
+ export { initProvider, mergeIndexes, runInit, setFirestoreDatabase };
@@ -6857,10 +6857,11 @@ const createFirestoreStore = ({ firestore, collection, nativePageSize }) => {
6857
6857
  };
6858
6858
  //#endregion
6859
6859
  //#region src/firebaseDatabase.ts
6860
- const adapterOf = ({ collection = FIREBASE_V1_COLLECTION, aggregateBatching: _, ...appOptions }) => {
6860
+ const adapterOf = ({ databaseId, collection = FIREBASE_V1_COLLECTION, aggregateBatching: _, ...appOptions }) => {
6861
6861
  const app = getApps().length ? getApp() : initializeApp(appOptions);
6862
+ const firestore = databaseId ? getFirestore(app, databaseId) : getFirestore(app);
6862
6863
  return createKvAdapter({ store: createFirestoreStore({
6863
- firestore: getFirestore(app),
6864
+ firestore,
6864
6865
  collection
6865
6866
  }) });
6866
6867
  };
@@ -6881,6 +6882,8 @@ const firebaseDatabase = (config) => createEngineDatabase({
6881
6882
  //#region iac/init/index.ts
6882
6883
  const isFirebaseRegion = (value) => value !== void 0 && /^[a-z]+(?:-[a-z]+)+[0-9]+$/.test(value);
6883
6884
  const isFirebaseProjectId = (value) => value !== void 0 && /^[a-z][a-z0-9-]{4,28}[a-z0-9]$/.test(value);
6885
+ /** `(default)`, or a named database's ID: 4-63 lowercase letters, digits, or hyphens. */
6886
+ const isFirestoreDatabaseId = (value) => value !== void 0 && (value === "(default)" || /^[a-z][a-z0-9-]{2,61}[a-z0-9]$/.test(value));
6884
6887
  const initProvider = {
6885
6888
  label: "Firebase",
6886
6889
  inputs: {
@@ -6903,6 +6906,16 @@ const initProvider = {
6903
6906
  },
6904
6907
  validate: isFirebaseRegion
6905
6908
  },
6909
+ databaseId: {
6910
+ envKey: "HOT_UPDATER_FIREBASE_DATABASE_ID",
6911
+ help: "Firestore database ID",
6912
+ optional: true,
6913
+ prompt: {
6914
+ message: "Select a Firestore database",
6915
+ type: "select"
6916
+ },
6917
+ validate: isFirestoreDatabaseId
6918
+ },
6906
6919
  applicationCredentials: {
6907
6920
  envKey: "GOOGLE_APPLICATION_CREDENTIALS",
6908
6921
  help: "Service account JSON path",
@@ -6947,15 +6960,15 @@ const firebaseEngineSettingDocumentId = () => firestoreDocumentId({
6947
6960
  pk: SETTINGS_TABLE.name,
6948
6961
  sk: encodeKvKey([ENGINE_SETTING])
6949
6962
  });
6950
- const assertFirebaseInfrastructureCanInitialize = async ({ applicationCredentials, projectId }) => {
6963
+ const assertFirebaseInfrastructureCanInitialize = async ({ applicationCredentials, databaseId, projectId }) => {
6951
6964
  const credential = applicationCredentials ? cert(JSON.parse(await fs$1.readFile(applicationCredentials, "utf8"))) : applicationDefault();
6952
6965
  const app = initializeApp({
6953
6966
  credential,
6954
6967
  projectId
6955
6968
  }, `hot-updater-init-${projectId}-${Date.now()}`);
6956
6969
  try {
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.`);
6970
+ const setting = await getFirestore(app, databaseId).collection(FIREBASE_V1_COLLECTION).doc(firebaseEngineSettingDocumentId()).get();
6971
+ if (resolveFirebaseInfrastructureState({ engine: setting.exists ? setting.get("row.value") : void 0 }) === "incompatible") throw new InitError(`Firebase v1 infrastructure in project ${projectId} (Firestore database ${databaseId}) records an unsupported database version. Use a new Firebase project, or delete the ${FIREBASE_V1_COLLECTION} collection, then rerun init.`);
6959
6972
  } catch (error) {
6960
6973
  if (error instanceof InitError) throw error;
6961
6974
  const code = typeof error === "object" && error !== null ? Reflect.get(error, "code") : void 0;
@@ -6999,6 +7012,7 @@ const resolveFirebaseInitInputs = (existingEnv) => {
6999
7012
  const applicationCredentials = resolveInitProviderInput(existingEnv, inputs.applicationCredentials);
7000
7013
  return {
7001
7014
  applicationCredentials: applicationCredentials === "your-credentials.json" ? void 0 : applicationCredentials,
7015
+ databaseId: resolveInitProviderInput(existingEnv, inputs.databaseId),
7002
7016
  projectId: resolveInitProviderInput(existingEnv, inputs.projectId),
7003
7017
  region: resolveInitProviderInput(existingEnv, inputs.region)
7004
7018
  };
@@ -7191,6 +7205,7 @@ const getConfigScaffold = (build) => {
7191
7205
  }],
7192
7206
  configString: `firebaseDatabase({
7193
7207
  projectId: process.env.HOT_UPDATER_FIREBASE_PROJECT_ID!,
7208
+ databaseId: process.env.HOT_UPDATER_FIREBASE_DATABASE_ID,
7194
7209
  credential,
7195
7210
  })`
7196
7211
  };
@@ -7221,8 +7236,58 @@ const credential = applicationDefault();`.trim()
7221
7236
  return createHotUpdaterConfigScaffoldFromBuilder(builder, { helperStatements });
7222
7237
  };
7223
7238
  //#endregion
7239
+ //#region iac/firestoreDatabase.ts
7240
+ const FIRESTORE_DEFAULT_DATABASE_ID = "(default)";
7241
+ /**
7242
+ * The IDs of a project's databases the Firestore API can use, from
7243
+ * `gcloud firestore databases list --format=json`, whose names are
7244
+ * `projects/<project>/databases/<id>`: not Datastore mode, and not an
7245
+ * Enterprise database with Firestore data access disabled, the default for
7246
+ * MongoDB-compatible ones.
7247
+ */
7248
+ const parseFirestoreDatabaseIds = (databases) => Array.isArray(databases) ? databases.flatMap((database) => {
7249
+ if (typeof database !== "object" || database === null) return [];
7250
+ const name = Reflect.get(database, "name");
7251
+ if (typeof name !== "string" || Reflect.get(database, "type") === "DATASTORE_MODE" || Reflect.get(database, "firestoreDataAccessMode") === "DATA_ACCESS_MODE_DISABLED") return [];
7252
+ return [name.slice(name.lastIndexOf("/") + 1)];
7253
+ }) : [];
7254
+ /**
7255
+ * The database init sets up: the saved one, the only one, or the one the
7256
+ * user selects. Without a saved one, non-interactive init keeps `(default)`
7257
+ * and needs `HOT_UPDATER_FIREBASE_DATABASE_ID` only to choose among named
7258
+ * databases.
7259
+ */
7260
+ const resolveFirestoreDatabaseId = async ({ databaseIds, nonInteractive, savedDatabaseId }) => {
7261
+ const input = initProvider.inputs.databaseId;
7262
+ if (savedDatabaseId !== void 0) {
7263
+ if (databaseIds.includes(savedDatabaseId)) return savedDatabaseId;
7264
+ if (nonInteractive) throw new MissingInitInputsError([input.envKey]);
7265
+ p.log.warn(`Saved Firestore database ${savedDatabaseId} was not found. Select a database again.`);
7266
+ }
7267
+ const [onlyDatabaseId] = databaseIds;
7268
+ if (databaseIds.length === 1 && onlyDatabaseId !== void 0) return onlyDatabaseId;
7269
+ const hasDefault = databaseIds.includes(FIRESTORE_DEFAULT_DATABASE_ID);
7270
+ if (nonInteractive) {
7271
+ if (hasDefault) return FIRESTORE_DEFAULT_DATABASE_ID;
7272
+ throw new MissingInitInputsError([input.envKey]);
7273
+ }
7274
+ const selected = await p.select({
7275
+ initialValue: hasDefault ? FIRESTORE_DEFAULT_DATABASE_ID : onlyDatabaseId,
7276
+ message: input.prompt.message,
7277
+ options: databaseIds.map((databaseId) => ({
7278
+ value: databaseId,
7279
+ label: databaseId
7280
+ }))
7281
+ });
7282
+ if (p.isCancel(selected)) {
7283
+ p.cancel("Operation cancelled.");
7284
+ process.exit(1);
7285
+ }
7286
+ return selected;
7287
+ };
7288
+ //#endregion
7224
7289
  //#region iac/select.ts
7225
- const setEnv = async ({ applicationCredentials, projectId, storageBucket, build, region }) => {
7290
+ const setEnv = async ({ applicationCredentials, databaseId, projectId, storageBucket, build, region }) => {
7226
7291
  await makeEnv({
7227
7292
  [initProvider.inputs.applicationCredentials.envKey]: {
7228
7293
  comment: "Project Settings > Service Accounts > New Private Key > Download JSON",
@@ -7230,6 +7295,7 @@ const setEnv = async ({ applicationCredentials, projectId, storageBucket, build,
7230
7295
  },
7231
7296
  [initProvider.inputs.projectId.envKey]: projectId,
7232
7297
  [initProvider.inputs.region.envKey]: region,
7298
+ [initProvider.inputs.databaseId.envKey]: databaseId,
7233
7299
  HOT_UPDATER_FIREBASE_STORAGE_BUCKET: storageBucket
7234
7300
  }, ".env.hotupdater", { preserveKeys: applicationCredentials ? [] : [initProvider.inputs.applicationCredentials.envKey] });
7235
7301
  p.log.success("Firebase credentials have been successfully configured.");
@@ -7279,7 +7345,7 @@ const listProjects = async (nonInteractive = false, cliEnv) => {
7279
7345
  throw error;
7280
7346
  }
7281
7347
  };
7282
- const initFirebaseUser = async (cwd, preferredProjectId, nonInteractive = false, cliEnv, resolveCliEnv) => {
7348
+ const initFirebaseUser = async (cwd, preferredProjectId, nonInteractive = false, cliEnv, resolveCliEnv, preferredDatabaseId) => {
7283
7349
  if (!cliEnv) try {
7284
7350
  const authList = await execa("gcloud", [
7285
7351
  "auth",
@@ -7369,6 +7435,7 @@ const initFirebaseUser = async (cwd, preferredProjectId, nonInteractive = false,
7369
7435
  }
7370
7436
  }
7371
7437
  }]);
7438
+ let databaseIds = [];
7372
7439
  try {
7373
7440
  const databases = await execa("gcloud", [
7374
7441
  "firestore",
@@ -7384,7 +7451,8 @@ const initFirebaseUser = async (cwd, preferredProjectId, nonInteractive = false,
7384
7451
  */
7385
7452
  input: "N\n"
7386
7453
  });
7387
- if (JSON.parse(databases.stdout).length === 0) {
7454
+ databaseIds = parseFirestoreDatabaseIds(JSON.parse(databases.stdout));
7455
+ if (databaseIds.length === 0) {
7388
7456
  p.log.warning("Firestore Database not found");
7389
7457
  p.log.step("Please enable Firestore in the Firebase Console:");
7390
7458
  p.log.step(link(`https://console.firebase.google.com/project/${projectId}/firestore`));
@@ -7394,6 +7462,11 @@ const initFirebaseUser = async (cwd, preferredProjectId, nonInteractive = false,
7394
7462
  } catch (err) {
7395
7463
  handleError(err instanceof Error ? err : new Error(String(err)));
7396
7464
  }
7465
+ const databaseId = await resolveFirestoreDatabaseId({
7466
+ databaseIds,
7467
+ nonInteractive,
7468
+ savedDatabaseId: preferredDatabaseId
7469
+ });
7397
7470
  let storageBucket = null;
7398
7471
  await p.tasks([{
7399
7472
  title: "Getting storage bucket...",
@@ -7433,6 +7506,7 @@ const initFirebaseUser = async (cwd, preferredProjectId, nonInteractive = false,
7433
7506
  }
7434
7507
  return {
7435
7508
  status: "ready",
7509
+ databaseId,
7436
7510
  storageBucket,
7437
7511
  projectNumber,
7438
7512
  projectId
@@ -7544,10 +7618,23 @@ const mergeIndexes = (originalIndexes, newIndexes) => {
7544
7618
  fieldOverrides: [...originalIndexes.fieldOverrides.filter((original) => !replaced(original)), ...newIndexes.fieldOverrides]
7545
7619
  };
7546
7620
  };
7547
- const deployFirestore = async (cwd, nonInteractive = false, cliEnv) => {
7621
+ /** Points the template's `firebase.json` at the database init sets up. */
7622
+ const setFirestoreDatabase = async (cwd, databaseId) => {
7623
+ const configPath = path.join(cwd, "firebase.json");
7624
+ const config = JSON.parse(await fs.promises.readFile(configPath, "utf-8"));
7625
+ config.firestore = {
7626
+ ...config.firestore,
7627
+ database: databaseId
7628
+ };
7629
+ await fs.promises.writeFile(configPath, JSON.stringify(config, null, 2));
7630
+ };
7631
+ const deployFirestore = async (cwd, databaseId, nonInteractive = false, cliEnv) => {
7632
+ await setFirestoreDatabase(cwd, databaseId);
7548
7633
  const original = await execa("npx", [
7549
7634
  "firebase",
7550
7635
  "firestore:indexes",
7636
+ "--database",
7637
+ databaseId,
7551
7638
  ...nonInteractive ? ["--non-interactive"] : []
7552
7639
  ], {
7553
7640
  cwd,
@@ -7667,10 +7754,11 @@ const runInit = async ({ build, envFile }) => {
7667
7754
  projectId
7668
7755
  });
7669
7756
  return cliEnv;
7670
- });
7757
+ }, savedInputs.databaseId);
7671
7758
  if (initializeVariable.status === "ready") {
7672
7759
  await assertFirebaseInfrastructureCanInitialize({
7673
7760
  applicationCredentials,
7761
+ databaseId: initializeVariable.databaseId,
7674
7762
  projectId: initializeVariable.projectId
7675
7763
  });
7676
7764
  await ensureCloudFunctionsApiEnabled(initializeVariable.projectId, cliEnv);
@@ -7686,6 +7774,7 @@ const runInit = async ({ build, envFile }) => {
7686
7774
  const resolvedInputs = {
7687
7775
  ...savedInputs,
7688
7776
  applicationCredentials: applicationCredentials || void 0,
7777
+ databaseId: initializeVariable.status === "ready" ? initializeVariable.databaseId : savedInputs.databaseId,
7689
7778
  projectId: initializeVariable.projectId,
7690
7779
  region: currentRegion
7691
7780
  };
@@ -7709,9 +7798,13 @@ const runInit = async ({ build, envFile }) => {
7709
7798
  await removeTmpDir();
7710
7799
  return;
7711
7800
  }
7712
- const functionsCode = transformEnv(functionsIndexPath, { REGION: currentRegion });
7801
+ const functionsCode = transformEnv(functionsIndexPath, {
7802
+ REGION: currentRegion,
7803
+ FIRESTORE_DATABASE_ID: initializeVariable.databaseId
7804
+ });
7713
7805
  await fs.promises.writeFile(functionsIndexPath, functionsCode);
7714
7806
  await setEnv({
7807
+ databaseId: initializeVariable.databaseId,
7715
7808
  projectId: initializeVariable.projectId,
7716
7809
  storageBucket: initializeVariable.storageBucket,
7717
7810
  build,
@@ -7732,11 +7825,12 @@ const runInit = async ({ build, envFile }) => {
7732
7825
  }
7733
7826
  }
7734
7827
  }]);
7735
- await deployFirestore(tmpDir, nonInteractive, cliEnv);
7828
+ await deployFirestore(tmpDir, initializeVariable.databaseId, nonInteractive, cliEnv);
7736
7829
  const credential = applicationCredentials ? cert(JSON.parse(await fs.promises.readFile(applicationCredentials, "utf-8"))) : applicationDefault();
7737
7830
  const existingApps = new Set(getApps());
7738
7831
  const databaseConfig = {
7739
7832
  credential,
7833
+ databaseId: initializeVariable.databaseId,
7740
7834
  projectId: initializeVariable.projectId
7741
7835
  };
7742
7836
  const database = firebaseDatabase(databaseConfig);
@@ -7810,4 +7904,4 @@ const runInit = async ({ build, envFile }) => {
7810
7904
  p.log.success("Done! 🎉");
7811
7905
  };
7812
7906
  //#endregion
7813
- export { initProvider, mergeIndexes, runInit };
7907
+ export { initProvider, mergeIndexes, runInit, setFirestoreDatabase };
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.47",
4
+ "version": "1.0.1",
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/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"
40
+ "@hot-updater/cli-tools": "1.0.1",
41
+ "@hot-updater/server": "1.0.1",
42
+ "@hot-updater/plugin-core": "1.0.1",
43
+ "@hot-updater/protocol": "1.0.1"
44
44
  },
45
45
  "publishConfig": {
46
46
  "access": "public"
@@ -55,11 +55,11 @@
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.47",
59
- "@hot-updater/test-utils": "1.0.0-rc.47"
58
+ "@hot-updater/mock": "1.0.1",
59
+ "@hot-updater/test-utils": "1.0.1"
60
60
  },
61
61
  "peerDependencies": {
62
- "firebase-admin": "*",
62
+ "firebase-admin": ">=11.10.0",
63
63
  "firebase-functions": "*"
64
64
  },
65
65
  "peerDependenciesMeta": {