mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-19 15:06:20 +00:00
useNitroQuery: add accept() predicate; migrate two mod-tools chatlog views
Many composer/parser pairs on the Nitro wire are correlation-key based:
the request carries a key (roomId, issueId, etc.) and the response shows
up on the globally-shared event bus, where other components may be
listening for the same parser type with a different key. The previous
useNitroQuery resolved on the FIRST matching parser event regardless of
key — useless for that pattern, which is why two obvious migration
targets (ModToolsChatlogView, CfhChatlogView) were skipped earlier.
Adapter change
- New optional `accept?: (event) => boolean` on NitroQueryConfig.
- In awaitNitroResponse, events for which accept returns false are
IGNORED rather than resolving the promise. The listener stays
registered, the timeout still applies. This lets callers do:
accept: e => e.getParser()?.data.roomId === roomId
Migrations
- src/components/mod-tools/views/room/ModToolsChatlogView.tsx
- Was: useState<ChatRecordData>(null) + useMessageEvent with
`if (parser.data.roomId !== roomId) return; setRoomChatlog(...)` +
a mount-only useEffect dispatching the composer.
- Now: a single useNitroQuery call keyed on roomId; accept filters
by roomId; the query is enabled only when roomId is set.
The composer is no longer re-dispatched on remount within
staleTime; switching to a different room still triggers a fresh
fetch because the queryKey changes.
- src/components/mod-tools/views/tickets/CfhChatlogView.tsx
- Same pattern, keyed on issueId.
Both migrations drop ~15 lines per file (no more local state + manual
listener + manual send) while gaining cache/dedup/loading/error
handling from TanStack Query.
Verification
- yarn eslint on the four files: 1 pre-existing error (the
IMessageEvent "redundant union" false positive in createNitroQuery
that we already documented — local sandbox doesn't have the
renderer SDK installed, so its types resolve as `any`).
- yarn test: 49/49 passing.
- yarn tsc on the four files: clean.
This commit is contained in:
@@ -23,6 +23,14 @@ export interface NitroQueryConfig<TParser extends IMessageEvent, TData>
|
||||
* 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).
|
||||
*/
|
||||
@@ -52,11 +60,11 @@ export const useNitroQuery = <TParser extends IMessageEvent, TData = TParser>(
|
||||
config: NitroQueryConfig<TParser, TData>
|
||||
): UseQueryResult<TData> =>
|
||||
{
|
||||
const { key, request, parser, select, timeoutMs = 15_000, enabled, staleTime, refetchOnMount } = config;
|
||||
const { key, request, parser, select, accept, timeoutMs = 15_000, enabled, staleTime, refetchOnMount } = config;
|
||||
|
||||
const options: UseQueryOptions<TData, Error, TData> = {
|
||||
queryKey: key,
|
||||
queryFn: () => awaitNitroResponse<TParser, TData>({ key, request, parser, select, timeoutMs }),
|
||||
queryFn: () => awaitNitroResponse<TParser, TData>({ key, request, parser, select, accept, timeoutMs }),
|
||||
enabled,
|
||||
staleTime,
|
||||
refetchOnMount
|
||||
@@ -71,11 +79,11 @@ export const useNitroQuery = <TParser extends IMessageEvent, TData = TParser>(
|
||||
* can use the same plumbing imperatively.
|
||||
*/
|
||||
export const awaitNitroResponse = <TParser extends IMessageEvent, TData>(
|
||||
config: Pick<NitroQueryConfig<TParser, TData>, 'request' | 'parser' | 'select' | 'timeoutMs'>
|
||||
config: Pick<NitroQueryConfig<TParser, TData>, 'request' | 'parser' | 'select' | 'accept' | 'timeoutMs'>
|
||||
): Promise<TData> =>
|
||||
new Promise<TData>((resolve, reject) =>
|
||||
{
|
||||
const { request, parser: ParserCtor, select, timeoutMs = 15_000 } = config;
|
||||
const { request, parser: ParserCtor, select, accept, timeoutMs = 15_000 } = config;
|
||||
|
||||
let settled = false;
|
||||
let timeoutHandle: ReturnType<typeof setTimeout> | null = null;
|
||||
@@ -90,6 +98,7 @@ export const awaitNitroResponse = <TParser extends IMessageEvent, TData>(
|
||||
listener = new (ParserCtor as any)((event: TParser) =>
|
||||
{
|
||||
if(settled) return;
|
||||
if(accept && !accept(event)) return;
|
||||
settled = true;
|
||||
|
||||
cleanup();
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { ChatRecordData, GetRoomChatlogMessageComposer, RoomChatlogEvent } from '@nitrots/nitro-renderer';
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { SendMessageComposer } from '../../../../api';
|
||||
import { FC } from 'react';
|
||||
import { useNitroQuery } from '../../../../api/nitro-query';
|
||||
import { DraggableWindowPosition, NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../common';
|
||||
import { useMessageEvent } from '../../../../hooks';
|
||||
import { ChatlogView } from '../chatlog/ChatlogView';
|
||||
|
||||
interface ModToolsChatlogViewProps
|
||||
@@ -14,22 +13,16 @@ interface ModToolsChatlogViewProps
|
||||
export const ModToolsChatlogView: FC<ModToolsChatlogViewProps> = props =>
|
||||
{
|
||||
const { roomId = null, onCloseClick = null } = props;
|
||||
const [ roomChatlog, setRoomChatlog ] = useState<ChatRecordData>(null);
|
||||
|
||||
useMessageEvent<RoomChatlogEvent>(RoomChatlogEvent, event =>
|
||||
{
|
||||
const parser = event.getParser();
|
||||
|
||||
if(!parser || parser.data.roomId !== roomId) return;
|
||||
|
||||
setRoomChatlog(parser.data);
|
||||
const { data: roomChatlog } = useNitroQuery<RoomChatlogEvent, ChatRecordData>({
|
||||
key: [ 'nitro', 'mod-tools', 'room-chatlog', roomId ],
|
||||
request: () => new GetRoomChatlogMessageComposer(roomId),
|
||||
parser: RoomChatlogEvent,
|
||||
accept: e => e.getParser()?.data.roomId === roomId,
|
||||
select: e => e.getParser().data,
|
||||
enabled: roomId !== null
|
||||
});
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
SendMessageComposer(new GetRoomChatlogMessageComposer(roomId));
|
||||
}, [ roomId ]);
|
||||
|
||||
if(!roomChatlog) return null;
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { CfhChatlogData, CfhChatlogEvent, GetCfhChatlogMessageComposer } from '@nitrots/nitro-renderer';
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { SendMessageComposer } from '../../../../api';
|
||||
import { FC } from 'react';
|
||||
import { useNitroQuery } from '../../../../api/nitro-query';
|
||||
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../common';
|
||||
import { useMessageEvent } from '../../../../hooks';
|
||||
import { ChatlogView } from '../chatlog/ChatlogView';
|
||||
|
||||
interface CfhChatlogViewProps
|
||||
@@ -14,22 +13,16 @@ interface CfhChatlogViewProps
|
||||
export const CfhChatlogView: FC<CfhChatlogViewProps> = props =>
|
||||
{
|
||||
const { onCloseClick = null, issueId = null } = props;
|
||||
const [ chatlogData, setChatlogData ] = useState<CfhChatlogData>(null);
|
||||
|
||||
useMessageEvent<CfhChatlogEvent>(CfhChatlogEvent, event =>
|
||||
{
|
||||
const parser = event.getParser();
|
||||
|
||||
if(!parser || parser.data.issueId !== issueId) return;
|
||||
|
||||
setChatlogData(parser.data);
|
||||
const { data: chatlogData } = useNitroQuery<CfhChatlogEvent, CfhChatlogData>({
|
||||
key: [ 'nitro', 'mod-tools', 'cfh-chatlog', issueId ],
|
||||
request: () => new GetCfhChatlogMessageComposer(issueId),
|
||||
parser: CfhChatlogEvent,
|
||||
accept: e => e.getParser()?.data.issueId === issueId,
|
||||
select: e => e.getParser().data,
|
||||
enabled: issueId !== null
|
||||
});
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
SendMessageComposer(new GetCfhChatlogMessageComposer(issueId));
|
||||
}, [ issueId ]);
|
||||
|
||||
return (
|
||||
<NitroCardView className="nitro-mod-tools-chatlog" theme="primary-slim">
|
||||
<NitroCardHeaderView headerText={ 'Issue Chatlog' } onCloseClick={ onCloseClick } />
|
||||
|
||||
Reference in New Issue
Block a user