From 19f334de8a2bb1e926db0cef155847225c491efd Mon Sep 17 00:00:00 2001 From: "Builder.io" Date: Wed, 5 Nov 2025 07:21:18 +0000 Subject: [PATCH] completionId: cgen-62bb5cdb686b41d7b91fdb77ebf7cd35 cgen-62bb5cdb686b41d7b91fdb77ebf7cd35 --- client/lib/supabase-service.ts | 103 +++++++++++++++++++++++---------- 1 file changed, 72 insertions(+), 31 deletions(-) diff --git a/client/lib/supabase-service.ts b/client/lib/supabase-service.ts index 1c607547..6a9cc054 100644 --- a/client/lib/supabase-service.ts +++ b/client/lib/supabase-service.ts @@ -231,15 +231,32 @@ export const achievementService = { }, }; +// Helper function for timeouts +function withTimeout( + promise: Promise, + ms: number, + label: string, +): Promise { + return Promise.race([ + promise, + new Promise((_, reject) => + setTimeout(() => reject(new Error(`${label} timeout after ${ms}ms`)), ms), + ), + ]); +} + // Community Services export const communityService = { async getPosts(limit = 10): Promise { - // 1) Try relational select with embedded author profile + const DEFAULT_TIMEOUT = 8000; // 8 seconds per attempt + + // 1) Try relational select with embedded author profile (with timeout) try { - const { data, error } = await supabase - .from("community_posts") - .select( - ` + const { data, error } = await withTimeout( + supabase + .from("community_posts") + .select( + ` *, user_profiles ( username, @@ -247,10 +264,13 @@ export const communityService = { avatar_url ) `, - ) - .eq("is_published", true) - .order("created_at", { ascending: false }) - .limit(limit); + ) + .eq("is_published", true) + .order("created_at", { ascending: false }) + .limit(limit), + DEFAULT_TIMEOUT, + "Relational select", + ); if (!error) { return (Array.isArray(data) ? data : []) as CommunityPost[]; @@ -266,34 +286,49 @@ export const communityService = { ); } - // 2) Fallback to simple posts select, then hydrate author profiles manually + // 2) Fallback to simple posts select, then hydrate author profiles manually (with timeout) try { - const { data: posts, error: postsErr } = await supabase - .from("community_posts") - .select("*") - .eq("is_published", true) - .order("created_at", { ascending: false }) - .limit(limit); + const { data: posts, error: postsErr } = await withTimeout( + supabase + .from("community_posts") + .select("*") + .eq("is_published", true) + .order("created_at", { ascending: false }) + .limit(limit), + DEFAULT_TIMEOUT, + "Simple posts select", + ); if (!postsErr && Array.isArray(posts) && posts.length) { const authorIds = Array.from( new Set(posts.map((p: any) => p.author_id).filter(Boolean)), ); let profilesById: Record = {}; if (authorIds.length) { - const { data: profiles, error: profErr } = await supabase - .from("user_profiles") - .select("id, username, full_name, avatar_url") - .in("id", authorIds); - if (!profErr && Array.isArray(profiles)) { - profilesById = Object.fromEntries( - profiles.map((u: any) => [ - u.id, - { - username: u.username, - full_name: u.full_name, - avatar_url: u.avatar_url, - }, - ]), + try { + const { data: profiles, error: profErr } = await withTimeout( + supabase + .from("user_profiles") + .select("id, username, full_name, avatar_url") + .in("id", authorIds), + DEFAULT_TIMEOUT, + "Profile hydration", + ); + if (!profErr && Array.isArray(profiles)) { + profilesById = Object.fromEntries( + profiles.map((u: any) => [ + u.id, + { + username: u.username, + full_name: u.full_name, + avatar_url: u.avatar_url, + }, + ]), + ); + } + } catch (profError) { + console.warn( + "Profile hydration timeout/error:", + (profError as any)?.message || profError, ); } } @@ -314,11 +349,17 @@ export const communityService = { ); } - // 3) Final fallback to API if available + // 3) Final fallback to API if available (with timeout) try { + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT); + const resp = await fetch( `/api/posts?limit=${encodeURIComponent(String(limit))}`, + { signal: controller.signal }, ); + clearTimeout(timeoutId); + if (resp.ok) { const ct = resp.headers.get("content-type") || ""; if (ct.includes("application/json") || ct.includes("json")) {