refactor: 리펙토링
This commit is contained in:
@@ -21,9 +21,11 @@ import {
|
||||
type AppFailure,
|
||||
} from "../../../contracts/errors.ts";
|
||||
import type { QueryInvalidationTopic } from "../../../contracts/query-invalidation.ts";
|
||||
import type {
|
||||
BoundMutation,
|
||||
BoundQuery,
|
||||
import {
|
||||
admitQueryResult,
|
||||
type BoundMutation,
|
||||
type BoundQuery,
|
||||
type MutationDuplicatePolicy,
|
||||
} from "../../../contracts/server-state.ts";
|
||||
import { runtimeIdentityToken } from "../../../contracts/query-keys.ts";
|
||||
import { useQueryInvalidationCoordinator } from "./query-invalidation-provider.tsx";
|
||||
@@ -64,6 +66,8 @@ export function useApplicationQuery<Value>(
|
||||
const profile = "profile" in options ? options.profile : undefined;
|
||||
const scope = "scope" in options ? options.scope : undefined;
|
||||
const identity = "identity" in options ? options.identity : undefined;
|
||||
const measureResult =
|
||||
"measureResult" in options ? options.measureResult : undefined;
|
||||
const queryDefinitionId =
|
||||
"definitionId" in options ? options.definitionId : "APPLICATION_QUERY";
|
||||
const [staleFailure, setStaleFailure] = useState(false);
|
||||
@@ -105,22 +109,22 @@ export function useApplicationQuery<Value>(
|
||||
);
|
||||
}
|
||||
if (result.ok) {
|
||||
if (
|
||||
profile &&
|
||||
!isAdmissibleResult(
|
||||
if (profile && measureResult) {
|
||||
const admission = admitQueryResult(
|
||||
measureResult,
|
||||
result.value,
|
||||
profile.maxResultItems,
|
||||
profile.maxEstimatedResultBytes,
|
||||
)
|
||||
) {
|
||||
throw new ApplicationQueryError(
|
||||
createFailure(
|
||||
"RESULT_LIMIT_EXCEEDED",
|
||||
"APPLICATION_QUERY",
|
||||
0,
|
||||
{ code: "RESULT_ADMISSION_LIMIT_EXCEEDED" },
|
||||
),
|
||||
profile,
|
||||
);
|
||||
if (!admission.ok) {
|
||||
throw new ApplicationQueryError(
|
||||
createFailure(
|
||||
"RESULT_LIMIT_EXCEEDED",
|
||||
queryDefinitionId,
|
||||
0,
|
||||
{ code: admission.code },
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
return result.value;
|
||||
}
|
||||
@@ -175,26 +179,34 @@ export function useApplicationQuery<Value>(
|
||||
});
|
||||
}
|
||||
|
||||
export function useApplicationMutation<Input, Value>(
|
||||
options:
|
||||
| BoundMutation<Input, Value>
|
||||
| Readonly<{
|
||||
execute(input: Input): Promise<ApplicationResult<Value>>;
|
||||
invalidate?: readonly QueryInvalidationTopic[];
|
||||
optimistic?: Readonly<{
|
||||
queryKey: readonly unknown[];
|
||||
update(previous: unknown, input: Input): unknown;
|
||||
}>;
|
||||
currentData?: unknown;
|
||||
}>,
|
||||
): Readonly<{
|
||||
type LegacyMutationOptions<Input, Value> = Readonly<{
|
||||
execute(input: Input): Promise<ApplicationResult<Value>>;
|
||||
duplicatePolicy?: MutationDuplicatePolicy;
|
||||
invalidate?: readonly QueryInvalidationTopic[];
|
||||
optimistic?: Readonly<{
|
||||
queryKey: readonly unknown[];
|
||||
update(previous: unknown, input: Input): unknown;
|
||||
}>;
|
||||
currentData?: unknown;
|
||||
}>;
|
||||
|
||||
type ApplicationMutationController<Input, Value> = Readonly<{
|
||||
state: AsyncState;
|
||||
submit(input: Input): Promise<ApplicationResult<Value>>;
|
||||
resolveConflict(): Promise<void>;
|
||||
}> {
|
||||
}>;
|
||||
|
||||
export function useApplicationMutation<Input, Value>(
|
||||
options: BoundMutation<Input, Value>,
|
||||
): ApplicationMutationController<Input, Value>;
|
||||
export function useApplicationMutation<Input, Value>(
|
||||
options: LegacyMutationOptions<Input, Value>,
|
||||
): ApplicationMutationController<Input, Value>;
|
||||
export function useApplicationMutation<Input, Value>(
|
||||
options: BoundMutation<Input, Value> | LegacyMutationOptions<Input, Value>,
|
||||
): ApplicationMutationController<Input, Value> {
|
||||
const queryClient = useQueryClient();
|
||||
const invalidationCoordinator = useQueryInvalidationCoordinator();
|
||||
const { execute } = options;
|
||||
const invalidate = useMemo(
|
||||
() => options.invalidate ?? [],
|
||||
[options.invalidate],
|
||||
@@ -204,7 +216,7 @@ export function useApplicationMutation<Input, Value>(
|
||||
const definitionId =
|
||||
"definitionId" in options ? options.definitionId : "LEGACY_MUTATION";
|
||||
const duplicatePolicy =
|
||||
"duplicatePolicy" in options ? options.duplicatePolicy : "JOIN_IDENTICAL";
|
||||
"duplicatePolicy" in options ? options.duplicatePolicy : "REJECT_WHILE_ACTIVE";
|
||||
const [conflict, setConflict] = useState<AppFailure | null>(null);
|
||||
const scope = "scope" in options ? options.scope : undefined;
|
||||
const mutation = useMutation<Value, ApplicationQueryError, Input>({
|
||||
@@ -220,14 +232,21 @@ export function useApplicationMutation<Input, Value>(
|
||||
),
|
||||
);
|
||||
}
|
||||
const result = await execute(input);
|
||||
const result =
|
||||
"scope" in options
|
||||
? await options.execute(input, { signal: options.scope.signal })
|
||||
: await options.execute(input);
|
||||
if (scope && !scope.isCurrent()) {
|
||||
const effect =
|
||||
!result.ok && result.error.effect !== undefined
|
||||
? result.error.effect
|
||||
: "MAYBE_APPLIED";
|
||||
throw new ApplicationQueryError(
|
||||
createFailure(
|
||||
"SCOPE_GENERATION_CHANGED",
|
||||
definitionId,
|
||||
0,
|
||||
{ code: "MUTATION_SCOPE_CHANGED" },
|
||||
{ code: "MUTATION_SCOPE_CHANGED", effect },
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -276,7 +295,7 @@ export function useApplicationMutation<Input, Value>(
|
||||
identityLease?.release();
|
||||
return active;
|
||||
}
|
||||
if (active && duplicatePolicy === "REJECT_DUPLICATE") {
|
||||
if (active && duplicatePolicy === "REJECT_WHILE_ACTIVE") {
|
||||
identityLease?.release();
|
||||
return Promise.resolve({
|
||||
ok: false,
|
||||
@@ -379,7 +398,7 @@ export function useApplicationMutation<Input, Value>(
|
||||
mutationExecutions(queryClient).delete(identity);
|
||||
}
|
||||
});
|
||||
if (duplicatePolicy !== "ALLOW_INDEPENDENT") {
|
||||
if (duplicatePolicy !== "ALLOW_PARALLEL") {
|
||||
mutationExecutions(queryClient).set(identity, pending);
|
||||
}
|
||||
return pending;
|
||||
@@ -445,36 +464,3 @@ function optimisticLayers(
|
||||
OPTIMISTIC_LAYER_RUNTIMES.set(queryClient, created);
|
||||
return created;
|
||||
}
|
||||
|
||||
function isAdmissibleResult(
|
||||
value: unknown,
|
||||
maxItems: number,
|
||||
maxBytes: number,
|
||||
): boolean {
|
||||
try {
|
||||
const seen = new WeakSet<object>();
|
||||
let items = 0;
|
||||
const visit = (candidate: unknown): boolean => {
|
||||
if (candidate === null || ["string", "number", "boolean"].includes(typeof candidate)) {
|
||||
return true;
|
||||
}
|
||||
if (!candidate || typeof candidate !== "object" || seen.has(candidate)) return false;
|
||||
seen.add(candidate);
|
||||
if (Array.isArray(candidate)) {
|
||||
items += candidate.length;
|
||||
return items <= maxItems && candidate.every(visit);
|
||||
}
|
||||
const prototype = Object.getPrototypeOf(candidate);
|
||||
return (
|
||||
(prototype === Object.prototype || prototype === null) &&
|
||||
Object.values(candidate).every(visit)
|
||||
);
|
||||
};
|
||||
return (
|
||||
visit(value) &&
|
||||
new TextEncoder().encode(JSON.stringify(value)).byteLength <= maxBytes
|
||||
);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user