mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-04-25 00:36:31 +02:00
feat: Reduce memory footprint for PGlite, Implement user-specific Electric SQL database management
- Added cleanup logic for other users' databases on login to ensure data isolation. - Introduced best-effort cleanup on logout to remove stale databases. - Updated Electric client initialization to support user-specific databases. - Refactored hooks to utilize the Electric context for better state management and prevent race conditions. - Enhanced error handling and logging for Electric SQL operations.
This commit is contained in:
parent
eb1ddf0c92
commit
703ec08d19
9 changed files with 752 additions and 489 deletions
|
|
@ -1,44 +1,95 @@
|
|||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { initElectric, isElectricInitialized } from "@/lib/electric/client";
|
||||
import { useEffect, useState, useRef } from "react";
|
||||
import { useAtomValue } from "jotai";
|
||||
import { currentUserAtom } from "@/atoms/user/user-query.atoms";
|
||||
import {
|
||||
initElectric,
|
||||
cleanupElectric,
|
||||
isElectricInitialized,
|
||||
type ElectricClient,
|
||||
} from "@/lib/electric/client";
|
||||
import { ElectricContext } from "@/lib/electric/context";
|
||||
|
||||
interface ElectricProviderProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* ElectricProvider initializes the Electric SQL client with PGlite
|
||||
* ElectricProvider initializes the Electric SQL client with user-specific PGlite database
|
||||
* and provides it to children via context.
|
||||
*
|
||||
* This provider ensures Electric is initialized before rendering children,
|
||||
* but doesn't block if initialization fails (app can still work without real-time sync)
|
||||
* KEY BEHAVIORS:
|
||||
* 1. Single initialization point - only this provider creates the Electric client
|
||||
* 2. Creates user-specific database (isolated per user)
|
||||
* 3. Cleans up other users' databases on login
|
||||
* 4. Re-initializes when user changes
|
||||
* 5. Provides client via context - hooks should use useElectricClient()
|
||||
*/
|
||||
export function ElectricProvider({ children }: ElectricProviderProps) {
|
||||
const [initialized, setInitialized] = useState(false);
|
||||
const [electricClient, setElectricClient] = useState<ElectricClient | null>(null);
|
||||
const [error, setError] = useState<Error | null>(null);
|
||||
const { data: user, isSuccess: isUserLoaded, isError: isUserError } = useAtomValue(currentUserAtom);
|
||||
const previousUserIdRef = useRef<string | null>(null);
|
||||
const initializingRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
// Skip if already initialized
|
||||
if (isElectricInitialized()) {
|
||||
setInitialized(true);
|
||||
// Skip on server side
|
||||
if (typeof window === "undefined") return;
|
||||
|
||||
// If no user is logged in, don't initialize Electric
|
||||
// The app can still function without real-time sync for non-authenticated pages
|
||||
if (!isUserLoaded || !user?.id) {
|
||||
// If we had a previous user and now logged out, cleanup
|
||||
if (previousUserIdRef.current && isElectricInitialized()) {
|
||||
console.log("[ElectricProvider] User logged out, cleaning up...");
|
||||
cleanupElectric().then(() => {
|
||||
previousUserIdRef.current = null;
|
||||
setElectricClient(null);
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const userId = String(user.id);
|
||||
|
||||
// If already initialized for THIS user, skip
|
||||
if (electricClient && previousUserIdRef.current === userId) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Prevent concurrent initialization attempts
|
||||
if (initializingRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
// User changed or first initialization
|
||||
initializingRef.current = true;
|
||||
let mounted = true;
|
||||
|
||||
async function init() {
|
||||
try {
|
||||
await initElectric();
|
||||
console.log(`[ElectricProvider] Initializing for user: ${userId}`);
|
||||
|
||||
// If different user was previously initialized, cleanup will happen inside initElectric
|
||||
const client = await initElectric(userId);
|
||||
|
||||
if (mounted) {
|
||||
setInitialized(true);
|
||||
previousUserIdRef.current = userId;
|
||||
setElectricClient(client);
|
||||
setError(null);
|
||||
console.log(`[ElectricProvider] ✅ Ready for user: ${userId}`);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to initialize Electric SQL:", err);
|
||||
console.error("[ElectricProvider] Failed to initialize:", err);
|
||||
if (mounted) {
|
||||
setError(err instanceof Error ? err : new Error("Failed to initialize Electric SQL"));
|
||||
// Don't block rendering if Electric SQL fails - app can still work
|
||||
setInitialized(true);
|
||||
// Set client to null so hooks know initialization failed
|
||||
setElectricClient(null);
|
||||
}
|
||||
} finally {
|
||||
if (mounted) {
|
||||
initializingRef.current = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -48,22 +99,38 @@ export function ElectricProvider({ children }: ElectricProviderProps) {
|
|||
return () => {
|
||||
mounted = false;
|
||||
};
|
||||
}, []);
|
||||
}, [user?.id, isUserLoaded, electricClient]);
|
||||
|
||||
// Show loading state only briefly, then render children
|
||||
// Electric SQL will sync in the background
|
||||
if (!initialized) {
|
||||
// For non-authenticated pages (like landing page), render immediately with null context
|
||||
// Also render immediately if user query failed (e.g., token expired)
|
||||
if (!isUserLoaded || !user?.id || isUserError) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-screen">
|
||||
<div className="text-muted-foreground">Initializing...</div>
|
||||
</div>
|
||||
<ElectricContext.Provider value={null}>
|
||||
{children}
|
||||
</ElectricContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
// If there's an error, still render children but log the error
|
||||
if (error) {
|
||||
console.warn("Electric SQL initialization failed, notifications may not sync:", error.message);
|
||||
// Show loading state while initializing for authenticated users
|
||||
if (!electricClient && !error) {
|
||||
return (
|
||||
<ElectricContext.Provider value={null}>
|
||||
<div className="flex items-center justify-center min-h-screen">
|
||||
<div className="text-muted-foreground">Initializing...</div>
|
||||
</div>
|
||||
</ElectricContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
return <>{children}</>;
|
||||
// If there's an error, still render but warn
|
||||
if (error) {
|
||||
console.warn("[ElectricProvider] Initialization failed, sync may not work:", error.message);
|
||||
}
|
||||
|
||||
// Provide the Electric client to children
|
||||
return (
|
||||
<ElectricContext.Provider value={electricClient}>
|
||||
{children}
|
||||
</ElectricContext.Provider>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue