@rebasepro/firebase 0.21.2-canary.g1ea48be → 0.23.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/hooks/useFirestoreDriver.d.ts +27 -0
- package/dist/index.es.js +151 -80
- package/dist/index.es.js.map +1 -1
- package/package.json +35 -8
|
@@ -56,6 +56,33 @@ export declare function resolveOffsetWindow(limit: number | undefined, offset: n
|
|
|
56
56
|
fetchLimit: number | undefined;
|
|
57
57
|
skip: number;
|
|
58
58
|
};
|
|
59
|
+
/**
|
|
60
|
+
* The rows for the ids a text search returned, read once: in the order the
|
|
61
|
+
* search ranked them, windowed by `limit` and `offset`. An id whose document is
|
|
62
|
+
* gone is left out.
|
|
63
|
+
*/
|
|
64
|
+
export declare function readSearchResults({ ids, limit, offset, readOne }: {
|
|
65
|
+
ids: readonly string[];
|
|
66
|
+
limit?: number;
|
|
67
|
+
offset?: number;
|
|
68
|
+
readOne: (id: string) => Promise<Record<string, unknown> | undefined>;
|
|
69
|
+
}): Promise<Record<string, unknown>[]>;
|
|
70
|
+
/**
|
|
71
|
+
* The rows for the ids a text search returned, kept live: one document
|
|
72
|
+
* listener per id in the `limit`/`offset` window, and every change delivered
|
|
73
|
+
* as the whole list, in the order the search ranked the ids.
|
|
74
|
+
*
|
|
75
|
+
* The returned function cancels all of it, a search still in flight included:
|
|
76
|
+
* its listeners are then never opened.
|
|
77
|
+
*/
|
|
78
|
+
export declare function listenToSearchResults({ ids, limit, offset, listenOne, onUpdate, onError }: {
|
|
79
|
+
ids: Promise<readonly string[] | undefined>;
|
|
80
|
+
limit?: number;
|
|
81
|
+
offset?: number;
|
|
82
|
+
listenOne: (id: string, onRow: (row: Record<string, unknown> | null) => void, onError?: (error: Error) => void) => () => void;
|
|
83
|
+
onUpdate: (rows: Record<string, unknown>[]) => void;
|
|
84
|
+
onError?: (error: Error) => void;
|
|
85
|
+
}): () => void;
|
|
59
86
|
/**
|
|
60
87
|
* Use this hook to build a {@link DataDriver} based on Firestore
|
|
61
88
|
* @param firebaseApp
|
package/dist/index.es.js
CHANGED
|
@@ -9,7 +9,7 @@ import { COLLECTION_PATH_SEPARATOR, buildRebaseData, normalizeDriverOrderBy, sor
|
|
|
9
9
|
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";
|
|
10
10
|
import Fuse from "fuse.js";
|
|
11
11
|
import { getFunctions, httpsCallable } from "firebase/functions";
|
|
12
|
-
import { endAt, equalTo, get, getDatabase, limitToFirst, onValue, orderByChild, orderByKey, push, query as query$1, ref as ref$1, remove, set, startAfter as startAfter$1, startAt } from "firebase/database";
|
|
12
|
+
import { endAt, equalTo, get, getDatabase, limitToFirst, onValue, orderByChild, orderByKey, push, query as query$1, ref as ref$1, remove, set, startAfter as startAfter$1, startAt, update } 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/app";
|
|
15
15
|
import { AppBar, CollectionRegistryContext, Drawer, NavigationStateContext, RebaseRoute, Scaffold, SideDialogs, SidePanelProvider, UrlContext, useBuildCollectionRegistryController, useBuildNavigationStateController, useBuildUrlController } from "@rebasepro/cms";
|
|
@@ -533,7 +533,10 @@ var docsToCollectionTree = (docs) => {
|
|
|
533
533
|
if (id.includes(COLLECTION_PATH_SEPARATOR)) {
|
|
534
534
|
const parentId = id.split(COLLECTION_PATH_SEPARATOR).slice(0, -1).join(COLLECTION_PATH_SEPARATOR);
|
|
535
535
|
const parentCollection = collectionsMap[parentId];
|
|
536
|
-
if (parentCollection)
|
|
536
|
+
if (parentCollection) {
|
|
537
|
+
const siblings = parentCollection.subcollections;
|
|
538
|
+
parentCollection.subcollections = () => [...siblings?.() ?? [], collection];
|
|
539
|
+
}
|
|
537
540
|
delete collectionsMap[id];
|
|
538
541
|
}
|
|
539
542
|
});
|
|
@@ -984,6 +987,53 @@ function resolveOffsetWindow(limit, offset) {
|
|
|
984
987
|
};
|
|
985
988
|
}
|
|
986
989
|
/**
|
|
990
|
+
* The rows for the ids a text search returned, read once: in the order the
|
|
991
|
+
* search ranked them, windowed by `limit` and `offset`. An id whose document is
|
|
992
|
+
* gone is left out.
|
|
993
|
+
*/
|
|
994
|
+
async function readSearchResults({ ids, limit, offset, readOne }) {
|
|
995
|
+
const { fetchLimit, skip } = resolveOffsetWindow(limit, offset);
|
|
996
|
+
return (await Promise.all(ids.slice(skip, fetchLimit).map(readOne))).filter((row) => row !== void 0);
|
|
997
|
+
}
|
|
998
|
+
/**
|
|
999
|
+
* The rows for the ids a text search returned, kept live: one document
|
|
1000
|
+
* listener per id in the `limit`/`offset` window, and every change delivered
|
|
1001
|
+
* as the whole list, in the order the search ranked the ids.
|
|
1002
|
+
*
|
|
1003
|
+
* The returned function cancels all of it, a search still in flight included:
|
|
1004
|
+
* its listeners are then never opened.
|
|
1005
|
+
*/
|
|
1006
|
+
function listenToSearchResults({ ids, limit, offset, listenOne, onUpdate, onError }) {
|
|
1007
|
+
let cancelled = false;
|
|
1008
|
+
let subscriptions = [];
|
|
1009
|
+
ids.then((found) => {
|
|
1010
|
+
if (cancelled) return;
|
|
1011
|
+
const { fetchLimit, skip } = resolveOffsetWindow(limit, offset);
|
|
1012
|
+
const windowIds = (found ?? []).slice(skip, fetchLimit);
|
|
1013
|
+
if (windowIds.length === 0) {
|
|
1014
|
+
onUpdate([]);
|
|
1015
|
+
return;
|
|
1016
|
+
}
|
|
1017
|
+
const rows = /* @__PURE__ */ new Map();
|
|
1018
|
+
const emit = () => onUpdate(windowIds.flatMap((id) => {
|
|
1019
|
+
const row = rows.get(id);
|
|
1020
|
+
return row ? [row] : [];
|
|
1021
|
+
}));
|
|
1022
|
+
subscriptions = windowIds.map((id) => listenOne(id, (row) => {
|
|
1023
|
+
if (row) rows.set(id, row);
|
|
1024
|
+
else rows.delete(id);
|
|
1025
|
+
emit();
|
|
1026
|
+
}, onError));
|
|
1027
|
+
}).catch((error) => {
|
|
1028
|
+
if (cancelled) return;
|
|
1029
|
+
onError?.(error instanceof Error ? error : new Error(String(error)));
|
|
1030
|
+
});
|
|
1031
|
+
return () => {
|
|
1032
|
+
cancelled = true;
|
|
1033
|
+
subscriptions.forEach((unsubscribe) => unsubscribe());
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
/**
|
|
987
1037
|
* Use this hook to build a {@link DataDriver} based on Firestore
|
|
988
1038
|
* @param firebaseApp
|
|
989
1039
|
* @param textSearchControllerBuilder
|
|
@@ -1044,11 +1094,11 @@ function useFirestoreDriver({ firebaseApp, textSearchControllerBuilder, firestor
|
|
|
1044
1094
|
error: onError
|
|
1045
1095
|
});
|
|
1046
1096
|
}, [firebaseApp]);
|
|
1047
|
-
|
|
1097
|
+
/** The ids the text search controller finds for `searchString` under `path`. */
|
|
1098
|
+
const searchIds = useCallback((path, searchString, databaseId) => {
|
|
1048
1099
|
if (!firebaseApp) throw Error("useFirestoreDriver Firebase not initialised");
|
|
1049
1100
|
const textSearchController = searchControllerRef.current;
|
|
1050
1101
|
if (!textSearchController) throw Error("Trying to make text search without specifying a FirestoreTextSearchController");
|
|
1051
|
-
let subscriptions = [];
|
|
1052
1102
|
const currentUser = getAuth(firebaseApp)?.currentUser;
|
|
1053
1103
|
const search = textSearchController.search({
|
|
1054
1104
|
path,
|
|
@@ -1057,37 +1107,24 @@ function useFirestoreDriver({ firebaseApp, textSearchControllerBuilder, firestor
|
|
|
1057
1107
|
databaseId
|
|
1058
1108
|
});
|
|
1059
1109
|
if (!search) throw Error("The current path is not supported by the specified FirestoreTextSearchController");
|
|
1060
|
-
search
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
rows.push(row);
|
|
1077
|
-
onUpdate(rows);
|
|
1078
|
-
}
|
|
1079
|
-
} else {
|
|
1080
|
-
addedEntitiesSet.delete(id);
|
|
1081
|
-
onUpdate([...rows.filter((r) => r.id !== id)]);
|
|
1082
|
-
}
|
|
1083
|
-
}
|
|
1084
|
-
});
|
|
1085
|
-
});
|
|
1110
|
+
return search;
|
|
1111
|
+
}, [firebaseApp]);
|
|
1112
|
+
const performTextSearch = useCallback(({ path, collection, searchString, limit, offset, onUpdate, onError }) => {
|
|
1113
|
+
return listenToSearchResults({
|
|
1114
|
+
ids: searchIds(path, searchString, collection?.databaseId),
|
|
1115
|
+
limit,
|
|
1116
|
+
offset,
|
|
1117
|
+
listenOne: (id, onRow, onRowError) => listenOne({
|
|
1118
|
+
path,
|
|
1119
|
+
id,
|
|
1120
|
+
collection,
|
|
1121
|
+
onUpdate: onRow,
|
|
1122
|
+
onError: onRowError
|
|
1123
|
+
}),
|
|
1124
|
+
onUpdate,
|
|
1125
|
+
onError
|
|
1086
1126
|
});
|
|
1087
|
-
|
|
1088
|
-
subscriptions.forEach((p) => p());
|
|
1089
|
-
};
|
|
1090
|
-
}, [firebaseApp, listenOne]);
|
|
1127
|
+
}, [searchIds, listenOne]);
|
|
1091
1128
|
const initTextSearch = useCallback(async (props) => {
|
|
1092
1129
|
console.debug("Init text search controller", searchControllerRef.current, props.path);
|
|
1093
1130
|
if (!searchControllerRef.current) {
|
|
@@ -1128,9 +1165,19 @@ function useFirestoreDriver({ firebaseApp, textSearchControllerBuilder, firestor
|
|
|
1128
1165
|
orderBy,
|
|
1129
1166
|
order
|
|
1130
1167
|
});
|
|
1168
|
+
if (searchString) return readSearchResults({
|
|
1169
|
+
ids: await searchIds(resolvedPath, searchString, databaseId) ?? [],
|
|
1170
|
+
limit,
|
|
1171
|
+
offset,
|
|
1172
|
+
readOne: (id) => getAndBuildEntity(resolvedPath, id, databaseId)
|
|
1173
|
+
});
|
|
1131
1174
|
const { fetchLimit, skip } = resolveOffsetWindow(limit, offset);
|
|
1132
1175
|
return (await getDocs(buildQuery(resolvedPath, filter, orderBy, order, startAfter, fetchLimit, databaseId))).docs.slice(skip).map((doc) => createRowFromDocument(doc));
|
|
1133
|
-
}, [
|
|
1176
|
+
}, [
|
|
1177
|
+
buildQuery,
|
|
1178
|
+
searchIds,
|
|
1179
|
+
getAndBuildEntity
|
|
1180
|
+
]);
|
|
1134
1181
|
/**
|
|
1135
1182
|
* Listen to a entities in a given path
|
|
1136
1183
|
* @param path
|
|
@@ -1147,7 +1194,7 @@ function useFirestoreDriver({ firebaseApp, textSearchControllerBuilder, firestor
|
|
|
1147
1194
|
* @see useCollection if you need this functionality implemented as a hook
|
|
1148
1195
|
* @group Firestore
|
|
1149
1196
|
*/
|
|
1150
|
-
const listenCollection = useCallback(({ path, filter, limit, startAfter, searchString, orderBy, order, onUpdate, onError, collection }) => {
|
|
1197
|
+
const listenCollection = useCallback(({ path, filter, limit, offset, startAfter, searchString, orderBy, order, onUpdate, onError, collection }) => {
|
|
1151
1198
|
console.debug("Listening collection", {
|
|
1152
1199
|
path,
|
|
1153
1200
|
searchString,
|
|
@@ -1162,18 +1209,22 @@ function useFirestoreDriver({ firebaseApp, textSearchControllerBuilder, firestor
|
|
|
1162
1209
|
const databaseId = collection?.databaseId;
|
|
1163
1210
|
if (searchString) return performTextSearch({
|
|
1164
1211
|
path,
|
|
1212
|
+
collection,
|
|
1165
1213
|
searchString,
|
|
1214
|
+
limit,
|
|
1215
|
+
offset,
|
|
1166
1216
|
onUpdate,
|
|
1167
|
-
|
|
1217
|
+
onError
|
|
1168
1218
|
});
|
|
1169
1219
|
const resolvedPath = path;
|
|
1170
1220
|
console.debug("Resolved path for listening", {
|
|
1171
1221
|
path,
|
|
1172
1222
|
resolvedPath
|
|
1173
1223
|
});
|
|
1174
|
-
|
|
1224
|
+
const { fetchLimit, skip } = resolveOffsetWindow(limit, offset);
|
|
1225
|
+
return onSnapshot(buildQuery(resolvedPath, filter, orderBy, order, startAfter, fetchLimit, databaseId), {
|
|
1175
1226
|
next: (entity) => {
|
|
1176
|
-
|
|
1227
|
+
onUpdate(entity.docs.slice(skip).map((doc) => createRowFromDocument(doc)));
|
|
1177
1228
|
},
|
|
1178
1229
|
error: onError
|
|
1179
1230
|
});
|
|
@@ -1267,11 +1318,17 @@ function useFirestoreDriver({ firebaseApp, textSearchControllerBuilder, firestor
|
|
|
1267
1318
|
if (value === void 0 || value === null) return Promise.resolve(true);
|
|
1268
1319
|
return (await getDocs(query(collection(firestore, path), where(name, "==", rebaseToFirestoreModel(value, firestore))))).docs.filter((doc) => doc.id !== id).length === 0;
|
|
1269
1320
|
}, [firebaseApp]);
|
|
1270
|
-
const count = useCallback(async ({ path, filter, order, orderBy, collection }) => {
|
|
1321
|
+
const count = useCallback(async ({ path, filter, order, orderBy, searchString, collection }) => {
|
|
1271
1322
|
if (!firebaseApp) throw Error("useFirestoreDriver Firebase not initialised");
|
|
1272
1323
|
const databaseId = collection?.databaseId;
|
|
1273
|
-
|
|
1274
|
-
|
|
1324
|
+
const resolvedPath = path;
|
|
1325
|
+
if (searchString) return (await searchIds(resolvedPath, searchString, databaseId) ?? []).length;
|
|
1326
|
+
return (await getCountFromServer(buildQuery(resolvedPath, filter, orderBy, order, void 0, void 0, databaseId))).data().count;
|
|
1327
|
+
}, [
|
|
1328
|
+
firebaseApp,
|
|
1329
|
+
buildQuery,
|
|
1330
|
+
searchIds
|
|
1331
|
+
]);
|
|
1275
1332
|
const isFilterCombinationValid = useCallback(({ path, collection, filterValues, sortBy }) => {
|
|
1276
1333
|
if (!firebaseApp) throw Error("useFirestoreDriver Firebase not initialised");
|
|
1277
1334
|
if (firestoreIndexesBuilder === void 0) return true;
|
|
@@ -1374,7 +1431,7 @@ function getCMSPathFromFirestorePath(fsPath) {
|
|
|
1374
1431
|
return fsPath.substring(0, to);
|
|
1375
1432
|
}
|
|
1376
1433
|
function rebaseToFirestoreModel(data, firestore, inArray = false) {
|
|
1377
|
-
if (data === void 0) return deleteField();
|
|
1434
|
+
if (data === void 0) return inArray ? void 0 : deleteField();
|
|
1378
1435
|
else if (data === null) return null;
|
|
1379
1436
|
else if (Array.isArray(data)) return data.filter((v) => v !== void 0).map((v) => rebaseToFirestoreModel(v, firestore, true));
|
|
1380
1437
|
else if (typeof data === "object" && data !== null && "isEntityReference" in data && typeof data.isEntityReference === "function" && data.isEntityReference()) {
|
|
@@ -1387,7 +1444,7 @@ function rebaseToFirestoreModel(data, firestore, inArray = false) {
|
|
|
1387
1444
|
else if (data instanceof Date) return Timestamp.fromDate(data);
|
|
1388
1445
|
else if (data && typeof data === "object" && "__type__" in data && data.__type__ === "__vector__") return vector(data.value || []);
|
|
1389
1446
|
else if (data && typeof data === "object") return Object.entries(data).map(([key, v]) => {
|
|
1390
|
-
const firestoreModel = rebaseToFirestoreModel(v, firestore);
|
|
1447
|
+
const firestoreModel = rebaseToFirestoreModel(v, firestore, inArray);
|
|
1391
1448
|
if (firestoreModel !== void 0) return { [key]: firestoreModel };
|
|
1392
1449
|
else return {};
|
|
1393
1450
|
}).reduce((a, b) => ({
|
|
@@ -1492,6 +1549,25 @@ function rtdbConstraints(plan) {
|
|
|
1492
1549
|
if (plan.limitToFirst !== void 0) constraints.push(limitToFirst(plan.limitToFirst));
|
|
1493
1550
|
return constraints;
|
|
1494
1551
|
}
|
|
1552
|
+
/**
|
|
1553
|
+
* The rows of a query result, in the query's order, with the first `skip` dropped.
|
|
1554
|
+
*
|
|
1555
|
+
* `snapshot.val()` is a plain object, and an object's keys come back in key
|
|
1556
|
+
* order (integer-like keys first), not in the order the query sorted the
|
|
1557
|
+
* children. Read that way, `orderBy` was lost and an offset page dropped rows
|
|
1558
|
+
* by key rather than by position — page two of a list ordered by `age` held
|
|
1559
|
+
* whichever rows sorted last by id. `forEach` walks the children in query order.
|
|
1560
|
+
*/
|
|
1561
|
+
function rowsFromSnapshot(snapshot, skip) {
|
|
1562
|
+
const rows = [];
|
|
1563
|
+
snapshot.forEach((child) => {
|
|
1564
|
+
rows.push({
|
|
1565
|
+
...delegateToCMSModel(child.val()),
|
|
1566
|
+
id: child.key
|
|
1567
|
+
});
|
|
1568
|
+
});
|
|
1569
|
+
return rows.slice(skip);
|
|
1570
|
+
}
|
|
1495
1571
|
function useFirebaseRTDBDelegate({ firebaseApp }) {
|
|
1496
1572
|
return {
|
|
1497
1573
|
key: "firebase_rtdb",
|
|
@@ -1499,27 +1575,14 @@ function useFirebaseRTDBDelegate({ firebaseApp }) {
|
|
|
1499
1575
|
if (!firebaseApp) throw new Error("Firebase app not provided");
|
|
1500
1576
|
const database = getDatabase(firebaseApp);
|
|
1501
1577
|
const plan = planRTDBQuery(props);
|
|
1502
|
-
|
|
1503
|
-
if (entity.exists()) return Object.entries(entity.val()).slice(plan.skip ?? 0).map(([id, values]) => ({
|
|
1504
|
-
...delegateToCMSModel(values),
|
|
1505
|
-
id
|
|
1506
|
-
}));
|
|
1507
|
-
return [];
|
|
1578
|
+
return rowsFromSnapshot(await get(query$1(ref$1(database, props.path), ...rtdbConstraints(plan))), plan.skip ?? 0);
|
|
1508
1579
|
}, [firebaseApp]),
|
|
1509
1580
|
listenCollection: useCallback((props) => {
|
|
1510
1581
|
if (!firebaseApp) throw new Error("Firebase app not provided");
|
|
1511
1582
|
const database = getDatabase(firebaseApp);
|
|
1512
1583
|
const { onUpdate, onError } = props;
|
|
1513
1584
|
const plan = planRTDBQuery(props);
|
|
1514
|
-
const unsubscribe = onValue(query$1(ref$1(database, props.path), ...rtdbConstraints(plan)), (entity) =>
|
|
1515
|
-
if (entity.exists()) {
|
|
1516
|
-
const result = Object.entries(entity.val()).slice(plan.skip ?? 0).map(([id, values]) => ({
|
|
1517
|
-
...delegateToCMSModel(values),
|
|
1518
|
-
id
|
|
1519
|
-
}));
|
|
1520
|
-
onUpdate(result);
|
|
1521
|
-
} else onUpdate([]);
|
|
1522
|
-
}, (error) => onError?.(error));
|
|
1585
|
+
const unsubscribe = onValue(query$1(ref$1(database, props.path), ...rtdbConstraints(plan)), (entity) => onUpdate(rowsFromSnapshot(entity, plan.skip ?? 0)), (error) => onError?.(error));
|
|
1523
1586
|
return () => unsubscribe();
|
|
1524
1587
|
}, [firebaseApp]),
|
|
1525
1588
|
fetchOne: useCallback(async ({ path, id }) => {
|
|
@@ -1533,21 +1596,22 @@ function useFirebaseRTDBDelegate({ firebaseApp }) {
|
|
|
1533
1596
|
listenOne: useCallback(({ path, id, onUpdate, onError }) => {
|
|
1534
1597
|
if (!firebaseApp) throw new Error("Firebase app not provided");
|
|
1535
1598
|
const unsubscribe = onValue(ref$1(getDatabase(firebaseApp), `${path}/${id}`), (entity) => {
|
|
1536
|
-
|
|
1599
|
+
onUpdate(entity.exists() ? {
|
|
1537
1600
|
...delegateToCMSModel(entity.val()),
|
|
1538
1601
|
id
|
|
1539
|
-
});
|
|
1540
|
-
|
|
1541
|
-
});
|
|
1602
|
+
} : null);
|
|
1603
|
+
}, (error) => onError?.(error));
|
|
1542
1604
|
return () => unsubscribe();
|
|
1543
1605
|
}, [firebaseApp]),
|
|
1544
|
-
save: useCallback(async ({ path, id, values }) => {
|
|
1606
|
+
save: useCallback(async ({ path, id, values, status }) => {
|
|
1545
1607
|
if (!firebaseApp) throw new Error("Firebase app not provided");
|
|
1546
1608
|
const database = getDatabase(firebaseApp);
|
|
1547
1609
|
const finalId = id ?? push(ref$1(database, path)).key;
|
|
1548
1610
|
if (!finalId) throw new Error("Could not generate a new id");
|
|
1549
|
-
const transformedValues =
|
|
1550
|
-
|
|
1611
|
+
const transformedValues = cmsToRTDBRecord(values, database);
|
|
1612
|
+
const rowRef = ref$1(database, `${path}/${finalId}`);
|
|
1613
|
+
if (status === "existing") await update(rowRef, transformedValues);
|
|
1614
|
+
else await set(rowRef, transformedValues);
|
|
1551
1615
|
return {
|
|
1552
1616
|
...values,
|
|
1553
1617
|
id: finalId
|
|
@@ -1559,11 +1623,19 @@ function useFirebaseRTDBDelegate({ firebaseApp }) {
|
|
|
1559
1623
|
}, [firebaseApp]),
|
|
1560
1624
|
checkUniqueField: useCallback(async (slug, name, value, id) => {
|
|
1561
1625
|
if (!firebaseApp) throw new Error("Firebase app not provided");
|
|
1562
|
-
const
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
if (
|
|
1566
|
-
|
|
1626
|
+
const database = getDatabase(firebaseApp);
|
|
1627
|
+
const stored = cmsToRTDBModel(value, database);
|
|
1628
|
+
if (stored === null) return true;
|
|
1629
|
+
if (!isRTDBValue(stored)) throw new Error(`${RTDB}: cannot check \`${name}\` for uniqueness by a ${Array.isArray(stored) ? "array" : typeof stored} value; the Realtime Database compares strings, numbers and booleans.`);
|
|
1630
|
+
const matches = await get(query$1(ref$1(database, slug), orderByChild(name), equalTo(stored), limitToFirst(2)));
|
|
1631
|
+
let unique = true;
|
|
1632
|
+
matches.forEach((child) => {
|
|
1633
|
+
if (id === void 0 || child.key !== String(id)) {
|
|
1634
|
+
unique = false;
|
|
1635
|
+
return true;
|
|
1636
|
+
}
|
|
1637
|
+
});
|
|
1638
|
+
return unique;
|
|
1567
1639
|
}, [firebaseApp]),
|
|
1568
1640
|
isFilterCombinationValid: useCallback(({ path, filter, sortBy }) => {
|
|
1569
1641
|
return false;
|
|
@@ -1600,16 +1672,15 @@ function cmsToRTDBModel(data, database) {
|
|
|
1600
1672
|
const entityRef = data;
|
|
1601
1673
|
return ref$1(database, `${entityRef.slug}/${entityRef.id}`);
|
|
1602
1674
|
} else if (data instanceof Date) return data.toISOString();
|
|
1603
|
-
else if (data && typeof data === "object") return
|
|
1604
|
-
const rtdbModel = cmsToRTDBModel(v, database);
|
|
1605
|
-
if (rtdbModel !== void 0) return { [key]: rtdbModel };
|
|
1606
|
-
else return {};
|
|
1607
|
-
}).reduce((a, b) => ({
|
|
1608
|
-
...a,
|
|
1609
|
-
...b
|
|
1610
|
-
}), {});
|
|
1675
|
+
else if (data && typeof data === "object") return cmsToRTDBRecord(data, database);
|
|
1611
1676
|
return data;
|
|
1612
1677
|
}
|
|
1678
|
+
/** {@link cmsToRTDBModel} for a map of values: each child converted, keys kept. */
|
|
1679
|
+
function cmsToRTDBRecord(data, database) {
|
|
1680
|
+
const result = {};
|
|
1681
|
+
for (const [key, v] of Object.entries(data)) result[key] = cmsToRTDBModel(v, database);
|
|
1682
|
+
return result;
|
|
1683
|
+
}
|
|
1613
1684
|
//#endregion
|
|
1614
1685
|
//#region src/hooks/useRecaptcha.tsx
|
|
1615
1686
|
var RECAPTCHA_CONTAINER_ID = "recaptcha-container";
|
|
@@ -2789,6 +2860,6 @@ function RebaseFirebaseApp({ name, logo, logoDark, accessGate, collections, view
|
|
|
2789
2860
|
}) });
|
|
2790
2861
|
}
|
|
2791
2862
|
//#endregion
|
|
2792
|
-
export { FirebaseLoginView, LoginButton, RECAPTCHA_CONTAINER_ID, RebaseFirebaseApp, buildCollectionId, buildExternalSearchController, buildPineconeSearchController, buildRebaseSearchController, docToCollection, docsToCollectionTree, firestoreToRebaseModel, getFirestoreDataInPath, localSearchControllerBuilder, performAlgoliaTextSearch, performPineconeTextSearch, planRTDBQuery, rebaseToFirestoreModel, resolveAccessDecision, resolveOffsetWindow, resolveRoleRefresh, useAppCheck, useBuildUserManagement, useFirebaseAccessGate, useFirebaseAuthController, useFirebaseRTDBDelegate, useFirebaseStorageSource, useFirestoreDriver, useInitialiseFirebase, useRecaptcha };
|
|
2863
|
+
export { FirebaseLoginView, LoginButton, RECAPTCHA_CONTAINER_ID, RebaseFirebaseApp, buildCollectionId, buildExternalSearchController, buildPineconeSearchController, buildRebaseSearchController, docToCollection, docsToCollectionTree, firestoreToRebaseModel, getFirestoreDataInPath, listenToSearchResults, localSearchControllerBuilder, performAlgoliaTextSearch, performPineconeTextSearch, planRTDBQuery, readSearchResults, rebaseToFirestoreModel, resolveAccessDecision, resolveOffsetWindow, resolveRoleRefresh, useAppCheck, useBuildUserManagement, useFirebaseAccessGate, useFirebaseAuthController, useFirebaseRTDBDelegate, useFirebaseStorageSource, useFirestoreDriver, useInitialiseFirebase, useRecaptcha };
|
|
2793
2864
|
|
|
2794
2865
|
//# sourceMappingURL=index.es.js.map
|