mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-20 15:36:18 +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.
|
* Maps the parser event to the data the component cares about.
|
||||||
*/
|
*/
|
||||||
select?: (event: TParser) => TData;
|
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).
|
* 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>
|
config: NitroQueryConfig<TParser, TData>
|
||||||
): UseQueryResult<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> = {
|
const options: UseQueryOptions<TData, Error, TData> = {
|
||||||
queryKey: key,
|
queryKey: key,
|
||||||
queryFn: () => awaitNitroResponse<TParser, TData>({ key, request, parser, select, timeoutMs }),
|
queryFn: () => awaitNitroResponse<TParser, TData>({ key, request, parser, select, accept, timeoutMs }),
|
||||||
enabled,
|
enabled,
|
||||||
staleTime,
|
staleTime,
|
||||||
refetchOnMount
|
refetchOnMount
|
||||||
@@ -71,11 +79,11 @@ export const useNitroQuery = <TParser extends IMessageEvent, TData = TParser>(
|
|||||||
* can use the same plumbing imperatively.
|
* can use the same plumbing imperatively.
|
||||||
*/
|
*/
|
||||||
export const awaitNitroResponse = <TParser extends IMessageEvent, TData>(
|
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> =>
|
): Promise<TData> =>
|
||||||
new Promise<TData>((resolve, reject) =>
|
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 settled = false;
|
||||||
let timeoutHandle: ReturnType<typeof setTimeout> | null = null;
|
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) =>
|
listener = new (ParserCtor as any)((event: TParser) =>
|
||||||
{
|
{
|
||||||
if(settled) return;
|
if(settled) return;
|
||||||
|
if(accept && !accept(event)) return;
|
||||||
settled = true;
|
settled = true;
|
||||||
|
|
||||||
cleanup();
|
cleanup();
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { ChatRecordData, GetRoomChatlogMessageComposer, RoomChatlogEvent } from '@nitrots/nitro-renderer';
|
import { ChatRecordData, GetRoomChatlogMessageComposer, RoomChatlogEvent } from '@nitrots/nitro-renderer';
|
||||||
import { FC, useEffect, useState } from 'react';
|
import { FC } from 'react';
|
||||||
import { SendMessageComposer } from '../../../../api';
|
import { useNitroQuery } from '../../../../api/nitro-query';
|
||||||
import { DraggableWindowPosition, NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../common';
|
import { DraggableWindowPosition, NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../common';
|
||||||
import { useMessageEvent } from '../../../../hooks';
|
|
||||||
import { ChatlogView } from '../chatlog/ChatlogView';
|
import { ChatlogView } from '../chatlog/ChatlogView';
|
||||||
|
|
||||||
interface ModToolsChatlogViewProps
|
interface ModToolsChatlogViewProps
|
||||||
@@ -14,22 +13,16 @@ interface ModToolsChatlogViewProps
|
|||||||
export const ModToolsChatlogView: FC<ModToolsChatlogViewProps> = props =>
|
export const ModToolsChatlogView: FC<ModToolsChatlogViewProps> = props =>
|
||||||
{
|
{
|
||||||
const { roomId = null, onCloseClick = null } = props;
|
const { roomId = null, onCloseClick = null } = props;
|
||||||
const [ roomChatlog, setRoomChatlog ] = useState<ChatRecordData>(null);
|
|
||||||
|
|
||||||
useMessageEvent<RoomChatlogEvent>(RoomChatlogEvent, event =>
|
const { data: roomChatlog } = useNitroQuery<RoomChatlogEvent, ChatRecordData>({
|
||||||
{
|
key: [ 'nitro', 'mod-tools', 'room-chatlog', roomId ],
|
||||||
const parser = event.getParser();
|
request: () => new GetRoomChatlogMessageComposer(roomId),
|
||||||
|
parser: RoomChatlogEvent,
|
||||||
if(!parser || parser.data.roomId !== roomId) return;
|
accept: e => e.getParser()?.data.roomId === roomId,
|
||||||
|
select: e => e.getParser().data,
|
||||||
setRoomChatlog(parser.data);
|
enabled: roomId !== null
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() =>
|
|
||||||
{
|
|
||||||
SendMessageComposer(new GetRoomChatlogMessageComposer(roomId));
|
|
||||||
}, [ roomId ]);
|
|
||||||
|
|
||||||
if(!roomChatlog) return null;
|
if(!roomChatlog) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { CfhChatlogData, CfhChatlogEvent, GetCfhChatlogMessageComposer } from '@nitrots/nitro-renderer';
|
import { CfhChatlogData, CfhChatlogEvent, GetCfhChatlogMessageComposer } from '@nitrots/nitro-renderer';
|
||||||
import { FC, useEffect, useState } from 'react';
|
import { FC } from 'react';
|
||||||
import { SendMessageComposer } from '../../../../api';
|
import { useNitroQuery } from '../../../../api/nitro-query';
|
||||||
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../common';
|
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../common';
|
||||||
import { useMessageEvent } from '../../../../hooks';
|
|
||||||
import { ChatlogView } from '../chatlog/ChatlogView';
|
import { ChatlogView } from '../chatlog/ChatlogView';
|
||||||
|
|
||||||
interface CfhChatlogViewProps
|
interface CfhChatlogViewProps
|
||||||
@@ -14,22 +13,16 @@ interface CfhChatlogViewProps
|
|||||||
export const CfhChatlogView: FC<CfhChatlogViewProps> = props =>
|
export const CfhChatlogView: FC<CfhChatlogViewProps> = props =>
|
||||||
{
|
{
|
||||||
const { onCloseClick = null, issueId = null } = props;
|
const { onCloseClick = null, issueId = null } = props;
|
||||||
const [ chatlogData, setChatlogData ] = useState<CfhChatlogData>(null);
|
|
||||||
|
|
||||||
useMessageEvent<CfhChatlogEvent>(CfhChatlogEvent, event =>
|
const { data: chatlogData } = useNitroQuery<CfhChatlogEvent, CfhChatlogData>({
|
||||||
{
|
key: [ 'nitro', 'mod-tools', 'cfh-chatlog', issueId ],
|
||||||
const parser = event.getParser();
|
request: () => new GetCfhChatlogMessageComposer(issueId),
|
||||||
|
parser: CfhChatlogEvent,
|
||||||
if(!parser || parser.data.issueId !== issueId) return;
|
accept: e => e.getParser()?.data.issueId === issueId,
|
||||||
|
select: e => e.getParser().data,
|
||||||
setChatlogData(parser.data);
|
enabled: issueId !== null
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() =>
|
|
||||||
{
|
|
||||||
SendMessageComposer(new GetCfhChatlogMessageComposer(issueId));
|
|
||||||
}, [ issueId ]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NitroCardView className="nitro-mod-tools-chatlog" theme="primary-slim">
|
<NitroCardView className="nitro-mod-tools-chatlog" theme="primary-slim">
|
||||||
<NitroCardHeaderView headerText={ 'Issue Chatlog' } onCloseClick={ onCloseClick } />
|
<NitroCardHeaderView headerText={ 'Issue Chatlog' } onCloseClick={ onCloseClick } />
|
||||||
|
|||||||
Reference in New Issue
Block a user