@rebasepro/client-firebase 0.0.1-canary.09e5ec5

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.
Files changed (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +4 -0
  3. package/dist/components/FirebaseLoginView.d.ts +72 -0
  4. package/dist/components/RebaseFirebaseApp.d.ts +19 -0
  5. package/dist/components/RebaseFirebaseAppProps.d.ts +144 -0
  6. package/dist/components/index.d.ts +3 -0
  7. package/dist/components/social_icons.d.ts +6 -0
  8. package/dist/hooks/index.d.ts +8 -0
  9. package/dist/hooks/useAppCheck.d.ts +20 -0
  10. package/dist/hooks/useBuildUserManagement.d.ts +46 -0
  11. package/dist/hooks/useFirebaseAuthController.d.ts +15 -0
  12. package/dist/hooks/useFirebaseRealTimeDBDelegate.d.ts +5 -0
  13. package/dist/hooks/useFirebaseStorageSource.d.ts +14 -0
  14. package/dist/hooks/useFirestoreDriver.d.ts +56 -0
  15. package/dist/hooks/useInitialiseFirebase.d.ts +34 -0
  16. package/dist/hooks/useRecaptcha.d.ts +8 -0
  17. package/dist/index.d.ts +4 -0
  18. package/dist/index.es.js +3060 -0
  19. package/dist/index.es.js.map +1 -0
  20. package/dist/index.umd.js +3043 -0
  21. package/dist/index.umd.js.map +1 -0
  22. package/dist/social_icons.d.ts +6 -0
  23. package/dist/types/appcheck.d.ts +10 -0
  24. package/dist/types/auth.d.ts +41 -0
  25. package/dist/types/index.d.ts +3 -0
  26. package/dist/types/text_search.d.ts +39 -0
  27. package/dist/utils/algolia.d.ts +9 -0
  28. package/dist/utils/collections_firestore.d.ts +5 -0
  29. package/dist/utils/database.d.ts +2 -0
  30. package/dist/utils/index.d.ts +7 -0
  31. package/dist/utils/local_text_search_controller.d.ts +2 -0
  32. package/dist/utils/pinecone.d.ts +24 -0
  33. package/dist/utils/rebase_search_controller.d.ts +73 -0
  34. package/dist/utils/text_search_controller.d.ts +13 -0
  35. package/package.json +63 -0
  36. package/src/components/FirebaseLoginView.tsx +693 -0
  37. package/src/components/RebaseFirebaseApp.tsx +291 -0
  38. package/src/components/RebaseFirebaseAppProps.tsx +180 -0
  39. package/src/components/index.ts +3 -0
  40. package/src/components/social_icons.tsx +135 -0
  41. package/src/hooks/index.ts +8 -0
  42. package/src/hooks/useAppCheck.ts +101 -0
  43. package/src/hooks/useBuildUserManagement.tsx +374 -0
  44. package/src/hooks/useFirebaseAuthController.ts +334 -0
  45. package/src/hooks/useFirebaseRealTimeDBDelegate.ts +269 -0
  46. package/src/hooks/useFirebaseStorageSource.ts +207 -0
  47. package/src/hooks/useFirestoreDriver.ts +784 -0
  48. package/src/hooks/useInitialiseFirebase.ts +132 -0
  49. package/src/hooks/useRecaptcha.tsx +28 -0
  50. package/src/index.ts +4 -0
  51. package/src/social_icons.tsx +135 -0
  52. package/src/types/appcheck.ts +11 -0
  53. package/src/types/auth.tsx +74 -0
  54. package/src/types/index.ts +3 -0
  55. package/src/types/text_search.ts +42 -0
  56. package/src/utils/algolia.ts +27 -0
  57. package/src/utils/collections_firestore.ts +148 -0
  58. package/src/utils/database.ts +39 -0
  59. package/src/utils/index.ts +7 -0
  60. package/src/utils/local_text_search_controller.ts +143 -0
  61. package/src/utils/pinecone.ts +75 -0
  62. package/src/utils/rebase_search_controller.ts +357 -0
  63. package/src/utils/text_search_controller.ts +34 -0
@@ -0,0 +1,334 @@
1
+ import { useCallback, useEffect, useRef, useState } from "react";
2
+ import { deepEqual as equal } from "fast-equals"
3
+
4
+ import {
5
+ ApplicationVerifier,
6
+ Auth,
7
+ ConfirmationResult,
8
+ createUserWithEmailAndPassword as createUserWithEmailAndPasswordFirebase,
9
+ FacebookAuthProvider,
10
+ fetchSignInMethodsForEmail as fetchSignInMethodsForEmailFirebase,
11
+ getAuth,
12
+ GithubAuthProvider,
13
+ GoogleAuthProvider,
14
+ OAuthProvider,
15
+ onAuthStateChanged,
16
+ sendPasswordResetEmail as sendPasswordResetEmailFirebase,
17
+ signInAnonymously,
18
+ signInWithEmailAndPassword,
19
+ signInWithPhoneNumber,
20
+ signInWithPopup,
21
+ signOut,
22
+ TwitterAuthProvider,
23
+ User as FirebaseUser
24
+ } from "@firebase/auth";
25
+ import { FirebaseApp } from "@firebase/app";
26
+ import { FirebaseAuthController, FirebaseSignInOption, FirebaseSignInProvider, FirebaseUserWrapper } from "../types";
27
+ import { Role, User } from "@rebasepro/types";
28
+
29
+ export interface FirebaseAuthControllerProps {
30
+ loading?: boolean;
31
+ firebaseApp?: FirebaseApp;
32
+ signInOptions?: Array<FirebaseSignInProvider | FirebaseSignInOption>;
33
+ onSignOut?: () => void;
34
+ defineRolesFor?: (user: User) => Promise<Role[] | undefined> | Role[] | undefined;
35
+ }
36
+
37
+ /**
38
+ * Use this hook to build an {@link AuthController} based on Firebase Auth
39
+ * @group Firebase
40
+ */
41
+ export const useFirebaseAuthController = <USER extends FirebaseUserWrapper = any, ExtraData = any>({
42
+ loading,
43
+ firebaseApp,
44
+ signInOptions,
45
+ onSignOut: onSignOutProp,
46
+ defineRolesFor
47
+ }: FirebaseAuthControllerProps): FirebaseAuthController<USER, ExtraData> => {
48
+
49
+ const [loggedUser, setLoggedUser] = useState<FirebaseUser | null | undefined>(undefined); // logged user, anonymous or logged out
50
+ const [authError, setAuthError] = useState<any>();
51
+ const [authProviderError, setAuthProviderError] = useState<any>();
52
+ const [initialLoading, setInitialLoading] = useState<boolean>(true);
53
+ const [authLoading, setAuthLoading] = useState(true);
54
+ const [loginSkipped, setLoginSkipped] = useState<boolean>(false);
55
+ const [confirmationResult, setConfirmationResult] = useState<undefined | ConfirmationResult>();
56
+ const [userRoles, _setUserRoles] = useState<Role[] | undefined>();
57
+ const [extra, setExtra] = useState<any>();
58
+
59
+ const setUserRoles = useCallback((roles: Role[] | undefined) => {
60
+ const currentRoleIds = userRoles?.map(r => r.id);
61
+ const newRoleIds = roles?.map(r => r.id);
62
+ if (!equal(currentRoleIds, newRoleIds)) {
63
+ _setUserRoles(roles);
64
+ }
65
+ }, [userRoles]);
66
+
67
+ const authRef = useRef<Auth | null>(null);
68
+
69
+ const updateUser = useCallback(async (user: FirebaseUser | null, initialize?: boolean) => {
70
+ if (loading) return;
71
+ if (defineRolesFor && user) {
72
+ setUserRoles(await defineRolesFor(user));
73
+ }
74
+ setLoggedUser(user);
75
+ setAuthLoading(false);
76
+ if (initialize) {
77
+ setInitialLoading(false);
78
+ }
79
+ }, [loading]);
80
+
81
+ const updateRoles = useCallback(async (user: User | null) => {
82
+ if (defineRolesFor && user) {
83
+ const userRoles = await defineRolesFor(user);
84
+ if (!equal(userRoles, userRoles)) {
85
+ setUserRoles(userRoles);
86
+ }
87
+ }
88
+ }, [defineRolesFor, userRoles]);
89
+
90
+ useEffect(() => {
91
+ if (updateRoles && loggedUser) {
92
+ updateRoles(loggedUser);
93
+ }
94
+ }, [updateRoles, loggedUser]);
95
+
96
+ useEffect(() => {
97
+ if (!firebaseApp) return;
98
+ try {
99
+ const auth = getAuth(firebaseApp);
100
+ authRef.current = auth;
101
+ setAuthError(undefined);
102
+ updateUser(auth.currentUser, false)
103
+ return onAuthStateChanged(
104
+ auth,
105
+ async (user) => {
106
+ console.debug("User state changed", user);
107
+ await updateUser(user, true);
108
+ },
109
+ error => setAuthProviderError(error)
110
+ );
111
+ } catch (e) {
112
+ setAuthError(e);
113
+ setInitialLoading(false);
114
+ return () => {
115
+ };
116
+ }
117
+ }, [firebaseApp, updateUser]);
118
+
119
+ useEffect(() => {
120
+ if (!loading && authRef.current) {
121
+ updateUser(authRef.current.currentUser, false);
122
+ }
123
+ }, [loading, updateUser]);
124
+
125
+ const getProviderOptions = useCallback((providerId: FirebaseSignInProvider): FirebaseSignInOption | undefined => {
126
+ return signInOptions?.find((option) => {
127
+ if (option === null) throw Error("useFirebaseAuthController");
128
+ if (typeof option === "object" && option.provider === providerId)
129
+ return option as FirebaseSignInOption;
130
+ return undefined;
131
+ }) as FirebaseSignInOption | undefined;
132
+ }, []);
133
+
134
+ const googleLogin = useCallback(() => {
135
+ const provider = new GoogleAuthProvider();
136
+ const options = getProviderOptions("google.com");
137
+ if (options?.scopes)
138
+ options.scopes.forEach((scope) => provider.addScope(scope));
139
+ if (options?.customParameters) {
140
+ provider.setCustomParameters(options.customParameters);
141
+ } else {
142
+ provider.setCustomParameters({
143
+ prompt: "select_account"
144
+ });
145
+ }
146
+ const auth = authRef.current;
147
+ if (!auth) throw Error("No auth");
148
+ signInWithPopup(auth, provider).catch(setAuthProviderError);
149
+ }, [getProviderOptions]);
150
+
151
+ const getAuthToken = useCallback(async (): Promise<string> => {
152
+ if (!loggedUser)
153
+ throw Error("No client user is logged in");
154
+ if (!loggedUser.getIdToken) {
155
+ throw Error("No getIdToken method available");
156
+ }
157
+ return loggedUser.getIdToken?.();
158
+ }, [loggedUser]);
159
+
160
+ const emailPasswordLogin = useCallback((email: string, password: string) => {
161
+ const auth = authRef.current;
162
+ if (!auth) throw Error("No auth");
163
+ setAuthLoading(true);
164
+ signInWithEmailAndPassword(auth, email, password)
165
+ .catch(setAuthProviderError)
166
+ .then(() => setAuthLoading(false));
167
+ }, []);
168
+
169
+ const createUserWithEmailAndPassword = useCallback((email: string, password: string) => {
170
+ const auth = authRef.current;
171
+ if (!auth) throw Error("No auth");
172
+ setAuthLoading(true);
173
+ createUserWithEmailAndPasswordFirebase(auth, email, password)
174
+ .catch(setAuthProviderError)
175
+ .then(() => setAuthLoading(false));
176
+ }, []);
177
+
178
+ const sendPasswordResetEmail = useCallback((email: string) => {
179
+ const auth = authRef.current;
180
+ if (!auth) throw Error("No auth");
181
+ return sendPasswordResetEmailFirebase(auth, email)
182
+ }, []);
183
+
184
+ const fetchSignInMethodsForEmail = useCallback((email: string): Promise<string[]> => {
185
+ const auth = authRef.current;
186
+ if (!auth) throw Error("No auth");
187
+ setAuthLoading(true);
188
+ return fetchSignInMethodsForEmailFirebase(auth, email)
189
+ .then((res) => {
190
+ setAuthLoading(false);
191
+ return res;
192
+ });
193
+ }, []);
194
+
195
+ const onSignOut = useCallback(async () => {
196
+ const auth = authRef.current;
197
+ if (!auth) throw Error("No auth");
198
+ await signOut(auth)
199
+ .then(_ => {
200
+ setLoggedUser(null);
201
+ setUserRoles(undefined);
202
+ setAuthProviderError(null);
203
+ onSignOutProp?.();
204
+ });
205
+ setLoginSkipped(false);
206
+ }, [onSignOutProp]);
207
+
208
+ const doOauthLogin = useCallback((auth: Auth, provider: OAuthProvider | FacebookAuthProvider | GithubAuthProvider | TwitterAuthProvider) => {
209
+ setAuthLoading(true);
210
+ signInWithPopup(auth, provider)
211
+ .catch(setAuthProviderError).then(() => setAuthLoading(false));
212
+ }, []);
213
+
214
+ const anonymousLogin = useCallback(() => {
215
+ const auth = authRef.current;
216
+ if (!auth) throw Error("No auth");
217
+ setAuthLoading(true);
218
+ signInAnonymously(auth)
219
+ .catch(setAuthProviderError)
220
+ .then(() => setAuthLoading(false));
221
+ }, []);
222
+
223
+ const phoneLogin = useCallback((phone: string, applicationVerifier: ApplicationVerifier) => {
224
+ const auth = authRef.current;
225
+ if (!auth) throw Error("No auth");
226
+ setAuthLoading(true);
227
+ return signInWithPhoneNumber(auth, phone, applicationVerifier)
228
+ .catch(setAuthProviderError)
229
+ .then((res) => {
230
+ setAuthLoading(false);
231
+ setConfirmationResult(res ?? undefined);
232
+ });
233
+ }, []);
234
+
235
+ const appleLogin = useCallback(() => {
236
+ const provider = new OAuthProvider("apple.com");
237
+ const options = getProviderOptions("apple.com");
238
+ if (options?.scopes)
239
+ options.scopes.forEach((scope) => provider.addScope(scope));
240
+ if (options?.customParameters)
241
+ provider.setCustomParameters(options.customParameters);
242
+ const auth = authRef.current;
243
+ if (!auth) throw Error("No auth");
244
+ doOauthLogin(auth, provider);
245
+ }, [doOauthLogin, getProviderOptions]);
246
+
247
+ const facebookLogin = useCallback(() => {
248
+ const provider = new FacebookAuthProvider();
249
+ const options = getProviderOptions("facebook.com");
250
+ if (options?.scopes)
251
+ options.scopes.forEach((scope) => provider.addScope(scope));
252
+ if (options?.customParameters)
253
+ provider.setCustomParameters(options.customParameters);
254
+ const auth = authRef.current;
255
+ if (!auth) throw Error("No auth");
256
+ doOauthLogin(auth, provider);
257
+ }, [doOauthLogin, getProviderOptions]);
258
+
259
+ const githubLogin = useCallback(() => {
260
+ const provider = new GithubAuthProvider();
261
+ const options = getProviderOptions("github.com");
262
+ if (options?.scopes)
263
+ options.scopes.forEach((scope) => provider.addScope(scope));
264
+ if (options?.customParameters)
265
+ provider.setCustomParameters(options.customParameters);
266
+ const auth = authRef.current;
267
+ if (!auth) throw Error("No auth");
268
+ doOauthLogin(auth, provider);
269
+ }, [doOauthLogin, getProviderOptions]);
270
+
271
+ const microsoftLogin = useCallback(() => {
272
+ const provider = new OAuthProvider("microsoft.com");
273
+ const options = getProviderOptions("microsoft.com");
274
+ if (options?.scopes)
275
+ options.scopes.forEach((scope) => provider.addScope(scope));
276
+ if (options?.customParameters)
277
+ provider.setCustomParameters(options.customParameters);
278
+ const auth = authRef.current;
279
+ if (!auth) throw Error("No auth");
280
+ doOauthLogin(auth, provider);
281
+ }, [doOauthLogin, getProviderOptions]);
282
+
283
+ const twitterLogin = useCallback(() => {
284
+ const provider = new TwitterAuthProvider();
285
+ const options = getProviderOptions("twitter.com");
286
+ if (options?.customParameters)
287
+ provider.setCustomParameters(options.customParameters);
288
+ const auth = authRef.current;
289
+ if (!auth) throw Error("No auth");
290
+ doOauthLogin(auth, provider);
291
+ }, [doOauthLogin, getProviderOptions]);
292
+
293
+ const skipLogin = useCallback(() => {
294
+ setLoginSkipped(true);
295
+ setLoggedUser(null);
296
+ setUserRoles(undefined);
297
+ }, []);
298
+
299
+ const firebaseUserWrapper: FirebaseUserWrapper | null = loggedUser
300
+ ? {
301
+ ...loggedUser,
302
+ roles: userRoles?.map(r => r.id), // User.roles is string[], keep Role[] internally only
303
+ firebaseUser: loggedUser
304
+ }
305
+ : null;
306
+
307
+ return {
308
+ user: firebaseUserWrapper as USER,
309
+ setUser: updateUser,
310
+ setUserRoles,
311
+ authProviderError,
312
+ authLoading,
313
+ initialLoading: loading || initialLoading,
314
+ signOut: onSignOut,
315
+ getAuthToken,
316
+ googleLogin,
317
+ skipLogin,
318
+ loginSkipped,
319
+ emailPasswordLogin,
320
+ createUserWithEmailAndPassword,
321
+ sendPasswordResetEmail,
322
+ fetchSignInMethodsForEmail,
323
+ anonymousLogin,
324
+ phoneLogin,
325
+ appleLogin,
326
+ facebookLogin,
327
+ githubLogin,
328
+ microsoftLogin,
329
+ twitterLogin,
330
+ confirmationResult,
331
+ extra,
332
+ setExtra
333
+ };
334
+ };
@@ -0,0 +1,269 @@
1
+ import { FirebaseApp } from "@firebase/app";
2
+ import {
3
+ Database,
4
+ get,
5
+ getDatabase,
6
+ limitToFirst,
7
+ onValue,
8
+ orderByChild,
9
+ orderByKey,
10
+ push,
11
+ query,
12
+ ref,
13
+ remove,
14
+ set,
15
+ startAt
16
+ } from "@firebase/database";
17
+ import { useCallback } from "react";
18
+ import { DataDriver, DeleteEntityProps, Entity, FetchCollectionProps, FetchEntityProps, ListenCollectionProps, ListenEntityProps, SaveEntityProps } from "@rebasepro/types";
19
+
20
+ export function useFirebaseRTDBDelegate({ firebaseApp }: { firebaseApp?: FirebaseApp }): DataDriver {
21
+
22
+ const fetchCollection = useCallback(async <M extends Record<string, any>>({
23
+ path,
24
+ filter,
25
+ limit,
26
+ startAfter,
27
+ orderBy,
28
+ order,
29
+ searchString
30
+ }: FetchCollectionProps<M>): Promise<Entity<M>[]> => {
31
+ if (!firebaseApp) {
32
+ throw new Error("Firebase app not provided");
33
+ }
34
+ const database = getDatabase(firebaseApp);
35
+
36
+ let dbQuery = query(ref(database, path));
37
+
38
+ // Example to apply "limit" and "startAfter"
39
+ if (startAfter !== undefined) {
40
+ dbQuery = query(dbQuery, orderByKey(), startAt(String(startAfter)));
41
+ }
42
+ if (limit !== undefined) {
43
+ dbQuery = query(dbQuery, limitToFirst(limit));
44
+ }
45
+
46
+ const snapshot = await get(dbQuery);
47
+ if (snapshot.exists()) {
48
+ return Object.entries(snapshot.val()).map(([id, values]) => ({
49
+ id,
50
+ path: path,
51
+ values: delegateToCMSModel(values) as M
52
+ }));
53
+ }
54
+ return [];
55
+ }, [firebaseApp]);
56
+
57
+ const listenCollection = useCallback(<M extends Record<string, any>>({
58
+ path,
59
+ onUpdate
60
+ // Realtime Database does not directly support onError in onValue
61
+ }: ListenCollectionProps<M>): () => void => {
62
+ if (!firebaseApp) {
63
+ throw new Error("Firebase app not provided");
64
+ }
65
+ const database = getDatabase(firebaseApp);
66
+
67
+ const dbRef = ref(database, path);
68
+ const unsubscribe = onValue(dbRef, (snapshot) => {
69
+ if (snapshot.exists()) {
70
+ const result: Entity<M>[] = Object.entries(snapshot.val()).map(([id, values]) => ({
71
+ id,
72
+ path: path,
73
+ values: delegateToCMSModel(values) as M
74
+ }));
75
+ onUpdate(result);
76
+ } else {
77
+ onUpdate([]);
78
+ }
79
+ });
80
+
81
+ return () => unsubscribe();
82
+ }, [firebaseApp]);
83
+
84
+ const fetchEntity = useCallback(async <M extends Record<string, any>>({
85
+ path,
86
+ entityId
87
+ }: FetchEntityProps<M>): Promise<Entity<M> | undefined> => {
88
+ if (!firebaseApp) {
89
+ throw new Error("Firebase app not provided");
90
+ }
91
+ const database = getDatabase(firebaseApp);
92
+
93
+ const snapshot = await get(ref(database, `${path}/${entityId}`));
94
+ if (snapshot.exists()) {
95
+ return {
96
+ id: entityId,
97
+ path: path,
98
+ values: delegateToCMSModel(snapshot.val()) as M
99
+ };
100
+ }
101
+ return undefined;
102
+ }, [firebaseApp]);
103
+
104
+ const listenEntity = useCallback(<M extends Record<string, any>>({
105
+ path,
106
+ entityId,
107
+ onUpdate,
108
+ onError
109
+ }: ListenEntityProps<M>): () => void => {
110
+ if (!firebaseApp) {
111
+ throw new Error("Firebase app not provided");
112
+ }
113
+ const database = getDatabase(firebaseApp);
114
+
115
+ const dbRef = ref(database, `${path}/${entityId}`);
116
+ const unsubscribe = onValue(dbRef, (snapshot) => {
117
+ if (snapshot.exists()) {
118
+ onUpdate({
119
+ id: entityId,
120
+ path,
121
+ values: delegateToCMSModel(snapshot.val()) as M
122
+ });
123
+ } else {
124
+ onError?.(new Error("Entity does not exist"));
125
+ }
126
+ });
127
+
128
+ return () => unsubscribe();
129
+ }, [firebaseApp]);
130
+
131
+ const saveEntity = useCallback(async <M extends Record<string, any>>({
132
+ path,
133
+ entityId,
134
+ values
135
+ }: SaveEntityProps<M>): Promise<Entity<M>> => {
136
+ if (!firebaseApp) {
137
+ throw new Error("Firebase app not provided");
138
+ }
139
+ const database = getDatabase(firebaseApp);
140
+
141
+ // If entityId is not provided, a new entity will be created
142
+ const finalId = entityId ?? push(ref(database, path)).key;
143
+ if (!finalId) {
144
+ throw new Error("Could not generate a new id");
145
+ }
146
+
147
+ // Transform the data to RTDB format before saving
148
+ const transformedValues = cmsToRTDBModel(values, database);
149
+ await set(ref(database, `${path}/${finalId}`), transformedValues);
150
+
151
+ return {
152
+ id: finalId,
153
+ path: path,
154
+ values: values as M
155
+ };
156
+ }, [firebaseApp]);
157
+
158
+ const deleteEntity = useCallback(async <M extends Record<string, any>>({
159
+ entity
160
+ }: DeleteEntityProps<M>): Promise<void> => {
161
+ if (!firebaseApp) {
162
+ throw new Error("Firebase app not provided");
163
+ }
164
+ const database = getDatabase(firebaseApp);
165
+
166
+ await remove(ref(database, `${entity.path}/${entity.id}`));
167
+ }, [firebaseApp]);
168
+
169
+ // Implementing additional methods required by DataDriver
170
+ const checkUniqueField = useCallback(async (slug: string, name: string, value: any, entityId?: string | number): Promise<boolean> => {
171
+ if (!firebaseApp) {
172
+ throw new Error("Firebase app not provided");
173
+ }
174
+ const database = getDatabase(firebaseApp);
175
+
176
+ // Simplified example; the Realtime Database does not support querying with "not equal" conditions
177
+ const dbRef = query(ref(database, slug), orderByChild(name), startAt(value), limitToFirst(1));
178
+ const snapshot = await get(dbRef);
179
+
180
+ if (!snapshot.exists()) {
181
+ return true;
182
+ }
183
+
184
+ // Check if the found entity is the same as the one being checked
185
+ const [key, entityValue] = Object.entries(snapshot.val())[0];
186
+ if (entityValue && typeof entityValue === "object" && (entityValue as Record<string, unknown>)[name] === value && key === entityId) {
187
+ return true;
188
+ }
189
+
190
+ return false;
191
+ }, [firebaseApp]);
192
+
193
+ const isFilterCombinationValid = useCallback(({
194
+ path,
195
+ filter,
196
+ sortBy
197
+ }: any): boolean => {
198
+ return false;
199
+ }, []);
200
+
201
+ return {
202
+ key: "firebase_rtdb",
203
+ fetchCollection,
204
+ listenCollection,
205
+ fetchEntity,
206
+ listenEntity,
207
+ saveEntity,
208
+ deleteEntity,
209
+ checkUniqueField,
210
+ isFilterCombinationValid,
211
+ currentTime: () => new Date()
212
+ };
213
+ }
214
+
215
+
216
+ /**
217
+ * Transform data from RTDB format back to CMS format
218
+ * This is used internally when fetching/listening to data
219
+ */
220
+ function delegateToCMSModel(data: any): any {
221
+ if (data === null || data === undefined) return null;
222
+
223
+ if (Array.isArray(data)) {
224
+ return data.map(delegateToCMSModel).filter(v => v !== undefined);
225
+ }
226
+
227
+ if (typeof data === "object") {
228
+ const result: Record<string, any> = {};
229
+ for (const key of Object.keys(data)) {
230
+ const childValue = delegateToCMSModel(data[key]);
231
+ if (childValue !== undefined)
232
+ result[key] = childValue;
233
+ }
234
+ return result;
235
+ }
236
+
237
+ return data;
238
+ }
239
+
240
+ /**
241
+ * Transform data from CMS format to RTDB format
242
+ * This is used internally when saving data
243
+ */
244
+ function cmsToRTDBModel(data: any, database: Database): any {
245
+ if (data === undefined) {
246
+ return null;
247
+ } else if (data === null) {
248
+ return null;
249
+ } else if (Array.isArray(data)) {
250
+ return data.filter(v => v !== undefined).map(v => cmsToRTDBModel(v, database));
251
+ } else if (data.isEntityReference && data.isEntityReference()) {
252
+ return ref(database, `${data.slug}/${data.id}`);
253
+ } else if (data instanceof Date) {
254
+ // For dates, convert to ISO string or timestamp.
255
+ return data.toISOString();
256
+ } else if (data && typeof data === "object") {
257
+ return Object.entries(data)
258
+ .map(([key, v]) => {
259
+ const rtdbModel = cmsToRTDBModel(v, database);
260
+ if (rtdbModel !== undefined)
261
+ return { [key]: rtdbModel };
262
+ else
263
+ return {};
264
+ })
265
+ .reduce((a, b) => ({ ...a,
266
+ ...b }), {});
267
+ }
268
+ return data;
269
+ }