@rebasepro/client-firebase 0.7.0 → 0.9.0

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.es.js CHANGED
@@ -4,7 +4,7 @@ import { FacebookAuthProvider, GithubAuthProvider, GoogleAuthProvider, OAuthProv
4
4
  import { deleteObject, getDownloadURL, getMetadata, getStorage, list, ref, uploadBytesResumable } from "@firebase/storage";
5
5
  import { deleteApp, getApps, initializeApp } from "@firebase/app";
6
6
  import { getToken, initializeAppCheck } from "@firebase/app-check";
7
- import { EntityReference, GeoPoint } from "@rebasepro/types";
7
+ import { DEFAULT_DATA_SOURCE_KEY, EntityReference, GeoPoint } from "@rebasepro/types";
8
8
  import { DocumentReference, GeoPoint as GeoPoint$1, Timestamp, VectorValue, collection, deleteDoc, deleteField, doc, getCountFromServer, getDoc, getDocs, getFirestore, limit, onSnapshot, orderBy, query, serverTimestamp, setDoc, startAfter, vector, where } from "@firebase/firestore";
9
9
  import { COLLECTION_PATH_SEPARATOR, buildRebaseData, sortProperties, stripCollectionPath } from "@rebasepro/common";
10
10
  import Fuse from "fuse.js";
@@ -12,7 +12,7 @@ import { getFunctions, httpsCallable } from "@firebase/functions";
12
12
  import { get, getDatabase, limitToFirst, onValue, orderByChild, orderByKey, push, query as query$1, ref as ref$1, remove, set, startAt } from "@firebase/database";
13
13
  import { removeUndefined } from "@rebasepro/utils";
14
14
  import { AdminModeControllerProvider, ErrorView, ModeControllerProvider, Rebase, RebaseLogo, RebaseRoutes, SnackbarProvider, useBrowserTitleAndIcon, useBuildAdminModeController, useBuildLocalConfigurationPersistence, useBuildModeController, useModeController, useSnackbarController } from "@rebasepro/core";
15
- import { AppBar, CollectionRegistryContext, Drawer, NavigationStateContext, RebaseRoute, Scaffold, SideDialogs, SideEntityProvider, UrlContext, useBuildCollectionRegistryController, useBuildNavigationStateController, useBuildUrlController } from "@rebasepro/admin";
15
+ import { AppBar, CollectionRegistryContext, Drawer, NavigationStateContext, RebaseRoute, Scaffold, SideDialogs, SidePanelProvider, UrlContext, useBuildCollectionRegistryController, useBuildNavigationStateController, useBuildUrlController } from "@rebasepro/admin";
16
16
  import { ArrowLeftIcon, Button, CenteredView, CircularProgress, CircularProgressCenter, IconButton, LoadingButton, MailIcon, PhoneIcon, TextField, Typography, UserIcon, cls, iconSize } from "@rebasepro/ui";
17
17
  import { Navigate, Outlet, Route } from "react-router-dom";
18
18
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
@@ -262,8 +262,8 @@ function useFirebaseStorageSource({ firebaseApp, bucketUrl }) {
262
262
  }, 5e3);
263
263
  };
264
264
  setProgressTimeout();
