"use client"; import * as React from "react"; import type { ColineCapabilities } from "@colineapp/sdk/v2"; import { useColine, useToast } from "../provider"; export interface ColineMutation { mutate: (input: TInput) => Promise; isPending: boolean; error: string | null; data: TResult | null; reset: () => void; } /** * Run a capability write with pending and error state. Errors are caught * (and shown as a host toast unless `silent`), so event handlers never * leak unhandled rejections. */ export function useColineMutation( run: (coline: ColineCapabilities, input: TInput) => Promise, options: { silent?: boolean; successMessage?: string; onSuccess?: (result: TResult) => void } = {}, ): ColineMutation { const coline = useColine(); const toast = useToast(); const [state, setState] = React.useState<{ isPending: boolean; error: string | null; data: TResult | null }>({ isPending: false, error: null, data: null, }); const runRef = React.useRef(run); const optionsRef = React.useRef(options); React.useEffect(() => { runRef.current = run; optionsRef.current = options; }); const mutate = React.useCallback( async (input: TInput) => { setState((current) => ({ ...current, isPending: true, error: null })); try { const result = await runRef.current(coline, input); setState({ isPending: false, error: null, data: result }); if (optionsRef.current.successMessage) toast(optionsRef.current.successMessage, "success"); optionsRef.current.onSuccess?.(result); return result; } catch (error) { const message = error instanceof Error ? error.message : String(error); setState((current) => ({ ...current, isPending: false, error: message })); if (!optionsRef.current.silent) toast(message, "error"); return undefined; } }, [coline, toast], ); const reset = React.useCallback(() => setState({ isPending: false, error: null, data: null }), []); return { mutate, reset, ...state }; }