@firecms/core
Version:
Awesome Firebase/Firestore-based headless open-source CMS
358 lines (331 loc) • 12.9 kB
text/typescript
import { useCallback } from "react";
import {
AuthController,
DataSource,
DataSourceDelegate,
DeleteEntityProps,
Entity,
EntityCollection,
EntityValues,
FetchCollectionProps,
FetchEntityProps,
FilterValues,
FireCMSContext,
ListenCollectionProps,
ListenEntityProps,
NavigationController,
PropertyConfig,
ResolvedProperties,
SaveEntityProps
} from "../types";
import { resolveCollection, updateDateAutoValues } from "../util";
/**
* Use this hook to build a {@link DataSource} based on Firestore
* @param firebaseApp
* @group Firebase
*/
export function useBuildDataSource({
delegate,
propertyConfigs,
navigationController,
authController
}: {
delegate: DataSourceDelegate,
propertyConfigs?: Record<string, PropertyConfig>;
navigationController: NavigationController;
authController: AuthController;
}): DataSource {
return {
/**
* Fetch entities in a Firestore path
* @param path
* @param collection
* @param filter
* @param limit
* @param startAfter
* @param searchString
* @param orderBy
* @param order
* @return Function to cancel subscription
* @see useCollectionFetch if you need this functionality implemented as a hook
* @group Firestore
*/
fetchCollection: useCallback(<M extends Record<string, any>>({
path,
collection,
filter,
limit,
startAfter,
searchString,
orderBy,
order,
}: FetchCollectionProps<M>
): Promise<Entity<M>[]> => {
const usedDelegate = collection?.overrides?.dataSourceDelegate ?? delegate;
return usedDelegate.fetchCollection<M>({
path,
filter,
limit,
startAfter,
searchString,
orderBy,
order,
collection
});
}, [delegate]),
/**
* Listen to a entities in a given path
* @param path
* @param collection
* @param onError
* @param filter
* @param limit
* @param startAfter
* @param searchString
* @param orderBy
* @param order
* @param onUpdate
* @return Function to cancel subscription
* @see useCollectionFetch if you need this functionality implemented as a hook
* @group Firestore
*/
listenCollection: delegate.listenCollection
? useCallback(<M extends Record<string, any>>(
{
path,
collection: collectionProp,
filter,
limit,
startAfter,
searchString,
orderBy,
order,
onUpdate,
onError
}: ListenCollectionProps<M>
): () => void => {
const collection = collectionProp ?? navigationController.getCollection(path);
const usedDelegate = collection?.overrides?.dataSourceDelegate ?? delegate;
if (!usedDelegate.listenCollection)
throw Error("useBuildDataSource delegate not initialised");
return usedDelegate.listenCollection<M>({
path,
filter,
limit,
startAfter,
searchString,
orderBy,
order,
onUpdate,
onError,
collection,
});
}, [delegate, navigationController.getCollection])
: undefined,
/**
* Retrieve an entity given a path and a collection
* @param path
* @param entityId
* @param collection
* @group Firestore
*/
fetchEntity: useCallback(<M extends Record<string, any>>({
path,
entityId,
collection
}: FetchEntityProps<M>
): Promise<Entity<M> | undefined> => {
const usedDelegate = collection?.overrides?.dataSourceDelegate ?? delegate;
return usedDelegate.fetchEntity({
path,
entityId,
collection
});
}, [delegate.fetchEntity]),
/**
*
* @param path
* @param entityId
* @param collection
* @param onUpdate
* @param onError
* @return Function to cancel subscription
* @group Firestore
*/
listenEntity: delegate.listenEntity
? useCallback(<M extends Record<string, any>>(
{
path,
entityId,
collection,
onUpdate,
onError
}: ListenEntityProps<M>): () => void => {
const usedDelegate = collection?.overrides?.dataSourceDelegate ?? delegate;
if (!usedDelegate.listenEntity)
throw Error("useBuildDataSource delegate not initialised");
return usedDelegate.listenEntity<M>({
path,
entityId,
onUpdate,
onError,
collection
})
}, [delegate.listenEntity]) : undefined,
/**
* Save entity to the specified path. Note that Firestore does not allow
* undefined values.
* @param path
* @param entityId
* @param values
* @param schemaId
* @param collection
* @param status
* @group Firestore
*/
saveEntity: useCallback(<M extends Record<string, any>>(
{
path,
entityId,
values,
collection: collectionProp,
status
}: SaveEntityProps<M>): Promise<Entity<M>> => {
const collection = collectionProp ?? navigationController.getCollection(path);
const usedDelegate = collection?.overrides?.dataSourceDelegate ?? delegate;
const resolvedCollection = collection
? resolveCollection<M>({
collection,
path,
entityId,
propertyConfigs: propertyConfigs,
authController
})
: undefined;
const properties: ResolvedProperties<M> | undefined = resolvedCollection?.properties;
const delegateValues = usedDelegate.cmsToDelegateModel(
values,
);
const updatedValues: EntityValues<M> = properties
? updateDateAutoValues(
{
inputValues: delegateValues,
properties,
status,
timestampNowValue: usedDelegate.currentTime?.() ?? new Date(),
setDateToMidnight: usedDelegate.setDateToMidnight
})
: delegateValues;
return usedDelegate.saveEntity({
path,
collection,
entityId,
values: updatedValues,
status
}).then((res) => {
return {
id: res.id,
path: res.path,
values: usedDelegate.delegateToCMSModel(updatedValues)
} as Entity<M>;
});
}, [delegate.saveEntity, navigationController.getCollection]),
/**
* Delete an entity
* @param entity
* @param collection
* @group Firestore
*/
deleteEntity: useCallback(<M extends Record<string, any>>(
{
entity,
collection
}: DeleteEntityProps<M>
): Promise<void> => {
const usedDelegate = collection?.overrides?.dataSourceDelegate ?? delegate;
return usedDelegate.deleteEntity({
entity,
collection
});
}, [delegate.deleteEntity]),
/**
* Check if the given property is unique in the given collection
* @param path Collection path
* @param name of the property
* @param value
* @param property
* @param entityId
* @return `true` if there are no other fields besides the given entity
* @group Firestore
*/
checkUniqueField: useCallback((
path: string,
name: string,
value: any,
entityId?: string,
collection?: EntityCollection
): Promise<boolean> => {
const usedDelegate = collection?.overrides?.dataSourceDelegate ?? delegate;
return usedDelegate.checkUniqueField(path, name, value, entityId, collection);
}, [delegate.checkUniqueField]),
generateEntityId: useCallback((path: string, collection: EntityCollection): string => {
const usedDelegate = collection?.overrides?.dataSourceDelegate ?? delegate;
return usedDelegate.generateEntityId(path, collection);
}, [delegate.generateEntityId]),
countEntities: delegate.countEntities ? async ({
path,
collection,
filter,
order,
orderBy
}: {
path: string,
collection: EntityCollection<any>,
filter?: FilterValues<Extract<keyof any, string>>,
orderBy?: string,
order?: "desc" | "asc",
}): Promise<number> => {
const usedDelegate = collection?.overrides?.dataSourceDelegate ?? delegate;
return usedDelegate.countEntities!({
path,
filter,
orderBy,
order,
collection
});
} : undefined,
isFilterCombinationValid: useCallback(({
path,
databaseId,
filterValues,
sortBy
}: {
path: string,
databaseId?: string,
filterValues: FilterValues<any>,
sortBy?: [string, "asc" | "desc"]
}): boolean => {
if (!delegate.isFilterCombinationValid)
return true;
return delegate.isFilterCombinationValid(
{
path,
databaseId,
filterValues,
sortBy
}
)
}, [delegate.isFilterCombinationValid]),
initTextSearch: useCallback(async (props: {
context: FireCMSContext,
path: string,
collection: EntityCollection,
parentCollectionIds?: string[]
}): Promise<boolean> => {
const usedDelegate = props.collection?.overrides?.dataSourceDelegate ?? delegate;
if (!usedDelegate.initTextSearch)
return false;
return usedDelegate.initTextSearch(props)
}, [delegate.initTextSearch]),
};
}