5180 fichiers 38.5 Mo 0 téléchargements 182 vues mis à jour il y a 5 heures ui
nitro-v36-with-renderer-v36 / src / api / octane-query / createOctaneQuery.ts
createOctaneQuery.ts 5.2 Ko Télécharger le fichier
import { GetCommunication, IMessageComposer, IMessageEvent, MessageEvent } from '@octane/renderer';
import { QueryKey, UseQueryOptions, UseQueryResult, useQuery } from '@tanstack/react-query';
import { SendMessageComposer } from '../octane/SendMessageComposer';

export interface OctaneQueryConfig<TParser extends IMessageEvent, TData> {
    /**
     * Stable key for caching/deduping. Convention:
     * `['octane', '<domain>', '<request>', ...args]`.
     */
    key: QueryKey;
    /**
     * Factory for the request composer. Called once per query execution.
     * `null` skips sending (useful when the server pushes the event
     * unprompted — you only want subscription, not a request).
     */
    request: (() => IMessageComposer<unknown[]>) | null;
    /**
     * The parser class to listen for as the response.
     */
    parser: typeof MessageEvent;
    /**
     * Maps the parser event to the data the component cares about.
     */
    select?: (event: TParser) => TData;
    /**
     * Optional predicate to ignore parser events that don't match this
     * query (typically used as a correlation-key filter on a globally
     * shared event stream — e.g. `e => e.getParser()?.roomId === roomId`).
     * When the predicate returns false, the listener stays registered
     * and keeps waiting; the timeout still applies.
     */
    accept?: (event: TParser) => boolean;
    /**
     * Max time to wait for the response before rejecting (default 15s).
     */
    timeoutMs?: number;
    /**
     * Forwarded to TanStack Query.
     */
    enabled?: boolean;
    staleTime?: number;
    refetchOnMount?: boolean | 'always';
    /**
     * Forwarded to TanStack Query. `keepPreviousData` keeps the last page
     * on screen while the next one loads.
     */
    placeholderData?: UseQueryOptions<TData, Error, TData>['placeholderData'];
    /**
     * Forwarded to TanStack Query. Overrides the app's default `retry: 1`
     * when a query's own timeout already accounts for one round trip.
     */
    retry?: UseQueryOptions<TData, Error, TData>['retry'];
}

/**
 * Wraps a Octane composer/parser request-response pair as a TanStack Query
 * `useQuery` call. The returned object is the standard TanStack result —
 * `{ data, isLoading, isError, error, refetch, ... }`.
 *
 * Behavior:
 * - On the first subscribe, registers the parser, sends the composer,
 *   resolves the Promise with the selected payload when the parser fires.
 * - Default `staleTime` is the QueryClient default (30s).
 * - Subsequent mounts within `staleTime` get the cached value immediately;
 *   the request is NOT re-sent.
 * - Identical concurrent calls (same `key`) are deduped.
 */
export const useOctaneQuery = <TParser extends IMessageEvent, TData = TParser>(config: OctaneQueryConfig<TParser, TData>): UseQueryResult<TData> => {
    const { key, request, parser, select, accept, timeoutMs = 15_000, enabled, staleTime, refetchOnMount, placeholderData, retry } = config;

    const options: UseQueryOptions<TData, Error, TData> = {
        queryKey: key,
        queryFn: () => awaitOctaneResponse<TParser, TData>({ request, parser, select, accept, timeoutMs }),
        enabled,
        staleTime,
        refetchOnMount,
        placeholderData,
        retry
    };

    return useQuery(options);
};

/**
 * Lower-level helper: send a composer (if any) and resolve with the next
 * matching parser event. Exposed so `queryClient.fetchQuery({...})` callers
 * can use the same plumbing imperatively.
 */
export const awaitOctaneResponse = <TParser extends IMessageEvent, TData>(
    config: Pick<OctaneQueryConfig<TParser, TData>, 'request' | 'parser' | 'select' | 'accept' | 'timeoutMs'>
): Promise<TData> =>
    new Promise<TData>((resolve, reject) => {
        const { request, parser: ParserCtor, select, accept, timeoutMs = 15_000 } = config;

        let settled = false;
        let timeoutHandle: ReturnType<typeof setTimeout> | null = null;
        let listener: IMessageEvent | undefined = undefined;

        const cleanup = () => {
            if (timeoutHandle !== null) clearTimeout(timeoutHandle);
            if (listener) GetCommunication().removeMessageEvent(listener);
        };

        listener = new (ParserCtor as any)((event: TParser) => {
            if (settled) return;
            if (accept && !accept(event)) return;
            settled = true;

            try {
                // Removing a message event disposes it and clears its parser.
                // Read the response payload before cleanup so selectors such
                // as event.getParser() still receive the parsed message.
                const data = select ? select(event) : (event as unknown as TData);

                cleanup();
                resolve(data);
            } catch (err) {
                cleanup();
                reject(err instanceof Error ? err : new Error(String(err)));
            }
        });

        GetCommunication().registerMessageEvent(listener);

        timeoutHandle = setTimeout(() => {
            if (settled) return;
            settled = true;
            cleanup();
            reject(new Error(`OctaneQuery timed out after ${timeoutMs}ms`));
        }, timeoutMs);

        if (request) SendMessageComposer(request());
    });
15Sujets
1Dépôts
20Membres
2En ligne
Profile
Inscrit
Dernière visite
Messages
0
Votes positifs
0
Votes négatifs
0

Nous utilisons des cookies pour assurer le bon fonctionnement du site et améliorer votre expérience. en savoir plus

Autoriser