@hot-updater/firebase 1.0.0-rc.0 → 1.0.0-rc.2

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.
package/dist/index.mjs CHANGED
@@ -1,10 +1,11 @@
1
- import { createDatabasePlugin, createStorageKeyBuilder, createStoragePlugin, createStorageUri, isDatabaseMetadataObject, parseStorageUri } from "@hot-updater/plugin-core";
1
+ import { compareInsightsText, createDatabasePlugin, createStorageKeyBuilder, createStoragePlugin, createStorageUri, isDatabaseMetadataObject, parseStorageUri } from "@hot-updater/plugin-core";
2
2
  import { createDatabasePluginAdapter } from "@hot-updater/plugin-core/internal";
3
3
  import { getApp, getApps, initializeApp } from "firebase-admin/app";
4
4
  import { getFirestore } from "firebase-admin/firestore";
5
5
  import { getStorage } from "firebase-admin/storage";
6
6
  //#region src/firebaseDatabaseParserShared.ts
7
7
  var FirebaseDatabaseDataError = class extends Error {
8
+ source;
8
9
  name = "FirebaseDatabaseDataError";
9
10
  constructor(source) {
10
11
  super(`Invalid Firebase database data at "${source}".`);
@@ -116,6 +117,24 @@ const parseFirebaseBundleEventRow = (value, source) => {
116
117
  received_at_ms: number(property(input, "received_at_ms"), source)
117
118
  };
118
119
  };
120
+ const parseFirebaseInsightsInstallationRow = (value, source) => {
121
+ const input = record(value, source);
122
+ const type = string(property(input, "type"), source);
123
+ if (type !== "UPDATE_APPLIED" && type !== "RECOVERED" && type !== "RELEASE_ADOPTED" && type !== "UNCHANGED") throw new FirebaseDatabaseDataError(source);
124
+ return {
125
+ id: string(property(input, "id"), source),
126
+ type,
127
+ install_id: string(property(input, "install_id"), source),
128
+ user_id: nullableString(property(input, "user_id"), source),
129
+ username: nullableString(property(input, "username"), source),
130
+ to_bundle_id: string(property(input, "to_bundle_id"), source),
131
+ platform: platform(property(input, "platform"), source),
132
+ app_version: string(property(input, "app_version"), source),
133
+ channel: string(property(input, "channel"), source),
134
+ cohort: string(property(input, "cohort"), source),
135
+ received_at_ms: number(property(input, "received_at_ms"), source)
136
+ };
137
+ };
119
138
  const parseFirebaseApiKeyRow = (value, source) => {
120
139
  const input = record(value, source);
121
140
  const role = string(property(input, "role"), source);
@@ -196,13 +215,15 @@ const matchesCondition = (row, condition) => {
196
215
  switch (condition.operator ?? "eq") {
197
216
  case "eq": {
198
217
  if (typeof expected !== "string") return actual === expected;
199
- const comparison = normalizeStringComparison(actual, expected, "mode" in condition ? condition.mode : void 0);
218
+ const mode = "mode" in condition ? condition.mode : void 0;
219
+ const comparison = normalizeStringComparison(actual, expected, mode);
200
220
  return comparison !== null && comparison[0] === comparison[1];
201
221
  }
202
222
  case "ne": {
203
223
  if (actual === null || actual === void 0) return false;
204
224
  if (typeof expected !== "string") return actual !== expected;
205
- const comparison = normalizeStringComparison(actual, expected, "mode" in condition ? condition.mode : void 0);
225
+ const mode = "mode" in condition ? condition.mode : void 0;
226
+ const comparison = normalizeStringComparison(actual, expected, mode);
206
227
  return comparison === null || comparison[0] !== comparison[1];
207
228
  }
208
229
  case "gt":
@@ -227,17 +248,20 @@ const matchesCondition = (row, condition) => {
227
248
  }
228
249
  case "contains": {
229
250
  if (typeof expected !== "string") return false;
230
- const comparison = normalizeStringComparison(actual, expected, "mode" in condition ? condition.mode : void 0);
251
+ const mode = "mode" in condition ? condition.mode : void 0;
252
+ const comparison = normalizeStringComparison(actual, expected, mode);
231
253
  return comparison?.[0].includes(comparison[1]) ?? false;
232
254
  }
233
255
  case "starts_with": {
234
256
  if (typeof expected !== "string") return false;
235
- const comparison = normalizeStringComparison(actual, expected, "mode" in condition ? condition.mode : void 0);
257
+ const mode = "mode" in condition ? condition.mode : void 0;
258
+ const comparison = normalizeStringComparison(actual, expected, mode);
236
259
  return comparison?.[0].startsWith(comparison[1]) ?? false;
237
260
  }
238
261
  case "ends_with": {
239
262
  if (typeof expected !== "string") return false;
240
- const comparison = normalizeStringComparison(actual, expected, "mode" in condition ? condition.mode : void 0);
263
+ const mode = "mode" in condition ? condition.mode : void 0;
264
+ const comparison = normalizeStringComparison(actual, expected, mode);
241
265
  return comparison?.[0].endsWith(comparison[1]) ?? false;
242
266
  }
243
267
  }
@@ -282,6 +306,7 @@ const queryFirebaseDatabaseRows = (rows, input) => {
282
306
  //#endregion
283
307
  //#region src/firebaseDatabaseState.ts
284
308
  var FirebaseDatabaseConstraintError = class extends Error {
309
+ constraint;
285
310
  name = "FirebaseDatabaseConstraintError";
286
311
  constructor(constraint) {
287
312
  super(`Firebase database constraint failed: ${constraint}`);
@@ -292,6 +317,7 @@ const cloneFirebaseDatabaseSnapshot = (snapshot) => ({
292
317
  bundles: new Map(snapshot.bundles),
293
318
  bundlePatches: new Map(snapshot.bundlePatches),
294
319
  bundleEvents: new Map(snapshot.bundleEvents),
320
+ bundleInstallations: new Map(snapshot.bundleInstallations),
295
321
  channels: new Map(snapshot.channels),
296
322
  apiKeys: new Map(snapshot.apiKeys),
297
323
  releaseCatalogs: new Map(snapshot.releaseCatalogs),
@@ -321,6 +347,13 @@ const createFirebaseDatabaseState = (snapshot) => ({
321
347
  requireUnique(snapshot.bundleEvents, input.data.id, input.model);
322
348
  snapshot.bundleEvents.set(input.data.id, input.data);
323
349
  return input.data;
350
+ case "bundle_installations": {
351
+ const current = snapshot.bundleInstallations.get(input.data.install_id);
352
+ if (current && input.onConflict === "ignore") return current;
353
+ if (current) throw new FirebaseDatabaseConstraintError("bundle_installations.install_id.unique");
354
+ snapshot.bundleInstallations.set(input.data.install_id, input.data);
355
+ return input.data;
356
+ }
324
357
  case "releases":
325
358
  requireUnique(snapshot.releases, input.data.id, input.model);
326
359
  if (!snapshot.channels.has(input.data.channel_id)) throw new FirebaseDatabaseConstraintError("releases.channel_id.foreign-key");
@@ -350,6 +383,16 @@ const createFirebaseDatabaseState = (snapshot) => ({
350
383
  }
351
384
  },
352
385
  async update(input) {
386
+ if (input.model === "bundle_installations") {
387
+ const current = [...snapshot.bundleInstallations.values()].find((row) => matchesFirebaseDatabaseWhere(row, input.where));
388
+ if (!current) return null;
389
+ const updated = {
390
+ ...current,
391
+ ...input.update
392
+ };
393
+ snapshot.bundleInstallations.set(current.install_id, updated);
394
+ return updated;
395
+ }
353
396
  if (input.model === "api_keys") {
354
397
  const current = [...snapshot.apiKeys.values()].find((row) => matchesFirebaseDatabaseWhere(row, input.where));
355
398
  if (!current) return null;
@@ -411,6 +454,8 @@ const createFirebaseDatabaseState = (snapshot) => ({
411
454
  case "bundles": return distinctCount([...snapshot.bundles.values()].filter((row) => matchesFirebaseDatabaseWhere(row, input.where)), input.distinct);
412
455
  case "bundle_patches": return distinctCount([...snapshot.bundlePatches.values()].filter((row) => matchesFirebaseDatabaseWhere(row, input.where)), input.distinct);
413
456
  case "releases": return distinctCount([...snapshot.releases.values()].filter((row) => matchesFirebaseDatabaseWhere(row, input.where)), input.distinct);
457
+ case "bundle_installations": return distinctCount([...snapshot.bundleInstallations.values()].filter((row) => matchesFirebaseDatabaseWhere(row, input.where)), input.distinct);
458
+ case "bundle_events": return distinctCount([...snapshot.bundleEvents.values()].filter((row) => matchesFirebaseDatabaseWhere(row, input.where)), input.distinct);
414
459
  }
415
460
  },
416
461
  async findOne(input) {
@@ -421,6 +466,7 @@ const createFirebaseDatabaseState = (snapshot) => ({
421
466
  case "bundle_patches": return [...snapshot.bundlePatches.values()].find((row) => matchesFirebaseDatabaseWhere(row, input.where)) ?? null;
422
467
  case "releases": return [...snapshot.releases.values()].find((row) => matchesFirebaseDatabaseWhere(row, input.where)) ?? null;
423
468
  case "release_catalogs": return [...snapshot.releaseCatalogs.values()].find((row) => matchesFirebaseDatabaseWhere(row, input.where)) ?? null;
469
+ case "bundle_installations": return [...snapshot.bundleInstallations.values()].find((row) => matchesFirebaseDatabaseWhere(row, input.where)) ?? null;
424
470
  }
425
471
  },
426
472
  async findMany(input) {
@@ -428,6 +474,7 @@ const createFirebaseDatabaseState = (snapshot) => ({
428
474
  case "bundles": return queryFirebaseDatabaseRows([...snapshot.bundles.values()], input);
429
475
  case "bundle_patches": return queryFirebaseDatabaseRows([...snapshot.bundlePatches.values()], input);
430
476
  case "bundle_events": return queryFirebaseDatabaseRows([...snapshot.bundleEvents.values()], input);
477
+ case "bundle_installations": return queryFirebaseDatabaseRows([...snapshot.bundleInstallations.values()], input);
431
478
  case "channels": return queryFirebaseDatabaseRows([...snapshot.channels.values()], input);
432
479
  case "api_keys": return queryFirebaseDatabaseRows([...snapshot.apiKeys.values()], input);
433
480
  case "releases": return queryFirebaseDatabaseRows([...snapshot.releases.values()], input);
@@ -440,6 +487,7 @@ const createFirebaseDatabaseState = (snapshot) => ({
440
487
  const FIREBASE_V1_COLLECTION_NAMES = {
441
488
  apiKeys: "hot_updater_v1_api_keys",
442
489
  bundleEvents: "hot_updater_v1_bundle_events",
490
+ bundleInstallations: "hot_updater_v1_bundle_installations",
443
491
  bundlePatches: "hot_updater_v1_bundle_patches",
444
492
  bundles: "hot_updater_v1_bundles",
445
493
  channels: "hot_updater_v1_channels",
@@ -450,6 +498,7 @@ const FIREBASE_V1_COLLECTION_NAMES = {
450
498
  //#endregion
451
499
  //#region src/firebaseDatabasePersistence.ts
452
500
  var FirebaseDatabaseAdapterVersionError = class extends Error {
501
+ version;
453
502
  name = "FirebaseDatabaseAdapterVersionError";
454
503
  constructor(version) {
455
504
  super(`Unsupported Firebase database adapter version: ${String(version)}`);
@@ -460,6 +509,7 @@ const createFirebaseDatabaseCollections = (db) => ({
460
509
  bundles: db.collection(FIREBASE_V1_COLLECTION_NAMES.bundles),
461
510
  bundlePatches: db.collection(FIREBASE_V1_COLLECTION_NAMES.bundlePatches),
462
511
  bundleEvents: db.collection(FIREBASE_V1_COLLECTION_NAMES.bundleEvents),
512
+ bundleInstallations: db.collection(FIREBASE_V1_COLLECTION_NAMES.bundleInstallations),
463
513
  channels: db.collection(FIREBASE_V1_COLLECTION_NAMES.channels),
464
514
  apiKeys: db.collection(FIREBASE_V1_COLLECTION_NAMES.apiKeys),
465
515
  releaseCatalogs: db.collection(FIREBASE_V1_COLLECTION_NAMES.releaseCatalogs),
@@ -468,14 +518,15 @@ const createFirebaseDatabaseCollections = (db) => ({
468
518
  });
469
519
  const firebaseChannelDocumentId = (name) => `name_${Buffer.from(name, "utf8").toString("base64url")}`;
470
520
  const firebaseChannelIdDocumentId = (id) => `channel_id_${Buffer.from(id, "utf8").toString("base64url")}`;
521
+ const firebaseInstallationDocumentId = (id) => `install_${Buffer.from(id, "utf8").toString("base64url")}`;
471
522
  const requireFirebaseDocumentKey = (model, documentId, row) => {
472
- if (documentId !== ("id" in row ? row.id : row.scope_key)) throw new FirebaseDatabaseConstraintError(`${model}.id.document-key`);
523
+ if (documentId !== (model === "bundle_installations" ? firebaseInstallationDocumentId(row.install_id) : "id" in row ? row.id : row.scope_key)) throw new FirebaseDatabaseConstraintError(`${model}.id.document-key`);
473
524
  return row;
474
525
  };
475
526
  const documentMap = (model, documents) => {
476
527
  const rows = /* @__PURE__ */ new Map();
477
528
  for (const { row } of documents) {
478
- const key = "id" in row ? row.id : row.scope_key;
529
+ const key = model === "bundle_installations" ? row.install_id : "id" in row ? row.id : row.scope_key;
479
530
  if (rows.has(key)) throw new FirebaseDatabaseConstraintError(`${model}.id.unique`);
480
531
  rows.set(key, row);
481
532
  }
@@ -490,10 +541,6 @@ const patchMap = (snapshot) => documentMap("bundle_patches", snapshot.docs.map((
490
541
  document,
491
542
  row: parseFirebasePatchRow(document.data(), `bundle_patches/${document.id}`)
492
543
  })));
493
- const eventMap = (snapshot) => documentMap("bundle_events", snapshot.docs.map((document) => ({
494
- document,
495
- row: parseFirebaseBundleEventRow(document.data(), `bundle_events/${document.id}`)
496
- })));
497
544
  const channelMap = (snapshot) => {
498
545
  const rows = /* @__PURE__ */ new Map();
499
546
  const names = /* @__PURE__ */ new Set();
@@ -524,18 +571,18 @@ const toSnapshot = (documents) => {
524
571
  return {
525
572
  bundles: bundleMap(documents[0]),
526
573
  bundlePatches: patchMap(documents[1]),
527
- bundleEvents: eventMap(documents[2]),
528
- channels: channelMap(documents[3]),
529
- apiKeys: apiKeyMap(documents[4]),
530
- releases: releaseMap(documents[5]),
531
- releaseCatalogs: releaseCatalogMap(documents[6])
574
+ bundleEvents: /* @__PURE__ */ new Map(),
575
+ bundleInstallations: /* @__PURE__ */ new Map(),
576
+ channels: channelMap(documents[2]),
577
+ apiKeys: apiKeyMap(documents[3]),
578
+ releases: releaseMap(documents[4]),
579
+ releaseCatalogs: releaseCatalogMap(documents[5])
532
580
  };
533
581
  };
534
582
  const loadFirebaseDatabaseSnapshot = async (collections) => {
535
- const [bundles, patches, events, channels, apiKeys, releases, releaseCatalogs] = await Promise.all([
583
+ const [bundles, patches, channels, apiKeys, releases, releaseCatalogs] = await Promise.all([
536
584
  collections.bundles.get(),
537
585
  collections.bundlePatches.get(),
538
- collections.bundleEvents.get(),
539
586
  collections.channels.get(),
540
587
  collections.apiKeys.get(),
541
588
  collections.releases.get(),
@@ -544,7 +591,6 @@ const loadFirebaseDatabaseSnapshot = async (collections) => {
544
591
  return toSnapshot([
545
592
  bundles,
546
593
  patches,
547
- events,
548
594
  channels,
549
595
  apiKeys,
550
596
  releases,
@@ -552,10 +598,9 @@ const loadFirebaseDatabaseSnapshot = async (collections) => {
552
598
  ]);
553
599
  };
554
600
  const loadFirebaseTransactionSnapshot = async (transaction, collections) => {
555
- const [bundles, patches, events, channels, apiKeys, releases, releaseCatalogs] = await Promise.all([
601
+ const [bundles, patches, channels, apiKeys, releases, releaseCatalogs] = await Promise.all([
556
602
  transaction.get(collections.bundles),
557
603
  transaction.get(collections.bundlePatches),
558
- transaction.get(collections.bundleEvents),
559
604
  transaction.get(collections.channels),
560
605
  transaction.get(collections.apiKeys),
561
606
  transaction.get(collections.releases),
@@ -564,7 +609,6 @@ const loadFirebaseTransactionSnapshot = async (transaction, collections) => {
564
609
  return toSnapshot([
565
610
  bundles,
566
611
  patches,
567
- events,
568
612
  channels,
569
613
  apiKeys,
570
614
  releases,
@@ -590,13 +634,6 @@ const persistFirebaseDatabaseSnapshot = ({ transaction, collections, before, aft
590
634
  after: after.bundlePatches,
591
635
  documentId: (row) => row.id
592
636
  });
593
- persistCollection({
594
- transaction,
595
- collection: collections.bundleEvents,
596
- before: before.bundleEvents,
597
- after: after.bundleEvents,
598
- documentId: (row) => row.id
599
- });
600
637
  persistCollection({
601
638
  transaction,
602
639
  collection: collections.channels,
@@ -628,7 +665,7 @@ const persistFirebaseDatabaseSnapshot = ({ transaction, collections, before, aft
628
665
  documentId: (row) => row.scope_key
629
666
  });
630
667
  };
631
- const migrateFirebaseDatabase = async (_db, collections) => {
668
+ const migrateFirebaseDatabase = async (collections) => {
632
669
  const versionDocument = collections.settings.doc("database_adapter_version");
633
670
  const version = await versionDocument.get();
634
671
  const adapterVersion = version.data()?.version;
@@ -639,7 +676,9 @@ const migrateFirebaseDatabase = async (_db, collections) => {
639
676
  collections.bundlePatches.limit(1).get(),
640
677
  collections.channels.limit(1).get(),
641
678
  collections.releases.limit(1).get(),
642
- collections.releaseCatalogs.limit(1).get()
679
+ collections.releaseCatalogs.limit(1).get(),
680
+ collections.bundleInstallations.limit(1).get(),
681
+ collections.bundleEvents.limit(1).get()
643
682
  ])).some((snapshot) => !snapshot.empty)) throw new FirebaseDatabaseAdapterVersionError("v0");
644
683
  try {
645
684
  await versionDocument.create({ version: 4 });
@@ -654,13 +693,40 @@ const exactId = (input) => {
654
693
  const [condition] = input.where;
655
694
  return condition.field === "id" && (condition.operator === void 0 || condition.operator === "eq") && typeof condition.value === "string" ? condition.value : void 0;
656
695
  };
696
+ const exactInstallId = (input) => {
697
+ const condition = input.where?.find(({ field, operator }) => field === "install_id" && (operator === void 0 || operator === "eq"));
698
+ return typeof condition?.value === "string" ? condition.value : void 0;
699
+ };
700
+ const firestoreOperator = (operator) => {
701
+ switch (operator ?? "eq") {
702
+ case "eq": return "==";
703
+ case "ne": return "!=";
704
+ case "gt": return ">";
705
+ case "gte": return ">=";
706
+ case "lt": return "<";
707
+ case "lte": return "<=";
708
+ case "in": return "in";
709
+ case "not_in": return "not-in";
710
+ default: return;
711
+ }
712
+ };
713
+ const applyFirebaseWhere = (initial, where) => {
714
+ let query = initial;
715
+ for (const condition of where) {
716
+ const operator = firestoreOperator(condition.operator);
717
+ if (condition.connector === "OR" || operator === void 0) throw new FirebaseDatabaseConstraintError("query.unsupported");
718
+ query = query.where(condition.field, operator, condition.value);
719
+ }
720
+ return query;
721
+ };
657
722
  const firebaseDatabase = (config) => {
658
- const adapter = createDatabasePluginAdapter("firebaseDatabase", (() => {
659
- const db = getFirestore(getApps().length ? getApp() : initializeApp(config));
723
+ const implementation = (() => {
724
+ const app = getApps().length ? getApp() : initializeApp(config);
725
+ const db = getFirestore(app);
660
726
  const collections = createFirebaseDatabaseCollections(db);
661
727
  let migration;
662
728
  const ensureMigrated = () => {
663
- migration ??= migrateFirebaseDatabase(db, collections).catch((error) => {
729
+ migration ??= migrateFirebaseDatabase(collections).catch((error) => {
664
730
  migration = void 0;
665
731
  throw error;
666
732
  });
@@ -683,14 +749,74 @@ const firebaseDatabase = (config) => {
683
749
  };
684
750
  const read = async (operation) => {
685
751
  await ensureMigrated();
686
- return operation(createFirebaseDatabaseState(await loadFirebaseDatabaseSnapshot(collections)));
752
+ const snapshot = await loadFirebaseDatabaseSnapshot(collections);
753
+ return operation(createFirebaseDatabaseState(snapshot));
687
754
  };
688
755
  return {
689
- create: (input) => mutate((database) => database.create(input)),
690
- update: (input) => mutate((database) => database.update(input)),
756
+ recordInsights: async ({ event, installation }) => {
757
+ await ensureMigrated();
758
+ await db.runTransaction(async (transaction) => {
759
+ const eventReference = collections.bundleEvents.doc(event.id);
760
+ const installationReference = collections.bundleInstallations.doc(firebaseInstallationDocumentId(installation.install_id));
761
+ const [storedEvent, storedInstallation] = await transaction.getAll(eventReference, installationReference);
762
+ if (storedEvent.exists) return;
763
+ const current = storedInstallation.exists ? requireFirebaseDocumentKey("bundle_installations", storedInstallation.id, parseFirebaseInsightsInstallationRow(storedInstallation.data(), `bundle_installations/${storedInstallation.id}`)) : null;
764
+ transaction.create(eventReference, event);
765
+ if (current === null || installation.received_at_ms > current.received_at_ms || installation.received_at_ms === current.received_at_ms && compareInsightsText(installation.id, current.id) > 0) transaction.set(installationReference, installation);
766
+ });
767
+ },
768
+ create: async (input) => {
769
+ if (input.model !== "bundle_events" && input.model !== "bundle_installations") return mutate((database) => database.create(input));
770
+ await ensureMigrated();
771
+ return db.runTransaction(async (transaction) => {
772
+ const collection = input.model === "bundle_events" ? collections.bundleEvents : collections.bundleInstallations;
773
+ const documentId = input.model === "bundle_events" ? input.data.id : firebaseInstallationDocumentId(input.data.install_id);
774
+ const reference = collection.doc(documentId);
775
+ const document = await transaction.get(reference);
776
+ if (document.exists) {
777
+ const row = input.model === "bundle_events" ? requireFirebaseDocumentKey("bundle_events", document.id, parseFirebaseBundleEventRow(document.data(), `bundle_events/${document.id}`)) : requireFirebaseDocumentKey("bundle_installations", document.id, parseFirebaseInsightsInstallationRow(document.data(), `bundle_installations/${document.id}`));
778
+ if (input.onConflict === "ignore") return row;
779
+ throw new FirebaseDatabaseConstraintError(`${input.model}.id.unique`);
780
+ }
781
+ transaction.create(reference, input.data);
782
+ return input.data;
783
+ });
784
+ },
785
+ update: async (input) => {
786
+ if (input.model !== "bundle_installations") return mutate((database) => database.update(input));
787
+ const installId = exactInstallId(input);
788
+ if (installId === void 0) return mutate((database) => database.update(input));
789
+ await ensureMigrated();
790
+ return db.runTransaction(async (transaction) => {
791
+ const reference = collections.bundleInstallations.doc(firebaseInstallationDocumentId(installId));
792
+ const document = await transaction.get(reference);
793
+ if (!document.exists) return null;
794
+ const current = requireFirebaseDocumentKey("bundle_installations", document.id, parseFirebaseInsightsInstallationRow(document.data(), `bundle_installations/${document.id}`));
795
+ if (!matchesFirebaseDatabaseWhere(current, input.where)) return null;
796
+ const updated = {
797
+ ...current,
798
+ ...input.update
799
+ };
800
+ transaction.set(reference, updated, { merge: true });
801
+ return updated;
802
+ });
803
+ },
691
804
  delete: (input) => mutate((database) => database.delete(input)),
692
- count: (input) => read((database) => database.count(input)),
805
+ count: async (input) => {
806
+ if (input.model !== "bundle_installations" && input.model !== "bundle_events") return read((database) => database.count(input));
807
+ await ensureMigrated();
808
+ let query = applyFirebaseWhere(input.model === "bundle_events" ? collections.bundleEvents : collections.bundleInstallations, input.where ?? []);
809
+ if (input.model === "bundle_events") query = query.orderBy("received_at_ms", "desc").orderBy("id", "desc");
810
+ return (await query.count().get()).data().count;
811
+ },
693
812
  findOne: async (input) => {
813
+ if (input.model === "bundle_installations") {
814
+ const installId = exactInstallId(input);
815
+ if (installId === void 0) return read((database) => database.findOne(input));
816
+ await ensureMigrated();
817
+ const document = await collections.bundleInstallations.doc(firebaseInstallationDocumentId(installId)).get();
818
+ return document.exists ? requireFirebaseDocumentKey("bundle_installations", document.id, parseFirebaseInsightsInstallationRow(document.data(), `bundle_installations/${document.id}`)) : null;
819
+ }
694
820
  const id = exactId(input);
695
821
  if (id === void 0) return read((database) => database.findOne(input));
696
822
  await ensureMigrated();
@@ -711,9 +837,21 @@ const firebaseDatabase = (config) => {
711
837
  }
712
838
  },
713
839
  findMany: async (input) => {
714
- if (input.model !== "channels") return read((database) => database.findMany(input));
715
- await ensureMigrated();
716
- return queryFirebaseDatabaseRows(await loadFirebaseChannels(collections), input);
840
+ if (input.model === "bundle_events" || input.model === "bundle_installations") {
841
+ await ensureMigrated();
842
+ const collection = input.model === "bundle_events" ? collections.bundleEvents : collections.bundleInstallations;
843
+ let query = applyFirebaseWhere(collection, input.where ?? []);
844
+ for (const order of input.orderBy ?? []) {
845
+ if (order.nulls !== void 0) throw new FirebaseDatabaseConstraintError("query.unsupported");
846
+ query = query.orderBy(order.field, order.direction);
847
+ }
848
+ return (await query.offset(input.offset).limit(input.limit).get()).docs.map((document) => input.model === "bundle_events" ? requireFirebaseDocumentKey("bundle_events", document.id, parseFirebaseBundleEventRow(document.data(), `bundle_events/${document.id}`)) : requireFirebaseDocumentKey("bundle_installations", document.id, parseFirebaseInsightsInstallationRow(document.data(), `bundle_installations/${document.id}`)));
849
+ }
850
+ if (input.model === "channels") {
851
+ await ensureMigrated();
852
+ return queryFirebaseDatabaseRows(await loadFirebaseChannels(collections), input);
853
+ }
854
+ return read((database) => database.findMany(input));
717
855
  },
718
856
  insertChannel: async (input) => {
719
857
  await ensureMigrated();
@@ -765,7 +903,8 @@ const firebaseDatabase = (config) => {
765
903
  },
766
904
  transaction: (callback) => mutate(callback)
767
905
  };
768
- })());
906
+ })();
907
+ const adapter = createDatabasePluginAdapter("firebaseDatabase", implementation);
769
908
  return createDatabasePlugin({
770
909
  name: "firebaseDatabase",
771
910
  models: adapter.models,
@@ -775,7 +914,8 @@ const firebaseDatabase = (config) => {
775
914
  //#endregion
776
915
  //#region src/firebaseStorage.ts
777
916
  const firebaseStorage = (config) => {
778
- const bucket = getStorage(getApps().length ? getApp() : initializeApp(config)).bucket(config.storageBucket);
917
+ const app = getApps().length ? getApp() : initializeApp(config);
918
+ const bucket = getStorage(app).bucket(config.storageBucket);
779
919
  const getStorageKey = createStorageKeyBuilder(config.basePath);
780
920
  const parseAndValidate = (storageUri) => {
781
921
  const parsed = parseStorageUri(storageUri, "gs");
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.0",
4
+ "version": "1.0.0-rc.2",
5
5
  "description": "React Native OTA solution for self-hosted",
6
6
  "main": "dist/index.cjs",
7
7
  "types": "dist/index.d.cts",
@@ -43,10 +43,10 @@
43
43
  ],
44
44
  "dependencies": {
45
45
  "hono": "4.12.34",
46
- "@hot-updater/cli-tools": "1.0.0-rc.0",
46
+ "@hot-updater/cli-tools": "1.0.0-rc.2",
47
47
  "@hot-updater/core": "1.0.0-rc.0",
48
- "@hot-updater/plugin-core": "1.0.0-rc.0",
49
- "@hot-updater/server": "1.0.0-rc.0"
48
+ "@hot-updater/plugin-core": "1.0.0-rc.2",
49
+ "@hot-updater/server": "1.0.0-rc.2"
50
50
  },
51
51
  "publishConfig": {
52
52
  "access": "public"
@@ -61,8 +61,8 @@
61
61
  "firebase-tools": "^13.32.0",
62
62
  "fkill": "^9.0.0",
63
63
  "mime": "^4.0.4",
64
- "@hot-updater/mock": "1.0.0-rc.0",
65
- "@hot-updater/test-utils": "1.0.0-rc.0"
64
+ "@hot-updater/mock": "1.0.0-rc.2",
65
+ "@hot-updater/test-utils": "1.0.0-rc.2"
66
66
  },
67
67
  "peerDependencies": {
68
68
  "firebase-admin": "*",