hooks_use-hermes-template.js

import { React } from '@gravityforms/libraries';
import { createHermesClient, serialize, raw, args } from '@gravityforms/request';

const { useCallback, useMemo, useState } = React;

/**
 * @module useHermesTemplate
 * @description A hook to interact with the Hermes API using template literal syntax.
 *              This provides a more intuitive API that closely mirrors the server-side
 *              query syntax, making queries easier to write and debug.
 *
 * @since 5.1.0
 *
 * @param {object} props                    The props for the hook.
 * @param {string} props.endpoint           The Hermes AJAX endpoint.
 * @param {string} props.queryAction        The AJAX action for queries.
 * @param {string} props.mutationAction     The AJAX action for mutations.
 * @param {string} props.security           The security nonce.
 * @param {string} props.queryKey           The key in the request body for queries (default: 'query').
 * @param {string} props.mutationKey        The key in the request body for mutations (default: 'mutation').
 *
 * @return {object} The Hermes state and actions.
 *
	 * @example
	 * const { query, mutation, isLoading } = useHermesTemplate();
	 *
	 * // Query with template literal
	 * const fetchContacts = async () => {
	 *     const result = await query`{
	 *         contact(${ args( { limit: 20 } ) }) {
	 *             id,
	 *             firstName,
	 *             lastName
	 *         }
	 *     }`;
	 *     return result?.data?.data?.contact;
	 * };
	 *
	 * // Mutation with template literal
	 * const createContact = async ( firstName, lastName ) => {
	 *     await mutation`{
	 *         insert_contact(objects: [${ { firstName, lastName } }]) {
	 *             returning { id, firstName, lastName }
	 *         }
	 *     }`;
	 * };
	 */
const useHermesTemplate = ( {
	endpoint = '',
	queryAction = '',
	mutationAction = '',
	security = '',
	queryKey = 'query',
	mutationKey = 'mutation',
} = {} ) => {
	const [ isLoading, setIsLoading ] = useState( false );

	/**
	 * @function handleLoadingChange
	 * @description Callback to update loading state when requests start/end.
	 *
	 * @since 5.1.0
	 *
	 * @param {boolean} loading Whether there are active requests.
	 */
	const handleLoadingChange = useCallback( ( loading ) => {
		setIsLoading( loading );
	}, [] );

	const client = useMemo( () => {
		if ( ! endpoint || ! queryAction || ! security ) {
			return null;
		}

		return createHermesClient( {
			endpoint,
			queryAction,
			mutationAction: mutationAction || queryAction,
			security,
			queryKey,
			mutationKey,
			onLoadingChange: handleLoadingChange,
		} );
	}, [ endpoint, queryAction, mutationAction, security, queryKey, mutationKey, handleLoadingChange ] );

	/**
	 * @function query
	 * @description Tagged template function for Hermes queries.
	 *              Use backticks to write queries in GraphQL-like syntax.
	 *
	 * @since 5.1.0
	 *
	 * @param {Array} strings The template literal strings.
	 * @param {...*}  values  The interpolated values.
	 *
	 * @return {Promise<object>} The query response.
	 *
	 * @example
	 * const result = await query`{
	 *     contact(limit: ${ limit }, search: ${ searchTerm }) {
	 *         id,
	 *         firstName,
	 *         lastName,
	 *         email { address, isPrimary }
	 *     }
	 * }`;
	 */
	const query = useCallback( ( strings, ...values ) => {
		if ( ! client ) {
			// eslint-disable-next-line no-console
			console.warn( 'useHermesTemplate: Client not configured. Ensure endpoint, queryAction, and security are provided.' );
			return Promise.resolve( { error: new Error( 'Hermes client not configured' ) } );
		}

		return client.query( strings, ...values );
	}, [ client ] );

	/**
	 * @function mutation
	 * @description Tagged template function for Hermes mutations.
	 *              Use backticks to write mutations in GraphQL-like syntax.
	 *
	 * @since 5.1.0
	 *
	 * @param {Array} strings The template literal strings.
	 * @param {...*}  values  The interpolated values.
	 *
	 * @return {Promise<object>} The mutation response.
	 *
	 * @example
	 * // Insert
	 * await mutation`{
	 *     insert_contact(objects: [${ { firstName: 'John', lastName: 'Doe' } }]) {
	 *         returning { id, firstName, lastName }
	 *     }
	 * }`;
	 *
	 * // Update
	 * await mutation`{
	 *     update_contact(id: ${ contactId }, firstName: ${ newName }) {
	 *         returning { id, firstName }
	 *     }
	 * }`;
	 *
	 * // Delete
	 * await mutation`{
	 *     delete_contact(id: ${ contactId }) {}
	 * }`;
	 *
	 * // Connect
	 * await mutation`{
	 *     connect_contact_company(objects: [${ { from: contactId, to: companyId } }]) {}
	 * }`;
	 */
	const mutation = useCallback( ( strings, ...values ) => {
		if ( ! client ) {
			// eslint-disable-next-line no-console
			console.warn( 'useHermesTemplate: Client not configured. Ensure endpoint, mutationAction, and security are provided.' );
			return Promise.resolve( { error: new Error( 'Hermes client not configured' ) } );
		}

		return client.mutation( strings, ...values );
	}, [ client ] );

	return {
		isLoading,
		query,
		mutation,
		serialize,
		raw,
		args,
	};
};

export default useHermesTemplate;