first attempt at stream sidebar, tasks, and events
This commit is contained in:
@@ -16,6 +16,7 @@ import {
|
||||
arrayRemove,
|
||||
FieldPath,
|
||||
type DocumentData,
|
||||
type DocumentSnapshot,
|
||||
type FirestoreDataConverter,
|
||||
type QueryDocumentSnapshot,
|
||||
type SnapshotOptions,
|
||||
@@ -129,6 +130,18 @@ const particleConverter: FirestoreDataConverter<Particle> = {
|
||||
},
|
||||
};
|
||||
|
||||
// Parse a snapshot defensively: newer clients may write particle types this
|
||||
// app version doesn't know yet. A single unparseable doc must not break the
|
||||
// whole subscription, so skip it instead of throwing inside onSnapshot.
|
||||
function safeData(snap: DocumentSnapshot<Particle>): Particle | null {
|
||||
try {
|
||||
return snap.data() ?? null;
|
||||
} catch (error) {
|
||||
console.warn(`Skipping unparseable particle ${snap.ref.path}:`, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Typed reference helpers ---
|
||||
|
||||
function typedDoc(path: string) {
|
||||
@@ -149,7 +162,7 @@ export function subscribeToParticle(
|
||||
return onSnapshot(
|
||||
typedDoc(docPath),
|
||||
(snap) => {
|
||||
onData(snap.exists() ? snap.data() : null);
|
||||
onData(safeData(snap));
|
||||
},
|
||||
onError,
|
||||
);
|
||||
@@ -160,7 +173,7 @@ export async function getParticle(docPath: string): Promise<Particle | null> {
|
||||
if (!docSnap.exists()) {
|
||||
return null;
|
||||
}
|
||||
return docSnap.data();
|
||||
return safeData(docSnap);
|
||||
}
|
||||
|
||||
export interface GetParticleChildrenOptions {
|
||||
@@ -183,7 +196,9 @@ export async function getParticleChildren(
|
||||
orderBy(orderByField, orderDirection),
|
||||
);
|
||||
const snap = await getDocs(q);
|
||||
return snap.docs.map((d) => d.data());
|
||||
return snap.docs
|
||||
.map((d) => safeData(d))
|
||||
.filter((p): p is Particle => p !== null);
|
||||
}
|
||||
|
||||
export interface SubscribeToParticleChildrenOptions {
|
||||
@@ -229,14 +244,18 @@ export function subscribeToParticleChildren(
|
||||
return onSnapshot(
|
||||
q,
|
||||
(snap) => {
|
||||
const updatedChildren = snap.docs.map((d) => d.data());
|
||||
const updatedChildren = snap.docs
|
||||
.map((d) => safeData(d))
|
||||
.filter((p): p is Particle => p !== null);
|
||||
onData(updatedChildren);
|
||||
|
||||
if (onAdded || onRemoved) {
|
||||
for (const change of snap.docChanges()) {
|
||||
if (change.type === 'added' && onAdded) onAdded(change.doc.data());
|
||||
const changed = safeData(change.doc);
|
||||
if (!changed) continue;
|
||||
if (change.type === 'added' && onAdded) onAdded(changed);
|
||||
if (change.type === 'removed' && onRemoved)
|
||||
onRemoved(change.doc.data(), updatedChildren);
|
||||
onRemoved(changed, updatedChildren);
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -257,7 +276,7 @@ export function subscribeToLatestChild(
|
||||
return onSnapshot(
|
||||
q,
|
||||
(snap) => {
|
||||
onData(snap.empty ? null : snap.docs[0].data());
|
||||
onData(snap.empty ? null : safeData(snap.docs[0]));
|
||||
},
|
||||
onError,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user