@pinia/colada
Version:
The smart data fetching layer for Vue.js
1 lines • 173 kB
Source Map (JSON)
{"version":3,"file":"index.mjs","names":[],"sources":["../src/entry-keys.ts","../src/utils.ts","../src/query-options.ts","../src/query-store.ts","../src/define-query.ts","../src/use-query.ts","../src/define-query-options.ts","../src/use-query-state.ts","../src/infinite-query.ts","../src/define-infinite-query-options.ts","../src/mutation-options.ts","../src/mutation-store.ts","../src/use-mutation.ts","../src/define-mutation.ts","../src/define-mutation-options.ts","../src/pinia-colada.ts","../src/plugins/query-hooks.ts","../src/plugins/no-gc-ssr.ts"],"sourcesContent":["import type { ErrorDefault } from './types-extension'\n\nexport function toCacheKey(key: undefined): undefined\nexport function toCacheKey(key: EntryKey): string\nexport function toCacheKey(key: EntryKey | undefined): string | undefined\n/**\n * Serializes the given {@link EntryKey | key} (query or mutation key) to a string.\n *\n * @param key - The key to serialize.\n *\n * @see {@link EntryKey}\n */\nexport function toCacheKey(key: EntryKey | undefined): string | undefined {\n return (\n key &&\n JSON.stringify(key, (_, val) =>\n !val || typeof val !== 'object' || Array.isArray(val)\n ? val\n : Object.keys(val)\n .sort()\n .reduce((result, key) => {\n result[key] = val[key]\n return result\n }, {} as any),\n )\n )\n}\n\n/**\n * Checks whether `subsetKey` is a subset of `fullsetKey` by matching partially objects and arrays.\n *\n * @param subsetKey - subset key to check\n * @param fullsetKey - fullset key to check against\n */\nexport function isSubsetOf(subsetKey: EntryKey, fullsetKey: EntryKey): boolean {\n return subsetKey === fullsetKey\n ? true\n : typeof subsetKey !== typeof fullsetKey\n ? false\n : subsetKey && fullsetKey && typeof subsetKey === 'object' && typeof fullsetKey === 'object'\n ? Object.keys(subsetKey).every((key) =>\n isSubsetOf(\n // NOTE: this or making them `any` in the function signature\n subsetKey[key as unknown as number] as EntryKey,\n fullsetKey[key as unknown as number] as EntryKey,\n ),\n )\n : false\n}\n\n/**\n * Used for keys\n *\n * @internal\n */\nexport type JSONPrimitive = string | number | boolean | null\n\n/**\n * Used for keys\n *\n * @internal\n */\nexport type JSONValue = JSONPrimitive | JSONObject | JSONArray\n\n/**\n * Used for keys. Interface to avoid deep recursion.\n *\n * @internal\n */\nexport type JSONObject = object\n// NOTE: this doesn't allow interfaces to be assigned to it due to its index signature\n// export interface JSONObject {\n// readonly [key: string]: JSONValue | undefined\n// }\n\n/**\n * Used for keys. Interface to avoid deep recursion.\n *\n * @internal\n */\nexport interface JSONArray extends Array<JSONValue> {}\n\n/**\n * Key used to identify a query or a mutation. Must be a JSON serializable\n * value. Type is unknwon to avoid annoying type errors like recursive types\n * and not being able to assign an interface to it due to its index signature.\n */\nexport type EntryKey = readonly JSONValue[]\n\n/**\n * Internal symbol used to tag the data type of the entry key.\n *\n * @internal\n */\nexport const ENTRY_DATA_TAG = Symbol('Pinia Colada data tag')\n\n/**\n * Internal symbol used to tag the error type of the entry key.\n *\n * @internal\n */\nexport const ENTRY_ERROR_TAG = Symbol('Pinia Colada error tag')\n\n/**\n * Internal symbol used to tag the data initial type of the entry key.\n *\n * @internal\n */\nexport const ENTRY_DATA_INITIAL_TAG = Symbol('Pinia Colada data initial tag')\n\n/**\n * Same as {@link EntryKey} but with a data tag that allows inference of the data type.\n * Used by `defineQueryOptions()`.\n */\nexport type EntryKeyTagged<\n TData,\n TError = ErrorDefault,\n TDataInitial extends TData | undefined = undefined,\n> = EntryKey & {\n [ENTRY_DATA_TAG]: TData\n [ENTRY_ERROR_TAG]: TError\n [ENTRY_DATA_INITIAL_TAG]: TDataInitial\n}\n\n/**\n * Finds entries that partially match the given key. If no key is provided, all\n * entries are returned.\n *\n * @param map - The map to search in.\n * @param partialKey - The key to match against. If not provided, all entries are yield.\n *\n * @internal\n */\nexport function* find<T extends { key: EntryKey | undefined }>(\n map: Map<string | number, T>,\n partialKey?: EntryKey,\n) {\n for (const entry of map.values()) {\n if (!partialKey || (entry.key && isSubsetOf(partialKey, entry.key))) {\n yield entry\n }\n }\n}\n\n/**\n * Empty starting object for extensions that allows to detect when to update.\n *\n * @internal\n */\nexport const START_EXT = Object.freeze(\n process.env.NODE_ENV === 'production'\n ? {}\n : {\n message:\n 'This is a placeholder object for Pinia Colada extensions, it should never be used directly, but only checked for identity. This is here to simplify debugging during dev.',\n },\n)\n","import { computed, getCurrentScope, onScopeDispose } from 'vue'\nimport type { MaybeRefOrGetter, Ref, ShallowRef } from 'vue'\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 *\n * @internal\n */\nexport type _MaybeArray<T> = T | T[]\n\n/**\n * Checks if a type is exactly `any`.\n *\n * @internal\n */\nexport type IsAny<T> = 0 extends 1 & T ? true : false\n\n/**\n * Checks if a type is exactly `unknown`. This is useful to determine if a type is\n *\n * @internal\n */\nexport type IsUnknown<T> = IsAny<T> extends true ? false : unknown extends T ? true : false\n\n/**\n * Type that represents a value that can be a function or a single value. Used\n * for `defineQuery()` and `defineMutation()`.\n *\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 *\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 *\n * @internal\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 *\n * @internal\n */\nexport type _Awaitable<T> = T | Promise<T>\n\n/**\n * Flattens an object type for readability.\n *\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\n/**\n * Valid primitives that can be stringified with `JSON.stringify`.\n *\n * @internal\n */\nexport type _JSONPrimitive = string | number | boolean | null | undefined\n\n/**\n * Utility type to represent a flat object that can be stringified with\n * `JSON.stringify` no matter the order of keys.\n *\n * @internal\n */\nexport interface _ObjectFlat {\n [key: string]: _JSONPrimitive | Array<_JSONPrimitive>\n}\n\n/**\n * Valid values that can be stringified with `JSON.stringify`.\n *\n * @internal\n */\nexport type _JSONValue = _JSONPrimitive | _JSONValue[] | { [key: string]: _JSONValue }\n\n/**\n * Stringifies an object no matter the order of keys. This is used to create a\n * hash for a given object. It only works with flat objects. It can contain\n * 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 * 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 * 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 *\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 * @internal\n */\nexport type _IsMaybeRefOrGetter<T> = [T] extends [MaybeRefOrGetter<infer U>]\n ? MaybeRefOrGetter<U> extends T // must match the wrapper, not just any function\n ? true\n : false\n : false\n\n/**\n * @internal\n */\nexport type _UnwrapMaybeRefOrGetter<T> = T extends MaybeRefOrGetter<infer U> ? U : T\n\n/**\n * Removes the `MaybeRefOrGetter` wrapper from all fields of an object,\n * except for fields specified in the `Ignore` type.\n * @internal\n */\nexport type _RemoveMaybeRef<T, Ignore extends keyof T = never> = {\n [K in keyof T]: K extends Ignore\n ? T[K]\n : _IsMaybeRefOrGetter<NonNullable<T[K]>> extends true\n ? _UnwrapMaybeRefOrGetter<T[K]>\n : T[K]\n}\n","import { inject } from 'vue'\nimport type { InjectionKey, MaybeRefOrGetter } from 'vue'\nimport type { EntryKey } from './entry-keys'\nimport type { ErrorDefault, QueryMeta } from './types-extension'\nimport type { UseQueryEntry } from './query-store'\n\n/**\n * Possible values for `refetchOnMount`, `refetchOnWindowFocus`, and `refetchOnReconnect`.\n * `true` refetches if data is stale (calles `refresh()`), `false` never refetches, `'always'` always refetches.\n */\nexport type RefetchOnControl = boolean | 'always'\n\n/**\n * Options for queries that can be globally overridden.\n */\nexport interface UseQueryOptionsGlobal {\n /**\n * Whether the query should be enabled or not. If `false`, the query will not\n * be executed until `refetch()` or `refresh()` is called. If it becomes\n * `true`, the query will be refreshed.\n */\n enabled?: MaybeRefOrGetter<boolean>\n\n /**\n * Time in ms after which the data is considered stale and will be refreshed\n * on next read.\n *\n * @default 5000 (5 seconds)\n */\n staleTime?: number\n\n /**\n * Time in ms after which, once the data is no longer being used, it will be\n * garbage collected to free resources. Set to `false` to disable garbage\n * collection.\n *\n * @default 300_000 (5 minutes)\n */\n gcTime?: number | false\n\n /**\n * Whether to refetch the query when the component is mounted.\n * @default true\n */\n refetchOnMount?: MaybeRefOrGetter<RefetchOnControl>\n\n /**\n * Whether to refetch the query when the window regains focus.\n * @default true\n */\n refetchOnWindowFocus?: MaybeRefOrGetter<RefetchOnControl>\n\n /**\n * Whether to refetch the query when the network reconnects.\n * @default true\n */\n refetchOnReconnect?: MaybeRefOrGetter<RefetchOnControl>\n\n /**\n * A placeholder data that is initially shown while the query is loading for\n * the first time. This will also show the `status` as `success` until the\n * query finishes loading (no matter the outcome of the query). Note: unlike\n * with `initialData`, the placeholder does not change the cache state.\n */\n placeholderData?: (previousData: unknown, previousEntry: UseQueryEntry | undefined) => any // any allows us to not worry about the types when merging options\n\n /**\n * Whether to catch errors during SSR (onServerPrefetch) when the query fails.\n * @default false\n */\n ssrCatchError?: boolean\n}\n\n/**\n * Context object passed to the `query` function of `useQuery()`.\n * @see {@link UseQueryOptions}\n */\nexport interface UseQueryFnContext<\n TData = unknown,\n TError = unknown,\n // allows for UseQueryEntry to have unknown everywhere (generic version)\n TDataInitial extends TData | undefined = unknown extends TData ? unknown : undefined,\n> {\n /**\n * `AbortSignal` instance attached to the query call. If the call becomes\n * outdated (e.g. due to a new call with the same key), the signal will be\n * aborted.\n */\n signal: AbortSignal\n\n /**\n * The query entry associated with the current query.\n */\n entry: UseQueryEntry<TData, TError, TDataInitial>\n}\n\n/**\n * Type-only symbol to keep the type\n *\n * @internal\n */\nexport declare const tErrorSymbol: unique symbol\n\n/**\n * Helper function type to keep callback arguments bivariant in object properties.\n *\n * @internal\n */\ntype BivariantCallback<TArgs extends readonly unknown[], TResult> = {\n bivarianceHack(...args: TArgs): TResult\n}['bivarianceHack']\n\n/**\n * Options for `useQuery()`. Can be extended by plugins.\n *\n * @example\n * ```ts\n * // use-query-plugin.d.ts\n * export {} // needed\n * declare module '@pinia/colada' {\n * interface UseQueryOptions {\n * // Whether to refresh the data when the component is mounted.\n * refreshOnMount?: boolean\n * }\n * }\n * ```\n */\nexport interface UseQueryOptions<\n TData = unknown,\n // eslint-disable-next-line unused-imports/no-unused-vars\n TError = ErrorDefault,\n TDataInitial extends TData | undefined = undefined,\n> extends Pick<\n UseQueryOptionsGlobal,\n | 'gcTime'\n | 'enabled'\n | 'refetchOnMount'\n | 'refetchOnReconnect'\n | 'refetchOnWindowFocus'\n | 'staleTime'\n | 'ssrCatchError'\n> {\n /**\n * The key used to identify the query. Array of primitives **without**\n * reactive values or a reactive array or getter. It should be treaded as an\n * array of dependencies of your queries, e.g. if you use the\n * `route.params.id` property, it should also be part of the key:\n *\n * ```ts\n * import { useRoute } from 'vue-router'\n * import { useQuery } from '@pinia/colada'\n *\n * const route = useRoute()\n * const { data } = useQuery({\n * // pass a getter function (or computed, ref, etc.) to ensure reactivity\n * key: () => ['user', route.params.id],\n * query: () => fetchUser(route.params.id),\n * })\n * ```\n */\n key: MaybeRefOrGetter<EntryKey>\n\n /**\n * The function that will be called to fetch the data. It **must** be async.\n */\n query(\n // NOTE: we can't use TData in the argument because it's used from the return type\n // https://github.com/microsoft/TypeScript/issues/49618\n // https://github.com/microsoft/TypeScript/issues/47599\n context: UseQueryFnContext<unknown, TError, TDataInitial>,\n ): Promise<TData>\n\n /**\n * The data which is initially set to the query while the query is loading\n * for the first time. Note: unlike with {@link placeholderData}, setting the\n * initial data changes the state of the query (it will be set to `success`).\n *\n * @see {@link placeholderData}\n */\n initialData?: () => TDataInitial\n\n /**\n * The timestamp (in milliseconds) when the initial data was last updated.\n * This determines the staleness of the {@link initialData}. If not provided,\n * defaults to `Date.now()` when initial data is set.\n *\n * @default Date.now() when {@link initialData} is used\n *\n * @example\n * ```ts\n * // Using a static timestamp\n * useQuery({\n * key: ['user'],\n * query: () => fetchUser(),\n * initialData: () => cachedUser,\n * initialDataUpdatedAt: 1234567890000\n * })\n *\n * // Using a function\n * useQuery({\n * key: ['user'],\n * query: () => fetchUser(),\n * initialData: () => cachedUser,\n * initialDataUpdatedAt: () => Number(localStorage.getItem('userTimestamp'))\n * })\n * ```\n */\n initialDataUpdatedAt?: number | (() => number)\n\n /**\n * A placeholder data that is initially shown while the query is loading for\n * the first time. This will also show the `status` as `success` until the\n * query finishes loading (no matter the outcome of the query). Note: unlike\n * with {@link initialData}, the placeholder does not change the cache state.\n *\n * @see {@link initialData}\n */\n placeholderData?:\n | NoInfer<TDataInitial>\n | NoInfer<TData>\n // NOTE: the generic here allows to make UseQueryOptions<T> assignable to UseQueryOptions<unknown>\n // https://www.typescriptlang.org/play/?#code/JYOwLgpgTgZghgYwgAgPIAczAPYgM4A8AKsgLzICuIA1iNgO4gB8yA3gFDLICOAXMgAoAlGRYAFKNgC2wPBGJNOydAH5eSrgHpNyABZwAbqADmyMLpRwoxilIjhkAIygQ41PMmBgNyAD6CBdBcjbAo8ABE4MDh+ADlsAEkQGGgFP0oQABMIGFAITJFSFniklKgFIR9tZCJdWWQDaDwcEGR6bCh3Kp1-AWISCAAPSCyPIiZA4JwwyOj+IhJ-KmzckHzCliJKgF92dlBIWEQUAFFwKABPYjIM2gZmNiVsTBa8fgwsXEJx9JAKABt-uxduwYFQEJ9WggAPr2MCXBQCZ6Qt5oF5fNL+P6AoT8M7wq4-DhcFxgChQVqsZDI17IXa7BBfMDIDzkNb0ZAAZQgYAI+MuE0qeAAdHBMpkBDC4ZcBMSuDx+HA8BcQAhBBtkAAWABMABofOh+AIQBrWgBCNkA-7IFTIVoAKmQ2uQ-E1wKElSAA\n | BivariantCallback<\n [\n previousData: TData | undefined,\n previousEntry: UseQueryEntry<TData, TError, TDataInitial> | undefined,\n ],\n NoInfer<TDataInitial> | NoInfer<TData> | undefined\n >\n\n /**\n * Meta information associated with the query. Can be a raw object, a function\n * returning the meta object, or a ref containing the meta object.\n * The meta is resolved when the entry is **created** and stored in\n * `entry.meta`.\n *\n * **Note**: Meta is serialized during SSR, so it must be serializable (no functions,\n * class instances, or circular references). You can also completely ignore\n * it during SSR with a ternary: `meta: import.meta.ev.SSR ? {} : actualMeta`\n *\n * @example\n * ```ts\n * // SSR-safe: simple serializable data\n * useQuery({\n * key: ['user', id],\n * query: () => fetchUser(id),\n * meta: { errorMessage: true }\n * })\n *\n * // Using a function to compute meta\n * useQuery({\n * key: ['user', id],\n * query: () => fetchUser(id),\n * meta: () => ({ timestamp: Date.now() })\n * })\n *\n * // Skipping meta during SSR\n * useQuery({\n * key: ['user', id],\n * query: () => fetchUser(id),\n * meta: {\n * onError: import.meta.env.SSR ? undefined : ((err) => console.log('error'))\n * }\n * })\n * ```\n */\n meta?: MaybeRefOrGetter<QueryMeta>\n\n /**\n * Ghost property to ensure TError generic parameter is included in the\n * interface structure. This property should never be used directly and is\n * only for type system correctness. it could be removed in the future if the\n * type can be inferred in any other way.\n *\n * @internal\n */\n readonly [tErrorSymbol]?: TError\n}\n\n/**\n * Default options for `useQuery()`. Modifying this object will affect all the queries that don't override these\n */\nexport const USE_QUERY_DEFAULTS = {\n staleTime: 1000 * 5, // 5 seconds\n gcTime: (1000 * 60 * 5) as NonNullable<UseQueryOptions['gcTime']>, // 5 minutes\n // avoid type narrowing to `true`\n refetchOnWindowFocus: true as NonNullable<UseQueryOptions['refetchOnWindowFocus']>,\n refetchOnReconnect: true as NonNullable<UseQueryOptions['refetchOnReconnect']>,\n refetchOnMount: true as NonNullable<UseQueryOptions['refetchOnMount']>,\n enabled: true as MaybeRefOrGetter<boolean>,\n} satisfies UseQueryOptionsGlobal\n\nexport type UseQueryOptionsWithDefaults<\n TData = unknown,\n TError = ErrorDefault,\n TDataInitial extends TData | undefined = undefined,\n> = UseQueryOptions<TData, TError, TDataInitial> & typeof USE_QUERY_DEFAULTS\n\n/**\n * Global default options for `useQuery()`.\n * @internal\n */\nexport type UseQueryOptionsGlobalDefaults = Pick<\n UseQueryOptionsGlobal,\n | 'gcTime'\n | 'enabled'\n | 'refetchOnMount'\n | 'refetchOnReconnect'\n | 'refetchOnWindowFocus'\n | 'staleTime'\n | 'ssrCatchError'\n> &\n typeof USE_QUERY_DEFAULTS\n\nexport const USE_QUERY_OPTIONS_KEY: InjectionKey<UseQueryOptionsGlobalDefaults> =\n process.env.NODE_ENV !== 'production' ? Symbol('useQueryOptions') : Symbol()\n\n/**\n * Injects the global query options.\n *\n * @internal\n */\nexport const useQueryOptions = (): UseQueryOptionsGlobalDefaults =>\n inject(USE_QUERY_OPTIONS_KEY, USE_QUERY_DEFAULTS)\n","import { defineStore, getActivePinia, skipHydrate } from 'pinia'\nimport {\n effectScope,\n getCurrentInstance,\n getCurrentScope,\n hasInjectionContext,\n markRaw,\n shallowRef,\n toValue,\n watch,\n triggerRef,\n} from 'vue'\nimport type { App, ComponentInternalInstance, EffectScope, ShallowRef } from 'vue'\nimport type { AsyncStatus, DataState } from './data-state'\nimport type { EntryKeyTagged, EntryKey } from './entry-keys'\nimport { useQueryOptions } from './query-options'\nimport type { UseQueryOptions, UseQueryOptionsWithDefaults } from './query-options'\nimport type { EntryFilter } from './entry-filter'\nimport { find, START_EXT, toCacheKey } from './entry-keys'\nimport type { ErrorDefault, QueryMeta } from './types-extension'\nimport { 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 TData,\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 TData | undefined = 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 TData = unknown,\n TError = unknown,\n // allows for UseQueryEntry to have unknown everywhere (generic version)\n TDataInitial extends TData | undefined = unknown extends TData ? unknown : undefined,\n> {\n /**\n * The state of the query. Contains the data, error and status.\n */\n state: ShallowRef<DataState<TData, 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 | TData | 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: EntryKey\n\n /**\n * Seriaized version of the key. Used to retrieve the entry from the cache.\n */\n keyHash: string\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 */\n refreshCall: Promise<DataState<TData, TError, TDataInitial>>\n /**\n * When was this `pending` object created.\n */\n when: number\n }\n\n /**\n * Options used to create the query. They can be `null` during hydration but are needed for fetching. This is why\n * `store.ensure()` sets this property. Note these options might be shared by multiple query entries when the key is\n * dynamic and that's why some methods like {@link fetch} receive the options as an argument.\n */\n options: UseQueryOptionsWithDefaults<TData, TError, TDataInitial> | null\n\n /**\n * Whether the data is stale or not, requires `options.staleTime` to be set.\n */\n readonly stale: boolean\n\n /**\n * Whether the query is currently being used by a Component or EffectScope (e.g. a store).\n */\n readonly active: boolean\n\n /**\n * Resolved meta information for this query. This is the computed value\n * from options.meta (function/ref resolved to raw object).\n */\n meta: QueryMeta\n\n /**\n * Extensions to the query entry added by plugins. You should only add\n * properties to the object, not replace it.\n */\n readonly ext: UseQueryEntryExtensions<TData, TError, TDataInitial>\n\n /**\n * Internal property used in development to detect when a component with an\n * active query was hot-updated and invalidate the entry. For each component\n * type (keyed by `__hmrId`) we remember the identity of its `setup` and\n * `render` functions; a real HMR reload replaces these references while a\n * plain remount of the same component keeps them identical. Keying by\n * `__hmrId` is required because multiple distinct component types can share\n * the same query key — without it, alternating mounts would ping-pong a\n * single snapshot and falsely invalidate the shared entry.\n *\n * @internal\n */\n __hmr?: {\n /**\n * Identity of the `setup` and `render` functions last seen for each\n * component type (keyed by `__hmrId`) that uses this entry.\n */\n components: Map<string, { setup: unknown; render: unknown }>\n }\n}\n\n/**\n * Keep track of the entry being defined so we can add the queries in ensure\n * this allows us to refresh the entry when a defined query is used again\n * and refetchOnMount is true\n *\n * @internal\n */\nexport let currentDefineQueryEntry: DefineQueryEntry | undefined | null\n\n/**\n * Returns whether the entry is using a placeholder data.\n *\n * @template TDataInitial - Initial data type\n * @param entry - entry to check\n */\nexport function isEntryUsingPlaceholderData<TDataInitial>(\n entry: UseQueryEntry<unknown, unknown, TDataInitial> | undefined | null,\n): entry is UseQueryEntry<unknown, unknown, TDataInitial> & { placeholderData: TDataInitial } {\n return entry?.placeholderData != null && entry.state.value.status === 'pending'\n}\n\n/**\n * Filter object to get entries from the query cache.\n *\n * @see {@link QueryCache.getEntries}\n * @see {@link QueryCache.cancelQueries}\n * @see {@link QueryCache#invalidateQueries}\n */\nexport type UseQueryEntryFilter = EntryFilter<UseQueryEntry>\n\n/**\n * UseQueryEntry method to serialize the entry to JSON.\n *\n * @param entry - entry to serialize\n * @returns Serialized version of the entry\n */\nexport const queryEntry_toJSON: <TData, TError>(\n entry: UseQueryEntry<TData, TError>,\n) => UseQueryEntryNodeValueSerializd<TData, TError> = ({ state: { value }, when, meta }) => [\n value.data,\n value.error,\n // because of time zones, we create a relative time\n when ? Date.now() - when : -1,\n meta,\n]\n// TODO: errors are not serializable by default. We should provide a way to serialize custom errors and, by default provide one that serializes the name and message\n\n/**\n * UseQueryEntry method to serialize the entry to a string.\n *\n * @internal\n * @param entry - entry to serialize\n * @returns Stringified version of the entry\n */\nexport const queryEntry_toString: <TData, TError>(entry: UseQueryEntry<TData, TError>) => string = (\n entry,\n) => String(queryEntry_toJSON(entry))\n\n/**\n * The id of the store used for queries.\n * @internal\n */\nexport const QUERY_STORE_ID = '_pc_query'\n\n/**\n * A query entry that is defined with {@link defineQuery}.\n * @internal\n */\ntype DefineQueryEntry = [\n lastEnsuredEntries: UseQueryEntry[],\n returnValue: unknown,\n effect: EffectScope,\n paused: ShallowRef<boolean>,\n]\n\n/**\n * Composable to get the cache of the queries. As any other composable, it can\n * be used inside the `setup` function of a component, within another\n * composable, or in injectable contexts like stores and navigation guards.\n */\nexport const useQueryCache = /* @__PURE__ */ defineStore(QUERY_STORE_ID, ({ 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 const cachesRaw = new Map<string, UseQueryEntry<unknown, unknown, unknown>>()\n const caches = skipHydrate(shallowRef(cachesRaw))\n\n if (process.env.NODE_ENV !== 'production') {\n watch(\n () => caches.value !== cachesRaw,\n (isDifferent) => {\n if (isDifferent) {\n console.error(\n `[@pinia/colada] The query cache cannot be directly set, it must be modified only. This will fail on production`,\n )\n }\n },\n )\n }\n\n // this version of the cache cannot be hydrated because it would miss all of the actions\n // and plugins won't be able to hook into entry creation and fetching\n // this allows use to attach reactive effects to the scope later on\n const scope = getCurrentScope()!\n const app: App<unknown> =\n // @ts-expect-error: internal\n getActivePinia()!._a\n\n if (process.env.NODE_ENV !== 'production') {\n if (!hasInjectionContext()) {\n warnOnce(\n `useQueryCache() was called outside of an injection context (component setup, store, navigation guard) You will get a warning about \"inject\" being used incorrectly from Vue. Make sure to use it only in allowed places.\\n` +\n `See https://vuejs.org/guide/reusability/composables.html#usage-restrictions`,\n )\n }\n }\n\n const optionDefaults = useQueryOptions()\n\n /**\n * Creates a new query entry in the cache. Shouldn't be called directly.\n *\n * @param key - Serialized key of the query\n * @param [options] - options attached to the query\n * @param [initialData] - initial data of the query if any\n * @param [error] - initial error of the query if any\n * @param [when] - relative when was the data or error fetched (will be substracted to Date.now())\n * @param [meta] - resolved meta information for the query\n */\n const create = action(\n <TData, TError, TDataInitial extends TData | undefined>(\n key: EntryKey,\n options: UseQueryOptionsWithDefaults<TData, TError, TDataInitial> | null = null,\n initialData?: TDataInitial,\n error: TError | null = null,\n when: number = 0,\n meta: QueryMeta = {},\n // when: number = initialData === undefined ? 0 : Date.now(),\n ): UseQueryEntry<TData, TError, TDataInitial> =>\n scope.run(() => {\n const state = shallowRef<DataState<TData, TError, TDataInitial>>(\n // @ts-expect-error: to make the code shorter we are using one declaration instead of multiple ternaries\n {\n // NOTE: we could move the `initialData` parameter before `options` and make it required\n // but that would force `create` call in `setQueryData` to pass an extra `undefined` argument\n data: initialData as TDataInitial,\n error,\n status: error ? 'error' : initialData !== undefined ? 'success' : 'pending',\n },\n )\n const asyncStatus = shallowRef<AsyncStatus>('idle')\n // we markRaw to avoid unnecessary vue traversal\n return markRaw<UseQueryEntry<TData, TError, TDataInitial>>({\n key,\n keyHash: toCacheKey(key),\n state,\n placeholderData: null,\n when: initialData === undefined ? 0 : Date.now() - when,\n asyncStatus,\n pending: null,\n // this set can contain components and effects and worsen the performance\n // and create weird warnings\n deps: markRaw(new Set()),\n gcTimeout: undefined,\n // eslint-disable-next-line ts/ban-ts-comment\n // @ts-ignore: some plugins are adding properties to the entry type\n ext: START_EXT,\n options,\n meta,\n get stale() {\n return !this.options || !this.when || Date.now() >= this.when + this.options.staleTime\n },\n get active() {\n return this.deps.size > 0\n },\n } satisfies UseQueryEntry<TData, TError, TDataInitial>)\n })!,\n )\n\n const defineQueryMap = new WeakMap<() => unknown, DefineQueryEntry>()\n\n /**\n * Ensures a query created with {@link defineQuery} 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 ensureDefinedQuery = action(<T>(fn: () => T) => {\n let defineQueryEntry = defineQueryMap.get(fn)\n if (!defineQueryEntry) {\n // create the entry first\n currentDefineQueryEntry = defineQueryEntry = scope.run(() => [\n [],\n null,\n effectScope(),\n shallowRef(false),\n ])!\n\n // then run it so it can add the queries to the entry\n // we use the app context for injections and the scope for effects\n defineQueryEntry[1] = app.runWithContext(() => defineQueryEntry![2].run(fn)!)\n currentDefineQueryEntry = null\n defineQueryMap.set(fn, defineQueryEntry)\n } else {\n // ensure the scope is active so effects computing inside `useQuery()` run (e.g. the entry computed)\n defineQueryEntry[2].resume()\n defineQueryEntry[3].value = false\n // if the entry already exists, we know the queries inside\n // we should consider as if they are activated again\n defineQueryEntry[0] = defineQueryEntry[0].map((oldEntry) =>\n // the entries' key might have changed (e.g. Nuxt navigation)\n // so we need to ensure them again\n oldEntry.options ? ensure(oldEntry.options, oldEntry) : oldEntry,\n )\n }\n\n return defineQueryEntry\n })\n\n /**\n * Tracks an effect or component that uses a query.\n *\n * @param entry - the entry of the query\n * @param effect - the effect or component to untrack\n *\n * @see {@link untrack}\n */\n function track(\n entry: UseQueryEntry,\n effect: EffectScope | ComponentInternalInstance | null | undefined,\n ) {\n if (!effect) return\n entry.deps.add(effect)\n // clearTimeout ignores anything that isn't a timerId\n clearTimeout(entry.gcTimeout)\n entry.gcTimeout = undefined\n triggerRef(caches)\n }\n\n /**\n * Untracks an effect or component that uses a query.\n *\n * @param entry - the entry of the query\n * @param effect - the effect or component to untrack\n *\n * @see {@link track}\n */\n function untrack(\n entry: UseQueryEntry,\n effect: EffectScope | ComponentInternalInstance | undefined | null,\n ) {\n // avoid clearing an existing timeout\n if (!effect || !entry.deps.has(effect)) return\n\n entry.deps.delete(effect)\n triggerRef(caches)\n\n scheduleGarbageCollection(entry)\n }\n\n function scheduleGarbageCollection(entry: UseQueryEntry) {\n // schedule a garbage collection if the entry is not active\n // and we know its gcTime value\n if (entry.deps.size > 0 || !entry.options) return\n clearTimeout(entry.gcTimeout)\n // only abort if the query hasn't settled yet because entry.pending might\n // still be set (cleared in .finally()) but the state is already\n // success/error\n if (entry.state.value.status === 'pending') {\n entry.pending?.abortController.abort()\n }\n // avoid setting a timeout with false, Infinity or NaN\n if ((Number.isFinite as (val: unknown) => val is number)(entry.options.gcTime)) {\n entry.gcTimeout = setTimeout(() => {\n remove(entry)\n }, entry.options.gcTime)\n }\n }\n\n /**\n * Invalidates and cancel matched queries, and then refetches (in parallel)\n * all active ones. If you need to further control which queries are\n * invalidated, canceled, and/or refetched, you can use the filters, you\n * can direcly call {@link invalidate} on {@link getEntries}:\n *\n * ```ts\n * // instead of doing\n * await queryCache.invalidateQueries(filters)\n * await Promise.all(queryCache.getEntries(filters).map(entry => {\n * queryCache.invalidate(entry)\n * // this is the default behavior of invalidateQueries\n * // return entry.active && queryCache.fetch(entry)\n * // here to refetch everything, even non active queries\n * return queryCache.fetch(entry)\n * })\n * ```\n *\n * @param filters - filters to apply to the entries\n * @param refetchActive - whether to refetch active queries or not. Set\n * to `'all'` to refetch all queries\n *\n * @see {@link invalidate}\n * @see {@link cancel}\n */\n const invalidateQueries = action(\n (filters?: UseQueryEntryFilter, refetchActive: boolean | 'all' = true): Promise<unknown> => {\n return Promise.all(\n getEntries(filters).map((entry) => {\n invalidate(entry)\n return (\n (refetchActive === 'all' || (entry.active && refetchActive)) &&\n toValue(entry.options?.enabled) &&\n fetch(entry)\n )\n }),\n )\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: UseQueryEntryFilter = {}): UseQueryEntry[] => {\n // TODO: Iterator.from(node) in 2028 once widely available? or maybe not worth it\n return (\n filters.exact\n ? filters.key\n ? [caches.value.get(toCacheKey(filters.key))].filter((v) => !!v)\n : [] // exact with no key can't match anything\n : [...find(caches.value, filters.key)]\n ).filter(\n (entry) =>\n (filters.stale == null || entry.stale === filters.stale) &&\n (filters.active == null || entry.active === filters.active) &&\n (!filters.status || entry.state.value.status === filters.status) &&\n (!filters.predicate || filters.predicate(entry)),\n )\n })\n\n /**\n * Ensures a query entry is present in the cache. If it's not, it creates a\n * new one. The resulting entry is required to call other methods like\n * {@link fetch}, {@link refresh}, or {@link invalidate}.\n *\n * @param opts - options to create the query\n * @param previousEntry - the previous entry that was associated with the same options\n */\n const ensure = action(\n <TData = unknown, TError = ErrorDefault, TDataInitial extends TData | undefined = undefined>(\n opts: UseQueryOptions<TData, TError, TDataInitial>,\n previousEntry?: UseQueryEntry<TData, TError, TDataInitial>,\n ): UseQueryEntry<TData, TError, TDataInitial> => {\n // NOTE: in the code we always pass the options with the defaults but it's convenient\n // to allow ensure be called with just the user options\n const options: UseQueryOptionsWithDefaults<TData, TError, TDataInitial> = {\n ...optionDefaults,\n ...opts,\n }\n const key = toValue(options.key)\n const keyHash = toCacheKey(key)\n\n if (process.env.NODE_ENV !== 'production' && keyHash === '[]') {\n throw new Error(\n `useQuery() was called with an empty array as the key. It must have at least one element.`,\n )\n }\n\n // do not reinitialize the entry\n // because of the immediate watcher in useQuery, the `ensure()` action is called twice on mount\n // we return early to avoid pushing to currentDefineQueryEntry\n if (previousEntry && keyHash === previousEntry.keyHash) {\n // with defineQueryOptions fn syntax, we need to update the options\n previousEntry.options = options\n return previousEntry\n }\n\n // Since ensure() is called within a computed, we cannot let Vue track cache, so we use the raw version instead\n let entry = cachesRaw.get(keyHash) as UseQueryEntry<TData, TError, TDataInitial> | undefined\n // ensure the state\n if (!entry) {\n // Resolve the meta from options.meta (could be function, ref, or raw object)\n\n const initialDataUpdatedAt = toValue(options.initialDataUpdatedAt)\n\n cachesRaw.set(\n keyHash,\n (entry = create(\n key,\n options,\n options.initialData?.(),\n null,\n initialDataUpdatedAt != null ? Date.now() - initialDataUpdatedAt : 0,\n toValue(options.meta),\n )),\n )\n // the placeholderData is only used if the entry is initially loading\n if (options.placeholderData && entry.state.value.status === 'pending') {\n entry.placeholderData = toValueWithArgs(\n options.placeholderData,\n // pass the previous entry placeholder data if it was in placeholder state\n isEntryUsingPlaceholderData(previousEntry)\n ? previousEntry.placeholderData\n : previousEntry?.state.value.data,\n previousEntry,\n )\n }\n triggerRef(caches)\n }\n\n // during HMR, staleTime could be long and if we change the query function, the query won't trigger a refetch\n // so we need to detect and trigger just in case. Vue's HMR reload mutates the component options object in place\n // and swaps `setup`/`render` with new function references while keeping `__hmrId` stable. Comparing the function\n // references lets us distinguish a real hot-update (invalidate) from a plain remount of the same component\n // (skip) — remounting the same component across ticks in a `v-for` must not cancel the shared pending request.\n // See https://github.com/posva/pinia-colada/issues/569.\n if (process.env.NODE_ENV !== 'production') {\n const currentInstance = getCurrentInstance()\n if (currentInstance) {\n const type = currentInstance.type as {\n __hmrId?: string\n setup?: unknown\n render?: unknown\n }\n if (type.__hmrId) {\n const components = (entry.__hmr ??= { components: new Map() }).components\n const prev = components.get(type.__hmrId)\n if (prev && (prev.setup !== type.setup || prev.render !== type.render)) {\n invalidate(entry)\n }\n components.set(type.__hmrId, { setup: type.setup, render: type.render })\n }\n }\n }\n\n // we set it every time to ensure we are using up to date key getters and others options\n entry.options = options\n\n // extend the entry with plugins the first time only\n if (entry.ext === START_EXT) {\n ;(entry as { ext: object }).ext = {}\n extend(entry)\n }\n\n // if this query was defined within a defineQuery call, add it to the list\n currentDefineQueryEntry?.[0].push(entry)\n\n return entry\n },\n )\n\n /**\n * Action called when an entry is ensured for the first time to allow plugins to extend it.\n *\n * @param _entry - the entry of the query to extend\n */\n const extend = action(\n <TData = unknown, TError = ErrorDefault, TDataInitial extends TData | undefined = undefined>(\n _entry: UseQueryEntry<TData, TError, TDataInitial>,\n ) => {},\n )\n\n /**\n * Invalidates and cancels a query entry. It effectively sets the `when`\n * property to `0` and {@link cancel | cancels} the pending request.\n *\n * @param entry - the entry of the query to invalidate\n *\n * @see {@link cancel}\n */\n const invalidate = action((entry: UseQueryEntry) => {\n // will force a fetch next time\n entry.when = 0\n // ignores the pending query\n cancel(entry)\n })\n\n /**\n * Ensures the current data is fresh. If the data is stale or if the status\n * is 'error', calls {@link fetch}, if not return the current data. Can only\n * be called if the entry has been initialized with `useQuery()` and has\n * options.\n *\n * @param entry - the entry of the query to refresh\n * @param options - the options to use for the fetch\n *\n * @see {@link fetch}\n */\n const refresh = action(\n async <TData, TError, TDataInitial extends TData | undefined>(\n entry: UseQueryEntry<TData, TError, TDataInitial>,\n options = entry.options,\n ): Promise<DataState<TData, TError, TDataInitial>> => {\n if (process.env.NODE_ENV !== 'production' && !options) {\n throw new Error(\n `\"entry.refresh()\" was called but the entry has no options. This is probably a bug, report it to pinia-colada with a boiled down example to reproduce it. Thank you!`,\n )\n }\n\n if (entry.state.value.error || entry.stale) {\n return entry.pending?.refreshCall ?? fetch(entry, options)\n }\n\n return entry.state.value\n },\n )\n\n /**\n * Fetch an entry. Ignores fresh data and triggers a new fetch. Can only be called if the entry has options.\n *\n * @param entry - the entry of the query to fetch\n * @param options - the options to use for the fetch\n */\n const fetch = action(\n async <TData, TError, TDataInitial extends TData | undefined>(\n entry: UseQueryEntry<TData, TError, TDataInitial>,\n options = entry.options,\n ): Promise<DataState<TData, TError, TDataInitial>> => {\n if (process.env.NODE_ENV !== 'production' && !options) {\n throw new Error(\n `\"entry.fetch()\" was called but the entry has no options. This is probably a bug, report it to pinia-colada with a boiled down example to reproduce it. Thank you!`,\n )\n }\n\n entry.asyncStatus.value = 'loading'\n\n const abortController = new AbortController()\n const { signal } = abortController\n // Abort any ongoing request without a reason to keep `AbortError` even with\n // signal.throwIfAborted() in the query function\n entry.pending?.abortController.abort()\n\n const pendingCall = (entry.pending = {\n abortController,\n // wrapping with async allows us to catch synchronous errors too\n refreshCall: (async () => options!.query({ signal, entry }))()\n .then((data) => {\n if (pendingCall === entry.pending) {\n setEntryState(entry, {\n data,\n error: null,\n status: 'success',\n })\n }\n return entry.state.value\n })\n .catch((error: unknown) => {\n // we skip updating the state if a new request was made\n // or if the error is from our own abort signal\n if (\n pendingCall === entry.pending &&\n // does the error come from a different reason than the abort signal?\n (error !== signal.reason || !signal.aborted)\n ) {\n setEntryState(entry, {\n status: 'error',\n data: entry.state.value.data,\n error: error as any,\n })\n }\n\n // always propagate up the error\n throw error\n // NOTE: other options included returning an ongoing request if the error was a cancellation but it seems not worth it\n })\n .finally(() => {\n entry.asyncStatus.value = 'idle'\n if (pendingCall === entry.pending) {\n entry.pending = null\n // there are cases when the result is ignored, in that case, we still\n // do not have a real result so we keep the placeholder data\n if (entry.state.value.status !== 'pending') {\n // reset the placeholder data to free up memory\n entry.placeholderData = null\n }\n }\n }),\n