mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-27 22:30:55 +00:00
145 lines
4.2 KiB
TypeScript
145 lines
4.2 KiB
TypeScript
import {
|
|
UseQueryOptions,
|
|
UseMutationOptions
|
|
} from "@tanstack/react-query";
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// React Query shared types
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export type QueryConfig<T> = Omit<
|
|
UseQueryOptions<T, Error, T, readonly unknown[]>,
|
|
"queryKey" | "queryFn"
|
|
>;
|
|
|
|
/**
|
|
* Query keys a mutation should invalidate on success. Receives the mutation
|
|
* input and response so keys can be derived from them. Returns a list of query
|
|
* keys — each is matched as a *prefix* by React Query, so returning a service
|
|
* root (e.g. `["cargoes"]`) invalidates every query nested under it.
|
|
*
|
|
* The keys are surfaced through `mutationOptions().meta.invalidates`; the
|
|
* app-wide `MutationCache` (see `lib/queryClient.ts`) reads them and invalidates
|
|
* automatically, so components never wire `onSuccess` invalidation by hand.
|
|
*/
|
|
export type InvalidatesFn<TInput, TResponse> = (
|
|
input: TInput,
|
|
data: TResponse,
|
|
) => ReadonlyArray<readonly unknown[]>;
|
|
|
|
/** Shape stored in `mutation.meta.invalidates` and consumed by the MutationCache. */
|
|
export type InvalidatesMeta = (
|
|
variables: unknown,
|
|
data: unknown,
|
|
) => ReadonlyArray<readonly unknown[]>;
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Endpoint interfaces
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export interface EndpointWithInput<TInput, TResponse> {
|
|
call(input: TInput): Promise<TResponse>;
|
|
queryKey(input: TInput): readonly unknown[];
|
|
queryOptions(
|
|
config: { input: TInput } & QueryConfig<TResponse>,
|
|
): UseQueryOptions<TResponse, Error, TResponse, readonly unknown[]>;
|
|
}
|
|
|
|
export interface EndpointWithoutInput<TResponse> {
|
|
call(): Promise<TResponse>;
|
|
queryKey(): readonly unknown[];
|
|
queryOptions(
|
|
config?: QueryConfig<TResponse>,
|
|
): UseQueryOptions<TResponse, Error, TResponse, readonly unknown[]>;
|
|
}
|
|
|
|
export type Endpoint<TInput, TResponse> = TInput extends void
|
|
? EndpointWithoutInput<TResponse>
|
|
: EndpointWithInput<TInput, TResponse>;
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Endpoint builder
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export function endpoint<TInput, TResponse>(
|
|
service: string,
|
|
action: string,
|
|
execute: (input: TInput) => Promise<TResponse>,
|
|
queryKeyBuilder?: (input: TInput) => readonly unknown[],
|
|
invalidates?: InvalidatesFn<TInput, TResponse>,
|
|
) {
|
|
const buildKey = (input?: TInput): readonly unknown[] => {
|
|
if (queryKeyBuilder && input !== undefined) {
|
|
return queryKeyBuilder(input as TInput);
|
|
}
|
|
return input === undefined
|
|
? [service, action]
|
|
: [service, action, input];
|
|
};
|
|
|
|
const call = (input: TInput) => execute(input);
|
|
|
|
const queryKey = (input?: TInput) => buildKey(input);
|
|
|
|
const queryOptions = (
|
|
config?: { input?: TInput } & QueryConfig<TResponse>,
|
|
): UseQueryOptions<
|
|
TResponse,
|
|
Error,
|
|
TResponse,
|
|
readonly unknown[]
|
|
> => {
|
|
const { input, ...rest } = config ?? {};
|
|
|
|
return {
|
|
...rest,
|
|
queryKey: buildKey(input),
|
|
queryFn: () => execute(input as TInput),
|
|
};
|
|
};
|
|
|
|
const mutationOptions = (
|
|
config?: Omit<UseMutationOptions<TResponse, Error, TInput>, "mutationFn">,
|
|
): UseMutationOptions<TResponse, Error, TInput> => {
|
|
const meta = invalidates
|
|
? {
|
|
...config?.meta,
|
|
invalidates: ((variables, data) =>
|
|
invalidates(
|
|
variables as TInput,
|
|
data as TResponse,
|
|
)) satisfies InvalidatesMeta,
|
|
}
|
|
: config?.meta;
|
|
|
|
return {
|
|
...config,
|
|
meta,
|
|
mutationFn: (variables: TInput): Promise<TResponse> => execute(variables),
|
|
};
|
|
};
|
|
|
|
return {
|
|
call,
|
|
queryKey,
|
|
queryOptions,
|
|
mutationOptions
|
|
};
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Helper utilities
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export function unwrap<T>(response: { data: T } | T): T {
|
|
if (
|
|
response &&
|
|
typeof response === "object" &&
|
|
"data" in (response as object)
|
|
) {
|
|
return (response as { data: T }).data;
|
|
}
|
|
|
|
return response as T;
|
|
}
|