import { type UseMutationResult, useMutation, useQueryClient, } from "@tanstack/react-query" import { useCallback, useEffect, useRef } from "react" import { type FlowDef_Input, FlowsService } from "@/client" export const flowKeys = { all: ["flows"] as const, detail: (name: string) => ["flows", name] as const, source: (name: string, nodeId: string) => ["flows", name, "source", nodeId] as const, nodeTypes: ["flows", "node-types"] as const, } export const flowsQueryOptions = () => ({ queryKey: flowKeys.all, queryFn: () => FlowsService.readFlows(), }) export const flowQueryOptions = (name: string) => ({ queryKey: flowKeys.detail(name), queryFn: () => FlowsService.readFlow({ name }), }) export const nodeTypesQueryOptions = () => ({ queryKey: flowKeys.nodeTypes, queryFn: () => FlowsService.readNodeTypes(), staleTime: Number.POSITIVE_INFINITY, }) export const nodeSourceQueryOptions = (name: string, nodeId: string) => ({ queryKey: flowKeys.source(name, nodeId), queryFn: () => FlowsService.readNodeSource({ name, nodeId }), }) const AUTOSAVE_DELAY = 800 /** * Saves the flow a moment after the last edit, and immediately when the editor * needs the server to be current (closing a panel, switching flow, running). * * Identical documents are skipped server-side, so a quiet canvas writes nothing. */ export function useAutosave(name: string): { save: (definition: FlowDef_Input) => void flush: () => void mutation: UseMutationResult } { const queryClient = useQueryClient() const pending = useRef(null) const timer = useRef | null>(null) const mutation = useMutation({ mutationFn: (definition: FlowDef_Input) => FlowsService.saveFlow({ name, requestBody: definition }), onSuccess: (detail) => { // Write the server's answer straight into the cache: invalidating would // pull the document back out from under edits still in flight. queryClient.setQueryData(flowKeys.detail(name), detail) queryClient.invalidateQueries({ queryKey: flowKeys.all, exact: true }) }, }) const flush = useCallback(() => { if (timer.current) { clearTimeout(timer.current) timer.current = null } const definition = pending.current pending.current = null if (definition) { mutation.mutate(definition) } }, [mutation]) const save = useCallback( (definition: FlowDef_Input) => { pending.current = definition if (timer.current) clearTimeout(timer.current) timer.current = setTimeout(flush, AUTOSAVE_DELAY) }, [flush], ) // Leaving the tab is the last chance to persist what is still queued. useEffect(() => { const onHidden = () => { if (document.visibilityState === "hidden") flush() } document.addEventListener("visibilitychange", onHidden) return () => { document.removeEventListener("visibilitychange", onHidden) if (timer.current) clearTimeout(timer.current) } }, [flush]) return { save, flush, mutation } }