265
- uploadTask.on("state_changed", (snapshot) => {
266
- const progress = snapshot.bytesTransferred / snapshot.totalBytes * 100;
265
+ uploadTask.on("state_changed", (entity) => {
266
+ const progress = entity.bytesTransferred / entity.totalBytes * 100;
267
267
  if (progress > lastProgress) {
268
268
  lastProgress = progress;
269
269
  setProgressTimeout();
@@ -586,8 +586,8 @@ function normalizePropertiesEnumValues(properties, sortObjectFormat = false) {
586
586
  //#region src/utils/database.ts
587
587
  async function getFirestoreDataInPath(firebaseApp, path, parentPaths, limit$2) {
588
588
  const firestore = getFirestore(firebaseApp);
589
- if (!parentPaths || parentPaths.length === 0) return getDocs(query(collection(firestore, path), limit(limit$2))).then((querySnapshot) => {
590
- return querySnapshot.docs.map((doc) => doc.data());
589
+ if (!parentPaths || parentPaths.length === 0) return getDocs(query(collection(firestore, path), limit(limit$2))).then((queryEntity) => {
590
+ return queryEntity.docs.map((doc) => doc.data());
591
591
  });
592
592
  else {
593
593
  let currentDocs = void 0;
@@ -699,9 +699,9 @@ var localSearchControllerBuilder = ({ firebaseApp }) => {
699
699
  if (collectionProp) {
700
700
  console.debug("Init local search controller", path);
701
701
  listeners[path] = onSnapshot(query(collection(databaseId ? getFirestore(firebaseApp, databaseId) : getFirestore(firebaseApp), path)), {
702
- next: (snapshot) => {
703
- if (snapshot.metadata.fromCache && snapshot.metadata.hasPendingWrites) return;
704
- const docs = snapshot.docs.map((doc) => ({
702
+ next: (entity) => {
703
+ if (entity.metadata.fromCache && entity.metadata.hasPendingWrites) return;
704
+ const docs = entity.docs.map((doc) => ({
705
705
  id: doc.id,
706
706
  ...doc.data()
707
707
  }));
@@ -968,6 +968,15 @@ function useFirestoreDriver({ firebaseApp, textSearchControllerBuilder, firestor
968
968
  const queryParams = [];
969
969
  if (filter) Object.entries(filter).filter(([_, entry]) => !!entry).forEach(([key, filterParameter]) => {
970
970
  const [op, value] = filterParameter;
971
+ if (op === "is-null") {
972
+ queryParams.push(where(key, "==", null));
973
+ return;
974
+ }
975
+ if (op === "is-not-null") {
976
+ queryParams.push(where(key, "!=", null));
977
+ return;
978
+ }
979
+ if (op === "like" || op === "ilike" || op === "not-like" || op === "not-ilike") throw new Error(`Firestore does not support the "${op}" operator (SQL pattern matching). Use a full-text search index or the collection's searchString instead.`);
971
980
  queryParams.push(where(key, op, cmsToFirestoreModel(value, firestore)));
972
981
  });
973
982
  if (orderBy$1 && order) queryParams.push(orderBy(orderBy$1, order));
@@ -975,19 +984,19 @@ function useFirestoreDriver({ firebaseApp, textSearchControllerBuilder, firestor
975
984
  if (limit$1) queryParams.push(limit(limit$1));
976
985
  return query(collectionReference, ...queryParams);
977
986
  }, [firebaseApp]);
978
- const getAndBuildEntity = useCallback((path, entityId, databaseId) => {
987
+ const getAndBuildEntity = useCallback((path, id, databaseId) => {
979
988
  if (!firebaseApp) throw Error("useFirestoreDriver Firebase not initialised");
980
- return getDoc(doc(databaseId ? getFirestore(firebaseApp, databaseId) : getFirestore(firebaseApp), path, String(entityId))).then((docSnapshot) => {
981
- if (!docSnapshot.exists()) return;
982
- return createEntityFromDocument(docSnapshot, databaseId);
989
+ return getDoc(doc(databaseId ? getFirestore(firebaseApp, databaseId) : getFirestore(firebaseApp), path, String(id))).then((docEntity) => {
990
+ if (!docEntity.exists()) return;
991
+ return createRowFromDocument(docEntity);
983
992
  });
984
993
  }, [firebaseApp]);
985
- const listenEntity = useCallback(({ path, entityId, collection, onUpdate, onError }) => {
994
+ const listenOne = useCallback(({ path, id, collection, onUpdate, onError }) => {
986
995
  if (!firebaseApp) throw Error("useFirestoreDriver Firebase not initialised");
987
996
  const databaseId = collection?.databaseId;
988
- return onSnapshot(doc(databaseId ? getFirestore(firebaseApp, databaseId) : getFirestore(firebaseApp), path, String(entityId)), {
989
- next: (docSnapshot) => {
990
- onUpdate(createEntityFromDocument(docSnapshot, databaseId));
997
+ return onSnapshot(doc(databaseId ? getFirestore(firebaseApp, databaseId) : getFirestore(firebaseApp), path, String(id)), {
998
+ next: (docEntity) => {
999
+ onUpdate(docEntity.exists() ? createRowFromDocument(docEntity) : null);
991
1000
  },
992
1001
  error: onError
993
1002
  });
@@ -1010,22 +1019,23 @@ function useFirestoreDriver({ firebaseApp, textSearchControllerBuilder, firestor
1010
1019
  subscriptions = [];
1011
1020
  onUpdate([]);
1012
1021
  }
1013
- const entities = [];
1022
+ const rows = [];
1014
1023
  const addedEntitiesSet = /* @__PURE__ */ new Set();
1015
- subscriptions = (ids ?? []).map((entityId) => {
1016
- return listenEntity({
1024
+ subscriptions = (ids ?? []).map((id) => {
1025
+ return listenOne({
1017
1026
  path,
1018
- entityId,
1019
- onUpdate: (entity) => {
1020
- if (entity?.values) {
1021
- if (entity.id && !addedEntitiesSet.has(entity.id)) {
1022
- addedEntitiesSet.add(entity.id);
1023
- entities.push(entity);
1024
- onUpdate(entities);
1027
+ id,
1028
+ onUpdate: (row) => {
1029
+ const incomingId = row?.id;
1030
+ if (row && incomingId !== void 0) {
1031
+ if (!addedEntitiesSet.has(incomingId)) {
1032
+ addedEntitiesSet.add(incomingId);
1033
+ rows.push(row);
1034
+ onUpdate(rows);
1025
1035
  }
1026
- } else if (entity?.id) {
1027
- addedEntitiesSet.delete(entity.id);
1028
- onUpdate([...entities.filter((e) => e.id !== entityId)]);
1036
+ } else {
1037
+ addedEntitiesSet.delete(id);
1038
+ onUpdate([...rows.filter((r) => r.id !== id)]);
1029
1039
  }
1030
1040
  }
1031
1041
  });
@@ -1034,231 +1044,237 @@ function useFirestoreDriver({ firebaseApp, textSearchControllerBuilder, firestor
1034
1044
  return () => {
1035
1045
  subscriptions.forEach((p) => p());
1036
1046
  };
1037
- }, [firebaseApp, listenEntity]);
1038
- return {
1047
+ }, [firebaseApp, listenOne]);
1048
+ const initTextSearch = useCallback(async (props) => {
1049
+ console.debug("Init text search controller", searchControllerRef.current, props.path);
1050
+ if (!searchControllerRef.current) {
1051
+ console.warn("You are trying to use text search, but have not provided a text search controller in `useFirestoreDriver`. You can also set the flag `localTextSearchEnabled` to use local search in `useFirestoreDriver`. Local text search can incur in performance issues and higher costs for large datasets.");
1052
+ return false;
1053
+ }
1054
+ try {
1055
+ return searchControllerRef.current.init(props);
1056
+ } catch (e) {
1057
+ console.error("Error initializing text search controller", e);
1058
+ return false;
1059
+ }
1060
+ }, []);
1061
+ /**
1062
+ * Fetch entities in a Firestore path
1063
+ * @param path
1064
+ * @param collection
1065
+ * @param filter
1066
+ * @param limit
1067
+ * @param startAfter
1068
+ * @param searchString
1069
+ * @param orderBy
1070
+ * @param order
1071
+ * @return Function to cancel subscription
1072
+ * @see useCollection if you need this functionality implemented as a hook
1073
+ * @group Firestore
1074
+ */
1075
+ const fetchCollection = useCallback(async ({ path, filter, limit, startAfter, searchString, orderBy, order, collection }) => {
1076
+ const databaseId = collection?.databaseId;
1077
+ const resolvedPath = path;
1078
+ console.debug("Fetching collection", {
1079
+ path,
1080
+ limit,
1081
+ filter,
1082
+ startAfter,
1083
+ orderBy,
1084
+ order
1085
+ });
1086
+ return (await getDocs(buildQuery(resolvedPath, filter, orderBy, order, startAfter, limit, databaseId))).docs.map((doc) => createRowFromDocument(doc));
1087
+ }, [buildQuery]);
1088
+ /**
1089
+ * Listen to a entities in a given path
1090
+ * @param path
1091
+ * @param collection
1092
+ * @param onError
1093
+ * @param filter
1094
+ * @param limit
1095
+ * @param startAfter
1096
+ * @param searchString
1097
+ * @param orderBy
1098
+ * @param order
1099
+ * @param onUpdate
1100
+ * @return Function to cancel subscription
1101
+ * @see useCollection if you need this functionality implemented as a hook
1102
+ * @group Firestore
1103
+ */
1104
+ const listenCollection = useCallback(({ path, filter, limit, startAfter, searchString, orderBy, order, onUpdate, onError, collection }) => {
1105
+ console.debug("Listening collection", {
1106
+ path,
1107
+ searchString,
1108
+ limit,
1109
+ filter,
1110
+ startAfter,
1111
+ orderBy,
1112
+ order,
1113
+ collection
1114
+ });
1115
+ if (!firebaseApp) throw Error("useFirestoreDriver Firebase not initialised");
1116
+ const databaseId = collection?.databaseId;
1117
+ if (searchString) return performTextSearch({
1118
+ path,
1119
+ searchString,
1120
+ onUpdate,
1121
+ databaseId
1122
+ });
1123
+ const resolvedPath = path;
1124
+ console.debug("Resolved path for listening", {
1125
+ path,
1126
+ resolvedPath
1127
+ });
1128
+ return onSnapshot(buildQuery(resolvedPath, filter, orderBy, order, startAfter, limit, databaseId), {
1129
+ next: (entity) => {
1130
+ if (!searchString) onUpdate(entity.docs.map((doc) => createRowFromDocument(doc)));
1131
+ },
1132
+ error: onError
1133
+ });
1134
+ }, [
1135
+ buildQuery,
1136
+ firebaseApp,
1137
+ performTextSearch
1138
+ ]);
1139
+ /**
1140
+ * Retrieve a entity given a path and a collection
1141
+ * @param path
1142
+ * @param id
1143
+ * @param collection
1144
+ * @group Firestore
1145
+ */
1146
+ const fetchOne = useCallback(({ path, id, collection }) => {
1147
+ return getAndBuildEntity(path, id, collection?.databaseId);
1148
+ }, [getAndBuildEntity]);
1149
+ /**
1150
+ * Save entity to the specified path. Note that Firestore does not allow
1151
+ * undefined values.
1152
+ * @param path
1153
+ * @param id
1154
+ * @param values
1155
+ * @param schemaId
1156
+ * @param collection
1157
+ * @param status
1158
+ * @group Firestore
1159
+ */
1160
+ const save = useCallback(({ path, id, values: valuesProp, collection: collection$1, status }) => {
1161
+ if (!firebaseApp) throw Error("useFirestoreDriver Firebase not initialised");
1162
+ console.debug("1", {
1163
+ path,
1164
+ id,
1165
+ values: valuesProp,
1166
+ collection: collection$1
1167
+ });
1168
+ const values = cmsToFirestoreModel(valuesProp, getFirestore(firebaseApp));
1169
+ console.debug("2", {
1170
+ path,
1171
+ id,
1172
+ values: valuesProp,
1173
+ collection: collection$1
1174
+ });
1175
+ const databaseId = collection$1?.databaseId;
1176
+ const collectionReference = collection(databaseId ? getFirestore(firebaseApp, databaseId) : getFirestore(firebaseApp), path);
1177
+ console.debug("Saving entity", {
1178
+ path,
1179
+ id,
1180
+ values,
1181
+ databaseId
1182
+ });
1183
+ let documentReference;
1184
+ if (id) documentReference = doc(collectionReference, String(id));
1185
+ else documentReference = doc(collectionReference);
1186
+ return setDoc(documentReference, values, { merge: true }).then(() => {
1187
+ return {
1188
+ ...firestoreToCMSModel(values),
1189
+ id: documentReference.id
1190
+ };
1191
+ }).catch((error) => {
1192
+ console.error("Error saving entity", error);
1193
+ throw error;
1194
+ });
1195
+ }, [firebaseApp]);
1196
+ /**
1197
+ * Delete a entity
1198
+ * @param entity
1199
+ * @param collection
1200
+ * @group Firestore
1201
+ */
1202
+ const deleteOne = useCallback(({ row, collection }) => {
1203
+ if (!firebaseApp) throw Error("useFirestoreDriver Firebase not initialised");
1204
+ const databaseId = collection?.databaseId;
1205
+ return deleteDoc(doc(databaseId ? getFirestore(firebaseApp, databaseId) : getFirestore(firebaseApp), row.path, String(row.id)));
1206
+ }, [firebaseApp]);
1207
+ /**
1208
+ * Check if the given property is unique in the given collection
1209
+ * @param path Collection path
1210
+ * @param name of the property
1211
+ * @param value
1212
+ * @param property
1213
+ * @param id
1214
+ * @return `true` if there are no other fields besides the given entity
1215
+ * @group Firestore
1216
+ */
1217
+ const checkUniqueField = useCallback(async (path, name, value, id, collection$2) => {
1218
+ if (!firebaseApp) throw Error("useFirestoreDriver Firebase not initialised");
1219
+ const databaseId = collection$2?.databaseId;
1220
+ const firestore = databaseId ? getFirestore(firebaseApp, databaseId) : getFirestore(firebaseApp);
1221
+ if (value === void 0 || value === null) return Promise.resolve(true);
1222
+ return (await getDocs(query(collection(firestore, path), where(name, "==", cmsToFirestoreModel(value, firestore))))).docs.filter((doc) => doc.id !== id).length === 0;
1223
+ }, [firebaseApp]);
1224
+ const count = useCallback(async ({ path, filter, order, orderBy, collection }) => {
1225
+ if (!firebaseApp) throw Error("useFirestoreDriver Firebase not initialised");
1226
+ const databaseId = collection?.databaseId;
1227
+ return (await getCountFromServer(buildQuery(path, filter, orderBy, order, void 0, void 0, databaseId))).data().count;
1228
+ }, [firebaseApp]);
1229
+ const isFilterCombinationValid = useCallback(({ path, collection, filterValues, sortBy }) => {
1230
+ if (!firebaseApp) throw Error("useFirestoreDriver Firebase not initialised");
1231
+ if (firestoreIndexesBuilder === void 0) return true;
1232
+ const indexes = firestoreIndexesBuilder?.({
1233
+ path,
1234
+ collection
1235
+ });
1236
+ const sortKey = sortBy ? sortBy[0] : void 0;
1237
+ const sortDirection = sortBy ? sortBy[1] : void 0;
1238
+ const values = Object.values(filterValues);
1239
+ const filterKeys = Object.keys(filterValues);
1240
+ const filtersCount = filterKeys.length;
1241
+ if (!sortKey && values.every((v) => v[0] === "==")) return true;
1242
+ if (filtersCount === 1 && (!sortKey || sortKey === filterKeys[0])) return true;
1243
+ if (!indexes && filtersCount > 1) return false;
1244
+ return !!indexes && indexes.filter((compositeIndex) => !sortKey || sortKey in compositeIndex).find((compositeIndex) => Object.entries(filterValues).every(([key, value]) => compositeIndex[key] !== void 0 && (!sortDirection || compositeIndex[key] === sortDirection))) !== void 0;
1245
+ }, [firebaseApp]);
1246
+ return useMemo(() => ({
1039
1247
  key: "firestore",
1040
1248
  currentTime,
1041
1249
  initialised: Boolean(firebaseApp),
1042
- initTextSearch: useCallback(async (props) => {
1043
- console.debug("Init text search controller", searchControllerRef.current, props.path);
1044
- if (!searchControllerRef.current) {
1045
- console.warn("You are trying to use text search, but have not provided a text search controller in `useFirestoreDriver`. You can also set the flag `localTextSearchEnabled` to use local search in `useFirestoreDriver`. Local text search can incur in performance issues and higher costs for large datasets.");
1046
- return false;
1047
- }
1048
- try {
1049
- return searchControllerRef.current.init(props);
1050
- } catch (e) {
1051
- console.error("Error initializing text search controller", e);
1052
- return false;
1053
- }
1054
- }, []),
1055
- /**
1056
- * Fetch entities in a Firestore path
1057
- * @param path
1058
- * @param collection
1059
- * @param filter
1060
- * @param limit
1061
- * @param startAfter
1062
- * @param searchString
1063
- * @param orderBy
1064
- * @param order
1065
- * @return Function to cancel subscription
1066
- * @see useCollectionFetch if you need this functionality implemented as a hook
1067
- * @group Firestore
1068
- */
1069
- fetchCollection: useCallback(async ({ path, filter, limit, startAfter, searchString, orderBy, order, collection }) => {
1070
- const databaseId = collection?.databaseId;
1071
- const resolvedPath = path;
1072
- console.debug("Fetching collection", {
1073
- path,
1074
- limit,
1075
- filter,
1076
- startAfter,
1077
- orderBy,
1078
- order
1079
- });
1080
- return (await getDocs(buildQuery(resolvedPath, filter, orderBy, order, startAfter, limit, databaseId))).docs.map((doc) => createEntityFromDocument(doc, databaseId));
1081
- }, [buildQuery]),
1082
- /**
1083
- * Listen to a entities in a given path
1084
- * @param path
1085
- * @param collection
1086
- * @param onError
1087
- * @param filter
1088
- * @param limit
1089
- * @param startAfter
1090
- * @param searchString
1091
- * @param orderBy
1092
- * @param order
1093
- * @param onUpdate
1094
- * @return Function to cancel subscription
1095
- * @see useCollectionFetch if you need this functionality implemented as a hook
1096
- * @group Firestore
1097
- */
1098
- listenCollection: useCallback(({ path, filter, limit, startAfter, searchString, orderBy, order, onUpdate, onError, collection }) => {
1099
- console.debug("Listening collection", {
1100
- path,
1101
- searchString,
1102
- limit,
1103
- filter,
1104
- startAfter,
1105
- orderBy,
1106
- order,
1107
- collection
1108
- });
1109
- if (!firebaseApp) throw Error("useFirestoreDriver Firebase not initialised");
1110
- const databaseId = collection?.databaseId;
1111
- if (searchString) return performTextSearch({
1112
- path,
1113
- searchString,
1114
- onUpdate,
1115
- databaseId
1116
- });
1117
- const resolvedPath = path;
1118
- console.debug("Resolved path for listening", {
1119
- path,
1120
- resolvedPath
1121
- });
1122
- return onSnapshot(buildQuery(resolvedPath, filter, orderBy, order, startAfter, limit, databaseId), {
1123
- next: (snapshot) => {
1124
- if (!searchString) onUpdate(snapshot.docs.map((doc) => createEntityFromDocument(doc, databaseId)));
1125
- },
1126
- error: onError
1127
- });
1128
- }, [
1129
- buildQuery,
1130
- firebaseApp,
1131
- performTextSearch
1132
- ]),
1133
- /**
1134
- * Retrieve an entity given a path and a collection
1135
- * @param path
1136
- * @param entityId
1137
- * @param collection
1138
- * @group Firestore
1139
- */
1140
- fetchEntity: useCallback(({ path, entityId, collection }) => {
1141
- return getAndBuildEntity(path, entityId, collection?.databaseId);
1142
- }, [getAndBuildEntity]),
1143
- /**
1144
- *
1145
- * @param path
1146
- * @param entityId
1147
- * @param collection
1148
- * @param onUpdate
1149
- * @param onError
1150
- * @return Function to cancel subscription
1151
- * @group Firestore
1152
- */
1153
- listenEntity,
1154
- /**
1155
- * Save entity to the specified path. Note that Firestore does not allow
1156
- * undefined values.
1157
- * @param path
1158
- * @param entityId
1159
- * @param values
1160
- * @param schemaId
1161
- * @param collection
1162
- * @param status
1163
- * @group Firestore
1164
- */
1165
- saveEntity: useCallback(({ path, entityId, values: valuesProp, collection: collection$1, status }) => {
1166
- if (!firebaseApp) throw Error("useFirestoreDriver Firebase not initialised");
1167
- console.debug("1", {
1168
- path,
1169
- entityId,
1170
- values: valuesProp,
1171
- collection: collection$1
1172
- });
1173
- const values = cmsToFirestoreModel(valuesProp, getFirestore(firebaseApp));
1174
- console.debug("2", {
1175
- path,
1176
- entityId,
1177
- values: valuesProp,
1178
- collection: collection$1
1179
- });
1180
- const databaseId = collection$1?.databaseId;
1181
- const collectionReference = collection(databaseId ? getFirestore(firebaseApp, databaseId) : getFirestore(firebaseApp), path);
1182
- console.debug("Saving entity", {
1183
- path,
1184
- entityId,
1185
- values,
1186
- databaseId
1187
- });
1188
- let documentReference;
1189
- if (entityId) documentReference = doc(collectionReference, String(entityId));
1190
- else documentReference = doc(collectionReference);
1191
- return setDoc(documentReference, values, { merge: true }).then(() => {
1192
- return {
1193
- id: documentReference.id,
1194
- path,
1195
- values: firestoreToCMSModel(values)
1196
- };
1197
- }).catch((error) => {
1198
- console.error("Error saving entity", error);
1199
- throw error;
1200
- });
1201
- }, [firebaseApp]),
1202
- /**
1203
- * Delete an entity
1204
- * @param entity
1205
- * @param collection
1206
- * @group Firestore
1207
- */
1208
- deleteEntity: useCallback(({ entity }) => {
1209
- if (!firebaseApp) throw Error("useFirestoreDriver Firebase not initialised");
1210
- const databaseId = entity.databaseId;
1211
- return deleteDoc(doc(databaseId ? getFirestore(firebaseApp, databaseId) : getFirestore(firebaseApp), entity.path, String(entity.id)));
1212
- }, [firebaseApp]),
1213
- /**
1214
- * Check if the given property is unique in the given collection
1215
- * @param path Collection path
1216
- * @param name of the property
1217
- * @param value
1218
- * @param property
1219
- * @param entityId
1220
- * @return `true` if there are no other fields besides the given entity
1221
- * @group Firestore
1222
- */
1223
- checkUniqueField: useCallback(async (path, name, value, entityId, collection$2) => {
1224
- if (!firebaseApp) throw Error("useFirestoreDriver Firebase not initialised");
1225
- const databaseId = collection$2?.databaseId;
1226
- const firestore = databaseId ? getFirestore(firebaseApp, databaseId) : getFirestore(firebaseApp);
1227
- if (value === void 0 || value === null) return Promise.resolve(true);
1228
- return (await getDocs(query(collection(firestore, path), where(name, "==", cmsToFirestoreModel(value, firestore))))).docs.filter((doc) => doc.id !== entityId).length === 0;
1229
- }, [firebaseApp]),
1230
- countEntities: useCallback(async ({ path, filter, order, orderBy, collection }) => {
1231
- if (!firebaseApp) throw Error("useFirestoreDriver Firebase not initialised");
1232
- const databaseId = collection?.databaseId;
1233
- return (await getCountFromServer(buildQuery(path, filter, orderBy, order, void 0, void 0, databaseId))).data().count;
1234
- }, [firebaseApp]),
1235
- isFilterCombinationValid: useCallback(({ path, collection, filterValues, sortBy }) => {
1236
- if (!firebaseApp) throw Error("useFirestoreDriver Firebase not initialised");
1237
- if (firestoreIndexesBuilder === void 0) return true;
1238
- const indexes = firestoreIndexesBuilder?.({
1239
- path,
1240
- collection
1241
- });
1242
- const sortKey = sortBy ? sortBy[0] : void 0;
1243
- const sortDirection = sortBy ? sortBy[1] : void 0;
1244
- const values = Object.values(filterValues);
1245
- const filterKeys = Object.keys(filterValues);
1246
- const filtersCount = filterKeys.length;
1247
- if (!sortKey && values.every((v) => v[0] === "==")) return true;
1248
- if (filtersCount === 1 && (!sortKey || sortKey === filterKeys[0])) return true;
1249
- if (!indexes && filtersCount > 1) return false;
1250
- return !!indexes && indexes.filter((compositeIndex) => !sortKey || sortKey in compositeIndex).find((compositeIndex) => Object.entries(filterValues).every(([key, value]) => compositeIndex[key] !== void 0 && (!sortDirection || compositeIndex[key] === sortDirection))) !== void 0;
1251
- }, [firebaseApp])
1252
- };
1250
+ initTextSearch,
1251
+ fetchCollection,
1252
+ listenCollection,
1253
+ fetchOne,
1254
+ listenOne,
1255
+ save,
1256
+ delete: deleteOne,
1257
+ checkUniqueField,
1258
+ count,
1259
+ isFilterCombinationValid
1260
+ }), [
1261
+ firebaseApp,
1262
+ initTextSearch,
1263
+ fetchCollection,
1264
+ listenCollection,
1265
+ fetchOne,
1266
+ listenOne,
1267
+ save,
1268
+ deleteOne,
1269
+ checkUniqueField,
1270
+ count,
1271
+ isFilterCombinationValid
1272
+ ]);
1253
1273
  }
1254
- var createEntityFromDocument = (docSnap, databaseId) => {
1255
- const values = firestoreToCMSModel(docSnap.data());
1256
- const path = getCMSPathFromFirestorePath(docSnap.ref.path);
1274
+ var createRowFromDocument = (docSnap) => {
1257
1275
  return {
1258
- id: docSnap.id,
1259
- path,
1260
- values,
1261
- databaseId
1276
+ ...firestoreToCMSModel(docSnap.data()),
1277
+ id: docSnap.id
1262
1278
  };
1263
1279
  };
1264
1280
  /**
@@ -1373,70 +1389,65 @@ function useFirebaseRTDBDelegate({ firebaseApp }) {
1373
1389
  let dbQuery = query$1(ref$1(getDatabase(firebaseApp), path));
1374
1390
  if (startAfter !== void 0) dbQuery = query$1(dbQuery, orderByKey(), startAt(String(startAfter)));
1375
1391
  if (limit !== void 0) dbQuery = query$1(dbQuery, limitToFirst(limit));
1376
- const snapshot = await get(dbQuery);
1377
- if (snapshot.exists()) return Object.entries(snapshot.val()).map(([id, values]) => ({
1378
- id,
1379
- path,
1380
- values: delegateToCMSModel(values)
1392
+ const entity = await get(dbQuery);
1393
+ if (entity.exists()) return Object.entries(entity.val()).map(([id, values]) => ({
1394
+ ...delegateToCMSModel(values),
1395
+ id
1381
1396
  }));
1382
1397
  return [];
1383
1398
  }, [firebaseApp]),
1384
1399
  listenCollection: useCallback(({ path, onUpdate }) => {
1385
1400
  if (!firebaseApp) throw new Error("Firebase app not provided");
1386
- const unsubscribe = onValue(ref$1(getDatabase(firebaseApp), path), (snapshot) => {
1387
- if (snapshot.exists()) onUpdate(Object.entries(snapshot.val()).map(([id, values]) => ({
1388
- id,
1389
- path,
1390
- values: delegateToCMSModel(values)
1401
+ const unsubscribe = onValue(ref$1(getDatabase(firebaseApp), path), (entity) => {
1402
+ if (entity.exists()) onUpdate(Object.entries(entity.val()).map(([id, values]) => ({
1403
+ ...delegateToCMSModel(values),
1404
+ id
1391
1405
  })));
1392
1406
  else onUpdate([]);
1393
1407
  });
1394
1408
  return () => unsubscribe();
1395
1409
  }, [firebaseApp]),
1396
- fetchEntity: useCallback(async ({ path, entityId }) => {
1410
+ fetchOne: useCallback(async ({ path, id }) => {
1397
1411
  if (!firebaseApp) throw new Error("Firebase app not provided");
1398
- const snapshot = await get(ref$1(getDatabase(firebaseApp), `${path}/${entityId}`));
1399
- if (snapshot.exists()) return {
1400
- id: entityId,
1401
- path,
1402
- values: delegateToCMSModel(snapshot.val())
1412
+ const entity = await get(ref$1(getDatabase(firebaseApp), `${path}/${id}`));
1413
+ if (entity.exists()) return {
1414
+ ...delegateToCMSModel(entity.val()),
1415
+ id
1403
1416
  };
1404
1417
  }, [firebaseApp]),
1405
- listenEntity: useCallback(({ path, entityId, onUpdate, onError }) => {
1418
+ listenOne: useCallback(({ path, id, onUpdate, onError }) => {
1406
1419
  if (!firebaseApp) throw new Error("Firebase app not provided");
1407
- const unsubscribe = onValue(ref$1(getDatabase(firebaseApp), `${path}/${entityId}`), (snapshot) => {
1408
- if (snapshot.exists()) onUpdate({
1409
- id: entityId,
1410
- path,
1411
- values: delegateToCMSModel(snapshot.val())
1420
+ const unsubscribe = onValue(ref$1(getDatabase(firebaseApp), `${path}/${id}`), (entity) => {
1421
+ if (entity.exists()) onUpdate({
1422
+ ...delegateToCMSModel(entity.val()),
1423
+ id
1412
1424
  });
1413
1425
  else onError?.(/* @__PURE__ */ new Error("Entity does not exist"));
1414
1426
  });
1415
1427
  return () => unsubscribe();
1416
1428
  }, [firebaseApp]),
1417
- saveEntity: useCallback(async ({ path, entityId, values }) => {
1429
+ save: useCallback(async ({ path, id, values }) => {
1418
1430
  if (!firebaseApp) throw new Error("Firebase app not provided");
1419
1431
  const database = getDatabase(firebaseApp);
1420
- const finalId = entityId ?? push(ref$1(database, path)).key;
1432
+ const finalId = id ?? push(ref$1(database, path)).key;
1421
1433
  if (!finalId) throw new Error("Could not generate a new id");
1422
1434
  const transformedValues = cmsToRTDBModel(values, database);
1423
1435
  await set(ref$1(database, `${path}/${finalId}`), transformedValues);
1424
1436
  return {
1425
- id: finalId,
1426
- path,
1427
- values
1437
+ ...values,
1438
+ id: finalId
1428
1439
  };
1429
1440
  }, [firebaseApp]),
1430
- deleteEntity: useCallback(async ({ entity }) => {
1441
+ delete: useCallback(async ({ row }) => {
1431
1442
  if (!firebaseApp) throw new Error("Firebase app not provided");
1432
- await remove(ref$1(getDatabase(firebaseApp), `${entity.path}/${entity.id}`));
1443
+ await remove(ref$1(getDatabase(firebaseApp), `${row.path}/${row.id}`));
1433
1444
  }, [firebaseApp]),
1434
- checkUniqueField: useCallback(async (slug, name, value, entityId) => {
1445
+ checkUniqueField: useCallback(async (slug, name, value, id) => {
1435
1446
  if (!firebaseApp) throw new Error("Firebase app not provided");
1436
- const snapshot = await get(query$1(ref$1(getDatabase(firebaseApp), slug), orderByChild(name), startAt(value), limitToFirst(1)));
1437
- if (!snapshot.exists()) return true;
1438
- const [key, entityValue] = Object.entries(snapshot.val())[0];
1439
- if (entityValue && typeof entityValue === "object" && entityValue[name] === value && key === entityId) return true;
1447
+ const entity = await get(query$1(ref$1(getDatabase(firebaseApp), slug), orderByChild(name), startAt(value), limitToFirst(1)));
1448
+ if (!entity.exists()) return true;
1449
+ const [key, entityValue] = Object.entries(entity.val())[0];
1450
+ if (entityValue && typeof entityValue === "object" && entityValue[name] === value && key === id) return true;
1440
1451
  return false;
1441
1452
  }, [firebaseApp]),
1442
1453
  isFilterCombinationValid: useCallback(({ path, filter, sortBy }) => {
@@ -1526,11 +1537,11 @@ function useBuildUserManagement({ authController, dataSourceDelegate, roles: rol
1526
1537
  setRolesLoading(true);
1527
1538
  return dataSourceDelegate.listenCollection?.({
1528
1539
  path: rolesPath,
1529
- onUpdate(entities) {
1540
+ onUpdate(rows) {
1530
1541
  setRolesError(void 0);
1531
- console.debug("Updating roles", entities);
1542
+ console.debug("Updating roles", rows);
1532
1543
  try {
1533
- const newRoles = entityToRoles(entities);
1544
+ const newRoles = rowsToRoles(rows);
1534
1545
  if (!deepEqual(newRoles, roles)) setRoles(newRoles);
1535
1546
  } catch (e) {
1536
1547
  setRoles([]);
@@ -1560,11 +1571,11 @@ function useBuildUserManagement({ authController, dataSourceDelegate, roles: rol
1560
1571
  setUsersLoading(true);
1561
1572
  return dataSourceDelegate.listenCollection?.({
1562
1573
  path: usersPath,
1563
- onUpdate(entities) {
1564
- console.debug("Updating users", entities);
1574
+ onUpdate(rows) {
1575
+ console.debug("Updating users", rows);
1565
1576
  setUsersError(void 0);
1566
1577
  try {
1567
- setUsersWithRoleIds(entitiesToUsers(entities));
1578
+ setUsersWithRoleIds(rowsToUsers(rows));
1568
1579
  } catch (e) {
1569
1580
  setUsersWithRoleIds([]);
1570
1581
  console.error("Error loading users", e);
@@ -1599,21 +1610,21 @@ function useBuildUserManagement({ authController, dataSourceDelegate, roles: rol
1599
1610
  ...userExists ? {} : { created_on: /* @__PURE__ */ new Date() }
1600
1611
  };
1601
1612
  if (userExists && userExists.uid !== user.uid) {
1602
- const entity = {
1613
+ const row = {
1603
1614
  values: {},
1604
1615
  path: usersPath,
1605
1616
  id: userExists.uid
1606
1617
  };
1607
- await dataSourceDelegate.deleteEntity({ entity }).then(() => {
1618
+ await dataSourceDelegate.delete({ row }).then(() => {
1608
1619
  console.debug("Deleted previous user", userExists);
1609
1620
  }).catch((e) => {
1610
1621
  console.error("Error deleting user", e);
1611
1622
  });
1612
1623
  }
1613
- return dataSourceDelegate.saveEntity({
1624
+ return dataSourceDelegate.save({
1614
1625
  status: "existing",
1615
1626
  path: usersPath,
1616
- entityId: email,
1627
+ id: email,
1617
1628
  values: removeUndefined(data)
1618
1629
  }).then(() => user);
1619
1630
  }, [usersPath, dataSourceDelegate?.initialised]);
@@ -1622,10 +1633,10 @@ function useBuildUserManagement({ authController, dataSourceDelegate, roles: rol
1622
1633
  if (!rolesPath) throw Error("useBuildUserManagement Firestore not initialised");
1623
1634
  console.debug("Persisting role", role);
1624
1635
  const { id, ...roleData } = role;
1625
- return dataSourceDelegate.saveEntity({
1636
+ return dataSourceDelegate.save({
1626
1637
  status: "existing",
1627
1638
  path: rolesPath,
1628
- entityId: id,
1639
+ id,
1629
1640
  values: removeUndefined(roleData)
1630
1641
  }).then(() => {});
1631
1642
  }, [rolesPath, dataSourceDelegate?.initialised]);
@@ -1634,24 +1645,24 @@ function useBuildUserManagement({ authController, dataSourceDelegate, roles: rol
1634
1645
  if (!usersPath) throw Error("useBuildUserManagement Firestore not initialised");
1635
1646
  console.debug("Deleting", user);
1636
1647
  const { uid } = user;
1637
- const entity = {
1648
+ const row = {
1638
1649
  path: usersPath,
1639
1650
  id: uid,
1640
1651
  values: {}
1641
1652
  };
1642
- await dataSourceDelegate.deleteEntity({ entity });
1653
+ await dataSourceDelegate.delete({ row });
1643
1654
  }, [usersPath, dataSourceDelegate?.initialised]);
1644
1655
  const deleteRole = useCallback(async (role) => {
1645
1656
  if (!dataSourceDelegate) throw Error("useBuildUserManagement Firebase not initialised");
1646
1657
  if (!rolesPath) throw Error("useBuildUserManagement Firestore not initialised");
1647
1658
  console.debug("Deleting", role);
1648
1659
  const { id } = role;
1649
- const entity = {
1660
+ const row = {
1650
1661
  path: rolesPath,
1651
1662
  id,
1652
1663
  values: {}
1653
1664
  };
1654
- await dataSourceDelegate.deleteEntity({ entity });
1665
+ await dataSourceDelegate.delete({ row });
1655
1666
  }, [rolesPath, dataSourceDelegate?.initialised]);
1656
1667
  const defineRolesFor = useCallback((user) => {
1657
1668
  if (!usersWithRoleIds) throw Error("Users not loaded");
@@ -1727,22 +1738,21 @@ function useBuildUserManagement({ authController, dataSourceDelegate, roles: rol
1727
1738
  } : null
1728
1739
  };
1729
1740
  }
1730
- var entitiesToUsers = (docs) => {
1731
- return docs.map((doc) => {
1732
- const data = doc.values;
1733
- const record = data;
1741
+ var rowsToUsers = (rows) => {
1742
+ return rows.map((row) => {
1743
+ const { id, ...data } = row;
1734
1744
  return {
1735
1745
  ...data,
1736
- uid: doc.id,
1737
- created_on: record.created_on,
1738
- updated_on: record.updated_on
1746
+ uid: id,
1747
+ created_on: data.created_on,
1748
+ updated_on: data.updated_on
1739
1749
  };
1740
1750
  });
1741
1751
  };
1742
- var entityToRoles = (entities) => {
1743
- return entities.map((doc) => ({
1744
- id: doc.id,
1745
- ...doc.values
1752
+ var rowsToRoles = (rows) => {
1753
+ return rows.map((row) => ({
1754
+ ...row,
1755
+ id: row.id
1746
1756
  }));
1747
1757
  };
1748
1758
  //#endregion
@@ -2570,7 +2580,12 @@ function RebaseFirebaseApp({ name, logo, logoDark, accessGate, collections, view
2570
2580
  authController,
2571
2581
  userConfigPersistence,
2572
2582
  dateTimeFormat,
2573
- driver: firestoreDelegate,
2583
+ dataSources: [{
2584
+ key: DEFAULT_DATA_SOURCE_KEY,
2585
+ engine: "firestore",
2586
+ transport: "direct",
2587
+ driver: firestoreDelegate
2588
+ }],
2574
2589
  storageSource,
2575
2590
  entityLinkBuilder: ({ entity }) => `https://console.firebase.google.com/project/${firebaseApp.options.projectId}/firestore/data/${entity.path}/${entity.id}`,
2576
2591
  locale,
@@ -2596,7 +2611,7 @@ function RebaseFirebaseApp({ name, logo, logoDark, accessGate, collections, view
2596
2611
  to: urlController.buildUrlCollectionPath(firstCollectionEntry.id),
2597
2612
  replace: true
2598
2613
  }) : /* @__PURE__ */ jsx(CenteredView, { children: "No home page or collections provided." });
2599
- component = /* @__PURE__ */ jsx(SideEntityProvider, { children: /* @__PURE__ */ jsx(RebaseRoutes, { children: /* @__PURE__ */ jsxs(Route, {
2614
+ component = /* @__PURE__ */ jsx(SidePanelProvider, { children: /* @__PURE__ */ jsx(RebaseRoutes, { children: /* @__PURE__ */ jsxs(Route, {
2600
2615
  element: /* @__PURE__ */ jsxs(Scaffold, {
2601
2616
  logo: usedLogo,
2602
2617
  autoOpenDrawer,