Skip to content

Instantly share code, notes, and snippets.

@Zehir
Last active September 26, 2022 18:02
Show Gist options
  • Save Zehir/be3b229e36963b1e4d6869207b231ac0 to your computer and use it in GitHub Desktop.
Save Zehir/be3b229e36963b1e4d6869207b231ac0 to your computer and use it in GitHub Desktop.
useFirestore
import type { MaybeRef } from '@vueuse/core'
import type { Ref, UnwrapRef } from 'vue'
import type { CollectionReference, DocumentData, DocumentReference, QueryConstraint, SetOptions, UpdateData } from 'firebase/firestore'
import { addDoc, collection as fireCollection, doc as fireDoc, query as fireQuery, onSnapshot, setDoc, updateDoc } from 'firebase/firestore'
import { tryOnScopeDispose } from '@vueuse/shared'
import type { useFirebase } from './useFirebase'
import type { MaybeArray } from '~/misc/types'
export type Path = MaybeArray<MaybeRef<string | undefined>>
export function concatenatePath(...paths: MaybeRef<string | undefined>[]) {
return computed(() => {
if (useArraySome(paths, path => path === undefined).value)
return undefined
return useArrayReduce(paths, (fullPath = '', path) => fullPath += `/${resolveUnref(path)}`)
.value?.replaceAll(/[\/]{2,}/g, '/')
})
}
export function asArray<T>(value: MaybeArray<T>) {
return Array.isArray(value) ? value : [value]
}
// export type FirestoreDocument = ReturnType<ReturnType<typeof useFirestore>['getDocument']>
export interface FirestoreDocument<T extends DocumentData> {
data: Ref<UnwrapRef<T>>
id: Readonly<Ref<string>>
path: Readonly<Ref<string>>
exists: Ref<boolean | undefined>
close: () => void
}
const DISCONNECT_TIMEOUT = 30 * 1000
export function useFirestore(firebase: ReturnType<typeof useFirebase>) {
const path = (path: Path) => computed(() => {
if (Array.isArray(path))
return concatenatePath(...path).value
return unref(path)
})
const doc = <T>(collectionPath: Path) => computed(() => {
const _path = path(collectionPath)
if (!_path.value)
return
return fireDoc(firebase.firestore, _path.value) as DocumentReference<T>
})
const collection = <T>(collectionPath: Path) => computed(() => {
const _path = path(collectionPath)
if (!_path.value)
return
return fireCollection(firebase.firestore, _path.value) as CollectionReference<T>
})
const query = <T>(collectionPath: Path, ...queryConstraints: MaybeRef<QueryConstraint | undefined>[]) => computed(() => {
const _collection = collection<T>(collectionPath)
if (_collection.value === undefined)
return
if (useArraySome(queryConstraints, constraint => constraint === undefined).value)
return
return fireQuery<T>(_collection.value, ...queryConstraints.map(unref) as QueryConstraint[])
})
const addDocument = async <T>(path: Path, value: T) => {
const colReference = collection<T>(path)
if (unref(colReference)) {
try {
return addDoc(colReference.value!, value)
}
catch (error) {
console.warn(error, 'addDocument', colReference.value?.path, value)
}
}
}
const setDocument = <T>(path: Path, value: T, options: SetOptions = {}) => {
const docReference = doc<T>(path)
if (unref(docReference))
setDoc(docReference.value!, value, options)
}
const updateDocument = <T>(path: Path, field: string | UpdateData<T>, value: unknown, ...moreFieldsAndValues: unknown[]) => {
const docReference = doc<T>(path)
console.log('updateDocument', unref(docReference)?.path)
if (unref(docReference)) {
if (typeof field === 'string')
return updateDoc(docReference.value!, field, value, ...moreFieldsAndValues)
else
return updateDoc<T>(docReference.value!, field)
}
}
const getDocument = <T extends DocumentData>(path: Path, placeHolder: T): FirestoreDocument<T> => {
const docReference = doc<T>(path)
const data = ref<T>(placeHolder)
const id = ref('')
const exists = ref<boolean | undefined>(undefined)
let close = () => { }
watchTriggerable(() => docReference.value, (isValid) => {
if (!isValid)
return
close()
console.log('getDocument', docReference.value?.path)
close = onSnapshot<T>(docReference.value!, (snapshot) => {
data.value = snapshot.data() as UnwrapRef<T>
id.value = snapshot.id
exists.value = snapshot.exists()
}, (error) => {
console.warn(error, 'getDocument', docReference.value?.path)
})
}).trigger()
tryOnScopeDispose(() => {
// console.log('On Scope Dispose of a document ...', docReference.value?.path)
useTimeoutFn(() => {
// console.log('On Scope Dispose of a document closed.', docReference.value?.path)
close()
}, DISCONNECT_TIMEOUT)
})
return {
data,
id: readonly(id),
path: readonly(computed(() => docReference.value?.path ?? '')),
exists,
close,
}
}
const findDocuments = <T extends DocumentData>(
_path: Path,
...queryConstraints: MaybeRef<QueryConstraint | undefined>[]
): {
data: Ref<UnwrapRef<FirestoreDocument<T>[]>>
close: FirestoreDocument<T>['close']
} => {
const basePath = path(_path)
const _query = query<T>(_path, ...queryConstraints)
const data = ref<FirestoreDocument<T>[]>([])
let close = () => { }
watchTriggerable(() => _query.value, (isValid) => {
if (!isValid)
return
close()
close = onSnapshot<T>(fireQuery(_query.value!), (snapshot) => {
data.value = snapshot.docs.map((value) => {
return {
data: value.data() as UnwrapRef<T>,
id: value.id,
path: `${basePath.value}/${value.id}`,
exists: value.exists(),
close,
}
}).filter(value => value.data !== undefined)
}, (error) => {
console.warn(error, 'findDocuments', basePath.value, queryConstraints.map((constrain) => { return unref(constrain) }))
})
}).trigger()
tryOnScopeDispose(() => {
// console.log('On Scope Dispose of a query ...', path(_path).value)
useTimeoutFn(() => {
// console.log('On Scope Dispose of a query closed.', path(_path).value)
close()
}, DISCONNECT_TIMEOUT)
})
return { data, close }
}
return {
path,
doc,
collection,
query,
addDocument,
setDocument,
updateDocument,
getDocument,
findDocuments,
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment