@accreation/rtkx
v1.0.6
Published
Extensions for Redux Toolkit Query — WebSocket support and chained queries
Maintainers
Readme
RTKX
A modular extension layer for Redux Toolkit Query - first-class WebSocket support, sequential chained queries, and opt-in notifications. Everything ships from a single import.
Features
| | Feature | Description |
|---|---|---|
| 🔌 | createWsEndpoint | Pure WebSocket RTK Query endpoint — supports providesTags, initialData, and WS lifecycle notifications |
| 🌊 | createStreamingEndpoint | HTTP seed + WebSocket updates in one endpoint — supports providesTags and WS lifecycle notifications |
| 🔧 | WebSocketManager | Low-level WS class with exponential back-off reconnect |
| 📨 | useWebSocketSend | Send messages over an already-open managed WS connection; optionally invalidates RTK Query tags after each send |
| 🔖 | createWsEndpointRef | Create a ref that links an endpoint to useWebSocketSend |
| 🔗 | createQueryChain | Fluent builder for sequential chains of RTK Query query endpoints |
| ⚛️ | useQueryChain | React hook that runs a query chain and tracks per-step progress |
| 🔀 | createMutationChain | Fluent builder for sequential chains of RTK Query mutation endpoints |
| 🚀 | useMutationChain | React hook that executes a mutation chain imperatively |
| 🔔 | createNotificationMiddleware | Library-agnostic opt-in toast/notification middleware |
| 🔀 | useParallelQueries | Fire one endpoint against N args in parallel, results keyed by position |
| 🔀 | useQueries | Fire N different endpoints in parallel, results keyed by name |
Table of contents
- Installation
- Single import source
- WebSocket endpoints
- Chained queries
- Notifications (opt-in)
- Parallel queries
- Reconnect options
- TypeScript generics reference
- Utilities
Installation
Install the package and its peer dependencies:
npm install @accreation/rtkx
# peer deps
npm install @reduxjs/toolkit react react-reduxPeer dependency requirements enforced at install time:
| Package | Required version |
|---|---|
| @reduxjs/toolkit | ^2.3.0 |
| react | ^17.0.0 \|\| ^18.0.0 |
| react-redux | ^9.0.0 |
Single import source
rtkx re-exports everything from @reduxjs/toolkit/query/react, @reduxjs/toolkit, and react-redux so your project never needs to import from those packages directly. This eliminates version mismatches - npm resolves a single copy by following rtkx's peer dependency constraints.
// Everything from one place
import {
// RTK Query
createApi, fetchBaseQuery, skipToken,
// Redux
configureStore,
// React-Redux
Provider, useSelector, useDispatch,
// rtkx extensions
createWsEndpoint, createStreamingEndpoint,
useWebSocketSend, createWsEndpointRef,
createQueryChain, useQueryChain,
createMutationChain, useMutationChain,
createNotificationMiddleware,
useParallelQueries, useQueries,
getBaseQueryWithAuthorization, configureBaseQueryAuth,
} from '@accreation/rtkx';Nothing is bundled - these are pure pass-throughs. @reduxjs/toolkit and react-redux remain external (peer deps) and are never duplicated in your bundle.
WebSocket endpoints
createWsEndpoint - pure WebSocket
Use when all data arrives over a socket with no initial HTTP request.
// store/api/chat.ts
import { createApi, fetchBaseQuery, createWsEndpoint } from 'rtkx';
interface ChatMessage {
id: string;
author: string;
text: string;
timestamp: number;
}
export const chatApi = createApi({
reducerPath: 'chatApi',
baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
endpoints: (builder) => ({
chatMessages: createWsEndpoint<
ChatMessage[], // TCache - what's stored in the RTK Query cache
ChatMessage, // TMessage - what each WS frame delivers
string // TArg - hook argument (room name)
>(builder, {
url: 'wss://example.com/chat',
// Optional: seed the cache before the first message arrives
initialData: [],
// Transform raw MessageEvent into TMessage.
// Return null to silently skip a frame (useful for filtering by event name).
transformMessage: (event) => JSON.parse(event.data),
// Mutate the Immer draft to update the cache
onMessage: (msg, { updateCachedData }) => {
updateCachedData((draft) => {
draft.push(msg);
if (draft.length > 100) draft.shift(); // keep rolling window
});
},
reconnect: { maxAttempts: 10, delay: 500 },
// Tie this endpoint into createNotificationMiddleware so WS lifecycle
// events (error / connect / disconnect) fire through the same handler.
// Use the same string you use as the object key in createApi.
endpointName: 'chatMessages',
// Tag the cache entry so other mutations can invalidate it.
providesTags: (result, _err, room) => [{ type: 'ChatRoom', id: room }],
}),
}),
});
export const { useChatMessagesQuery } = chatApi;function ChatRoom({ room }: { room: string }) {
const { data: messages = [] } = useChatMessagesQuery(room);
return (
<ul>
{messages.map((m) => (
<li key={m.id}><strong>{m.author}</strong>: {m.text}</li>
))}
</ul>
);
}Filtering frames by event name
When a single socket carries multiple event types (e.g. a { event, data } envelope), return null from transformMessage to skip non-matching frames:
function parseEvent<T>(eventName: string) {
return (raw: MessageEvent): T | null => {
try {
const msg = JSON.parse(raw.data) as { event: string; data: T };
return msg.event === eventName ? msg.data : null; // null = skip
} catch { return null; }
};
}
// Each endpoint only processes its own event type
chatMessages: createWsEndpoint(builder, { url: 'ws://...', transformMessage: parseEvent('chat-message') }),
userPresence: createWsEndpoint(builder, { url: 'ws://...', transformMessage: parseEvent('presence') }),createStreamingEndpoint - HTTP + WebSocket
Use when the initial data comes from REST and subsequent updates stream in over a socket.
interface Todo { id: string; title: string; completed: boolean; }
todoList: createStreamingEndpoint<
Todo[], // TCache - stored in cache (shape after HTTP + WS updates)
Todo[], // TMessage - shape of each WS frame
void // TArg
>(builder, {
// HTTP: seed cache on mount
query: () => '/todos',
// WebSocket: stream live updates
url: 'wss://example.com/todos/live',
// Replace cache with the latest snapshot on each push
onMessage: (updated, { updateCachedData }) => {
updateCachedData((draft) => {
draft.splice(0, draft.length, ...updated);
});
},
reconnect: true, // enable with defaults
// Middleware integration & tag support
endpointName: 'todoList',
providesTags: ['Todo'],
}),WebSocketManager - standalone
For cases where you need a WebSocket connection outside of RTK Query (e.g. presence tracking, custom protocols):
import { WebSocketManager } from 'rtkx';
const ws = new WebSocketManager<{ type: string; payload: unknown }>(
'wss://example.com/notifications',
{ reconnect: { maxAttempts: 5 } },
);
const unsubscribe = ws.subscribe((msg) => console.log(msg));
ws.connect();
ws.send({ type: 'subscribe', payload: { channel: 'team-updates' } });
// Clean up
unsubscribe();
ws.disconnect();Note:
createWsEndpointandcreateStreamingEndpointuseWebSocketManagerinternally - you only need this for lower-level scenarios.
useWebSocketSend - sending messages
useWebSocketSend returns a stable send callback that writes to the already-open WebSocket connection that rtkx manages for a given endpoint + arg combination. This is the recommended way to send messages bidirectionally without managing the socket yourself.
Setup - three steps:
1. Create a ref once, outside createApi:
// store/api/chat.ts
import { createApi, createWsEndpoint, createWsEndpointRef } from 'rtkx';
export const chatMessagesRef = createWsEndpointRef<string>(); // TArg = room name2. Pass the ref to the endpoint's options.ref:
export const chatApi = createApi({
reducerPath: 'chatApi',
baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
endpoints: (builder) => ({
chatMessages: createWsEndpoint<ChatMessage[], ChatMessage, string>(builder, {
ref: chatMessagesRef, // ← link the endpoint to the ref
url: 'wss://example.com/chat',
initialData: [],
transformMessage: parseEvent<ChatMessage>('chat-message'),
onMessage: (msg, { updateCachedData }) => {
updateCachedData((draft) => { draft.push(msg); });
},
}),
}),
});3. Use the ref in any component to send messages:
import { useWebSocketSend } from 'rtkx';
import { chatMessagesRef, useChatMessagesQuery } from '../store/api/chat';
interface OutgoingMessage {
event: string;
data: { author: string; text: string; room: string };
}
function ChatInput({ room, author }: { room: string; author: string }) {
// The query must be subscribed before send() is called - subscribe here
// or in a parent component.
useChatMessagesQuery(room);
const send = useWebSocketSend<OutgoingMessage, string>(chatMessagesRef, room);
const [text, setText] = useState('');
const handleSubmit = () => {
send({ event: 'chat-message', data: { author, text, room } });
setText('');
};
return (
<div>
<input value={text} onChange={(e) => setText(e.target.value)} />
<button onClick={handleSubmit}>Send</button>
</div>
);
}How it works:
createWsEndpointRef<TArg>()returns an opaque handle that starts unpopulated.- When
createApievaluates your endpoints callback,createWsEndpointpopulates the ref with an internal endpoint ID synchronously. - rtkx maintains an internal registry mapping
endpointId + argto the liveWebSocketManagerinstance. The manager is registered when the first subscriber mounts and unregistered when the last one unmounts. useWebSocketSendlooks up the manager from that registry and returns auseCallback-stablesendfunction.
Requirement: The matching query (
useChatMessagesQuery(room)) must be subscribed (mounted somewhere in the tree) beforesend()is called - rtkx opens the socket when the query subscribes and closes it when the last subscriber unmounts. Ifsend()is called with no active subscription a warning is logged and the message is dropped.
Serialisation: Objects are JSON-serialised automatically. Pass a plain
stringto send a raw text frame.
WebSocket tags — providesTags / invalidatesTags
Both createWsEndpoint and createStreamingEndpoint accept a providesTags option, identical to a regular builder.query() definition.
chatMessages: createWsEndpoint<ChatMessage[], ChatMessage, string>(builder, {
url: 'wss://example.com/chat',
onMessage: (msg, { updateCachedData }) => {
updateCachedData((draft) => { draft.push(msg); });
},
providesTags: (result, _err, room) => [{ type: 'ChatRoom', id: room }],
}),useWebSocketSend accepts an optional third argument with api and invalidatesTags. When provided, the tags are invalidated immediately after each successful send() call:
import { useWebSocketSend } from 'rtkx';
import { chatMessagesRef, chatApi } from '../store/api/chat';
function ChatInput({ room }: { room: string }) {
const send = useWebSocketSend<OutgoingMessage, string>(chatMessagesRef, room, {
api: chatApi,
invalidatesTags: [{ type: 'MessageCount', id: room }],
});
// Calling send() will automatically dispatch:
// chatApi.util.invalidateTags([{ type: 'MessageCount', id: room }])
return <button onClick={() => send({ event: 'ping' })}>Ping</button>;
}createWsEndpointRef works with createStreamingEndpoint too:
export const livePostsRef = createWsEndpointRef<void>();
livePosts: createStreamingEndpoint<Post[], Post, void>(builder, {
ref: livePostsRef,
query: () => '/posts',
url: 'wss://example.com/posts/live',
onMessage: (post, { updateCachedData }) => {
updateCachedData((draft) => { draft.push(post); });
},
}),Chained queries
When one endpoint's response contains the argument for the next request, createQueryChain + useQueryChain handle the sequencing cleanly - no nested useEffect, no manual loading gates.
Results are keyed by the string name you give each step, so you get data.getAuthor instead of a positional index.
Key uniqueness: every step key within a single chain must be unique. Duplicate keys will silently overwrite the earlier step's result in the
dataobject.
createQueryChain
Define the chain once, outside your component:
// store/chains.ts
import { createQueryChain } from 'rtkx';
import { api } from './api';
// Imagine these endpoints exist in your api:
// getAuthor(authorId: string) -> Author { id, publicationId, name }
// getPublication(pubId: string) -> Publication { id, name }
// getSubscribers(pubId: string) -> Subscriber[]
export const publicationSubscribersChain = createQueryChain('getAuthor', api.endpoints.getAuthor)
.next('getPublication', api.endpoints.getPublication,
(author) => author.publicationId, // step 2: derive arg from step 1 result
)
.next('getSubscribers', api.endpoints.getSubscribers,
(pub) => pub.id, // step 3: derive arg from step 2 result
)
.build();Each .next() call's deriveArg receives two arguments:
prev- the immediately previous step's resultall- a record of every completed step's result keyed by name
This lets any step reach back to any earlier result:
export const chain = createQueryChain('getAuthor', api.endpoints.getAuthor)
.next('getPublication', api.endpoints.getPublication, (author) => author.publicationId)
.next('getAnalytics', api.endpoints.getAnalytics,
// prev = Publication all.getAuthor = Author from step 1
(pub, all) => ({ pubId: pub.id, authorId: all.getAuthor.id }),
)
.build();useQueryChain
// components/PublicationSubscribers.tsx
import { useQueryChain } from 'rtkx';
import { publicationSubscribersChain } from '../store/chains';
function PublicationSubscribers({ authorId }: { authorId: string }) {
const {
data, // { getAuthor: Author, getPublication: Publication, getSubscribers: Subscriber[] } | undefined
finalData, // Subscriber[] - last step's result (shorthand)
isLoading,
isError,
error,
currentStep, // 0-based index of the step currently running
totalSteps, // 3
reset, // re-run chain from scratch
} = useQueryChain(publicationSubscribersChain, authorId);
if (isLoading) return <p>Step {currentStep + 1} / {totalSteps}...</p>;
if (isError) return <p>Failed: {String(error)}</p>;
const { getAuthor, getPublication, getSubscribers } = data!;
return (
<section>
<h2>{getAuthor.name} - {getPublication.name}</h2>
<ul>{getSubscribers.map((s) => <li key={s.id}>{s.email}</li>)}</ul>
<button onClick={reset}>Refresh</button>
</section>
);
}Behaviours:
- Re-runs automatically when
authorIdchanges (shallow equality check) - Pass
{ skip: true }as the third argument to pause execution - Each step dispatches via RTK Query - results are cached and deduped normally
createMutationChain
Like createQueryChain but for mutations - executed imperatively via a trigger function returned by useMutationChain. Useful for multi-step write flows where each step's output feeds the next.
Key uniqueness: step keys must be unique within each chain definition.
// store/chains.ts
import { createMutationChain } from 'rtkx';
import { postsApi } from './api/posts';
export const publishPostChain = createMutationChain(
'createPost',
postsApi.endpoints.createPost,
)
.next('uploadAssets', postsApi.endpoints.uploadAssets,
// prev = CreatePostResult (step 1 output)
(prev) => ({ postId: prev.postId, files: prev.pendingFiles }),
)
.next('publish', postsApi.endpoints.publishPost,
(prev) => ({ postId: prev.postId }),
)
.build();The same all cross-step access is available just as in createQueryChain:
.next('notifyFollowers', postsApi.endpoints.notifyFollowers,
(prev, all) => ({ postId: prev.postId, authorId: all.createPost.authorId }),
)useMutationChain
// components/NewPost.tsx
import { useMutationChain } from 'rtkx';
import { publishPostChain } from '../store/chains';
function NewPost() {
const [publish, {
isLoading,
isSuccess,
isError,
error,
finalData, // PublishPostResult - last step's result
data, // { createPost: CreatePostResult, uploadAssets: UploadResult, publish: PublishPostResult }
currentStep, // 0-based index of the step currently executing
totalSteps, // 3
reset, // reset state back to idle
}] = useMutationChain(publishPostChain);
const handleClick = () => {
publish({ title: 'Hello World', body: '...', tags: ['intro'] });
};
if (isLoading) return <p>Step {currentStep + 1} / {totalSteps}…</p>;
if (isError) return <p>Failed at step {currentStep + 1}: {String(error)}</p>;
if (isSuccess) return <p>✓ Published: {finalData?.url}</p>;
return <button onClick={handleClick}>Publish post</button>;
}Behaviours:
execute(arg)returns aPromisethat resolves with the last step's result- Calling
execute()again while a chain is running cancels the in-flight chain first - Steps are executed sequentially with
dispatch(endpoint.initiate(...))- each step's result feeds the next viaderiveArg reset()clears all state back to idle without re-running
Parallel queries
Both hooks are zero-config - no chain definition file, no builder. Just pass the endpoint(s) and args directly in the component.
useParallelQueries
Fires one endpoint against an array of args simultaneously and returns a single aggregated state. Perfect for loading a batch of items by ID when no server-side bulk endpoint exists.
Subscriptions are managed incrementally: adding an ID subscribes a new query; removing an ID tears down that subscription immediately.
import { useParallelQueries } from 'rtkx';
import { postsApi } from './store/api';
function PostBatch({ postIds }: { postIds: string[] }) {
const {
data, // (Post | undefined)[] - same order as postIds
isLoading, // true while ANY query is still in its initial load
isFetching, // true while ANY query is re-fetching
isSuccess, // true only when EVERY query has succeeded
isError, // true when at least one query has errored
errors, // (unknown | undefined)[] - per ID, same order as postIds
refetchAll, // re-fetches every active query immediately
} = useParallelQueries(
postsApi.endpoints.getPost, // single endpoint
postIds, // string[] - one request per element
// optional shared options:
// { skip, pollingInterval, refetchOnMountOrArgChange, concurrent }
);
if (isLoading) return <p>Loading {postIds.length} posts…</p>;
if (isError) return <p>Some requests failed</p>;
// isSuccess gates the whole view - data is complete when true
return (
<ul>
{data.map((post, i) =>
post ? <li key={postIds[i]}>{post.title}</li> : null,
)}
</ul>
);
}Behaviours:
dataentries areundefinedwhile their query is still loading.isSuccessistrueonly when every query has resolved - use it to gate a combined view that should appear all at once.- An empty
postIdsarray short-circuits withisSuccess: trueimmediately. - Dynamically responds if the array changes length between renders.
Concurrency limiting (concurrent):
By default all requests fire simultaneously. Pass concurrent to cap how many can be in-flight at once. As each request settles (succeeds or errors), the next queued request starts automatically - sliding-window semantics.
// Fire at most 10 requests at a time; the remaining 90 queue up and
// start as earlier requests complete.
const { data, isSuccess } = useParallelQueries(
postsApi.endpoints.getPost,
hundredPostIds,
{ concurrent: 10 },
);useQueries
Fires N different endpoints in parallel. Each entry is labelled with a string key; results are returned as a named object so you destructure by key instead of accessing by index.
The key type-propagates through a mapped conditional type (DataRecord<T>), so each field of data carries the exact TypeScript type for its endpoint - no casting required.
import { useQueries } from 'rtkx';
import { statsApi, postsApi, commentsApi } from './store/api';
function Dashboard() {
const {
data, // named record - each key typed to its endpoint's result
isLoading,
isSuccess,
isError,
errors, // Partial<{ stats: unknown; posts: unknown; comments: unknown }>
refetchAll,
} = useQueries([
['stats', statsApi.endpoints.getSiteStats, undefined ],
['posts', postsApi.endpoints.getRecentPosts, undefined ],
['comments', commentsApi.endpoints.getComments, 'latest' ],
] as const);
// Destructure by name - fully typed, no data[0] / data[1]
const { stats, posts, comments } = data;
// ^ SiteStats | undefined
// ^ Post[] | undefined
// ^ Comment[] | undefined
if (isLoading) return <p>Loading…</p>;
// isSuccess gates the entire view
if (!isSuccess) return null;
return (
<section>
<p>Posts published: {stats!.totalPosts} | Comments today: {stats!.commentsToday}</p>
<ul>{posts!.map((p) => <li key={p.id}>{p.title}</li>)}</ul>
<ul>{comments!.map((c) => <li key={c.id}>{c.author}: {c.text}</li>)}</ul>
</section>
);
}Per-entry options are passed as a fourth tuple element:
useQueries([
['author', api.endpoints.getAuthor, authorId, { skip: !authorId } ],
['posts', api.endpoints.getPosts, authorId, { pollingInterval: 30_000 } ],
['drafts', api.endpoints.getDrafts, 'mine', { refetchOnMountOrArgChange: true } ],
] as const);Behaviours:
isLoading- any non-skipped query is in its initial load.isSuccess- all non-skipped queries have succeeded; skipped entries are treated as already settled.errorsis a partial named record matching the input keys.- Subscriptions are established on mount and torn down on unmount.
| Entry position | Content |
|---|---|
| [0] | Unique string key - becomes the property name on data and errors |
| [1] | RTK Query endpoint (api.endpoints.<name>) |
| [2] | Arg passed to the endpoint (use undefined for void endpoints) |
| [3] (optional) | { skip?, pollingInterval?, refetchOnMountOrArgChange? } |
Notifications (opt-in)
createNotificationMiddleware is completely independent - add it to configureStore only if you want it. It fires a typed callback on every RTK Query success or failure. No UI library is included or required - you plug in whatever toast system you use.
createNotificationMiddleware
// store/store.ts
import { configureStore, createNotificationMiddleware } from 'rtkx';
import { toast } from 'react-toastify'; // or notistack, sonner, anything
import { api } from './api';
const notifMiddleware = createNotificationMiddleware({
// Your toast library - or any callback you like
handler: (n) => {
// n is a fully-typed RtkxNotification:
// { type, message, endpointName, operationType, error?, isNetworkError, isAuthError }
toast(n.message, { type: n.type }); // react-toastify
// enqueueSnackbar(n.message, { variant: n.type }); // notistack
// sonnerToast[n.type](n.message); // sonner
},
defaults: {
onError: true, // show errors for ALL endpoints (default: true)
onSuccess: 'mutations-only', // 'mutations-only' | true | false (default)
},
});
export const store = configureStore({
middleware: (getDefault) =>
getDefault().concat(api.middleware, notifMiddleware.middleware),
});How mutation detection works: rtkx checks
action.meta.arg.type === 'mutation'- RTK Query's own flag, not name heuristics.
Network & auth errors always fire (
FETCH_ERRORand HTTP 401), bypassing all per-endpoint config.
Per-endpoint control
const notifMiddleware = createNotificationMiddleware({
handler: (n) => toast(n.message, { type: n.type }),
endpoints: {
// Suppress errors from a noisy polling endpoint
syncComments: { onError: false },
// Custom success message for a specific mutation
createPost: { onSuccess: 'Post published!' },
// Force-show success even if global onSuccess is false
deleteAccount: { onSuccess: true },
// Suppress WS connection errors for a chatty socket
getTimeline: { onError: false },
},
});| Value for onError / onSuccess | Behaviour |
|---|---|
| true | Always show |
| false | Always suppress |
| 'some string' | Always show with this custom message |
| (omitted) | Follows defaults |
WebSocket lifecycle notifications
When you provide endpointName on a createWsEndpoint or createStreamingEndpoint, the endpoint dispatches Redux actions (wsConnectedAction, wsDisconnectedAction, wsErrorAction) on lifecycle events. createNotificationMiddleware intercepts these actions automatically — no extra wiring needed.
// 1. Add endpointName to the endpoint definition
endpoints: (builder) => ({
getTimeline: createStreamingEndpoint(builder, {
endpointName: 'getTimeline', // must match the object key
query: () => '/timeline',
url: 'wss://example.com/timeline/live',
onMessage: (update, { updateCachedData }) => {
updateCachedData((draft) => { draft.push(update); });
},
}),
}),
// 2. Configure notifications in one place — same config for HTTP and WS
const notifMiddleware = createNotificationMiddleware({
handler: (n) => toast(n.message, { type: n.type }),
// Per-endpoint override — applies to BOTH HTTP errors and WS errors
endpoints: {
getTimeline: { onError: false }, // silence WS connection errors
},
// Global WS lifecycle defaults
ws: {
onError: true, // default: true — show WS errors globally
onConnect: false, // default: false
onDisconnect: 'Connection lost — reconnecting…', // custom message
},
});WS event → notification mapping:
| Event | Default | Respects per-endpoint onError? |
|---|---|---|
| WS error | ws.onError (default true) | ✅ yes |
| WS connected | ws.onConnect (default false) | — |
| WS disconnected | ws.onDisconnect (default false) | — |
The action creators are also exported from the package if you need to dispatch them manually or write custom middleware:
import { wsConnectedAction, wsDisconnectedAction, wsErrorAction } from 'rtkx';Custom messages
Override the default message generators for full control:
const notifMiddleware = createNotificationMiddleware({
handler: (n) => toast(n.message, { type: n.type }),
messages: {
// Return empty string to fall back to the built-in default
error: (endpointName, operationType, error) => {
const msg = (error as any)?.data?.message;
return msg ? `${operationType} failed: ${msg}` : '';
},
success: (endpointName, operationType) => `${operationType} successful`,
network: 'Check your internet connection',
authError: 'Your session has expired - please log in again',
unknown: 'Something went wrong',
},
});RtkxNotification type
interface RtkxNotification {
type: 'success' | 'error' | 'warning' | 'info';
message: string;
endpointName: string;
operationType: 'create' | 'update' | 'delete' | 'fetch' | 'other';
error?: unknown; // raw RTK Query error payload
isNetworkError: boolean;
isAuthError: boolean;
}Reconnect options
Applies to createWsEndpoint, createStreamingEndpoint, and WebSocketManager.
| Option | Type | Default | Description |
|---|---|---|---|
| enabled | boolean | true | Enable auto-reconnect |
| maxAttempts | number | 5 | Give up after this many attempts |
| delay | number | 1000 | Initial delay in ms |
| maxDelay | number | 30000 | Delay is capped at this value |
| factor | number | 2 | Multiply delay by this on each attempt (exponential back-off) |
reconnect: { maxAttempts: 10, delay: 500, maxDelay: 15_000, factor: 1.5 }
reconnect: true // enable with all defaults
reconnect: false // disable entirelyTypeScript generics reference
| Function | Generics | Notes |
|---|---|---|
| createWsEndpoint<TCache, TMessage, TArg> | TCache - cache shape; TMessage - per-frame type; TArg - hook arg | transformMessage may return TMessage \| null; providesTags mirrors builder.query() |
| createStreamingEndpoint<TCache, TMessage, TArg> | same as above | query provides the HTTP seed; providesTags mirrors builder.query() |
| createWsEndpointRef<TArg>() | TArg - query-arg type | Returns a WsEndpointRef<TArg>; pass to options.ref and useWebSocketSend |
| useWebSocketSend<TMessage, TArg>(ref, arg, opts?) | TMessage - outgoing message type; TArg - inferred from ref | opts.invalidatesTags + opts.api dispatch tag invalidation after each send |
| createQueryChain(key, endpoint) | key - unique step name; endpoint - first RTK Query endpoint | Returns a QueryChainBuilder; add steps with .next(key, endpoint, deriveArg) |
| .next(key, endpoint, deriveArg) | all inferred from the endpoint | key must be unique within the chain; deriveArg receives prev result + full all record |
| createMutationChain(key, endpoint) | same as createQueryChain | Same fluent API; executed imperatively via useMutationChain's trigger |
| useMutationChain(chain) | inferred from chain | Returns [execute, state] tuple |
| useParallelQueries(endpoint, args, opts?) | inferred from endpoint | data is (TResult \| undefined)[] in arg order; errors is (unknown \| undefined)[]; opts.concurrent limits in-flight requests |
| useQueries(entries) | inferred from the as const tuple | data is DataRecord<T> - each key typed to its endpoint's result; errors is a matching partial record |
License
MIT
