UNPKG

svelte-firebase-state

Version:

Simplify Firebase integration in Svelte and SvelteKit with reactive state management for Firestore and Realtime Database.

154 lines (153 loc) 5.07 kB
import { onSnapshot, doc, getDoc, setDoc, getDocs, collection, query, limit } from "firebase/firestore"; import { FirestoreState } from "./FirestoreState.svelte.js"; export class DocumentState extends FirestoreState { collectionPathFunctionOrString; queryParams; docRef; queryRef; constructor({ auth, firestore, path: pathFunctionOrString, collectionPath: collectionPathFunctionOrString, query: queryParams, listen = false, fromFirestore, toFirestore, converter }) { super({ auth, firestore, listen, fromFirestore, toFirestore, pathFunctionOrString: pathFunctionOrString ?? undefined, converter }); this.collectionPathFunctionOrString = collectionPathFunctionOrString; this.queryParams = queryParams; } async init() { if (this.docRef) { return this.docRef; } const user = await this.getUserPromise; if (this.pathFunctionOrString) { const pathStr = this.get_path_string(user); if (!pathStr) { this.docRef = null; return this.docRef; } const pathArray = pathStr.split("/"); this.docRef = doc(this.firestore, ...pathArray).withConverter(this.converter); } else if (this.collectionPathFunctionOrString) { // Run query and get first document ref let collectionPath; if (typeof this.collectionPathFunctionOrString === "function") { collectionPath = this.collectionPathFunctionOrString(user); } else { collectionPath = this.collectionPathFunctionOrString; } if (!collectionPath || !this.queryParams) { throw new Error(`"collectionPath" and "query" options must be defined.`); } const queryRef = query(collection(this.firestore, collectionPath), ...this.queryParams(user), limit(1)); if (!queryRef) { this.docRef = undefined; return this.docRef; } this.queryRef = queryRef; const querySnapshot = await getDocs(queryRef); this.docRef = querySnapshot.docs[0]?.ref.withConverter(this.converter); } return this.docRef; } async fetch_data() { this.loading = true; if (!this.docRef) { this.data = null; return; } const docSnap = await getDoc(this.docRef); if (!docSnap.exists()) { this.data = null; } else { this.data = docSnap.data(); } this.loading = false; } async listen_data() { if (this.unsub) { return; } if (!this.docRef) { // If there is no docRef, we can still try to listen to the queryRef this.listen_to_query(); return; } this.listen_to_doc(); } listen_to_query() { if (!this.queryRef) { return; } this.unsub?.(); this.unsub = onSnapshot(this.queryRef, (querySnapshot) => { if (!querySnapshot.empty) { // We found a document const docSnap = querySnapshot.docs[0]; this.docRef = docSnap.ref; this.unsub?.(); this.listen_to_doc(); } else { this.data = null; } }); return this.unsub; } listen_to_doc() { if (!this.docRef) { return; } this.unsub?.(); this.unsub = onSnapshot(this.docRef, (docSnap) => { if (!docSnap.exists()) { this.data = null; // If the document doesn't exist // We can still try to listen to the queryRef this.listen_to_query(); return; } const newData = docSnap.data(); // TODO: Check if the data we receive is the same as the one we have // in this case we don't need to update the value this.data = newData; }); return this.unsub; } save_data_to_firebase() { if (!this.docRef) { return; } return setDoc(this.docRef, this.data || null, { merge: true }); } async get_doc_ref() { await this.initPromise; return this.docRef; } save(key, update) { if (!key) { this.save_data_to_firebase(); return; } if (!update || !this.docRef || !this.data) { return; } let newValue; if (typeof update === "function") { const updateFn = update; const prevValue = this.data[key]; newValue = updateFn(prevValue); } else { newValue = update; } this.data[key] = newValue; this.save_data_to_firebase(); } }