infra: add linting and formatting for js projects (#230)

* wip

* wip

* wip

* format

* wip(mobile): lint and format

* cleanup

* nits

* nits

* idiomatic react

* nit

* format all root files
This commit was merged in pull request #230.
This commit is contained in:
Arjun Patel
2026-06-02 07:44:24 -07:00
committed by GitHub
parent 2fe562ce2b
commit a8a0b7db1b
258 changed files with 7822 additions and 5195 deletions
+76 -55
View File
@@ -21,17 +21,23 @@ import {
type SnapshotOptions,
type Unsubscribe,
QueryFieldFilterConstraint,
} from "firebase/firestore";
import { firestoreDb } from "@/firebase";
import { isContainerType, ParticleSchema } from "@/api/types";
import type { Particle, ParticleType, ParticlePropertiesMap, Reactions } from "@/api/types";
} from 'firebase/firestore';
import { firestoreDb } from '@/firebase';
import { isContainerType, ParticleSchema } from '@/api/types';
import type {
Particle,
ParticleType,
ParticlePropertiesMap,
Reactions,
} from '@/api/types';
// --- Converter ---
const particleConverter: FirestoreDataConverter<Particle> = {
toFirestore(particle: Particle): DocumentData {
const { id: _id, created_at, updated_at, ...rest } = particle;
const deletedAt = "deleted_at" in particle ? particle.deleted_at : undefined;
const deletedAt =
'deleted_at' in particle ? particle.deleted_at : undefined;
return {
...rest,
created_at: Timestamp.fromDate(created_at),
@@ -44,51 +50,58 @@ const particleConverter: FirestoreDataConverter<Particle> = {
options?: SnapshotOptions,
): Particle {
const raw = snap.data(options);
if (typeof raw.type !== "string") {
if (typeof raw.type !== 'string') {
throw new Error(`Invalid particle type: ${raw.type}`);
}
const type = raw.type as ParticleType;
switch (type) {
case "stream":
case 'stream':
return ParticleSchema.parse({
id: snap.id,
type: raw.type,
properties: raw.properties,
created_at: (raw.created_at as Timestamp).toDate(),
created_by_human_id: raw.created_by_human_id,
updated_at: raw.updated_at ? (raw.updated_at as Timestamp).toDate() : undefined,
updated_at: raw.updated_at
? (raw.updated_at as Timestamp).toDate()
: undefined,
visible_to: raw.visible_to,
playback_markers: raw.playback_markers
? Object.fromEntries(
Object.entries(raw.playback_markers).map(([key, value]) => [
key,
(value as Timestamp).toDate(),
]),
)
Object.entries(raw.playback_markers).map(([key, value]) => [
key,
(value as Timestamp).toDate(),
]),
)
: undefined,
last_child_created_at: raw.last_child_created_at ? (raw.last_child_created_at as Timestamp).toDate() : undefined,
huddle_active_participants: raw.huddle_active_participants ?? undefined,
last_child_created_at: raw.last_child_created_at
? (raw.last_child_created_at as Timestamp).toDate()
: undefined,
huddle_active_participants:
raw.huddle_active_participants ?? undefined,
status: raw.status ?? undefined,
});
case "folder":
case 'folder':
return ParticleSchema.parse({
id: snap.id,
type: raw.type,
properties: raw.properties,
created_at: (raw.created_at as Timestamp).toDate(),
created_by_human_id: raw.created_by_human_id,
updated_at: raw.updated_at ? (raw.updated_at as Timestamp).toDate() : undefined,
updated_at: raw.updated_at
? (raw.updated_at as Timestamp).toDate()
: undefined,
visible_to: raw.visible_to,
});
case "media":
case "file":
case "text":
case "quest":
case "paper": {
case 'media':
case 'file':
case 'text':
case 'quest':
case 'paper': {
// Firestore stores timestamps as `Timestamp`; zod expects `Date`. Text
// particles carry `properties.edited_at`, so coerce it if present.
const properties =
type === "text" && raw.properties?.edited_at
type === 'text' && raw.properties?.edited_at
? {
...raw.properties,
edited_at: (raw.properties.edited_at as Timestamp).toDate(),
@@ -100,9 +113,13 @@ const particleConverter: FirestoreDataConverter<Particle> = {
properties,
created_at: (raw.created_at as Timestamp).toDate(),
created_by_human_id: raw.created_by_human_id,
updated_at: raw.updated_at ? (raw.updated_at as Timestamp).toDate() : undefined,
updated_at: raw.updated_at
? (raw.updated_at as Timestamp).toDate()
: undefined,
reactions: raw.reactions ?? undefined,
deleted_at: raw.deleted_at ? (raw.deleted_at as Timestamp).toDate() : undefined,
deleted_at: raw.deleted_at
? (raw.deleted_at as Timestamp).toDate()
: undefined,
deleted_by_human_id: raw.deleted_by_human_id ?? undefined,
});
}
@@ -149,15 +166,18 @@ export async function getParticle(docPath: string): Promise<Particle | null> {
export interface GetParticleChildrenOptions {
orderByField: string;
orderDirection: "asc" | "desc";
orderDirection: 'asc' | 'desc';
}
export async function getParticleChildren(
collectionPath: string,
{
orderByField = "created_at",
orderDirection = "asc",
}: GetParticleChildrenOptions = { orderByField: "created_at", orderDirection: "asc" },
orderByField = 'created_at',
orderDirection = 'asc',
}: GetParticleChildrenOptions = {
orderByField: 'created_at',
orderDirection: 'asc',
},
): Promise<Particle[]> {
const q = query(
typedCollection(collectionPath),
@@ -172,7 +192,7 @@ export interface SubscribeToParticleChildrenOptions {
onError: (error: Error) => void;
visibilityScopes?: string[];
orderByField?: string;
orderDirection?: "asc" | "desc";
orderDirection?: 'asc' | 'desc';
onAdded?: (child: Particle) => void;
onRemoved?: (child: Particle, updatedChildren: Particle[]) => void;
whereFilter?: QueryFieldFilterConstraint;
@@ -186,20 +206,20 @@ export function subscribeToParticleChildren(
onData,
onError,
visibilityScopes = [],
orderByField = "created_at",
orderDirection = "desc",
orderByField = 'created_at',
orderDirection = 'desc',
onAdded,
onRemoved,
whereFilter,
limit: limitValue,
}: SubscribeToParticleChildrenOptions
}: SubscribeToParticleChildrenOptions,
): Unsubscribe {
let q = query(typedCollection(collectionPath), orderBy(orderByField, orderDirection));
let q = query(
typedCollection(collectionPath),
orderBy(orderByField, orderDirection),
);
if (visibilityScopes.length > 0) {
q = query(
q,
where("visible_to", "array-contains-any", visibilityScopes),
);
q = query(q, where('visible_to', 'array-contains-any', visibilityScopes));
}
if (whereFilter) {
q = query(q, whereFilter);
@@ -215,8 +235,9 @@ export function subscribeToParticleChildren(
if (onAdded || onRemoved) {
for (const change of snap.docChanges()) {
if (change.type === "added" && onAdded) onAdded(change.doc.data());
if (change.type === "removed" && onRemoved) onRemoved(change.doc.data(), updatedChildren);
if (change.type === 'added' && onAdded) onAdded(change.doc.data());
if (change.type === 'removed' && onRemoved)
onRemoved(change.doc.data(), updatedChildren);
}
}
},
@@ -231,7 +252,7 @@ export function subscribeToLatestChild(
): Unsubscribe {
const q = query(
typedCollection(collectionPath),
orderBy("created_at", "desc"),
orderBy('created_at', 'desc'),
limit(1),
);
return onSnapshot(
@@ -259,7 +280,7 @@ export async function createParticle<T extends ParticleType>(
}
const particle: Particle = ParticleSchema.parse({
id: "", // ignored by toFirestore, but needed to satisfy the type
id: '', // ignored by toFirestore, but needed to satisfy the type
type,
properties,
created_at: new Date(),
@@ -272,22 +293,22 @@ export async function createParticle<T extends ParticleType>(
export async function createStreamParticle(
collectionPath: string,
properties: ParticlePropertiesMap["stream"],
properties: ParticlePropertiesMap['stream'],
createdByHumanId: string,
visibleTo?: string[],
): Promise<string> {
if (!visibleTo || visibleTo.length === 0) {
throw new Error("visibleTo is required for streams and cannot be empty");
throw new Error('visibleTo is required for streams and cannot be empty');
}
const particle: Particle = ParticleSchema.parse({
id: "",
type: "stream",
id: '',
type: 'stream',
properties,
created_at: new Date(),
created_by_human_id: createdByHumanId,
visible_to: visibleTo,
status: "open",
status: 'open',
});
const ref = await addDoc(typedCollection(collectionPath), particle);
return ref.id;
@@ -301,7 +322,7 @@ export async function updateParticleProperties<T extends ParticleType>(
const particleRef = typedDoc(docPath);
// Take the partial and create a new object with dot notation
// e.g. { title: "New Title" } becomes { "properties.title": "New Title" }
const updatedProperties: Record<string, any> = {};
const updatedProperties: Record<string, any> = {}; // eslint-disable-line @typescript-eslint/no-explicit-any
for (const key in properties) {
updatedProperties[`properties.${key}`] = properties[key];
}
@@ -320,8 +341,8 @@ export async function editTextParticleContent(
): Promise<void> {
const particleRef = typedDoc(docPath);
await updateDoc(particleRef, {
"properties.content": content,
"properties.edited_at": serverTimestamp(),
'properties.content': content,
'properties.edited_at': serverTimestamp(),
updated_at: serverTimestamp(),
});
}
@@ -352,7 +373,7 @@ export async function updateParticleVisibleTo(
export async function updateParticle(
docPath: string,
fieldName: string,
value: any,
value: any, // eslint-disable-line @typescript-eslint/no-explicit-any
): Promise<void> {
const particleRef = typedDoc(docPath);
await updateDoc(particleRef, {
@@ -363,7 +384,7 @@ export async function updateParticle(
export async function updateStreamStatus(
docPath: string,
status: "open" | "closed",
status: 'open' | 'closed',
): Promise<void> {
const particleRef = typedDoc(docPath);
await updateDoc(particleRef, { status, updated_at: serverTimestamp() });
@@ -406,7 +427,7 @@ export async function updateStreamPlaybackMarker(
const RESERVED_REACTION_CHARS = /[~*/[\]]/g;
export function sanitizeReactionText(text: string): string {
return text.replace(RESERVED_REACTION_CHARS, "");
return text.replace(RESERVED_REACTION_CHARS, '');
}
export async function toggleParticleReaction(
@@ -421,9 +442,9 @@ export async function toggleParticleReaction(
const alreadyReacted = currentReactions?.[key]?.includes(humanId) ?? false;
await updateDoc(
particleRef,
new FieldPath("reactions", key),
new FieldPath('reactions', key),
alreadyReacted ? arrayRemove(humanId) : arrayUnion(humanId),
"updated_at",
'updated_at',
serverTimestamp(),
);
}