@pinia/colada
Version:
The smart data fetching layer for Pinia
1 lines • 134 kB
Source Map (JSON)
{"version":3,"sources":["../src/index.ts","../src/mutation-store.ts","../src/tree-map.ts","../src/utils.ts","../src/mutation-options.ts","../src/use-mutation.ts","../src/define-mutation.ts","../src/define-query.ts","../src/query-store.ts","../src/query-options.ts","../src/use-query.ts","../src/devtools/plugin.ts","../src/pinia-colada.ts","../src/plugins/query-hooks.ts","../src/infinite-query.ts"],"sourcesContent":["/**\n * Pinia Colada\n * @module @pinia/colada\n */\nexport { defineMutation } from './define-mutation'\nexport {\n type _ReduceContext,\n useMutation,\n type UseMutationReturn,\n} from './use-mutation'\n\nexport { defineQuery } from './define-query'\nexport { useQuery, type UseQueryReturn } from './use-query'\n\n// export { type UseQueryKeyList } from './query-keys'\n\nexport type {\n _DataState_Base,\n AsyncStatus,\n DataState,\n DataState_Error,\n DataState_Pending,\n DataState_Success,\n DataStateStatus,\n} from './data-state'\nexport { type EntryKey } from './entry-options'\nexport type {\n RefetchOnControl,\n UseQueryOptions,\n UseQueryOptionsGlobal,\n UseQueryOptionsWithDefaults,\n} from './query-options'\n\nexport type {\n UseMutationOptions,\n // UseMutationOptionsGlobal,\n} from './mutation-options'\n\nexport { PiniaColada } from './pinia-colada'\nexport type { PiniaColadaOptions } from './pinia-colada'\n\nexport {\n PiniaColadaQueryHooksPlugin,\n type PiniaColadaQueryHooksPluginOptions,\n} from './plugins/query-hooks'\n\nexport {\n hydrateQueryCache,\n type QueryCache,\n serializeQueryCache,\n serializeTreeMap,\n useQueryCache,\n type UseQueryEntry,\n type UseQueryEntryExtensions,\n type UseQueryEntryFilter,\n} from './query-store'\n\nexport { type EntryNodeKey, TreeMapNode } from './tree-map'\n\nexport { type _Awaitable, type _EmptyObject, type _MaybeArray, toCacheKey } from './utils'\n\nexport type { TypesConfig } from './types-extension'\n\nexport type { PiniaColadaPlugin, PiniaColadaPluginContext } from './plugins'\n\nexport { useInfiniteQuery, type UseInfiniteQueryOptions } from './infinite-query'\n","import type { ComponentInternalInstance, EffectScope, ShallowRef } from 'vue'\nimport type { AsyncStatus, DataState, DataStateStatus } from './data-state'\nimport type { EntryNodeKey } from './tree-map'\nimport { defineStore, skipHydrate } from 'pinia'\nimport { customRef, getCurrentScope, shallowRef } from 'vue'\nimport { TreeMapNode } from './tree-map'\nimport type { _EmptyObject } from './utils'\nimport { isSameArray, noop, stringifyFlatObject, toCacheKey, toValueWithArgs } from './utils'\nimport type { _ReduceContext, UseMutationGlobalContext } from './use-mutation'\nimport { useMutationOptions } from './mutation-options'\nimport type { UseMutationOptions } from './mutation-options'\nimport type { EntryKey } from './entry-options'\n\n/**\n * A mutation entry in the cache.\n */\nexport interface UseMutationEntry<\n TResult = unknown,\n TVars = unknown,\n TError = unknown,\n TContext extends Record<any, any> = _EmptyObject,\n> {\n /**\n * The state of the mutation. Contains the data, error and status.\n */\n state: ShallowRef<DataState<TResult, TError>>\n\n /**\n * The async status of the mutation.\n */\n asyncStatus: ShallowRef<AsyncStatus>\n\n /**\n * When was this data fetched the last time in ms\n */\n when: number\n\n /**\n * The serialized key associated with this mutation entry.\n */\n key: EntryNodeKey[] | undefined\n\n /**\n * The variables used to call the mutation.\n */\n vars: ShallowRef<TVars | undefined>\n\n options: UseMutationOptions<TResult, TVars, TError, TContext>\n\n pending: symbol | null\n\n /**\n * Component `__hmrId` to track wrong usage of `useQuery` and warn the user.\n * @internal\n */\n __hmr?: {\n id?: string\n deps?: Set<EffectScope | ComponentInternalInstance>\n skip?: boolean\n }\n}\n\n/**\n * Filter to get mutation entries from the cache.\n */\nexport interface UseMutationEntryFilter {\n /**\n * A key to filter the entries.\n */\n key?: EntryKey\n\n /**\n * If true, it will only match the exact key, not the children.\n *\n * @example\n * ```ts\n * { key: ['a'], exact: true }\n * // will match ['a'] but not ['a', 'b'], while\n * { key: ['a'] }\n * // will match both\n * ```\n */\n exact?: boolean\n\n /**\n * If defined, it will only return the entries with the given status.\n */\n status?: DataStateStatus\n\n /**\n * Pass a predicate to filter the entries. This will be executed for each entry matching the other filters.\n * @param entry - entry to filter\n */\n predicate?: (entry: UseMutationEntry) => boolean\n}\n\nfunction createMutationEntry<\n TResult = unknown,\n TVars = unknown,\n TError = unknown,\n TContext extends Record<any, any> = _EmptyObject,\n>(\n options: UseMutationOptions<TResult, TVars, TError, TContext>,\n key: EntryNodeKey[] | undefined,\n vars?: TVars,\n): UseMutationEntry<TResult, TVars, TError, TContext> {\n return {\n state: shallowRef<DataState<TResult, TError>>({\n status: 'pending',\n data: undefined,\n error: null,\n }),\n asyncStatus: shallowRef<AsyncStatus>('idle'),\n when: 0,\n vars: shallowRef(vars),\n key,\n options,\n pending: null,\n }\n}\n\nexport const useMutationCache = /* @__PURE__ */ defineStore('_pc_mutation', ({ action }) => {\n // We have two versions of the cache, one that track changes and another that doesn't so the actions can be used\n // inside computed properties\n // We have two versions of the cache, one that track changes and another that doesn't so the actions can be used\n // inside computed properties\n const cachesRaw = new TreeMapNode<UseMutationEntry<unknown, any, unknown, any>>()\n let triggerCache!: () => void\n const caches = skipHydrate(\n customRef(\n (track, trigger) =>\n (triggerCache = trigger) && {\n // eslint-disable-next-line no-sequences\n get: () => (track(), cachesRaw),\n set:\n process.env.NODE_ENV !== 'production'\n ? () => {\n console.error(\n `[@pinia/colada]: The mutation cache instance cannot be set directly, it must be modified. This will fail in production.`,\n )\n }\n : noop,\n },\n ),\n )\n\n // this allows use to attach reactive effects to the scope later on\n const scope = getCurrentScope()!\n\n const globalOptions = useMutationOptions()\n const defineMutationMap = new WeakMap<() => unknown, unknown>()\n\n function ensure<\n TResult = unknown,\n TVars = unknown,\n TError = unknown,\n TContext extends Record<any, any> = _EmptyObject,\n >(\n options: UseMutationOptions<TResult, TVars, TError, TContext>,\n ): UseMutationEntry<TResult, TVars, TError, TContext>\n function ensure<\n TResult = unknown,\n TVars = unknown,\n TError = unknown,\n TContext extends Record<any, any> = _EmptyObject,\n >(\n options: UseMutationOptions<TResult, TVars, TError, TContext>,\n entry: UseMutationEntry<TResult, TVars, TError, TContext>,\n vars: NoInfer<TVars>,\n ): UseMutationEntry<TResult, TVars, TError, TContext>\n\n function ensure<\n TResult = unknown,\n TVars = unknown,\n TError = unknown,\n TContext extends Record<any, any> = _EmptyObject,\n >(\n options: UseMutationOptions<TResult, TVars, TError, TContext>,\n entry?: UseMutationEntry<TResult, TVars, TError, TContext>,\n vars?: NoInfer<TVars>,\n ): UseMutationEntry<TResult, TVars, TError, TContext> {\n const key = vars && toValueWithArgs(options.key, vars)?.map(stringifyFlatObject)\n\n if (!entry) {\n entry = createMutationEntry(options, key)\n if (key) {\n cachesRaw.set(\n key,\n // @ts-expect-error: function types with generics are incompatible\n entry,\n )\n triggerCache()\n }\n // TODO: store it somewhere during dev mode to show in devtools\n return createMutationEntry(options, key)\n }\n // reuse the entry when no key is provided\n if (key) {\n // update key\n if (!entry.key) {\n entry.key = key\n } else if (!isSameArray(entry.key, key)) {\n entry = createMutationEntry(\n options,\n key,\n // the type NonNullable<TVars> is not assignable to TVars\n vars as TVars,\n )\n cachesRaw.set(\n key,\n // @ts-expect-error: function types with generics are incompatible\n entry,\n )\n triggerCache()\n }\n }\n\n return entry\n }\n\n /**\n * Ensures a query created with {@link defineMutation} is present in the cache. If it's not, it creates a new one.\n * @param fn - function that defines the query\n */\n const ensureDefinedMutation = action(<T>(fn: () => T) => {\n let defineMutationResult = defineMutationMap.get(fn)\n if (!defineMutationResult) {\n defineMutationMap.set(fn, (defineMutationResult = scope.run(fn)))\n }\n\n return defineMutationResult\n })\n\n /**\n * Sets the state of a query entry in the cache and updates the\n * {@link UseQueryEntry['pending']['when'] | `when` property}. This action is\n * called every time the cache state changes and can be used by plugins to\n * detect changes.\n *\n * @param entry - the entry of the query to set the state\n * @param state - the new state of the entry\n */\n const setEntryState = action(\n <\n TResult = unknown,\n TVars = unknown,\n TError = unknown,\n TContext extends Record<any, any> = _EmptyObject,\n >(\n entry: UseMutationEntry<TResult, TVars, TError, TContext>,\n // NOTE: NoInfer ensures correct inference of TResult and TError\n state: DataState<NoInfer<TResult>, NoInfer<TError>>,\n ) => {\n entry.state.value = state\n entry.when = Date.now()\n },\n )\n\n /**\n * Removes a query entry from the cache if it has a key. If it doesn't then it does nothing.\n *\n * @param entry - the entry of the query to remove\n */\n const remove = action((entry: UseMutationEntry) => {\n if (entry.key != null) {\n cachesRaw.delete(entry.key)\n triggerCache()\n }\n })\n\n /**\n * Returns all the entries in the cache that match the filters.\n *\n * @param filters - filters to apply to the entries\n */\n const getEntries = action((filters: UseMutationEntryFilter = {}): UseMutationEntry[] => {\n const node = filters.key ? caches.value.find(toCacheKey(filters.key)) : caches.value\n\n if (!node) return []\n\n return (filters.exact ? (node.value ? [node.value] : []) : [...node]).filter(\n (entry) =>\n (filters.status == null || entry.state.value.status === filters.status)\n && (!filters.predicate || filters.predicate(entry)),\n )\n })\n\n async function mutate<\n TResult = unknown,\n TVars = unknown,\n TError = unknown,\n TContext extends Record<any, any> = _EmptyObject,\n >(\n currentEntry: UseMutationEntry<TResult, TVars, TError, TContext>,\n vars: NoInfer<TVars>,\n ): Promise<TResult> {\n currentEntry.asyncStatus.value = 'loading'\n currentEntry.vars.value = vars\n\n // TODO: AbortSignal that is aborted when the mutation is called again so we can throw in pending\n let currentData: TResult | undefined\n let currentError: TError | undefined\n type OnMutateContext = Parameters<\n Required<UseMutationOptions<TResult, TVars, TError, TContext>>['onMutate']\n >['1']\n type OnSuccessContext = Parameters<\n Required<UseMutationOptions<TResult, TVars, TError, TContext>>['onSuccess']\n >['2']\n type OnErrorContext = Parameters<\n Required<UseMutationOptions<TResult, TVars, TError, TContext>>['onError']\n >['2']\n const { options } = currentEntry\n\n let context: OnMutateContext | OnErrorContext | OnSuccessContext = {}\n\n const currentCall = (currentEntry.pending = Symbol())\n try {\n const globalOnMutateContext = globalOptions.onMutate?.(vars)\n\n context\n = (globalOnMutateContext instanceof Promise\n ? await globalOnMutateContext\n : globalOnMutateContext) || {}\n\n const onMutateContext = (await options.onMutate?.(\n vars,\n context,\n // NOTE: the cast makes it easier to write without extra code. It's safe because { ...null, ...undefined } works and TContext must be a Record<any, any>\n )) as _ReduceContext<TContext>\n\n // we set the context here so it can be used by other hooks\n context = {\n ...context,\n ...onMutateContext,\n // NOTE: needed for onSuccess cast\n } satisfies OnSuccessContext\n\n const newData = (currentData = await options.mutation(vars, context as OnSuccessContext))\n\n await globalOptions.onSuccess?.(newData, vars, context as OnSuccessContext)\n await options.onSuccess?.(\n newData,\n vars,\n // NOTE: cast is safe because of the satisfies above\n // using a spread also works\n context as OnSuccessContext,\n )\n\n if (currentEntry.pending === currentCall) {\n setEntryState(currentEntry, {\n status: 'success',\n data: newData,\n error: null,\n })\n }\n } catch (newError: unknown) {\n currentError = newError as TError\n await globalOptions.onError?.(currentError, vars, context)\n await options.onError?.(currentError, vars, context)\n if (currentEntry.pending === currentCall) {\n setEntryState(currentEntry, {\n status: 'error',\n data: currentEntry.state.value.data,\n error: currentError,\n })\n }\n throw newError\n } finally {\n // TODO: should we catch and log it?\n await globalOptions.onSettled?.(currentData, currentError, vars, context)\n await options.onSettled?.(currentData, currentError, vars, context)\n if (currentEntry.pending === currentCall) {\n currentEntry.asyncStatus.value = 'idle'\n }\n }\n\n return currentData\n }\n\n return {\n caches,\n ensure,\n ensureDefinedMutation,\n mutate,\n remove,\n\n setEntryState,\n getEntries,\n }\n})\n","import type { UseQueryOptionsWithDefaults } from './query-options'\n\n/**\n * Key type for nodes in the tree map. Differently from {@link EntryKey}, this type is serializable to JSON.\n */\nexport type EntryNodeKey = string | number\n\n/**\n * Internal data structure used to store the data of `useQuery()`. `T` should be serializable to JSON.\n * @internal\n */\nexport class TreeMapNode<T = unknown> {\n value: T | undefined\n children?: Map<EntryNodeKey, TreeMapNode<T>>\n\n constructor()\n constructor(keys: EntryNodeKey[], value: T | undefined)\n constructor(...args: [] | [EntryNodeKey[], T]) {\n if (args.length) {\n this.set(...args)\n }\n }\n\n /**\n * Sets the value while building the tree\n *\n * @param keys - key as an array\n * @param value - value to set\n */\n set(keys: EntryNodeKey[], value?: T) {\n if (keys.length === 0) {\n this.value = value\n } else {\n // this.children ??= new Map<EntryNodeKey,\n const [top, ...otherKeys] = keys as [top: EntryNodeKey, ...otherKeys: EntryNodeKey[]]\n const node: TreeMapNode<T> | undefined = this.children?.get(top)\n if (node) {\n node.set(otherKeys, value)\n } else {\n this.children ??= new Map()\n this.children.set(top, new TreeMapNode(otherKeys, value))\n }\n }\n }\n\n /**\n * Finds the node at the given path of keys.\n *\n * @param keys - path of keys\n */\n find(keys: EntryNodeKey[]): TreeMapNode<T> | undefined {\n if (keys.length === 0) {\n return this\n } else {\n const [top, ...otherKeys] = keys as [top: EntryNodeKey, ...otherKeys: EntryNodeKey[]]\n return this.children?.get(top)?.find(otherKeys)\n }\n }\n\n /**\n * Gets the value at the given path of keys.\n *\n * @param keys - path of keys\n */\n get(keys: EntryNodeKey[]): T | undefined {\n return this.find(keys)?.value\n }\n\n /**\n * Delete the node at the given path of keys and all its children.\n *\n * @param keys - path of keys\n */\n delete(keys: EntryNodeKey[]) {\n if (keys.length === 1) {\n this.children?.delete(keys[0]!)\n } else {\n const [top, ...otherKeys] = keys as [top: EntryNodeKey, ...otherKeys: EntryNodeKey[]]\n this.children?.get(top)?.delete(otherKeys)\n }\n }\n\n /**\n * Iterates over the node values if not null or undefined and all its children. Goes in depth first order. Allows a `for (const of node)` loop.\n */\n * [Symbol.iterator](): IterableIterator<T> {\n if (this.value != null) {\n yield this.value\n }\n if (this.children) {\n for (const child of this.children.values()) {\n yield *child\n }\n }\n }\n}\n\n// NOTE: this function is outside of TreeMapNode because it's only needed for SSR apps and shouldn't add to the bundle\n// size if they are client only\n/**\n * Revives and appends a serialized node to the tree.\n *\n * @param parent - parent node\n * @param serializedEntryNode serialized entry\n * @param serializedEntryNode.0 entry key\n * @param serializedEntryNode.1 entry data value\n * @param serializedEntryNode.2 entry children\n * @param createNodeValue - function to create the node value\n * @param parentKey parent key\n */\nexport function appendSerializedNodeToTree<T>(\n parent: TreeMapNode<T>,\n [key, value, children]: UseQueryEntryNodeSerialized,\n createNodeValue: (\n key: EntryNodeKey[],\n options?: UseQueryOptionsWithDefaults<unknown, unknown> | null,\n initialData?: unknown,\n error?: unknown | null,\n when?: number,\n ) => T,\n parentKey: EntryNodeKey[] = [],\n) {\n parent.children ??= new Map()\n const entryKey = [...parentKey, key]\n const node = new TreeMapNode<T>(\n [],\n // NOTE: this could happen outside of an effect scope but since it's only for client side hydration, it should be\n // fine to have global shallowRefs as they can still be cleared when needed\n value && createNodeValue(entryKey, null, ...value),\n )\n parent.children.set(key, node)\n if (children) {\n for (const child of children) {\n appendSerializedNodeToTree(node, child, createNodeValue, entryKey)\n }\n }\n}\n\n/**\n * Raw data of a query entry. Can be serialized from the server and used to hydrate the store.\n * @internal\n */\nexport type _UseQueryEntryNodeValueSerialized<TResult = unknown, TError = unknown> = [\n /**\n * The data returned by the query.\n */\n data: TResult | undefined,\n\n /**\n * The error thrown by the query.\n */\n error: TError | null,\n\n /**\n * When was this data fetched the last time in ms\n */\n when?: number,\n]\n\n/**\n * Serialized version of a query entry node.\n * @internal\n */\nexport type UseQueryEntryNodeSerialized = [\n key: EntryNodeKey,\n value: undefined | _UseQueryEntryNodeValueSerialized,\n children?: UseQueryEntryNodeSerialized[],\n]\n\n// -------------------------------------\n// --- Below are debugging internals ---\n// -------------------------------------\n/* v8 ignore start */\n\n/**\n * Calculates the size of the node and all its children. Used in tests.\n *\n * @internal\n * @param node - The node to calculate the size of\n * @returns The size of the node and all its children\n */\nexport function entryNodeSize(node: TreeMapNode): number {\n return (\n (node.children?.size ?? 0)\n + [...(node.children?.values() || [])].reduce((acc, child) => acc + entryNodeSize(child), 0)\n )\n}\n\n/**\n * Logs the tree to the console. Used in tests.\n * @internal\n *\n * @param tree - tree to log\n * @param log - function to log the tree\n */\nexport function logTree(\n tree: TreeMapNode,\n\n log: (str: string) => any = console.log,\n) {\n log(printTreeMap(tree))\n}\n\nconst MAX_LEVEL = 1000\nfunction printTreeMap(\n tree: TreeMapNode | TreeMapNode['children'],\n level = 0,\n parentPre = '',\n treeStr = '',\n): string {\n // end of recursion\n if (typeof tree !== 'object' || level >= MAX_LEVEL) return ''\n\n if (tree instanceof Map) {\n const total = tree.size\n let index = 0\n for (const [key, child] of tree) {\n const hasNext = index++ < total - 1\n const { children } = child\n\n treeStr += `${`${parentPre}${hasNext ? '├' : '└'}${`─${(children?.size ?? 0) > 0 ? '┬' : ''}`} `}${key}${child.value != null ? ` · ${String(child.value)}` : ''}\\n`\n\n if (children) {\n treeStr += printTreeMap(children, level + 1, `${parentPre}${hasNext ? '│' : ' '} `)\n }\n }\n } else {\n const children = tree.children\n treeStr = `${String(\n typeof tree.value === 'object' && tree.value ? JSON.stringify(tree.value) : '<root>',\n )}\\n`\n if (children) {\n treeStr += printTreeMap(children, level + 1)\n }\n }\n\n return treeStr\n}\n/* v8 ignore stop */\n","import { computed, getCurrentScope, onScopeDispose } from 'vue'\nimport type { MaybeRefOrGetter, Ref, ShallowRef } from 'vue'\nimport type { EntryKey } from './entry-options'\nimport type { EntryNodeKey } from './tree-map'\nimport type { QueryCache } from './query-store'\nimport type { UseQueryOptions } from './query-options'\n\n/**\n * Adds an event listener to Window that is automatically removed on scope dispose.\n */\nexport function useEventListener<E extends keyof WindowEventMap>(\n target: Window,\n event: E,\n listener: (this: Window, ev: WindowEventMap[E]) => any,\n options?: boolean | AddEventListenerOptions,\n): void\n\n/**\n * Adds an event listener to Document that is automatically removed on scope dispose.\n */\nexport function useEventListener<E extends keyof DocumentEventMap>(\n target: Document,\n event: E,\n listener: (this: Document, ev: DocumentEventMap[E]) => any,\n options?: boolean | AddEventListenerOptions,\n): void\n\nexport function useEventListener(\n target: Document | Window | EventTarget,\n event: string,\n listener: (this: EventTarget, ev: Event) => any,\n options?: boolean | AddEventListenerOptions,\n) {\n target.addEventListener(event, listener, options)\n if (getCurrentScope()) {\n onScopeDispose(() => {\n target.removeEventListener(event, listener)\n })\n }\n}\n\nexport const IS_CLIENT = typeof window !== 'undefined'\n\n/**\n * Type that represents a value that can be an array or a single value.\n * @internal\n */\nexport type _MaybeArray<T> = T | T[]\n\n/**\n * Type that represents a value that can be a function or a single value. Used for `defineQuery()` and\n * `defineMutation()`.\n * @internal\n */\nexport type _MaybeFunction<T, Args extends any[] = []> = T | ((...args: Args) => T)\n\n/**\n * Transforms a value or a function that returns a value to a value.\n * @param valFn either a value or a function that returns a value\n * @param args arguments to pass to the function if `valFn` is a function\n */\nexport function toValueWithArgs<T, Args extends any[]>(\n valFn: T | ((...args: Args) => T),\n ...args: Args\n): T {\n return typeof valFn === 'function' ? (valFn as (...args: Args) => T)(...args) : valFn\n}\n\n/**\n * Type that represents a value that can be a promise or a single value.\n * @internal\n */\nexport type _Awaitable<T> = T | Promise<T>\n\n/**\n * Flattens an object type for readability.\n * @internal\n */\nexport type _Simplify<T> = { [K in keyof T]: T[K] }\n\n/**\n * Converts a value to an array if necessary.\n *\n * @param value - value to convert\n */\nexport const toArray = <T>(value: _MaybeArray<T>): T[] => (Array.isArray(value) ? value : [value])\n\nexport type _JSONPrimitive = string | number | boolean | null | undefined\n\n/**\n * Utility type to represent a flat object that can be stringified with `JSON.stringify` no matter the order of keys.\n * @internal\n */\nexport interface _ObjectFlat {\n [key: string]: _JSONPrimitive | Array<_JSONPrimitive>\n}\n\n/**\n * Stringifies an object no matter the order of keys. This is used to create a hash for a given object. It only works\n * with flat objects. It can contain arrays of primitives only. `undefined` values are skipped.\n *\n * @param obj - object to stringify\n */\nexport function stringifyFlatObject(obj: _ObjectFlat | _JSONPrimitive): string {\n return obj && typeof obj === 'object' ? JSON.stringify(obj, Object.keys(obj).sort()) : String(obj)\n}\n\n/**\n * Creates a {@link QueryCache}'s `caches` key from an entry's {@link UseQueryOptions#key}.\n * @param key - key of the entry\n */\nexport const toCacheKey = (key: EntryKey): EntryNodeKey[] => key.map(stringifyFlatObject)\n\n/**\n * Merges two types when the second one can be null | undefined. Allows to safely use the returned type for { ...a,\n * ...undefined, ...null }\n * @internal\n */\nexport type _MergeObjects<Obj, MaybeNull> = MaybeNull extends undefined | null | void\n ? Obj\n : _Simplify<Obj & MaybeNull>\n\n/**\n * @internal\n */\nexport const noop = () => {}\n\n/**\n * Wraps a getter to be used as a ref. This is useful when you want to use a getter as a ref but you need to modify the\n * value.\n *\n * @internal\n * @param other - getter of the ref to compute\n * @returns a wrapper around a writable getter that can be used as a ref\n */\nexport const computedRef = <T>(other: () => Ref<T>): ShallowRef<T> =>\n computed({\n get: () => other().value,\n set: (value) => (other().value = value),\n })\n\n/**\n * Renames a property in an object type.\n */\nexport type _RenameProperty<T, Key extends keyof T, NewKey extends string> = {\n [P in keyof T as P extends Key ? NewKey : P]: T[P]\n}\n\n/**\n * Type safe version of `Object.assign` that allows to set all properties of a reactive object at once. Used to set\n * {@link DataState} properties in a type safe way.\n */\nexport const setReactiveValue = Object.assign as <T>(value: T, ...args: T[]) => T\n\n/**\n * To avoid using `{}`\n * @internal\n */\nexport interface _EmptyObject {}\n\n/**\n * Compares two arrays to check if they are the same. Used for keys.\n *\n * @param arr1 - first array to compare\n * @param arr2 - second array to compare\n */\nexport function isSameArray(arr1: unknown[], arr2: unknown[]): boolean {\n if (arr1.length !== arr2.length) return false\n for (let i = 0; i < arr1.length; i++) {\n if (arr1[i] !== arr2[i]) return false\n }\n return true\n}\n\n/**\n * Dev only warning that is only shown once.\n */\nconst warnedMessages = new Set<string>()\n\n/**\n * Warns only once. This should only be used in dev\n * @param message - Message to show\n * @param id - Unique id for the message, defaults to the message\n */\nexport function warnOnce(message: string, id: string = message) {\n if (warnedMessages.has(id)) return\n warnedMessages.add(id)\n console.warn(`[@pinia/colada]: ${message}`)\n}\n\n/**\n * Removes the `MaybeRefOrGetter` wrapper from all fields of an object.\n * @internal\n */\nexport type _RemoveMaybeRef<T> = {\n [K in keyof T]: T[K] extends MaybeRefOrGetter<infer U>\n ? MaybeRefOrGetter<U> extends T[K]\n ? U\n : T[K]\n : T[K]\n}\n","import { inject } from 'vue'\nimport type { InjectionKey } from 'vue'\nimport type { ErrorDefault } from './types-extension'\nimport type { _ReduceContext, _MutationKey, UseMutationGlobalContext } from './use-mutation'\nimport type { _EmptyObject, _Awaitable } from './utils'\n\n/**\n * Options for mutations that can be globally overridden.\n */\nexport interface UseMutationOptionsGlobal {\n /**\n * Runs before a mutation is executed. It can return a value that will be\n * passed to `mutation`, `onSuccess`, `onError` and `onSettled`. If it\n * returns a promise, it will be awaited before running `mutation`.\n */\n onMutate?: (\n /**\n * The variables passed to the mutation.\n */\n vars: unknown,\n ) => _Awaitable<UseMutationGlobalContext | undefined | void | null>\n\n /**\n * Runs when a mutation is successful.\n */\n onSuccess?: (\n /**\n * The result of the mutation.\n */\n data: unknown,\n /**\n * The variables passed to the mutation.\n */\n vars: unknown,\n /**\n * The merged context from `onMutate` and the global context.\n */\n context: UseMutationGlobalContext,\n ) => unknown\n\n /**\n * Runs when a mutation encounters an error.\n */\n onError?: (\n /**\n * The error thrown by the mutation.\n */\n error: unknown,\n /**\n * The variables passed to the mutation.\n */\n vars: unknown,\n /**\n * The merged context from `onMutate` and the global context. Properties returned by `onMutate` can be `undefined`\n * if `onMutate` throws.\n */\n context:\n | Partial<Record<keyof UseMutationGlobalContext, never>>\n // this is the success case where everything is defined\n // undefined if global onMutate throws\n | UseMutationGlobalContext,\n ) => unknown\n\n /**\n * Runs after the mutation is settled, regardless of the result.\n */\n onSettled?: (\n /**\n * The result of the mutation. `undefined` when a mutation failed.\n */\n data: unknown | undefined,\n /**\n * The error thrown by the mutation. `undefined` if the mutation was successful.\n */\n error: unknown | undefined,\n /**\n * The variables passed to the mutation.\n */\n vars: unknown,\n /**\n * The merged context from `onMutate` and the global context. Properties returned by `onMutate` can be `undefined`\n * if `onMutate` throws.\n */\n context:\n | Partial<Record<keyof UseMutationGlobalContext, never>>\n // this is the success case where everything is defined\n // undefined if global onMutate throws\n | UseMutationGlobalContext,\n ) => unknown\n}\n\n/**\n * Options to create a mutation.\n */\nexport interface UseMutationOptions<\n TResult = unknown,\n TVars = void,\n TError = ErrorDefault,\n TContext extends Record<any, any> = _EmptyObject,\n> {\n /**\n * The key of the mutation. If the mutation is successful, it will invalidate the mutation with the same key and refetch it\n */\n mutation: (vars: TVars, context: _ReduceContext<NoInfer<TContext>>) => Promise<TResult>\n\n /**\n * Optional key to identify the mutation globally and access it through other\n * helpers like `useMutationState()`. If you don't need to reference the\n * mutation elsewhere, you should ignore this option.\n */\n key?: _MutationKey<NoInfer<TVars>>\n\n /**\n * Runs before the mutation is executed. **It should be placed before `mutation()` for `context` to be inferred**. It\n * can return a value that will be passed to `mutation`, `onSuccess`, `onError` and `onSettled`. If it returns a\n * promise, it will be awaited before running `mutation`.\n *\n * @example\n * ```ts\n * useMutation({\n * // must appear before `mutation` for `{ foo: string }` to be inferred\n * // within `mutation`\n * onMutate() {\n * return { foo: 'bar' }\n * },\n * mutation: (id: number, { foo }) => {\n * console.log(foo) // bar\n * return fetch(`/api/todos/${id}`)\n * },\n * onSuccess(context) {\n * console.log(context.foo) // bar\n * },\n * })\n * ```\n */\n onMutate?: (\n /**\n * The variables passed to the mutation.\n */\n vars: NoInfer<TVars>,\n context: UseMutationGlobalContext,\n ) => _Awaitable<TContext | undefined | void | null>\n\n /**\n * Runs if the mutation is successful.\n */\n onSuccess?: (\n /**\n * The result of the mutation.\n */\n data: NoInfer<TResult>,\n /**\n * The variables passed to the mutation.\n */\n vars: NoInfer<TVars>,\n /**\n * The merged context from `onMutate` and the global context.\n */\n context: UseMutationGlobalContext & _ReduceContext<NoInfer<TContext>>,\n ) => unknown\n\n /**\n * Runs if the mutation encounters an error.\n */\n onError?: (\n /**\n * The error thrown by the mutation.\n */\n error: TError,\n /**\n * The variables passed to the mutation.\n */\n vars: NoInfer<TVars>,\n /**\n * The merged context from `onMutate` and the global context. Properties returned by `onMutate` can be `undefined`\n * if `onMutate` throws.\n */\n context:\n | (Partial<Record<keyof UseMutationGlobalContext, never>> &\n Partial<Record<keyof _ReduceContext<NoInfer<TContext>>, never>>)\n // this is the success case where everything is defined\n // undefined if global onMutate throws\n | (UseMutationGlobalContext & _ReduceContext<NoInfer<TContext>>),\n ) => unknown\n\n /**\n * Runs after the mutation is settled, regardless of the result.\n */\n onSettled?: (\n /**\n * The result of the mutation. `undefined` if the mutation failed.\n */\n data: NoInfer<TResult> | undefined,\n /**\n * The error thrown by the mutation. `undefined` if the mutation was successful.\n */\n error: TError | undefined,\n /**\n * The variables passed to the mutation.\n */\n vars: NoInfer<TVars>,\n /**\n * The merged context from `onMutate` and the global context. Properties returned by `onMutate` can be `undefined`\n * if `onMutate` throws.\n */\n context:\n | (Partial<Record<keyof UseMutationGlobalContext, never>> &\n Partial<Record<keyof _ReduceContext<NoInfer<TContext>>, never>>)\n // this is the success case where everything is defined\n // undefined if global onMutate throws\n | (UseMutationGlobalContext & _ReduceContext<NoInfer<TContext>>),\n ) => unknown\n}\n\nexport const USE_MUTATION_OPTIONS_KEY: InjectionKey<UseMutationOptionsGlobal>\n = process.env.NODE_ENV !== 'production' ? Symbol('useMutationOptions') : Symbol()\n\n/**\n * Injects the global query options.\n * @internal\n */\nexport const useMutationOptions = (): UseMutationOptionsGlobal =>\n inject(USE_MUTATION_OPTIONS_KEY, {})\n","import type { ComputedRef, ShallowRef } from 'vue'\nimport type { AsyncStatus, DataState, DataStateStatus } from './data-state'\nimport type { EntryKey } from './entry-options'\nimport type { ErrorDefault } from './types-extension'\nimport { computed, shallowRef } from 'vue'\nimport { useMutationCache } from './mutation-store'\nimport type { UseMutationEntry } from './mutation-store'\nimport { noop } from './utils'\nimport type { _EmptyObject } from './utils'\nimport type { UseMutationOptions } from './mutation-options'\n\n/**\n * Valid keys for a mutation. Similar to query keys.\n * @see {@link EntryKey}\n * @internal\n */\nexport type _MutationKey<TVars> = EntryKey | ((vars: TVars) => EntryKey)\n\n/**\n * Removes the nullish types from the context type to make `A & TContext` work instead of yield `never`.\n * @internal\n */\nexport type _ReduceContext<TContext> = TContext extends void | null | undefined\n ? _EmptyObject\n : Record<any, any> extends TContext\n ? _EmptyObject\n : TContext\n\n/**\n * Context object returned by a global `onMutate` function that is merged with the context returned by a local\n * `onMutate`.\n * @example\n * ```ts\n * declare module '@pinia/colada' {\n * export interface UseMutationGlobalContext {\n * router: Router // from vue-router\n * }\n * }\n *\n * // add the `router` to the context\n * app.use(MutationPlugin, {\n * onMutate() {\n * return { router }\n * },\n * })\n * ```\n */\nexport interface UseMutationGlobalContext {}\n\n// export const USE_MUTATIONS_DEFAULTS = {} satisfies Partial<UseMutationsOptions>\n\nexport interface UseMutationReturn<TResult, TVars, TError> {\n key?: EntryKey | ((vars: NoInfer<TVars>) => EntryKey)\n\n /**\n * The combined state of the mutation. Contains its data, error, and status. It enables type narrowing based on the {@link UseMutationReturn.status}.\n */\n state: ComputedRef<DataState<TResult, TError>>\n\n /**\n * The status of the mutation.\n * @see {@link DataStateStatus}\n */\n status: ShallowRef<DataStateStatus>\n\n /**\n * Status of the mutation. Becomes `'loading'` while the mutation is being fetched, is `'idle'` otherwise.\n */\n asyncStatus: ShallowRef<AsyncStatus>\n\n /**\n * The result of the mutation. `undefined` if the mutation has not been called yet.\n */\n data: ShallowRef<TResult | undefined>\n\n /**\n * The error of the mutation. `null` if the mutation has not been called yet or if it was successful.\n */\n error: ShallowRef<TError | null>\n\n /**\n * Whether the mutation is currently executing.\n */\n isLoading: ComputedRef<boolean>\n\n /**\n * The variables passed to the mutation. They are initially `undefined` and change every time the mutation is called.\n */\n variables: ShallowRef<TVars | undefined>\n\n /**\n * Calls the mutation and returns a promise with the result.\n *\n * @param vars - parameters to pass to the mutation\n */\n mutateAsync: unknown | void extends TVars\n ? () => Promise<TResult>\n : (vars: TVars) => Promise<TResult>\n\n /**\n * Calls the mutation without returning a promise to avoid unhandled promise rejections.\n *\n * @param args - parameters to pass to the mutation\n */\n mutate: (...args: unknown | void extends TVars ? [] : [vars: TVars]) => void\n\n /**\n * Resets the state of the mutation to its initial state.\n */\n reset: () => void\n}\n\n/**\n * Setups a mutation.\n *\n * @param options - Options to create the mutation\n * @example\n * ```ts\n * const queryCache = useQueryCache()\n * const { mutate, status, error } = useMutation({\n * mutation: (id: number) => fetch(`/api/todos/${id}`),\n * onSuccess() {\n * queryCache.invalidateQueries('todos')\n * },\n * })\n * ```\n */\nexport function useMutation<\n TResult,\n TVars = void,\n TError = ErrorDefault,\n TContext extends Record<any, any> = _EmptyObject,\n>(\n options: UseMutationOptions<TResult, TVars, TError, TContext>,\n): UseMutationReturn<TResult, TVars, TError> {\n const mutationCache = useMutationCache()\n // always create an initial entry with no key (cannot be computed without vars)\n const entry = shallowRef<UseMutationEntry<TResult, TVars, TError, TContext>>(\n mutationCache.ensure(options),\n )\n\n const state = computed(() => entry.value.state.value)\n const status = computed(() => state.value.status)\n const data = computed(() => state.value.data)\n const error = computed(() => state.value.error)\n const asyncStatus = computed(() => entry.value.asyncStatus.value)\n const variables = computed(() => entry.value.vars.value)\n\n async function mutateAsync(vars: TVars): Promise<TResult> {\n // either create a new entry, transform the initial one with the correct keys, or reuse the same if keys are undefined\n return mutationCache.mutate(\n (entry.value = mutationCache.ensure(options, entry.value, vars)),\n vars,\n )\n }\n\n function mutate(vars: NoInfer<TVars>) {\n mutateAsync(vars).catch(noop)\n }\n\n function reset() {\n mutationCache.setEntryState(entry.value, {\n status: 'pending',\n data: undefined,\n error: null,\n })\n entry.value.asyncStatus.value = 'idle'\n }\n\n return {\n state,\n data,\n isLoading: computed(() => asyncStatus.value === 'loading'),\n status,\n variables,\n asyncStatus,\n error,\n // @ts-expect-error: because of the conditional type in UseMutationReturn\n // it would be nice to find a type-only refactor that works\n mutate,\n // @ts-expect-error: same as above\n mutateAsync,\n reset,\n }\n}\n","import { useMutationCache } from './mutation-store'\nimport type { ErrorDefault } from './types-extension'\nimport { useMutation } from './use-mutation'\nimport type { UseMutationReturn } from './use-mutation'\nimport type { UseMutationOptions } from './mutation-options'\nimport type { _EmptyObject } from './utils'\n\n/**\n * Define a mutation with the given options. Similar to `useMutation(options)` but allows you to reuse the mutation in\n * multiple places.\n *\n * @param options - the options to define the mutation\n * @example\n * ```ts\n * const useCreateTodo = defineMutation({\n * mutation: (todoText: string) =>\n * fetch('/api/todos', {\n * method: 'POST',\n * body: JSON.stringify({ text: todoText }),\n * }),\n * })\n * ```\n */\nexport function defineMutation<\n TResult,\n TVars = void,\n TError = ErrorDefault,\n TContext extends Record<any, any> = _EmptyObject,\n>(\n options: UseMutationOptions<TResult, TVars, TError, TContext>,\n): () => UseMutationReturn<TResult, TVars, TError>\n\n/**\n * Define a mutation with a function setup. Allows to return arbitrary values from the mutation function, create\n * contextual refs, rename the returned values, etc.\n *\n * @param setup - a function to setup the mutation\n * @example\n * ```ts\n * const useCreateTodo = defineMutation(() => {\n * const todoText = ref('')\n * const { data, mutate, ...rest } = useMutation({\n * mutation: () =>\n * fetch('/api/todos', {\n * method: 'POST',\n * body: JSON.stringify({ text: todoText.value }),\n * }),\n * })\n * // expose the todoText ref and rename other methods for convenience\n * return { ...rest, createTodo: mutate, todo: data, todoText }\n * })\n * ```\n */\nexport function defineMutation<T>(setup: () => T): () => T\nexport function defineMutation(\n optionsOrSetup: UseMutationOptions | (() => unknown),\n): () => unknown {\n const setupFn\n = typeof optionsOrSetup === 'function' ? optionsOrSetup : () => useMutation(optionsOrSetup)\n return () => {\n // TODO: provide a way to clean them up `mutationCache.clear()`\n const mutationCache = useMutationCache()\n return mutationCache.ensureDefinedMutation(setupFn)\n }\n}\n","import { getCurrentInstance, getCurrentScope, onScopeDispose, toValue } from 'vue'\nimport type { EffectScope } from 'vue'\nimport type { UseQueryOptions } from './query-options'\nimport { useQueryCache } from './query-store'\nimport type { ErrorDefault } from './types-extension'\nimport type { UseQueryReturn } from './use-query'\nimport { useQuery } from './use-query'\nimport type { _RemoveMaybeRef } from './utils'\n\n/**\n * The current effect scope where the function returned by `defineQuery` is being called. This allows `useQuery()` to know if it should be attached to an effect scope or not\n */\nlet currentDefineQueryEffect: undefined | EffectScope\n\n/**\n * Options to define a query with `defineQuery()`. Similar to {@link UseQueryOptions} but disallows reactive values as\n * `defineQuery()` is used outside of an effect scope.\n */\nexport interface DefineQueryOptions<TResult = unknown, TError = ErrorDefault>\n extends _RemoveMaybeRef<UseQueryOptions<TResult, TError>> {}\n\n// NOTE: no setter because it cannot be set outside of defineQuery()\n\n/**\n * Gets the current defineQuery effect scope. This is used internally by `useQuery` to attach the effect to the query\n * entry dependency list.\n * @internal\n */\nexport function getCurrentDefineQueryEffect() {\n return currentDefineQueryEffect\n}\n\n/**\n * Define a query with the given options. Similar to `useQuery(options)` but allows you to reuse the query in multiple\n * places. It only allow static values in options. If you need dynamic values, use the function version.\n *\n * @param options - the options to define the query\n * @example\n * ```ts\n * const useTodoList = defineQuery({\n * key: ['todos'],\n * query: () => fetch('/api/todos', { method: 'GET' }),\n * })\n * ```\n */\nexport function defineQuery<TResult, TError = ErrorDefault>(\n options: DefineQueryOptions<TResult, TError>,\n): () => UseQueryReturn<TResult, TError>\n\n/**\n * Define a query with a setup function. Allows to return arbitrary values from the query function, create contextual\n * refs, rename the returned values, etc. The setup function will be called only once, like stores, and **must be\n * synchronous**.\n *\n * @param setup - a function to setup the query\n * @example\n * ```ts\n * const useFilteredTodos = defineQuery(() => {\n * const todoFilter = ref<'all' | 'finished' | 'unfinished'>('all')\n * const { data, ...rest } = useQuery({\n * key: ['todos', { filter: todoFilter.value }],\n * query: () =>\n * fetch(`/api/todos?filter=${todoFilter.value}`, { method: 'GET' }),\n * })\n * // expose the todoFilter ref and rename data for convenience\n * return { ...rest, todoList: data, todoFilter }\n * })\n * ```\n */\nexport function defineQuery<T>(setup: () => T): () => T\nexport function defineQuery(optionsOrSetup: DefineQueryOptions | (() => unknown)): () => unknown {\n const setupFn\n = typeof optionsOrSetup === 'function' ? optionsOrSetup : () => useQuery(optionsOrSetup)\n\n let hasBeenEnsured: boolean | undefined\n return () => {\n const queryCache = useQueryCache()\n // preserve any current effect to account for nested usage of these functions\n const previousEffect = currentDefineQueryEffect\n const currentScope = getCurrentInstance() || (currentDefineQueryEffect = getCurrentScope())\n\n const [entries, ret, scope] = queryCache.ensureDefinedQuery(setupFn)\n\n // subsequent calls to the composable returned by useQuery will not trigger the `useQuery()`,\n // this ensures the refetchOnMount option is respected\n if (hasBeenEnsured) {\n entries.forEach((entry) => {\n // TODO: should happen in useQuery and defineQuery\n if (entry.options?.refetchOnMount && toValue(entry.options.enabled)) {\n if (toValue(entry.options.refetchOnMount) === 'always') {\n queryCache.fetch(entry)\n } else {\n queryCache.refresh(entry)\n }\n }\n })\n }\n hasBeenEnsured = true\n\n // NOTE: most of the time this should be set, so maybe we should show a dev warning\n // if it's not set instead\n if (currentScope) {\n entries.forEach((entry) => {\n queryCache.track(entry, currentScope)\n if (process.env.NODE_ENV !== 'production') {\n entry.__hmr ??= {}\n entry.__hmr.skip = true\n }\n })\n onScopeDispose(() => {\n // if all entries become inactive, we pause the scope\n // to avoid triggering the effects within useQuery. This immitates the behavior\n // of a component that unmounts\n if (\n entries.every((entry) => {\n queryCache.untrack(entry, currentScope)\n return !entry.active\n })\n ) {\n scope.pause()\n }\n })\n }\n\n // reset the previous effect\n currentDefineQueryEffect = previousEffect\n\n return ret\n }\n}\n","import { defineStore, getActivePinia, skipHydrate } from 'pinia'\nimport {\n customRef,\n effectScope,\n getCurrentInstance,\n getCurrentScope,\n hasInjectionContext,\n markRaw,\n shallowRef,\n toValue,\n} from 'vue'\nimport type { App, ComponentInternalInstance, EffectScope, ShallowRef } from 'vue'\nimport type { AsyncStatus, DataState, DataState_Success, DataStateStatus } from './data-state'\nimport type { EntryKey } from './entry-options'\nimport { useQueryOptions } from './query-options'\nimport type { UseQueryOptions, UseQueryOptionsWithDefaults } from './query-options'\nimport type {\n _UseQueryEntryNodeValueSerialized,\n EntryNodeKey,\n UseQueryEntryNodeSerialized,\n} from './tree-map'\nimport { appendSerializedNodeToTree, TreeMapNode } from './tree-map'\nimport type { ErrorDefault } from './types-extension'\nimport { noop, toCacheKey, toValueWithArgs, warnOnce } from './utils'\n\n/**\n * Allows defining extensions to the query entry that are returned by `useQuery()`.\n */\n\nexport interface UseQueryEntryExtensions<\n TResult,\n /* eslint-disable-next-line unused-imports/no-unused-vars */\n TError,\n /* eslint-disable-next-line unused-imports/no-unused-vars */\n TDataInitial extends TResult | undefined = TResult | undefined,\n> {}\n\n/**\n * NOTE: Entries could be classes but the point of having all functions within the store is to allow plugins to hook\n * into actions.\n */\n\n/**\n * A query entry in the cache.\n */\nexport interface UseQueryEntry<\n TResult = unknown,\n TError = unknown,\n TDataInitial extends TResult | undefined = TResult | undefined,\n> {\n /**\n * The state of the query. Contains the data, error and status.\n */\n state: ShallowRef<DataState<TResult, TError, TDataInitial>>\n\n /**\n * A placeholder `data` that is initially shown while the query is loading for the first time. This will also show the\n * `status` as `success` until the query finishes loading (no matter the outcome).\n */\n placeholderData: TDataInitial | TResult | null | undefined\n\n /**\n * The status of the query.\n */\n asyncStatus: ShallowRef<AsyncStatus>\n\n /**\n * When was this data set in the entry for the last time in ms. It can also\n * be 0 if the entry has been invalidated.\n */\n when: number\n\n /**\n * The serialized key associated with this query entry.\n */\n key: EntryNodeKey[]\n\n /**\n * Components and effects scopes that use this query entry.\n */\n deps: Set<EffectScope | ComponentInternalInstance>\n\n /**\n * Timeout id that scheduled a garbage collection. It is set here to clear it when the entry is used by a different component\n */\n gcTimeout: ReturnType<typeof setTimeout> | undefined\n\n /**\n * The current pending request.\n */\n pending: null | {\n /**\n * The abort controller used to cancel the request and which `signal` is passed to the query function.\n */\n abortController: AbortController\n /**\n * The promise created by `queryCache.fetch` that is currently pending.\n */\