import { useCallback, useEffect, useMemo, useState, useSyncExternalStore, } from "react"; import { hashKey, type QueryClient, useMutation, useQuery, useQueryClient, } from "@tanstack/react-query"; import { deriveAsyncState, type AsyncState, } from "../../../application/view-models/async-state.ts"; import type { Result } from "../../../contracts/result.ts"; import { createFailure, normalizeUnknownFailure, withFailureEffect, type AppFailure, } from "../../../contracts/errors.ts"; import type { QueryInvalidationCoordinator, QueryInvalidationTopic, } from "../../../contracts/query-invalidation.ts"; import type { MutationIntent } from "../../../contracts/mutation-intent.ts"; import type { CacheScopeSnapshot } from "../../../contracts/server-state-scope.ts"; import { admitQueryResult, type BoundMutation, type BoundQuery, MUTATION_COORDINATOR_BOUNDS, type MutationDuplicatePolicy, } from "../../../contracts/server-state.ts"; import { runtimeIdentityToken } from "../../../contracts/query-keys.ts"; import { useQueryInvalidationCoordinator } from "./query-invalidation-provider.tsx"; import { useMutationIntentFactory } from "./mutation-intent-provider.tsx"; import { createOptimisticLayerRuntime, type OptimisticLayerLease, } from "./optimistic-layer-runtime.ts"; export type ApplicationResult = Result; class ApplicationQueryError extends Error { readonly failure: AppFailure; constructor(failure: AppFailure) { super(failure.kind); this.name = "ApplicationQueryError"; this.failure = failure; } } export function useApplicationQuery( options: | BoundQuery | Readonly<{ queryKey: readonly unknown[]; execute(context: Readonly<{ signal: AbortSignal }>): Promise< ApplicationResult >; enabled?: boolean; }>, ): Readonly<{ data: Value | undefined; state: AsyncState; retry(): Promise; }> { const { queryKey, execute } = options; const enabled = "enabled" in options ? options.enabled ?? true : true; 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); useEffect(() => { identity?.acquire(); return () => identity?.release(); }, [identity]); const query = useQuery({ queryKey, enabled, retry: false, staleTime: profile?.staleTimeMs, gcTime: profile?.gcTimeMs, refetchOnMount: profile?.refetchOnMount, refetchOnWindowFocus: profile?.refetchOnFocus, refetchOnReconnect: profile?.refetchOnReconnect, queryFn: async ({ signal }) => { identity?.acquire(); try { if (scope && !scope.isCurrent()) { throw new ApplicationQueryError( createFailure( "SCOPE_GENERATION_CHANGED", queryDefinitionId, 0, { code: "QUERY_SCOPE_STALE" }, ), ); } const result = await execute({ signal }); if (scope && !scope.isCurrent()) { throw new ApplicationQueryError( createFailure( "SCOPE_GENERATION_CHANGED", queryDefinitionId, 0, { code: "QUERY_SCOPE_CHANGED" }, ), ); } if (result.ok) { if (profile && measureResult) { const admission = admitQueryResult( measureResult, result.value, profile, ); if (!admission.ok) { throw new ApplicationQueryError( createFailure( "RESULT_LIMIT_EXCEEDED", queryDefinitionId, 0, { code: admission.code }, ), ); } } return result.value; } if (signal.aborted) { throw new DOMException("Query cancelled", "AbortError"); } throw new ApplicationQueryError(result.error); } catch (error) { if (signal.aborted) { throw new DOMException("Query cancelled", "AbortError"); } if (error instanceof ApplicationQueryError) throw error; throw new ApplicationQueryError( normalizeUnknownFailure(error, { operationId: "APPLICATION_QUERY", }), ); } finally { identity?.release(); } }, }); const hasData = query.data !== undefined && query.data !== null; useEffect(() => { if (query.isError && hasData) { setStaleFailure(true); } else if (query.isSuccess && !query.isFetching) { setStaleFailure(false); } }, [hasData, query.isError, query.isFetching, query.isSuccess]); const retry = useCallback(async () => { setStaleFailure(false); await query.refetch(); }, [query]); return Object.freeze({ data: query.data, state: deriveAsyncState({ data: query.data, isInitialLoading: query.isPending, failure: !hasData && query.error instanceof ApplicationQueryError ? query.error.failure : undefined, isFetching: query.isFetching && !query.isPending, isStale: staleFailure, isDegraded: staleFailure, }), retry, }); } type LegacyMutationBase = Readonly<{ execute(input: Input): Promise>; duplicatePolicy?: MutationDuplicatePolicy; invalidate?: readonly QueryInvalidationTopic[]; currentData?: unknown; }>; type LegacyMutationOptions = LegacyMutationBase & ( | Readonly<{ /** Stable logical identity required to retain an unknown effect across remounts. */ definitionId: string; optimistic?: never; }> | Readonly<{ definitionId?: string; optimistic: Readonly<{ queryKey: readonly unknown[]; update(previous: unknown, input: Input): unknown; }>; }> ); type ApplicationMutationController = Readonly<{ state: AsyncState; submit(input: Input): Promise>; resolveConflict(): Promise; reconcileUnknownEffect( resolution: "APPLIED" | "NOT_APPLIED", ): Promise; }>; type MutationExecution = | Readonly<{ kind: "BOUND"; input: Input; intent: MutationIntent }> | Readonly<{ kind: "LEGACY"; input: Input }>; type MutationAdmission = { sequence: number; state: "ACTIVE" | "UNKNOWN" | "RECONCILING" | "SETTLED"; intent: MutationIntent | null; scope: CacheScopeSnapshot | null; optimisticLayer: OptimisticLayerLease | null; optimisticQueryKey: readonly unknown[] | null; readonly invalidate: readonly QueryInvalidationTopic[]; invalidationCoordinator: QueryInvalidationCoordinator | null; }; type UnknownEffectChannel = { readonly key: string; readonly registry: UnknownEffectRegistry; readonly owner: QueryClient; readonly scope: CacheScopeSnapshot | null; nextSequence: number; admissions: MutationAdmission[]; version: number; reconciliationInFlight: boolean; readonly listeners: Set<() => void>; disposeScopeListener: (() => void) | null; }; type UnknownEffectRegistry = { readonly channels: Map; activeAdmissions: number; }; const LEGACY_OPTIMISTIC_SCOPE = Object.freeze({ isCurrent: () => true, }); export function useApplicationMutation( options: BoundMutation, ): ApplicationMutationController; export function useApplicationMutation( options: LegacyMutationOptions, ): ApplicationMutationController; export function useApplicationMutation( options: BoundMutation | LegacyMutationOptions, ): ApplicationMutationController { const queryClient = useQueryClient(); const invalidationCoordinator = useQueryInvalidationCoordinator(); const mutationIntentFactory = useMutationIntentFactory(); const invalidate = useMemo( () => options.invalidate ?? [], [options.invalidate], ); const optimistic = "optimistic" in options ? options.optimistic : undefined; const currentData = "currentData" in options ? options.currentData : undefined; const definitionId = "definitionId" in options ? options.definitionId ?? "LEGACY_MUTATION" : "LEGACY_MUTATION"; const definitionVersion = "definitionVersion" in options ? options.definitionVersion : 0; const duplicatePolicy = "duplicatePolicy" in options ? options.duplicatePolicy : "REJECT_WHILE_ACTIVE"; const [conflict, setConflict] = useState(null); const scope = "scope" in options ? options.scope : undefined; const mutationOperationId = "operationId" in options ? options.operationId : definitionId; const requiresIdempotencyKey = "requiresIdempotencyKey" in options ? options.requiresIdempotencyKey : false; const unknownEffectChannelKey = scope ? `bound:${scope.generation}:${scope.fingerprint}:${definitionId}:${definitionVersion}` : `legacy:${definitionId}:${ optimistic ? hashKey(optimistic.queryKey) : "non-optimistic" }`; const [acquiredUnknownEffectChannel, setAcquiredUnknownEffectChannel] = useState(null); const unknownEffectChannel = acquiredUnknownEffectChannel?.owner === queryClient && acquiredUnknownEffectChannel.key === unknownEffectChannelKey && acquiredUnknownEffectChannel.scope === (scope ?? null) ? acquiredUnknownEffectChannel : null; useEffect(() => { const acquired = acquireUnknownEffectChannel( queryClient, unknownEffectChannelKey, scope, ); setAcquiredUnknownEffectChannel(acquired); return () => { if (acquired) { releaseUnknownEffectChannelIfUnused(queryClient, acquired); } }; }, [queryClient, scope, unknownEffectChannelKey]); const subscribeToUnknownEffects = useCallback( (listener: () => void) => { if (!unknownEffectChannel) return () => {}; unknownEffectChannel.listeners.add(listener); return () => { unknownEffectChannel.listeners.delete(listener); releaseUnknownEffectChannelIfUnused( queryClient, unknownEffectChannel, ); }; }, [queryClient, unknownEffectChannel], ); useSyncExternalStore( subscribeToUnknownEffects, () => unknownEffectChannel?.version ?? 0, () => unknownEffectChannel?.version ?? 0, ); const mutation = useMutation< Value, ApplicationQueryError, MutationExecution >({ retry: false, mutationFn: async (execution) => { const input = execution.input; if (scope && !scope.isCurrent()) { throw new ApplicationQueryError( createFailure( "SCOPE_GENERATION_CHANGED", definitionId, 0, { code: "MUTATION_SCOPE_STALE", effect: "NOT_STARTED" }, ), ); } const result = "scope" in options ? await options.execute(input, { signal: options.scope.signal, intent: execution.kind === "BOUND" ? execution.intent : (() => { throw new TypeError( "Bound mutation execution requires an intent.", ); })(), }) : 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", effect }, ), ); } if (result.ok) return result.value; throw new ApplicationQueryError(result.error); }, }); const submit = useCallback( (input: Input): Promise> => { let identity: string; let identityLease: ReturnType< NonNullable["identities"]["intern"] > | null = null; try { if (scope) { if (!scope.isCurrent()) { return Promise.resolve({ ok: false, error: createFailure( "SCOPE_GENERATION_CHANGED", definitionId, 0, { code: "MUTATION_SCOPE_STALE", effect: "NOT_STARTED" }, ), }); } identityLease = scope.identities.intern(input); identityLease.acquire(); identity = `${scope.fingerprint}:${definitionId}:${identityLease.token}`; } else { identity = `${definitionId}:${runtimeIdentityToken(input)}`; } } catch (error) { return Promise.resolve({ ok: false, error: withFailureEffect( normalizeUnknownFailure(error, { operationId: definitionId, }), "NOT_STARTED", ), }); } const active = mutationExecutions(queryClient).get(identity) as | Promise> | undefined; if (active && duplicatePolicy === "JOIN_IDENTICAL") { identityLease?.release(); return active; } if (active && duplicatePolicy === "REJECT_WHILE_ACTIVE") { identityLease?.release(); return Promise.resolve({ ok: false, error: createFailure( "DUPLICATE_IN_FLIGHT", definitionId, 0, { code: "DUPLICATE_IN_FLIGHT", effect: "NOT_STARTED" }, ), }); } if ( !unknownEffectChannel || unknownEffectChannel.registry.activeAdmissions >= MUTATION_COORDINATOR_BOUNDS.activeIntentsTotal ) { identityLease?.release(); return Promise.resolve({ ok: false, error: createFailure( "IDENTITY_INTERN_LIMIT_EXCEEDED", definitionId, 0, { code: "UNKNOWN_EFFECT_CHANNEL_LIMIT_EXCEEDED", effect: "NOT_STARTED", }, ), }); } const admission: MutationAdmission = { sequence: unknownEffectChannel.nextSequence++, state: "ACTIVE", intent: null, scope: scope ?? null, optimisticLayer: null, optimisticQueryKey: optimistic?.queryKey ?? null, invalidate, invalidationCoordinator: invalidationCoordinator ?? null, }; unknownEffectChannel.admissions.push(admission); unknownEffectChannel.registry.activeAdmissions += 1; notifyUnknownEffectChannel(unknownEffectChannel); setConflict(null); mutation.reset(); const pending = (async (): Promise> => { let execution: MutationExecution; if (scope) { const intent = mutationIntentFactory.create({ operationId: mutationOperationId, canonicalInputIdentity: identity, requiresIdempotencyKey, }); admission.intent = intent; execution = Object.freeze({ kind: "BOUND", input, intent }); } else { execution = Object.freeze({ kind: "LEGACY", input }); } const mutationLease = invalidate.length === 0 ? null : invalidationCoordinator?.beginMutation(invalidate); if (invalidate.length > 0 && !mutationLease) { throw new Error("Query invalidation coordinator is not installed."); } try { let optimisticLayer: OptimisticLayerLease | null = null; if (optimistic) { await queryClient.cancelQueries({ queryKey: optimistic.queryKey, exact: true, }); optimisticLayer = optimisticLayers(queryClient).begin( optimistic.queryKey, input, optimistic.update, scope ?? LEGACY_OPTIMISTIC_SCOPE, ); admission.optimisticLayer = optimisticLayer; } let value: Value; try { value = await mutation.mutateAsync(execution); } catch (error: unknown) { const reportedFailure = error instanceof ApplicationQueryError ? error.failure : normalizeUnknownFailure(error, { operationId: "APPLICATION_MUTATION", }); const failure = mutationFailureWithEffect(reportedFailure); switch (failure.effect) { case "NOT_STARTED": case "NOT_APPLIED": optimisticLayer?.rollback(); if (failure.kind === "CONFLICT") setConflict(failure); settleMutationAdmission(unknownEffectChannel, admission); break; case "APPLIED_CONFIRMED": optimisticLayer?.commit(); if (!scope || scope.isCurrent()) { try { await invalidationCoordinator?.invalidate(invalidate); } catch { // A confirmed command remains committed if refresh fails. } } settleMutationAdmission(unknownEffectChannel, admission); break; case "MAYBE_APPLIED": optimisticLayer?.markUncertain(); if (!scope || scope.isCurrent()) { admission.state = "UNKNOWN"; notifyUnknownEffectChannel(unknownEffectChannel); } else { settleMutationAdmission(unknownEffectChannel, admission); } break; } return { ok: false, error: failure }; } optimisticLayer?.commit(); try { await invalidationCoordinator?.invalidate(invalidate); } catch { // Cache refresh remains best effort after the server has committed. } settleMutationAdmission(unknownEffectChannel, admission); return { ok: true, value }; } finally { try { await mutationLease?.release(); } catch { // A cache coordination defect cannot change the committed command. } } })() .catch((error: unknown) => { const failure = withFailureEffect( normalizeUnknownFailure(error, { operationId: "APPLICATION_MUTATION", }), "NOT_STARTED", ); settleMutationAdmission(unknownEffectChannel, admission); if (failure.kind === "CONFLICT") setConflict(failure); return { ok: false as const, error: failure }; }) .finally(() => { identityLease?.release(); if (mutationExecutions(queryClient).get(identity) === pending) { mutationExecutions(queryClient).delete(identity); } }); if (duplicatePolicy !== "ALLOW_PARALLEL") { mutationExecutions(queryClient).set(identity, pending); } return pending; }, [ invalidate, invalidationCoordinator, mutation, optimistic, queryClient, definitionId, duplicatePolicy, mutationIntentFactory, mutationOperationId, requiresIdempotencyKey, scope, unknownEffectChannel, ], ); const resolveConflict = useCallback(async () => { setConflict(null); mutation.reset(); if (invalidate.length > 0 && !invalidationCoordinator) { throw new Error("Query invalidation coordinator is not installed."); } await invalidationCoordinator?.invalidate(invalidate); }, [invalidate, invalidationCoordinator, mutation]); const reconcileUnknownEffect = useCallback( async (resolution: "APPLIED" | "NOT_APPLIED") => { if ( !unknownEffectChannel || unknownEffectChannel.reconciliationInFlight ) { return; } const pending = unknownEffectChannel.admissions[0]; if (!pending || pending.state !== "UNKNOWN") return; if (pending.scope && !pending.intent) return; unknownEffectChannel.reconciliationInFlight = true; pending.state = "RECONCILING"; notifyUnknownEffectChannel(unknownEffectChannel); try { pending.optimisticLayer?.reconcile(resolution); if ( resolution === "APPLIED" && (!pending.scope || pending.scope.isCurrent()) ) { try { await pending.invalidationCoordinator?.invalidate( pending.invalidate, ); } catch { // Explicit reconciliation remains settled if refresh fails. } } } finally { if (pending.scope && !pending.scope.isCurrent()) { removeAdmissionOptimisticQuery(queryClient, pending); } settleMutationAdmission(unknownEffectChannel, pending); if ( !unknownEffectChannel.admissions.some( (admission) => admission.state === "ACTIVE", ) ) { mutation.reset(); } // Keep the head intent locked through the current event turn so two // clicks cannot consume two FIFO records when reconciliation itself // has no asynchronous invalidation work. await Promise.resolve(); unknownEffectChannel.reconciliationInFlight = false; notifyUnknownEffectChannel(unknownEffectChannel); } }, [mutation, queryClient, unknownEffectChannel], ); const unknownEffectHead = unknownEffectChannel?.admissions[0]; return Object.freeze({ state: deriveAsyncState({ data: currentData ?? true, isMutationPending: mutation.isPending || unknownEffectHead?.state === "ACTIVE" || unknownEffectHead?.state === "RECONCILING", hasMutationEffectUnknown: unknownEffectHead?.state === "UNKNOWN", hasMutationConflict: conflict !== null, }), submit, resolveConflict, reconcileUnknownEffect, }); } function mutationFailureWithEffect(failure: AppFailure): AppFailure { switch (failure.effect) { case "NOT_STARTED": case "NOT_APPLIED": case "APPLIED_CONFIRMED": case "MAYBE_APPLIED": return withFailureEffect(failure, failure.effect); case "NOT_APPLICABLE": case undefined: return withFailureEffect(failure, "MAYBE_APPLIED"); } } const UNKNOWN_EFFECT_CHANNELS = new WeakMap< object, UnknownEffectRegistry >(); function acquireUnknownEffectChannel( queryClient: QueryClient, key: string, scope: CacheScopeSnapshot | undefined, ): UnknownEffectChannel | null { let registry = UNKNOWN_EFFECT_CHANNELS.get(queryClient); if (!registry) { registry = { channels: new Map(), activeAdmissions: 0 }; UNKNOWN_EFFECT_CHANNELS.set(queryClient, registry); } const { channels } = registry; const existing = channels.get(key); if (existing) return existing.scope === (scope ?? null) ? existing : null; if ( channels.size >= MUTATION_COORDINATOR_BOUNDS.activeDefinitionsPerRuntime ) { return null; } const created: UnknownEffectChannel = { key, registry, owner: queryClient, scope: scope ?? null, nextSequence: 1, admissions: [], version: 0, reconciliationInFlight: false, listeners: new Set(), disposeScopeListener: null, }; channels.set(key, created); if (scope) { const discardScope = () => { for (const admission of created.admissions) { removeAdmissionOptimisticQuery(queryClient, admission); markMutationAdmissionSettled(created, admission); } created.admissions = []; notifyUnknownEffectChannel(created); releaseUnknownEffectChannelIfUnused(queryClient, created); }; scope.signal.addEventListener("abort", discardScope, { once: true }); created.disposeScopeListener = () => scope.signal.removeEventListener("abort", discardScope); if (scope.signal.aborted || !scope.isCurrent()) discardScope(); } return created; } function releaseUnknownEffectChannelIfUnused( queryClient: QueryClient, channel: UnknownEffectChannel, ): void { if (channel.admissions.length > 0 || channel.listeners.size > 0) return; const registry = UNKNOWN_EFFECT_CHANNELS.get(queryClient); const channels = registry?.channels; if (channels?.get(channel.key) !== channel) return; channel.disposeScopeListener?.(); channels.delete(channel.key); if (channels.size === 0 && registry?.activeAdmissions === 0) { UNKNOWN_EFFECT_CHANNELS.delete(queryClient); } } function notifyUnknownEffectChannel(channel: UnknownEffectChannel): void { channel.version += 1; for (const listener of [...channel.listeners]) listener(); } function settleMutationAdmission( channel: UnknownEffectChannel, admission: MutationAdmission, ): void { markMutationAdmissionSettled(channel, admission); while (channel.admissions[0]?.state === "SETTLED") { channel.admissions.shift(); } notifyUnknownEffectChannel(channel); releaseUnknownEffectChannelIfUnused(channel.owner, channel); } function markMutationAdmissionSettled( channel: UnknownEffectChannel, admission: MutationAdmission, ): void { if (admission.state === "SETTLED") return; admission.state = "SETTLED"; channel.registry.activeAdmissions = Math.max( 0, channel.registry.activeAdmissions - 1, ); } function removeAdmissionOptimisticQuery( queryClient: QueryClient, admission: MutationAdmission, ): void { if (!admission.optimisticQueryKey) return; queryClient.removeQueries({ queryKey: admission.optimisticQueryKey, exact: true, }); } const RUNTIME_MUTATION_EXECUTIONS = new WeakMap< object, Map>> >(); function mutationExecutions( owner: object, ): Map>> { const existing = RUNTIME_MUTATION_EXECUTIONS.get(owner); if (existing) return existing; const created = new Map>>(); RUNTIME_MUTATION_EXECUTIONS.set(owner, created); return created; } const OPTIMISTIC_LAYER_RUNTIMES = new WeakMap< object, ReturnType >(); function optimisticLayers( queryClient: Parameters[0], ): ReturnType { const existing = OPTIMISTIC_LAYER_RUNTIMES.get(queryClient); if (existing) return existing; const created = createOptimisticLayerRuntime(queryClient); OPTIMISTIC_LAYER_RUNTIMES.set(queryClient, created); return created; }