refactor: optimize drawing data handling and cache management

- Improve cache key generation using JSON.stringify for consistent formatting
- Add promise deduplication in DrawingCard to prevent redundant API calls for full drawing data
- Clear full data state when drawing ID changes to ensure fresh data loading
- Fix async cache invalidation in drawing update and collection delete endpoints
- Move cache invalidation after database operations in SQLite import endpoint
- Add HydratedDrawingData type for better type safety in drawing data management
This commit is contained in:
Adrian Acala
2025-11-29 11:35:57 -08:00
parent 6f050aec7d
commit c4352185d6
2 changed files with 39 additions and 19 deletions
+8 -6
View File
@@ -137,9 +137,11 @@ const buildDrawingsCacheKey = (keyParts: {
collectionFilter: string;
includeData: boolean;
}) =>
`${keyParts.searchTerm}|${keyParts.collectionFilter}|${
keyParts.includeData ? "full" : "summary"
}`;
JSON.stringify([
keyParts.searchTerm,
keyParts.collectionFilter,
keyParts.includeData ? "full" : "summary",
]);
const getCachedDrawingsBody = (key: string): Buffer | null => {
const entry = drawingsCache.get(key);
@@ -796,7 +798,7 @@ app.put("/drawings/:id", async (req, res) => {
where: { id },
data,
});
invalidateDrawingsCache();
await invalidateDrawingsCache();
console.log("[API] Update complete", {
id,
@@ -925,7 +927,7 @@ app.delete("/collections/:id", async (req, res) => {
where: { id },
}),
]);
invalidateDrawingsCache();
await invalidateDrawingsCache();
res.json({ success: true });
} catch (error) {
@@ -1191,9 +1193,9 @@ app.post("/import/sqlite", upload.single("db"), async (req, res) => {
// Reinitialize Prisma client
await prisma.$disconnect();
invalidateDrawingsCache();
res.json({ success: true, message: "Database imported successfully" });
invalidateDrawingsCache();
} catch (error) {
console.error(error);
if (req.file) {
+31 -13
View File
@@ -10,6 +10,12 @@ import { exportDrawingToFile } from '../utils/exportUtils';
import * as api from '../api';
type HydratedDrawingData = {
elements: any[];
appState: any;
files: Record<string, any>;
};
interface DrawingCardProps {
drawing: DrawingSummary;
collections: Collection[];
@@ -52,27 +58,39 @@ export const DrawingCard: React.FC<DrawingCardProps> = ({
const [previewSvg, setPreviewSvg] = useState<string | null>(drawing.preview ?? null);
const [contextMenu, setContextMenu] = useState<{ x: number; y: number } | null>(null);
const [isExporting, setIsExporting] = useState(false);
const [fullData, setFullData] = useState<{
elements: any[];
appState: any;
files: Record<string, any> | null;
} | null>(null);
const [fullData, setFullData] = useState<HydratedDrawingData | null>(null);
const fullDataRef = React.useRef(fullData);
fullDataRef.current = fullData;
const fullDataPromiseRef = React.useRef<Promise<HydratedDrawingData> | null>(null);
useEffect(() => {
setFullData(null);
fullDataPromiseRef.current = null;
}, [drawing.id]);
const ensureFullData = useCallback(async () => {
if (fullDataRef.current) {
return fullDataRef.current;
}
const fullDrawing = await api.getDrawing(drawing.id);
const payload = {
elements: fullDrawing.elements || [],
appState: fullDrawing.appState || {},
files: fullDrawing.files || {},
};
setFullData(payload);
return payload;
if (fullDataPromiseRef.current) {
return fullDataPromiseRef.current;
}
const promise = api.getDrawing(drawing.id).then((fullDrawing) => {
const payload: HydratedDrawingData = {
elements: fullDrawing.elements || [],
appState: fullDrawing.appState || {},
files: fullDrawing.files || {},
};
setFullData(payload);
fullDataPromiseRef.current = null;
return payload;
}).catch((error) => {
fullDataPromiseRef.current = null;
throw error;
});
fullDataPromiseRef.current = promise;
return promise;
}, [drawing.id]);
useEffect(() => {