UNPKG

@tanstack/query-core

Version:

The framework agnostic core that powers TanStack Query

1 lines • 12.6 kB
{"version":3,"file":"mutationObserver.cjs","names":["Subscribable","shallowEqualObjects","hashKey","getDefaultState"],"sources":["../../src/mutationObserver.ts"],"sourcesContent":["import { getDefaultState } from './mutation'\nimport { notifyManager } from './notifyManager'\nimport { Subscribable } from './subscribable'\nimport { hashKey, shallowEqualObjects } from './utils'\nimport type { QueryClient } from './queryClient'\nimport type {\n DefaultError,\n MutateOptions,\n MutationFunctionContext,\n MutationObserverOptions,\n MutationObserverResult,\n} from './types'\nimport type { Action, Mutation } from './mutation'\n\n// TYPES\n\ntype MutationObserverListener<TData, TError, TVariables, TOnMutateResult> = (\n result: MutationObserverResult<TData, TError, TVariables, TOnMutateResult>,\n) => void\n\n// CLASS\n\n/**\n * Observes a single mutation and derives a `MutationObserverResult` from it.\n * A framework hook like `useMutation` creates one `MutationObserver` per hook\n * call, keeps it stable across re-renders, calls `setOptions` when the options\n * passed to the hook change, subscribes to it to re-render on updates, and\n * reads `getCurrentResult()` for the value to return. Calling `mutate()`\n * builds a new underlying `Mutation` in the `MutationCache` and executes it.\n * @example\n * ```ts\n * const observer = new MutationObserver(queryClient, {\n * mutationFn: (variables: { title: string }) => addPost(variables),\n * })\n * ```\n */\nexport class MutationObserver<\n TData = unknown,\n TError = DefaultError,\n TVariables = void,\n TOnMutateResult = unknown,\n> extends Subscribable<\n MutationObserverListener<TData, TError, TVariables, TOnMutateResult>\n> {\n options!: MutationObserverOptions<TData, TError, TVariables, TOnMutateResult>\n\n #client: QueryClient\n #currentResult: MutationObserverResult<\n TData,\n TError,\n TVariables,\n TOnMutateResult\n > = undefined!\n #currentMutation?: Mutation<TData, TError, TVariables, TOnMutateResult>\n #mutateOptions?: MutateOptions<TData, TError, TVariables, TOnMutateResult>\n\n constructor(\n client: QueryClient,\n options: MutationObserverOptions<\n TData,\n TError,\n TVariables,\n TOnMutateResult\n >,\n ) {\n super()\n\n this.#client = client\n this.setOptions(options)\n this.bindMethods()\n this.#updateResult()\n }\n\n /**\n * Binds the methods of the result (`mutate` and `reset`) to the observer, so they keep working when\n * destructured from it.\n */\n protected bindMethods(): void {\n this.mutate = this.mutate.bind(this)\n this.reset = this.reset.bind(this)\n }\n\n /**\n * Updates the observer's options.\n *\n * If the new `mutationKey` differs from the previous one (and both were\n * defined), the observer is reset, detaching it from the mutation it was\n * observing. Otherwise, if the currently observed mutation is still\n * `pending`, its options are updated in place as well.\n * @param options - The new mutation observer options. They are defaulted with {@link\n * QueryClient#defaultMutationOptions} before being applied.\n * @example\n * ```ts\n * observer.setOptions({\n * mutationFn: (variables: { title: string }) => addPost(variables),\n * onSuccess: (data) => console.log(data),\n * })\n * ```\n */\n setOptions(\n options: MutationObserverOptions<\n TData,\n TError,\n TVariables,\n TOnMutateResult\n >,\n ) {\n const prevOptions = this.options as\n | MutationObserverOptions<TData, TError, TVariables, TOnMutateResult>\n | undefined\n this.options = this.#client.defaultMutationOptions(options)\n if (!shallowEqualObjects(this.options, prevOptions)) {\n this.#client.getMutationCache().notify({\n type: 'observerOptionsUpdated',\n mutation: this.#currentMutation,\n observer: this,\n })\n }\n\n if (\n prevOptions?.mutationKey &&\n this.options.mutationKey &&\n hashKey(prevOptions.mutationKey) !== hashKey(this.options.mutationKey)\n ) {\n this.reset()\n } else if (this.#currentMutation?.state.status === 'pending') {\n this.#currentMutation.setOptions(this.options)\n }\n }\n\n protected override onSubscribe(): void {\n if (this.listeners.size === 1 && this.#currentMutation) {\n this.#currentMutation.addObserver(this)\n\n this.#updateResult()\n }\n }\n\n protected override onUnsubscribe(): void {\n if (!this.hasListeners()) {\n this.#currentMutation?.removeObserver(this)\n }\n }\n\n /**\n * Updates the observer's result when the observed mutation changes, and notifies its listeners.\n * @internal\n * @param action - The action that updated the observed mutation, passed on to the observer's\n * callbacks.\n */\n onMutationUpdate(\n action: Action<TData, TError, TVariables, TOnMutateResult>,\n ): void {\n this.#updateResult()\n\n this.#notify(action)\n }\n\n /**\n * Returns the observer's current result, derived from the observed\n * mutation's state (or the default, `idle` state if no mutation has been\n * built yet, e.g. before the first `mutate()` call or after `reset()`).\n * @returns The observer's latest result.\n */\n getCurrentResult(): MutationObserverResult<\n TData,\n TError,\n TVariables,\n TOnMutateResult\n > {\n return this.#currentResult\n }\n\n /**\n * Detaches the observer from the mutation it is currently observing (if\n * any) and resets the observed result back to its default, `idle` state.\n *\n * This does not cancel an in-flight mutation; the mutation itself keeps\n * running to completion and its own callbacks still fire, but this\n * observer stops reflecting its state and a subsequent `mutate()` call\n * will build a brand new mutation.\n * @see {@link MutationObserver#mutate}\n * @example\n * ```ts\n * observer.reset()\n * ```\n */\n reset(): void {\n // reset needs to remove the observer from the mutation because there is no way to \"get it back\"\n // another mutate call will yield a new mutation!\n this.#currentMutation?.removeObserver(this)\n this.#currentMutation = undefined\n this.#updateResult()\n this.#notify()\n }\n\n /**\n * Builds a new `Mutation` in the `MutationCache` using the observer's\n * current options, detaches this observer from any previously observed\n * mutation, attaches it to the new one, and executes it with the given\n * variables.\n *\n * The optional per-call `options` (`onSuccess`/`onError`/`onSettled`) are\n * invoked once the mutation settles, in addition to any callbacks defined\n * on the observer's own options.\n * @param variables - The variables passed to the `mutationFn`.\n * @param options - Per-call `onSuccess`, `onError`, and `onSettled` callbacks.\n * @returns A promise that resolves with the mutation's data, or rejects with its error.\n * @example\n * ```ts\n * await observer.mutate(\n * { title: 'New post' },\n * { onSuccess: (data) => console.log(data) },\n * )\n * ```\n */\n mutate(\n variables: TVariables,\n options?: MutateOptions<TData, TError, TVariables, TOnMutateResult>,\n ): Promise<TData> {\n this.#mutateOptions = options\n\n this.#currentMutation?.removeObserver(this)\n\n this.#currentMutation = this.#client\n .getMutationCache()\n .build(this.#client, this.options)\n\n this.#currentMutation.addObserver(this)\n\n return this.#currentMutation.execute(variables)\n }\n\n #updateResult(): void {\n const state =\n this.#currentMutation?.state ??\n getDefaultState<TData, TError, TVariables, TOnMutateResult>()\n\n this.#currentResult = {\n ...state,\n isPending: state.status === 'pending',\n isSuccess: state.status === 'success',\n isError: state.status === 'error',\n isIdle: state.status === 'idle',\n mutate: this.mutate,\n reset: this.reset,\n } as MutationObserverResult<TData, TError, TVariables, TOnMutateResult>\n }\n\n #notify(action?: Action<TData, TError, TVariables, TOnMutateResult>): void {\n notifyManager.batch(() => {\n // First trigger the mutate callbacks\n // Snapshot mutateOptions before dispatching — a callback (e.g. onSuccess)\n // may call mutate() again, which overwrites this.#mutateOptions in place.\n // Without the snapshot, onSettled would fire with the *next* mutation's\n // options but the *current* mutation's data/variables.\n const mutateOptions = this.#mutateOptions\n if (mutateOptions && this.hasListeners()) {\n const variables = this.#currentResult.variables!\n const onMutateResult = this.#currentResult.context\n\n const context = {\n client: this.#client,\n meta: this.options.meta,\n mutationKey: this.options.mutationKey,\n } satisfies MutationFunctionContext\n\n if (action?.type === 'success') {\n try {\n mutateOptions.onSuccess?.(\n action.data,\n variables,\n onMutateResult,\n context,\n )\n } catch (e) {\n void Promise.reject(e)\n }\n try {\n mutateOptions.onSettled?.(\n action.data,\n null,\n variables,\n onMutateResult,\n context,\n )\n } catch (e) {\n void Promise.reject(e)\n }\n } else if (action?.type === 'error') {\n try {\n mutateOptions.onError?.(\n action.error,\n variables,\n onMutateResult,\n context,\n )\n } catch (e) {\n void Promise.reject(e)\n }\n try {\n mutateOptions.onSettled?.(\n undefined,\n action.error,\n variables,\n onMutateResult,\n context,\n )\n } catch (e) {\n void Promise.reject(e)\n }\n }\n }\n\n // Then trigger the listeners\n this.listeners.forEach((listener) => {\n listener(this.#currentResult)\n })\n })\n }\n}\n"],"mappings":"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"}