chore: finish frontend ESLint migration #18
Enable every plugin recommended preset and replace Biome with ESLint formatting. Preserve API contracts and JSX whitespace; verify source, tests, builds and tooling.
This commit is contained in:
@@ -1,15 +1,15 @@
|
||||
import type { QueryClient } from "@tanstack/react-query";
|
||||
import {
|
||||
import { type QueryClient,
|
||||
type QueryFunctionContext,
|
||||
type QueryKey,
|
||||
type UseQueryOptions,
|
||||
type UseQueryResult,
|
||||
useQueryClient,
|
||||
} from "@tanstack/react-query";
|
||||
type UseQueryOptions,
|
||||
type UseQueryResult } from "@tanstack/react-query";
|
||||
import { useEffect } from "react";
|
||||
|
||||
import type { components } from "./generated/schema";
|
||||
|
||||
import { useAuthenticatedQuery } from "./authenticated-query";
|
||||
import { getAuthenticatedSessionSnapshot } from "./client";
|
||||
import type { components } from "./generated/schema";
|
||||
import { apiErrorCode } from "./problem-details";
|
||||
|
||||
const accessEpochs = new WeakMap<QueryClient, Map<string, number>>();
|
||||
@@ -18,31 +18,6 @@ type Resource = {
|
||||
data: unknown;
|
||||
meta: components["schemas"]["InvestmentResourceMetaDto"];
|
||||
};
|
||||
function unwrapResourceResult<T extends Resource, E>(
|
||||
result: UseQueryResult<T, E>,
|
||||
) {
|
||||
const missing = result.isSuccess && result.data.data === null;
|
||||
const data: T["data"] | undefined = result.data?.data;
|
||||
const meta: T["meta"] | undefined = result.data?.meta;
|
||||
const status: UseQueryResult<T, E>["status"] = missing
|
||||
? "pending"
|
||||
: result.status;
|
||||
// Project resource fields; refetch still returns the transport envelope.
|
||||
return {
|
||||
...result,
|
||||
transport: {
|
||||
status: result.status,
|
||||
fetchStatus: result.fetchStatus,
|
||||
error: result.error,
|
||||
},
|
||||
data: data ?? undefined,
|
||||
meta,
|
||||
isPending: result.isPending || missing,
|
||||
isSuccess: result.isSuccess && !missing,
|
||||
status,
|
||||
};
|
||||
}
|
||||
|
||||
function invalidateResourceAccess(
|
||||
client: QueryClient,
|
||||
group: string,
|
||||
@@ -50,10 +25,10 @@ function invalidateResourceAccess(
|
||||
) {
|
||||
if (
|
||||
![
|
||||
"NOT_FOUND",
|
||||
"FORBIDDEN",
|
||||
"TBANK_NOT_CONNECTED",
|
||||
"INVALID_TBANK_TOKEN",
|
||||
"NOT_FOUND",
|
||||
"TBANK_NOT_CONNECTED",
|
||||
].includes(apiErrorCode(error) ?? "")
|
||||
)
|
||||
return;
|
||||
@@ -69,19 +44,44 @@ function invalidateResourceAccess(
|
||||
cached.setState({
|
||||
data: undefined,
|
||||
dataUpdatedAt: 0,
|
||||
status: "error",
|
||||
error: error as Error,
|
||||
fetchStatus: "idle",
|
||||
status: "error",
|
||||
});
|
||||
}
|
||||
|
||||
function unwrapResourceResult<T extends Resource, E>(
|
||||
result: UseQueryResult<T, E>,
|
||||
) {
|
||||
const isMissing = result.isSuccess && result.data.data === null;
|
||||
const data: T["data"] | undefined = result.data?.data;
|
||||
const meta: T["meta"] | undefined = result.data?.meta;
|
||||
const status: UseQueryResult<T, E>["status"] = isMissing
|
||||
? "pending"
|
||||
: result.status;
|
||||
// Project resource fields; refetch still returns the transport envelope.
|
||||
return {
|
||||
...result,
|
||||
data: data ?? undefined,
|
||||
isPending: result.isPending || isMissing,
|
||||
isSuccess: result.isSuccess && !isMissing,
|
||||
meta,
|
||||
status,
|
||||
transport: {
|
||||
error: result.error,
|
||||
fetchStatus: result.fetchStatus,
|
||||
status: result.status,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Observation changes transport mode, never the generated cache identity.
|
||||
function useResourceQuery<T extends Resource, E, K extends QueryKey>(
|
||||
options: UseQueryOptions<T, E, T, K>,
|
||||
) {
|
||||
const client = useQueryClient();
|
||||
const group =
|
||||
typeof options.meta?.accessGroup === "string"
|
||||
const group
|
||||
= typeof options.meta?.accessGroup === "string"
|
||||
? options.meta.accessGroup
|
||||
: undefined;
|
||||
let epochs = accessEpochs.get(client);
|
||||
@@ -90,40 +90,38 @@ function useResourceQuery<T extends Resource, E, K extends QueryKey>(
|
||||
accessEpochs.set(client, epochs);
|
||||
}
|
||||
const groupEpochs = epochs;
|
||||
const queryFn = options.queryFn;
|
||||
const guarded =
|
||||
typeof queryFn === "function"
|
||||
const queryFunction = options.queryFn;
|
||||
const guarded
|
||||
= typeof queryFunction === "function"
|
||||
? async (context: QueryFunctionContext<K>) => {
|
||||
const epoch = group ? (groupEpochs.get(group) ?? 0) : 0;
|
||||
const result = await queryFn(context);
|
||||
if (group && epoch !== (groupEpochs.get(group) ?? 0))
|
||||
throw new DOMException("Access changed", "AbortError");
|
||||
return result;
|
||||
}
|
||||
: queryFn;
|
||||
const epoch = group ? (groupEpochs.get(group) ?? 0) : 0;
|
||||
const result = await queryFunction(context);
|
||||
if (group && epoch !== (groupEpochs.get(group) ?? 0))
|
||||
throw new DOMException("Access changed", "AbortError");
|
||||
return result;
|
||||
}
|
||||
: queryFunction;
|
||||
const query = useAuthenticatedQuery({ ...options, queryFn: guarded });
|
||||
useEffect(() => {
|
||||
if (group) invalidateResourceAccess(client, group, query.error);
|
||||
}, [client, group, query.error]);
|
||||
const observe = async (expectedStateVersion?: string) => {
|
||||
const queryFn = guarded;
|
||||
const queryFunction = guarded;
|
||||
if (
|
||||
!query.isEnabled ||
|
||||
!getAuthenticatedSessionSnapshot() ||
|
||||
typeof queryFn !== "function"
|
||||
typeof queryFunction !== "function"
|
||||
|| !query.isEnabled
|
||||
|| !getAuthenticatedSessionSnapshot()
|
||||
)
|
||||
return;
|
||||
// Cancellation is synchronous; start the replacement in the same boundary.
|
||||
void client.cancelQueries({ queryKey: options.queryKey, exact: true });
|
||||
return client.fetchQuery({
|
||||
void client.cancelQueries({ exact: true, queryKey: options.queryKey });
|
||||
return client.query({
|
||||
...options,
|
||||
staleTime: 0,
|
||||
retry: false,
|
||||
queryFn: async (context: QueryFunctionContext<K>) => {
|
||||
const [method, path, input] = context.queryKey;
|
||||
const init = input as
|
||||
| { params?: { query?: Record<string, unknown> } }
|
||||
| undefined;
|
||||
| undefined
|
||||
| { params?: { query?: Record<string, unknown> } };
|
||||
const queryKey = [
|
||||
method,
|
||||
path,
|
||||
@@ -135,25 +133,29 @@ function useResourceQuery<T extends Resource, E, K extends QueryKey>(
|
||||
},
|
||||
},
|
||||
] as unknown as K;
|
||||
const response = await queryFn({ ...context, queryKey });
|
||||
const response = await queryFunction({ ...context, queryKey });
|
||||
if (
|
||||
expectedStateVersion &&
|
||||
response.meta.stateVersion !== expectedStateVersion
|
||||
expectedStateVersion
|
||||
&& response.meta.stateVersion !== expectedStateVersion
|
||||
) {
|
||||
// One more observational read; never turn a stale notification into an import.
|
||||
const current = await queryFn({ ...context, queryKey });
|
||||
const current = await queryFunction({ ...context, queryKey });
|
||||
if (current.meta.stateVersion !== expectedStateVersion)
|
||||
throw new DOMException("Resource changed", "AbortError");
|
||||
return current;
|
||||
}
|
||||
return response;
|
||||
},
|
||||
retry: false,
|
||||
// Observation validates raw resource metadata, before any observer projection.
|
||||
select: undefined,
|
||||
staleTime: 0,
|
||||
});
|
||||
};
|
||||
return {
|
||||
...unwrapResourceResult(query),
|
||||
queryKey: options.queryKey,
|
||||
observe,
|
||||
queryKey: options.queryKey,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user