fix: preserve reconciliation authorities
This commit is contained in:
@@ -5,7 +5,7 @@ import { REFERENCE_RUNTIME_SCHEMA_CODECS } from "./reference-schemas.ts";
|
||||
import { REFERENCE_BOUNDARY_MAPPERS } from "./reference-mapper.ts";
|
||||
|
||||
export const REFERENCE_FEATURE_ID = "reference-feature";
|
||||
const REFERENCE_RESOURCE_QUERY_NAMESPACE = defineQueryNamespaceIdentity(
|
||||
export const REFERENCE_RESOURCE_QUERY_NAMESPACE = defineQueryNamespaceIdentity(
|
||||
"reference-resource",
|
||||
1,
|
||||
);
|
||||
|
||||
@@ -131,7 +131,11 @@ export default function ReferenceResourceFormPage() {
|
||||
state={mutation.state}
|
||||
onReconcileUnknownEffect={(resolution) => {
|
||||
void mutation.reconcileUnknownEffect(resolution).then(() => {
|
||||
if (resolution === "APPLIED") form.settleApplied();
|
||||
if (resolution === "APPLIED") {
|
||||
form.settleApplied();
|
||||
} else {
|
||||
form.settleNotApplied();
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -7,6 +7,7 @@ import { useRouteInput } from "../../../presentation/routes/route-input.tsx";
|
||||
import {
|
||||
REFERENCE_FEATURE_ID,
|
||||
REFERENCE_RESOURCE_INVALIDATION_TOPIC,
|
||||
REFERENCE_RESOURCE_QUERY_NAMESPACE,
|
||||
} from "../contracts/reference-feature-contract.ts";
|
||||
import type {
|
||||
ReferenceCreateCommand,
|
||||
@@ -62,8 +63,9 @@ export function useReferenceDetail(resourceId: string) {
|
||||
definitionId: "reference-resource-detail-v1",
|
||||
definitionVersion: 1,
|
||||
owner: REFERENCE_FEATURE_ID,
|
||||
namespace: "reference-resource",
|
||||
namespaceVersion: 1,
|
||||
namespace: REFERENCE_RESOURCE_QUERY_NAMESPACE.namespaceId,
|
||||
namespaceVersion:
|
||||
REFERENCE_RESOURCE_QUERY_NAMESPACE.namespaceVersion,
|
||||
operationId: "GET_REFERENCE_RESOURCE",
|
||||
profileId: "DETAIL_STANDARD",
|
||||
measureResult: measureResourceView,
|
||||
@@ -108,8 +110,9 @@ export function useReferenceFeature() {
|
||||
definitionId: "reference-resource-list-v1",
|
||||
definitionVersion: 1,
|
||||
owner: REFERENCE_FEATURE_ID,
|
||||
namespace: "reference-resource",
|
||||
namespaceVersion: 1,
|
||||
namespace: REFERENCE_RESOURCE_QUERY_NAMESPACE.namespaceId,
|
||||
namespaceVersion:
|
||||
REFERENCE_RESOURCE_QUERY_NAMESPACE.namespaceVersion,
|
||||
operationId: "LIST_REFERENCE_RESOURCES",
|
||||
profileId: "LIST_STANDARD",
|
||||
measureResult: measureResourceList,
|
||||
|
||||
@@ -38,6 +38,15 @@ export function createOptimisticLayerRuntime(queryClient: QueryClient) {
|
||||
entry.disposeScopeListener = null;
|
||||
}
|
||||
|
||||
function writeProjection(entry: EntryState, value: unknown): void {
|
||||
writing = true;
|
||||
try {
|
||||
queryClient.setQueryData(entry.queryKey, value);
|
||||
} finally {
|
||||
writing = false;
|
||||
}
|
||||
}
|
||||
|
||||
queryClient.getQueryCache().subscribe((event) => {
|
||||
if (
|
||||
writing ||
|
||||
@@ -70,12 +79,7 @@ export function createOptimisticLayerRuntime(queryClient: QueryClient) {
|
||||
removeEntry(key, entry);
|
||||
return;
|
||||
}
|
||||
writing = true;
|
||||
try {
|
||||
queryClient.setQueryData(entry.queryKey, value);
|
||||
} finally {
|
||||
writing = false;
|
||||
}
|
||||
writeProjection(entry, value);
|
||||
}
|
||||
|
||||
function collapse(key: string, entry: EntryState): void {
|
||||
@@ -132,15 +136,20 @@ export function createOptimisticLayerRuntime(queryClient: QueryClient) {
|
||||
return null;
|
||||
}
|
||||
const layer: Layer = {
|
||||
id: nextId++,
|
||||
id: nextId,
|
||||
status: "pending",
|
||||
apply: (value) => update(value, input),
|
||||
};
|
||||
let projected: unknown;
|
||||
try {
|
||||
projected = update(entry.base, input);
|
||||
projected = entry.base;
|
||||
for (const existingLayer of entry.layers) {
|
||||
projected = existingLayer.apply(projected);
|
||||
}
|
||||
projected = layer.apply(projected);
|
||||
} catch (error) {
|
||||
if (entry.layers.length === 0) removeEntry(key, entry);
|
||||
if (entry.layers.length > 0) return null;
|
||||
removeEntry(key, entry);
|
||||
throw error;
|
||||
}
|
||||
if (
|
||||
@@ -150,8 +159,9 @@ export function createOptimisticLayerRuntime(queryClient: QueryClient) {
|
||||
if (entry.layers.length === 0) removeEntry(key, entry);
|
||||
return null;
|
||||
}
|
||||
nextId += 1;
|
||||
entry.layers.push(layer);
|
||||
project(key, entry);
|
||||
writeProjection(entry, projected);
|
||||
let state: "pending" | "uncertain" | "settled" = "pending";
|
||||
const selectedLayer = () => {
|
||||
if (entries.get(key) !== entry) return undefined;
|
||||
|
||||
@@ -64,6 +64,7 @@ export function useAppForm<
|
||||
const [pending, setPending] = useState(false);
|
||||
const [result, setResult] = useState<FormResultState>("idle");
|
||||
const pendingRef = useRef<Promise<FormResult<Output>> | null>(null);
|
||||
const unknownSubmissionRef = useRef<Values | null>(null);
|
||||
|
||||
const dirty = useMemo(
|
||||
() => JSON.stringify(values) !== JSON.stringify(initialValues),
|
||||
@@ -101,7 +102,9 @@ export function useAppForm<
|
||||
return next;
|
||||
});
|
||||
setFormErrors([]);
|
||||
setResult("idle");
|
||||
setResult((current) =>
|
||||
unknownSubmissionRef.current ? current : "idle",
|
||||
);
|
||||
},
|
||||
[],
|
||||
);
|
||||
@@ -125,6 +128,7 @@ export function useAppForm<
|
||||
|
||||
const reset = useCallback(
|
||||
(nextValues: Values = defaultValues) => {
|
||||
unknownSubmissionRef.current = null;
|
||||
setValues(nextValues);
|
||||
setInitialValues(nextValues);
|
||||
setTouched(new Set());
|
||||
@@ -137,6 +141,7 @@ export function useAppForm<
|
||||
|
||||
const settleSuccessfulValues = useCallback(
|
||||
(settledValues: Values) => {
|
||||
unknownSubmissionRef.current = null;
|
||||
setFieldErrors({} as FieldErrors<Values>);
|
||||
setFormErrors([]);
|
||||
setResult("success");
|
||||
@@ -152,13 +157,24 @@ export function useAppForm<
|
||||
);
|
||||
|
||||
const settleApplied = useCallback(() => {
|
||||
settleSuccessfulValues(values);
|
||||
}, [settleSuccessfulValues, values]);
|
||||
const submittedValues = unknownSubmissionRef.current;
|
||||
if (!submittedValues) return;
|
||||
settleSuccessfulValues(submittedValues);
|
||||
}, [settleSuccessfulValues]);
|
||||
|
||||
const settleNotApplied = useCallback(() => {
|
||||
if (!unknownSubmissionRef.current) return;
|
||||
unknownSubmissionRef.current = null;
|
||||
setFieldErrors({} as FieldErrors<Values>);
|
||||
setFormErrors([]);
|
||||
setResult("idle");
|
||||
}, []);
|
||||
|
||||
const submitForm = useCallback(
|
||||
async (event?: FormEvent<HTMLFormElement>): Promise<FormResult<Output> | null> => {
|
||||
event?.preventDefault();
|
||||
if (pendingRef.current) return pendingRef.current;
|
||||
if (unknownSubmissionRef.current) return null;
|
||||
setFieldErrors({} as FieldErrors<Values>);
|
||||
setFormErrors([]);
|
||||
|
||||
@@ -194,6 +210,7 @@ export function useAppForm<
|
||||
return outcome;
|
||||
}
|
||||
if (outcome.error.kind === "VALIDATION_REJECTED") {
|
||||
unknownSubmissionRef.current = null;
|
||||
const mapped = mapValidationFailureToFields<Values>(
|
||||
outcome.error,
|
||||
allowedServerFields,
|
||||
@@ -204,16 +221,19 @@ export function useAppForm<
|
||||
setResult("validation-error");
|
||||
focusFirstError(mapped.fieldErrors);
|
||||
} else if (outcome.error.effect === "MAYBE_APPLIED") {
|
||||
unknownSubmissionRef.current = parsed.data;
|
||||
setFormErrors([]);
|
||||
setResult("effect-unknown");
|
||||
} else if (outcome.error.effect === "APPLIED_CONFIRMED") {
|
||||
settleSuccessfulValues(parsed.data);
|
||||
} else if (outcome.error.kind === "CONFLICT") {
|
||||
unknownSubmissionRef.current = null;
|
||||
setFormErrors([
|
||||
message("form.conflict"),
|
||||
]);
|
||||
setResult("conflict");
|
||||
} else {
|
||||
unknownSubmissionRef.current = null;
|
||||
setFormErrors([message("form.unavailable")]);
|
||||
setResult("unavailable");
|
||||
}
|
||||
@@ -251,6 +271,7 @@ export function useAppForm<
|
||||
submitForm,
|
||||
reset,
|
||||
settleApplied,
|
||||
settleNotApplied,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user