@rebasepro/firebase 0.17.3 → 0.18.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +4 -0
- package/package.json +34 -20
- package/src/components/FirebaseLoginView.tsx +0 -704
- package/src/components/RebaseFirebaseApp.tsx +0 -293
- package/src/components/RebaseFirebaseAppProps.tsx +0 -177
- package/src/components/index.ts +0 -3
- package/src/components/social_icons.tsx +0 -135
- package/src/hooks/index.ts +0 -9
- package/src/hooks/useAppCheck.ts +0 -101
- package/src/hooks/useBuildUserManagement.tsx +0 -429
- package/src/hooks/useFirebaseAccessGate.tsx +0 -146
- package/src/hooks/useFirebaseAuthController.ts +0 -357
- package/src/hooks/useFirebaseRealTimeDBDelegate.ts +0 -423
- package/src/hooks/useFirebaseStorageSource.ts +0 -207
- package/src/hooks/useFirestoreDriver.ts +0 -855
- package/src/hooks/useInitialiseFirebase.ts +0 -132
- package/src/hooks/useRecaptcha.tsx +0 -28
- package/src/index.ts +0 -4
- package/src/types/appcheck.ts +0 -11
- package/src/types/auth.tsx +0 -75
- package/src/types/index.ts +0 -3
- package/src/types/text_search.ts +0 -42
- package/src/utils/algolia.ts +0 -27
- package/src/utils/collections_firestore.ts +0 -150
- package/src/utils/database.ts +0 -39
- package/src/utils/index.ts +0 -7
- package/src/utils/local_text_search_controller.ts +0 -143
- package/src/utils/pinecone.ts +0 -75
- package/src/utils/rebase_search_controller.ts +0 -357
- package/src/utils/text_search_controller.ts +0 -34
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
import { useCallback, useEffect, useState } from "react";
|
|
2
|
-
|
|
3
|
-
import { deleteApp, FirebaseApp, getApps, initializeApp } from "firebase/app";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* @group Firebase
|
|
7
|
-
*/
|
|
8
|
-
export interface InitialiseFirebaseResult {
|
|
9
|
-
firebaseConfigLoading: boolean,
|
|
10
|
-
firebaseApp?: FirebaseApp;
|
|
11
|
-
configError?: string,
|
|
12
|
-
firebaseConfigError?: Error
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const hostingError = "It seems like the provided Firebase config is not correct. If you \n" +
|
|
16
|
-
"are using the credentials provided automatically by Firebase \n" +
|
|
17
|
-
"Hosting, make sure you link your Firebase app to Firebase Hosting. \n";
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Function used to initialise Firebase, either by using the provided config,
|
|
21
|
-
* or by fetching it by Firebase Hosting, if not specified.
|
|
22
|
-
*
|
|
23
|
-
* It works as a hook that gives you the loading state and the used
|
|
24
|
-
* configuration.
|
|
25
|
-
*
|
|
26
|
-
* You most likely only need to use this if you are developing a custom app. You can also not use this component
|
|
27
|
-
* and initialise Firebase yourself.
|
|
28
|
-
*
|
|
29
|
-
* @param onFirebaseInit
|
|
30
|
-
* @param firebaseConfig
|
|
31
|
-
* @param fromUrl
|
|
32
|
-
* @param name
|
|
33
|
-
* @param authDomain
|
|
34
|
-
* @group Firebase
|
|
35
|
-
*/
|
|
36
|
-
export function useInitialiseFirebase({
|
|
37
|
-
firebaseConfig,
|
|
38
|
-
fromUrl,
|
|
39
|
-
onFirebaseInit,
|
|
40
|
-
name,
|
|
41
|
-
authDomain
|
|
42
|
-
}: {
|
|
43
|
-
firebaseConfig?: Record<string, unknown>,
|
|
44
|
-
fromUrl?: string | undefined,
|
|
45
|
-
onFirebaseInit?: ((config: object, firebaseApp: FirebaseApp) => void) | undefined,
|
|
46
|
-
name?: string;
|
|
47
|
-
authDomain?: string;
|
|
48
|
-
}): InitialiseFirebaseResult {
|
|
49
|
-
|
|
50
|
-
const [firebaseApp, setFirebaseApp] = useState<FirebaseApp | undefined>();
|
|
51
|
-
const [firebaseConfigLoading, setFirebaseConfigLoading] = useState<boolean>(false);
|
|
52
|
-
const [configError, setConfigError] = useState<string>();
|
|
53
|
-
|
|
54
|
-
const initFirebase = useCallback((config: Record<string, unknown>) => {
|
|
55
|
-
|
|
56
|
-
if (config.projectId === firebaseApp?.options.projectId) {
|
|
57
|
-
console.debug("Firebase app already initialised with the same project ID. This should happen only in development mode.");
|
|
58
|
-
setConfigError(undefined);
|
|
59
|
-
setFirebaseConfigLoading(false);
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
try {
|
|
64
|
-
const targetName = name ?? "[DEFAULT]";
|
|
65
|
-
const currentApps = getApps();
|
|
66
|
-
const existingApp = currentApps.find(app => app.name === targetName);
|
|
67
|
-
if (existingApp) {
|
|
68
|
-
deleteApp(existingApp);
|
|
69
|
-
}
|
|
70
|
-
const initialisedFirebaseApp = initializeApp(config, targetName);
|
|
71
|
-
setConfigError(undefined);
|
|
72
|
-
setFirebaseConfigLoading(false);
|
|
73
|
-
setFirebaseApp(initialisedFirebaseApp);
|
|
74
|
-
} catch (e: unknown) {
|
|
75
|
-
console.error("Error initialising Firebase", e);
|
|
76
|
-
setConfigError(hostingError + "\n" + (e instanceof Error ? e.message : JSON.stringify(e)));
|
|
77
|
-
}
|
|
78
|
-
}, [name]);
|
|
79
|
-
|
|
80
|
-
useEffect(() => {
|
|
81
|
-
if (onFirebaseInit && firebaseConfig && firebaseApp) {
|
|
82
|
-
onFirebaseInit(firebaseConfig, firebaseApp);
|
|
83
|
-
}
|
|
84
|
-
}, [firebaseApp]);
|
|
85
|
-
|
|
86
|
-
useEffect(() => {
|
|
87
|
-
|
|
88
|
-
setFirebaseConfigLoading(true);
|
|
89
|
-
|
|
90
|
-
function fetchFromUrl(url: string) {
|
|
91
|
-
fetch(url)
|
|
92
|
-
.then(async response => {
|
|
93
|
-
console.debug("Firebase init response", response.status);
|
|
94
|
-
if (response && response.status < 300) {
|
|
95
|
-
const config = await response.json();
|
|
96
|
-
if (authDomain) config.authDomain = authDomain;
|
|
97
|
-
initFirebase(config);
|
|
98
|
-
}
|
|
99
|
-
})
|
|
100
|
-
.catch(e => {
|
|
101
|
-
setFirebaseConfigLoading(false);
|
|
102
|
-
setConfigError(
|
|
103
|
-
"Could not load Firebase configuration from Firebase hosting. " +
|
|
104
|
-
"If the app is not deployed in Firebase hosting, you need to specify the configuration manually" +
|
|
105
|
-
e.toString()
|
|
106
|
-
);
|
|
107
|
-
}
|
|
108
|
-
);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
if (firebaseConfig) {
|
|
112
|
-
initFirebase(firebaseConfig);
|
|
113
|
-
} else {
|
|
114
|
-
if (fromUrl) {
|
|
115
|
-
fetchFromUrl(fromUrl);
|
|
116
|
-
} else if (process.env.NODE_ENV === "production") {
|
|
117
|
-
fetchFromUrl("/__/firebase/init.json");
|
|
118
|
-
} else {
|
|
119
|
-
setFirebaseConfigLoading(false);
|
|
120
|
-
setConfigError(
|
|
121
|
-
"You need to deploy the app to Firebase hosting or specify a Firebase configuration object"
|
|
122
|
-
);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}, []);
|
|
126
|
-
|
|
127
|
-
return {
|
|
128
|
-
firebaseApp,
|
|
129
|
-
firebaseConfigLoading,
|
|
130
|
-
configError
|
|
131
|
-
};
|
|
132
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { useEffect } from "react";
|
|
2
|
-
import { getAuth, RecaptchaVerifier } from "firebase/auth";
|
|
3
|
-
|
|
4
|
-
declare global {
|
|
5
|
-
interface Window {
|
|
6
|
-
recaptchaVerifier: RecaptchaVerifier;
|
|
7
|
-
}
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export const RECAPTCHA_CONTAINER_ID = "recaptcha-container" as const;
|
|
11
|
-
|
|
12
|
-
export function useRecaptcha() {
|
|
13
|
-
useEffect(() => {
|
|
14
|
-
if (!window || window?.recaptchaVerifier) return;
|
|
15
|
-
|
|
16
|
-
const auth = getAuth();
|
|
17
|
-
|
|
18
|
-
window.recaptchaVerifier = new RecaptchaVerifier(
|
|
19
|
-
auth,
|
|
20
|
-
RECAPTCHA_CONTAINER_ID,
|
|
21
|
-
{
|
|
22
|
-
size: "invisible"
|
|
23
|
-
}
|
|
24
|
-
);
|
|
25
|
-
}, []);
|
|
26
|
-
|
|
27
|
-
return null;
|
|
28
|
-
}
|
package/src/index.ts
DELETED
package/src/types/appcheck.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { CustomProvider, ReCaptchaEnterpriseProvider, ReCaptchaV3Provider } from "firebase/app-check";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* @group Firebase
|
|
5
|
-
*/
|
|
6
|
-
export interface AppCheckOptions {
|
|
7
|
-
provider: CustomProvider | ReCaptchaV3Provider | ReCaptchaEnterpriseProvider;
|
|
8
|
-
isTokenAutoRefreshEnabled?: boolean;
|
|
9
|
-
debugToken?: string;
|
|
10
|
-
forceRefresh?: boolean;
|
|
11
|
-
}
|
package/src/types/auth.tsx
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { ApplicationVerifier, ConfirmationResult, User as FirebaseUser } from "firebase/auth";
|
|
2
|
-
|
|
3
|
-
import type { User } from "@rebasepro/types";
|
|
4
|
-
import type { AuthController } from "@rebasepro/cms-types";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* @group Firebase
|
|
8
|
-
*/
|
|
9
|
-
export type FirebaseSignInProvider =
|
|
10
|
-
| "password"
|
|
11
|
-
| "phone"
|
|
12
|
-
| "anonymous"
|
|
13
|
-
| "google.com"
|
|
14
|
-
| "facebook.com"
|
|
15
|
-
| "github.com"
|
|
16
|
-
| "twitter.com"
|
|
17
|
-
| "microsoft.com"
|
|
18
|
-
| "apple.com";
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* @group Firebase
|
|
22
|
-
*/
|
|
23
|
-
export type FirebaseSignInOption = {
|
|
24
|
-
provider: FirebaseSignInProvider;
|
|
25
|
-
scopes?: string[];
|
|
26
|
-
customParameters?: Record<string, string>;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export type FirebaseUserWrapper = User & FirebaseUser & {
|
|
30
|
-
firebaseUser: FirebaseUser | null;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* @group Firebase
|
|
35
|
-
*/
|
|
36
|
-
export type FirebaseAuthController<USER extends User = FirebaseUserWrapper, ExtraData = any> =
|
|
37
|
-
AuthController<USER, ExtraData>
|
|
38
|
-
& {
|
|
39
|
-
|
|
40
|
-
confirmationResult?: ConfirmationResult;
|
|
41
|
-
|
|
42
|
-
googleLogin: () => void;
|
|
43
|
-
|
|
44
|
-
anonymousLogin: () => void;
|
|
45
|
-
|
|
46
|
-
appleLogin: () => void;
|
|
47
|
-
|
|
48
|
-
facebookLogin: () => void;
|
|
49
|
-
|
|
50
|
-
githubLogin: () => void;
|
|
51
|
-
|
|
52
|
-
microsoftLogin: () => void;
|
|
53
|
-
|
|
54
|
-
twitterLogin: () => void;
|
|
55
|
-
|
|
56
|
-
emailPasswordLogin: (email: string, password: string) => void;
|
|
57
|
-
|
|
58
|
-
fetchSignInMethodsForEmail: (email: string) => Promise<string[]>;
|
|
59
|
-
|
|
60
|
-
createUserWithEmailAndPassword: (email: string, password: string) => void;
|
|
61
|
-
|
|
62
|
-
sendPasswordResetEmail: (email: string) => Promise<void>;
|
|
63
|
-
|
|
64
|
-
phoneLogin: (phone: string, applicationVerifier: ApplicationVerifier) => void;
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Skip login
|
|
68
|
-
*/
|
|
69
|
-
skipLogin: () => void;
|
|
70
|
-
|
|
71
|
-
setUser: (user: USER | null) => void;
|
|
72
|
-
|
|
73
|
-
setUserRoles: (roles: string[]) => void;
|
|
74
|
-
|
|
75
|
-
};
|
package/src/types/index.ts
DELETED
package/src/types/text_search.ts
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { User as FirebaseUser } from "firebase/auth";
|
|
2
|
-
import { FirebaseApp } from "firebase/app";
|
|
3
|
-
import { CollectionConfig } from "@rebasepro/types";
|
|
4
|
-
|
|
5
|
-
export type FirestoreTextSearchControllerBuilder = (props: {
|
|
6
|
-
firebaseApp: FirebaseApp;
|
|
7
|
-
}) => FirestoreTextSearchController;
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Use this controller to return a list of ids from a search index, given a
|
|
11
|
-
* `path` and a `searchString`.
|
|
12
|
-
* Firestore does not support text search directly, so we need to rely on an external
|
|
13
|
-
* index, such as Algolia.
|
|
14
|
-
* Note that you will get text search requests for collections that have the
|
|
15
|
-
* `textSearchEnabled` flag set to `true`.
|
|
16
|
-
* @see performAlgoliaTextSearch
|
|
17
|
-
* @group Firebase
|
|
18
|
-
*/
|
|
19
|
-
export type FirestoreTextSearchController = {
|
|
20
|
-
/**
|
|
21
|
-
* This method is called when a search delegate is ready to be used.
|
|
22
|
-
* Return true if this path can be handled by this controller.
|
|
23
|
-
* @param props
|
|
24
|
-
*/
|
|
25
|
-
init: (props: {
|
|
26
|
-
path: string,
|
|
27
|
-
databaseId?: string,
|
|
28
|
-
collection?: CollectionConfig
|
|
29
|
-
}) => Promise<boolean>,
|
|
30
|
-
/**
|
|
31
|
-
* Do the search and return a list of ids.
|
|
32
|
-
* @param props
|
|
33
|
-
*/
|
|
34
|
-
search: (props: {
|
|
35
|
-
searchString: string,
|
|
36
|
-
path: string,
|
|
37
|
-
currentUser?: FirebaseUser,
|
|
38
|
-
databaseId?: string,
|
|
39
|
-
collection?: CollectionConfig
|
|
40
|
-
}) => (Promise<readonly string[] | undefined>),
|
|
41
|
-
|
|
42
|
-
};
|
package/src/utils/algolia.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { buildExternalSearchController } from "./text_search_controller";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Utility function to perform a text search in an algolia index,
|
|
5
|
-
* returning the ids of the entities.
|
|
6
|
-
* @param client The algolia client
|
|
7
|
-
* @param indexName
|
|
8
|
-
* @param query
|
|
9
|
-
* @group Firebase
|
|
10
|
-
*/
|
|
11
|
-
export function performAlgoliaTextSearch(client: { searchSingleIndex: (params: Record<string, unknown>) => Promise<{ hits: Array<{ objectID: string }> }> }, indexName: string, query: string): Promise<readonly string[]> {
|
|
12
|
-
|
|
13
|
-
console.debug("Performing Algolia query", client, query);
|
|
14
|
-
|
|
15
|
-
return client.searchSingleIndex({
|
|
16
|
-
indexName,
|
|
17
|
-
searchParams: { query }
|
|
18
|
-
}).then(({ hits }) => {
|
|
19
|
-
return hits.map((hit) => hit.objectID);
|
|
20
|
-
})
|
|
21
|
-
.catch((err: unknown) => {
|
|
22
|
-
console.error(err);
|
|
23
|
-
return [];
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
|
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
import { deleteField, DocumentSnapshot } from "firebase/firestore";
|
|
2
|
-
import { CollectionConfig, FirebaseCollectionConfig, Properties, Property } from "@rebasepro/types";
|
|
3
|
-
import { COLLECTION_PATH_SEPARATOR, sortProperties, stripCollectionPath } from "@rebasepro/common";
|
|
4
|
-
|
|
5
|
-
export function buildCollectionId(idOrPath: string, parentCollectionSlugs?: string[], parentEntityIds?: string[]): string {
|
|
6
|
-
if (!parentCollectionSlugs)
|
|
7
|
-
return stripCollectionPath(idOrPath);
|
|
8
|
-
return [...parentCollectionSlugs.map(stripCollectionPath), stripCollectionPath(idOrPath)].join(COLLECTION_PATH_SEPARATOR);
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
export const docsToCollectionTree = (docs: DocumentSnapshot[]): CollectionConfig[] => {
|
|
13
|
-
|
|
14
|
-
const collectionsMap = docs.map((doc) => {
|
|
15
|
-
const id: string = doc.id;
|
|
16
|
-
const collection = docToCollection(doc);
|
|
17
|
-
return { [id]: collection };
|
|
18
|
-
}).reduce((a, b) => ({ ...a,
|
|
19
|
-
...b }), {});
|
|
20
|
-
|
|
21
|
-
const orderedKeys = Object.keys(collectionsMap).sort((a, b) => b.split(COLLECTION_PATH_SEPARATOR).length - a.split(COLLECTION_PATH_SEPARATOR).length);
|
|
22
|
-
|
|
23
|
-
orderedKeys.forEach((id) => {
|
|
24
|
-
const collection = collectionsMap[id];
|
|
25
|
-
if (id.includes(COLLECTION_PATH_SEPARATOR)) {
|
|
26
|
-
const parentId = id.split(COLLECTION_PATH_SEPARATOR).slice(0, -1).join(COLLECTION_PATH_SEPARATOR);
|
|
27
|
-
const parentCollection = collectionsMap[parentId];
|
|
28
|
-
if (parentCollection)
|
|
29
|
-
(parentCollection as FirebaseCollectionConfig).subcollections = () => [...((parentCollection as FirebaseCollectionConfig).subcollections?.() ?? []), collection];
|
|
30
|
-
delete collectionsMap[id];
|
|
31
|
-
}
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
return Object.values(collectionsMap);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export const docToCollection = (doc: DocumentSnapshot): CollectionConfig => {
|
|
38
|
-
const data = doc.data();
|
|
39
|
-
if (!data)
|
|
40
|
-
throw Error("Entity collection has not been persisted correctly");
|
|
41
|
-
const propertiesOrder = data.propertiesOrder;
|
|
42
|
-
const properties = data.properties as Properties ?? {};
|
|
43
|
-
|
|
44
|
-
// Normalize enum values from object format to array format (sorted alphabetically)
|
|
45
|
-
const normalizedProperties = normalizePropertiesEnumValues(properties, true);
|
|
46
|
-
const sortedProperties = sortProperties(normalizedProperties, propertiesOrder);
|
|
47
|
-
return {
|
|
48
|
-
...data,
|
|
49
|
-
properties: sortedProperties,
|
|
50
|
-
slug: data.id ?? data.alias ?? data.slug
|
|
51
|
-
} as CollectionConfig;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Converts enum values from object format to array format.
|
|
57
|
-
* Firestore doesn't preserve object key order, so we must use arrays.
|
|
58
|
-
* When enum values are already stored as an array, their order is preserved
|
|
59
|
-
* (this is intentional - users can reorder columns in Kanban view).
|
|
60
|
-
* Only sort alphabetically when converting from legacy object format.
|
|
61
|
-
* @param enumValues - The enum values (object or array format)
|
|
62
|
-
* @param sortObjectFormat - If true, sort by id alphabetically when converting from object format
|
|
63
|
-
* @returns Array of EnumValueConfig objects
|
|
64
|
-
*/
|
|
65
|
-
function normalizeEnumValuesToArray(
|
|
66
|
-
enumValues: unknown,
|
|
67
|
-
sortObjectFormat = false
|
|
68
|
-
): unknown[] {
|
|
69
|
-
if (Array.isArray(enumValues)) {
|
|
70
|
-
// Already an array - preserve order! This order is intentional
|
|
71
|
-
// (e.g., user reordered Kanban columns)
|
|
72
|
-
return enumValues;
|
|
73
|
-
} else if (typeof enumValues === "object" && enumValues !== null) {
|
|
74
|
-
// Convert object to array format
|
|
75
|
-
// Object keys don't have guaranteed order in Firestore, so we sort alphabetically
|
|
76
|
-
const entries = Object.entries(enumValues).map(([id, value]) =>
|
|
77
|
-
typeof value === "string"
|
|
78
|
-
? {
|
|
79
|
-
id,
|
|
80
|
-
label: value
|
|
81
|
-
}
|
|
82
|
-
: {
|
|
83
|
-
...(value as object),
|
|
84
|
-
id
|
|
85
|
-
}
|
|
86
|
-
);
|
|
87
|
-
// Sort alphabetically by id when loading from Firestore object format
|
|
88
|
-
// This is the only case where sorting makes sense, since object key order is not preserved
|
|
89
|
-
if (sortObjectFormat) {
|
|
90
|
-
entries.sort((a, b) => String(a.id).localeCompare(String(b.id)));
|
|
91
|
-
}
|
|
92
|
-
return entries;
|
|
93
|
-
}
|
|
94
|
-
return [];
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Normalizes all enum values in a properties object.
|
|
99
|
-
* @param properties - The properties object to normalize
|
|
100
|
-
* @param sortObjectFormat - If true, sort enum values alphabetically when converting from object format
|
|
101
|
-
* @returns Properties with normalized enum values
|
|
102
|
-
*/
|
|
103
|
-
function normalizePropertiesEnumValues(
|
|
104
|
-
properties: Properties,
|
|
105
|
-
sortObjectFormat = false
|
|
106
|
-
): Properties {
|
|
107
|
-
const result: Properties = {};
|
|
108
|
-
Object.entries(properties).forEach(([key, property]) => {
|
|
109
|
-
if (typeof property === "object" && property !== null) {
|
|
110
|
-
const normalizedProperty = { ...property } as Record<string, unknown>;
|
|
111
|
-
|
|
112
|
-
// Handle direct enum values
|
|
113
|
-
if (normalizedProperty.enum) {
|
|
114
|
-
normalizedProperty.enum = normalizeEnumValuesToArray(
|
|
115
|
-
normalizedProperty.enum,
|
|
116
|
-
sortObjectFormat
|
|
117
|
-
);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const propType = normalizedProperty.type ?? normalizedProperty.dataType;
|
|
121
|
-
|
|
122
|
-
// Handle array properties with enum values in "of"
|
|
123
|
-
if (propType === "array" && typeof normalizedProperty.of === "object" && normalizedProperty.of !== null) {
|
|
124
|
-
const ofProp = normalizedProperty.of as Record<string, unknown>;
|
|
125
|
-
if (ofProp.enum) {
|
|
126
|
-
normalizedProperty.of = {
|
|
127
|
-
...ofProp,
|
|
128
|
-
enum: normalizeEnumValuesToArray(
|
|
129
|
-
ofProp.enum,
|
|
130
|
-
sortObjectFormat
|
|
131
|
-
)
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Handle map properties recursively
|
|
137
|
-
if (propType === "map" && normalizedProperty.properties) {
|
|
138
|
-
normalizedProperty.properties = normalizePropertiesEnumValues(
|
|
139
|
-
normalizedProperty.properties as Properties,
|
|
140
|
-
sortObjectFormat
|
|
141
|
-
);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
result[key] = normalizedProperty as unknown as Property;
|
|
145
|
-
} else {
|
|
146
|
-
result[key] = property;
|
|
147
|
-
}
|
|
148
|
-
});
|
|
149
|
-
return result;
|
|
150
|
-
}
|
package/src/utils/database.ts
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { FirebaseApp } from "firebase/app";
|
|
2
|
-
import {
|
|
3
|
-
collection,
|
|
4
|
-
getDocs,
|
|
5
|
-
getFirestore,
|
|
6
|
-
limit as limitClause,
|
|
7
|
-
query,
|
|
8
|
-
QueryDocumentSnapshot
|
|
9
|
-
} from "firebase/firestore";
|
|
10
|
-
|
|
11
|
-
export async function getFirestoreDataInPath(firebaseApp: FirebaseApp, path: string, parentPaths: string[], limit: number): Promise<object[]> {
|
|
12
|
-
const firestore = getFirestore(firebaseApp);
|
|
13
|
-
if (!parentPaths || parentPaths.length === 0) {
|
|
14
|
-
const q = query(collection(firestore, path), limitClause(limit));
|
|
15
|
-
return getDocs(q).then((queryEntity) => {
|
|
16
|
-
return queryEntity.docs.map(doc => doc.data());
|
|
17
|
-
});
|
|
18
|
-
} else {
|
|
19
|
-
let currentDocs: QueryDocumentSnapshot[] | undefined = undefined;
|
|
20
|
-
let index = 0;
|
|
21
|
-
const allPaths = parentPaths;
|
|
22
|
-
allPaths.push(path);
|
|
23
|
-
let parentPath: string | undefined = allPaths[0];
|
|
24
|
-
while (parentPath) {
|
|
25
|
-
if (currentDocs) {
|
|
26
|
-
currentDocs = (await Promise.all(currentDocs.map(async (doc) => {
|
|
27
|
-
const q = query(collection(firestore, doc.ref.path, parentPath as string), limitClause(5));
|
|
28
|
-
return (await getDocs(q)).docs;
|
|
29
|
-
}))).flat();
|
|
30
|
-
} else {
|
|
31
|
-
const q = query(collection(firestore, parentPath), limitClause(5));
|
|
32
|
-
currentDocs = (await getDocs(q)).docs;
|
|
33
|
-
}
|
|
34
|
-
index++;
|
|
35
|
-
parentPath = index < allPaths.length ? allPaths[index] : undefined;
|
|
36
|
-
}
|
|
37
|
-
return currentDocs ? currentDocs.map(doc => doc.data()) : [];
|
|
38
|
-
}
|
|
39
|
-
}
|
package/src/utils/index.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
export * from "./collections_firestore";
|
|
2
|
-
export * from "./database";
|
|
3
|
-
export * from "./algolia";
|
|
4
|
-
export * from "./pinecone";
|
|
5
|
-
export * from "./text_search_controller";
|
|
6
|
-
export * from "./local_text_search_controller";
|
|
7
|
-
export * from "./rebase_search_controller";
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
import { collection, getFirestore, onSnapshot, query } from "firebase/firestore";
|
|
2
|
-
import Fuse from "fuse.js"
|
|
3
|
-
|
|
4
|
-
import { FirebaseApp } from "firebase/app";
|
|
5
|
-
import { CollectionConfig } from "@rebasepro/types";
|
|
6
|
-
import { FirestoreTextSearchController, FirestoreTextSearchControllerBuilder } from "../types";
|
|
7
|
-
|
|
8
|
-
const MAX_SEARCH_RESULTS = 80;
|
|
9
|
-
|
|
10
|
-
export const localSearchControllerBuilder: FirestoreTextSearchControllerBuilder = ({
|
|
11
|
-
firebaseApp
|
|
12
|
-
}: {
|
|
13
|
-
|
|
14
|
-
firebaseApp: FirebaseApp,
|
|
15
|
-
}): FirestoreTextSearchController => {
|
|
16
|
-
|
|
17
|
-
let currentPath: string | undefined;
|
|
18
|
-
const indexes: Record<string, Fuse<object & { id: string }>> = {};
|
|
19
|
-
const listeners: Record<string, () => void> = {};
|
|
20
|
-
|
|
21
|
-
const destroyListener = (path: string) => {
|
|
22
|
-
if (listeners[path]) {
|
|
23
|
-
listeners[path]();
|
|
24
|
-
delete listeners[path];
|
|
25
|
-
delete indexes[path];
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const init = ({
|
|
30
|
-
path,
|
|
31
|
-
collection: collectionProp,
|
|
32
|
-
databaseId
|
|
33
|
-
}: {
|
|
34
|
-
path: string,
|
|
35
|
-
collection?: CollectionConfig,
|
|
36
|
-
databaseId?: string
|
|
37
|
-
}): Promise<boolean> => {
|
|
38
|
-
|
|
39
|
-
if (currentPath && path !== currentPath) {
|
|
40
|
-
destroyListener(currentPath)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
currentPath = path;
|
|
44
|
-
|
|
45
|
-
return new Promise((resolve, reject) => {
|
|
46
|
-
if (collectionProp) {
|
|
47
|
-
console.debug("Init local search controller", path);
|
|
48
|
-
const firestore = databaseId ? getFirestore(firebaseApp, databaseId) : getFirestore(firebaseApp);
|
|
49
|
-
const col = collection(firestore, path);
|
|
50
|
-
listeners[path] = onSnapshot(query(col),
|
|
51
|
-
{
|
|
52
|
-
next: (entity) => {
|
|
53
|
-
if (entity.metadata.fromCache && entity.metadata.hasPendingWrites) {
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
const docs = entity.docs.map(doc => ({
|
|
57
|
-
id: doc.id,
|
|
58
|
-
...doc.data()
|
|
59
|
-
}));
|
|
60
|
-
|
|
61
|
-
indexes[path] = buildIndex(docs, collectionProp);
|
|
62
|
-
console.debug("Added docs to index", path, docs.length);
|
|
63
|
-
resolve(true);
|
|
64
|
-
},
|
|
65
|
-
error: (e) => {
|
|
66
|
-
console.error("Error initializing local search controller", path);
|
|
67
|
-
console.error(e);
|
|
68
|
-
reject(e);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const search = async ({
|
|
77
|
-
searchString,
|
|
78
|
-
path
|
|
79
|
-
}: {
|
|
80
|
-
searchString: string,
|
|
81
|
-
path: string,
|
|
82
|
-
databaseId?: string
|
|
83
|
-
}) => {
|
|
84
|
-
console.debug("Searching local index", path, searchString);
|
|
85
|
-
const index = indexes[path];
|
|
86
|
-
if (!index) {
|
|
87
|
-
throw new Error(`Index not found for path ${path}`);
|
|
88
|
-
}
|
|
89
|
-
let searchResult = index.search(searchString);
|
|
90
|
-
searchResult = searchResult.splice(0, MAX_SEARCH_RESULTS);
|
|
91
|
-
searchResult = searchResult.sort((a, b) => {
|
|
92
|
-
// Check if item A is an exact match
|
|
93
|
-
const aExactMatch = a.item.id === searchString;
|
|
94
|
-
// Check if item B is an exact match
|
|
95
|
-
const bExactMatch = b.item.id === searchString;
|
|
96
|
-
|
|
97
|
-
if (aExactMatch && !bExactMatch) {
|
|
98
|
-
return -1; // Prioritize item A
|
|
99
|
-
} else if (!aExactMatch && bExactMatch) {
|
|
100
|
-
return 1; // Prioritize item B
|
|
101
|
-
} else {
|
|
102
|
-
// If both are exact matches or both are not, sort by Fuse's original score
|
|
103
|
-
return (a.score ?? 0) - (b.score ?? 0);
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
return searchResult.map((e) => e.item.id);
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
return {
|
|
110
|
-
init,
|
|
111
|
-
search
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function buildIndex(list: (object & { id: string })[], collection: CollectionConfig) {
|
|
116
|
-
|
|
117
|
-
const keys = ["id", ...Object.keys(collection.properties)];
|
|
118
|
-
|
|
119
|
-
const fuseOptions = {
|
|
120
|
-
// isCaseSensitive: false,
|
|
121
|
-
// includeScore: false,
|
|
122
|
-
// shouldSort: true,
|
|
123
|
-
// includeMatches: false,
|
|
124
|
-
// findAllMatches: false,
|
|
125
|
-
// minMatchCharLength: 1,
|
|
126
|
-
// location: 0,
|
|
127
|
-
threshold: 0.6,
|
|
128
|
-
// distance: 100,
|
|
129
|
-
// useExtendedSearch: false,
|
|
130
|
-
// ignoreLocation: false,
|
|
131
|
-
// ignoreFieldNorm: false,
|
|
132
|
-
// fieldNormWeight: 1,
|
|
133
|
-
includeScore: true,
|
|
134
|
-
keys: [{
|
|
135
|
-
name: "title",
|
|
136
|
-
weight: 1.0
|
|
137
|
-
}, ...keys.map(key => ({
|
|
138
|
-
name: key,
|
|
139
|
-
weight: 0.5
|
|
140
|
-
}))]
|
|
141
|
-
};
|
|
142
|
-
return new Fuse<object & { id: string }>(list, fuseOptions);
|
|
143
|
-
}
|