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
JavaScript
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();
}
}