UNPKG

@pinia/colada

Version:

The smart data fetching layer for Pinia

1,232 lines (1,214 loc) 40.5 kB
var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { PiniaColada: () => PiniaColada, PiniaColadaQueryHooksPlugin: () => PiniaColadaQueryHooksPlugin, TreeMapNode: () => TreeMapNode, defineMutation: () => defineMutation, defineQuery: () => defineQuery, hydrateQueryCache: () => hydrateQueryCache, serializeQueryCache: () => serializeQueryCache, serializeTreeMap: () => serializeTreeMap, toCacheKey: () => toCacheKey, useInfiniteQuery: () => useInfiniteQuery, useMutation: () => useMutation, useQuery: () => useQuery, useQueryCache: () => useQueryCache }); module.exports = __toCommonJS(index_exports); // src/mutation-store.ts var import_pinia = require("pinia"); var import_vue3 = require("vue"); // src/tree-map.ts var TreeMapNode = class _TreeMapNode { value; children; constructor(...args) { if (args.length) { this.set(...args); } } /** * Sets the value while building the tree * * @param keys - key as an array * @param value - value to set */ set(keys, value) { if (keys.length === 0) { this.value = value; } else { const [top, ...otherKeys] = keys; const node = this.children?.get(top); if (node) { node.set(otherKeys, value); } else { this.children ??= /* @__PURE__ */ new Map(); this.children.set(top, new _TreeMapNode(otherKeys, value)); } } } /** * Finds the node at the given path of keys. * * @param keys - path of keys */ find(keys) { if (keys.length === 0) { return this; } else { const [top, ...otherKeys] = keys; return this.children?.get(top)?.find(otherKeys); } } /** * Gets the value at the given path of keys. * * @param keys - path of keys */ get(keys) { return this.find(keys)?.value; } /** * Delete the node at the given path of keys and all its children. * * @param keys - path of keys */ delete(keys) { if (keys.length === 1) { this.children?.delete(keys[0]); } else { const [top, ...otherKeys] = keys; this.children?.get(top)?.delete(otherKeys); } } /** * 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. */ *[Symbol.iterator]() { if (this.value != null) { yield this.value; } if (this.children) { for (const child of this.children.values()) { yield* child; } } } }; function appendSerializedNodeToTree(parent, [key, value, children], createNodeValue, parentKey = []) { parent.children ??= /* @__PURE__ */ new Map(); const entryKey = [...parentKey, key]; const node = new TreeMapNode( [], // NOTE: this could happen outside of an effect scope but since it's only for client side hydration, it should be // fine to have global shallowRefs as they can still be cleared when needed value && createNodeValue(entryKey, null, ...value) ); parent.children.set(key, node); if (children) { for (const child of children) { appendSerializedNodeToTree(node, child, createNodeValue, entryKey); } } } // src/utils.ts var import_vue = require("vue"); function useEventListener(target, event, listener, options) { target.addEventListener(event, listener, options); if ((0, import_vue.getCurrentScope)()) { (0, import_vue.onScopeDispose)(() => { target.removeEventListener(event, listener); }); } } var IS_CLIENT = typeof window !== "undefined"; function toValueWithArgs(valFn, ...args) { return typeof valFn === "function" ? valFn(...args) : valFn; } function stringifyFlatObject(obj) { return obj && typeof obj === "object" ? JSON.stringify(obj, Object.keys(obj).sort()) : String(obj); } var toCacheKey = (key) => key.map(stringifyFlatObject); var noop = () => { }; function isSameArray(arr1, arr2) { if (arr1.length !== arr2.length) return false; for (let i = 0; i < arr1.length; i++) { if (arr1[i] !== arr2[i]) return false; } return true; } var warnedMessages = /* @__PURE__ */ new Set(); function warnOnce(message, id = message) { if (warnedMessages.has(id)) return; warnedMessages.add(id); console.warn(`[@pinia/colada]: ${message}`); } // src/mutation-options.ts var import_vue2 = require("vue"); var USE_MUTATION_OPTIONS_KEY = process.env.NODE_ENV !== "production" ? Symbol("useMutationOptions") : Symbol(); var useMutationOptions = () => (0, import_vue2.inject)(USE_MUTATION_OPTIONS_KEY, {}); // src/mutation-store.ts function createMutationEntry(options, key, vars) { return { state: (0, import_vue3.shallowRef)({ status: "pending", data: void 0, error: null }), asyncStatus: (0, import_vue3.shallowRef)("idle"), when: 0, vars: (0, import_vue3.shallowRef)(vars), key, options, pending: null }; } var useMutationCache = /* @__PURE__ */ (0, import_pinia.defineStore)("_pc_mutation", ({ action }) => { const cachesRaw = new TreeMapNode(); let triggerCache; const caches = (0, import_pinia.skipHydrate)( (0, import_vue3.customRef)( (track, trigger) => (triggerCache = trigger) && { // eslint-disable-next-line no-sequences get: () => (track(), cachesRaw), set: process.env.NODE_ENV !== "production" ? () => { console.error( `[@pinia/colada]: The mutation cache instance cannot be set directly, it must be modified. This will fail in production.` ); } : noop } ) ); const scope = (0, import_vue3.getCurrentScope)(); const globalOptions = useMutationOptions(); const defineMutationMap = /* @__PURE__ */ new WeakMap(); function ensure(options, entry, vars) { const key = vars && toValueWithArgs(options.key, vars)?.map(stringifyFlatObject); if (!entry) { entry = createMutationEntry(options, key); if (key) { cachesRaw.set( key, // @ts-expect-error: function types with generics are incompatible entry ); triggerCache(); } return createMutationEntry(options, key); } if (key) { if (!entry.key) { entry.key = key; } else if (!isSameArray(entry.key, key)) { entry = createMutationEntry( options, key, // the type NonNullable<TVars> is not assignable to TVars vars ); cachesRaw.set( key, // @ts-expect-error: function types with generics are incompatible entry ); triggerCache(); } } return entry; } const ensureDefinedMutation = action((fn) => { let defineMutationResult = defineMutationMap.get(fn); if (!defineMutationResult) { defineMutationMap.set(fn, defineMutationResult = scope.run(fn)); } return defineMutationResult; }); const setEntryState = action( (entry, state) => { entry.state.value = state; entry.when = Date.now(); } ); const remove = action((entry) => { if (entry.key != null) { cachesRaw.delete(entry.key); triggerCache(); } }); const getEntries = action((filters = {}) => { const node = filters.key ? caches.value.find(toCacheKey(filters.key)) : caches.value; if (!node) return []; return (filters.exact ? node.value ? [node.value] : [] : [...node]).filter( (entry) => (filters.status == null || entry.state.value.status === filters.status) && (!filters.predicate || filters.predicate(entry)) ); }); async function mutate(currentEntry, vars) { currentEntry.asyncStatus.value = "loading"; currentEntry.vars.value = vars; let currentData; let currentError; const { options } = currentEntry; let context = {}; const currentCall = currentEntry.pending = Symbol(); try { const globalOnMutateContext = globalOptions.onMutate?.(vars); context = (globalOnMutateContext instanceof Promise ? await globalOnMutateContext : globalOnMutateContext) || {}; const onMutateContext = await options.onMutate?.( vars, context // 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> ); context = { ...context, ...onMutateContext // NOTE: needed for onSuccess cast }; const newData = currentData = await options.mutation(vars, context); await globalOptions.onSuccess?.(newData, vars, context); await options.onSuccess?.( newData, vars, // NOTE: cast is safe because of the satisfies above // using a spread also works context ); if (currentEntry.pending === currentCall) { setEntryState(currentEntry, { status: "success", data: newData, error: null }); } } catch (newError) { currentError = newError; await globalOptions.onError?.(currentError, vars, context); await options.onError?.(currentError, vars, context); if (currentEntry.pending === currentCall) { setEntryState(currentEntry, { status: "error", data: currentEntry.state.value.data, error: currentError }); } throw newError; } finally { await globalOptions.onSettled?.(currentData, currentError, vars, context); await options.onSettled?.(currentData, currentError, vars, context); if (currentEntry.pending === currentCall) { currentEntry.asyncStatus.value = "idle"; } } return currentData; } return { caches, ensure, ensureDefinedMutation, mutate, remove, setEntryState, getEntries }; }); // src/use-mutation.ts var import_vue4 = require("vue"); function useMutation(options) { const mutationCache = useMutationCache(); const entry = (0, import_vue4.shallowRef)( mutationCache.ensure(options) ); const state = (0, import_vue4.computed)(() => entry.value.state.value); const status = (0, import_vue4.computed)(() => state.value.status); const data = (0, import_vue4.computed)(() => state.value.data); const error = (0, import_vue4.computed)(() => state.value.error); const asyncStatus = (0, import_vue4.computed)(() => entry.value.asyncStatus.value); const variables = (0, import_vue4.computed)(() => entry.value.vars.value); async function mutateAsync(vars) { return mutationCache.mutate( entry.value = mutationCache.ensure(options, entry.value, vars), vars ); } function mutate(vars) { mutateAsync(vars).catch(noop); } function reset() { mutationCache.setEntryState(entry.value, { status: "pending", data: void 0, error: null }); entry.value.asyncStatus.value = "idle"; } return { state, data, isLoading: (0, import_vue4.computed)(() => asyncStatus.value === "loading"), status, variables, asyncStatus, error, // @ts-expect-error: because of the conditional type in UseMutationReturn // it would be nice to find a type-only refactor that works mutate, // @ts-expect-error: same as above mutateAsync, reset }; } // src/define-mutation.ts function defineMutation(optionsOrSetup) { const setupFn = typeof optionsOrSetup === "function" ? optionsOrSetup : () => useMutation(optionsOrSetup); return () => { const mutationCache = useMutationCache(); return mutationCache.ensureDefinedMutation(setupFn); }; } // src/define-query.ts var import_vue8 = require("vue"); // src/query-store.ts var import_pinia2 = require("pinia"); var import_vue6 = require("vue"); // src/query-options.ts var import_vue5 = require("vue"); var USE_QUERY_DEFAULTS = { staleTime: 1e3 * 5, // 5 seconds gcTime: 1e3 * 60 * 5, // 5 minutes // avoid type narrowing to `true` refetchOnWindowFocus: true, refetchOnReconnect: true, refetchOnMount: true, enabled: true }; var USE_QUERY_OPTIONS_KEY = process.env.NODE_ENV !== "production" ? Symbol("useQueryOptions") : Symbol(); var useQueryOptions = () => (0, import_vue5.inject)(USE_QUERY_OPTIONS_KEY, USE_QUERY_DEFAULTS); // src/query-store.ts function isEntryUsingPlaceholderData(entry) { return entry?.placeholderData != null && entry.state.value.status === "pending"; } var START_EXT = {}; var queryEntry_toJSON = ({ state: { value }, when }) => [ value.data, value.error, when ]; var QUERY_STORE_ID = "_pc_query"; var useQueryCache = /* @__PURE__ */ (0, import_pinia2.defineStore)(QUERY_STORE_ID, ({ action }) => { const cachesRaw = new TreeMapNode(); let triggerCache; const caches = (0, import_pinia2.skipHydrate)( (0, import_vue6.customRef)( (track2, trigger) => (triggerCache = trigger) && { // eslint-disable-next-line no-sequences get: () => (track2(), cachesRaw), set: process.env.NODE_ENV !== "production" ? () => { console.error( `[@pinia/colada]: The query cache instance cannot be set directly, it must be modified. This will fail in production.` ); } : noop } ) ); const scope = (0, import_vue6.getCurrentScope)(); const app = (0, import_pinia2.getActivePinia)()._a; if (process.env.NODE_ENV !== "production") { if (!(0, import_vue6.hasInjectionContext)()) { warnOnce( `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. See https://vuejs.org/guide/reusability/composables.html#usage-restrictions` ); } } const optionDefaults = useQueryOptions(); const create = action( (key, options = null, initialData, error = null, when = 0) => scope.run(() => { const state = (0, import_vue6.shallowRef)( // @ts-expect-error: to make the code shorter we are using one declaration instead of multiple ternaries { // NOTE: we could move the `initialData` parameter before `options` and make it required // but that would force `create` call in `setQueryData` to pass an extra `undefined` argument data: initialData, error, status: error ? "error" : initialData !== void 0 ? "success" : "pending" } ); const asyncStatus = (0, import_vue6.shallowRef)("idle"); return (0, import_vue6.markRaw)({ key, state, placeholderData: null, when, asyncStatus, pending: null, // this set can contain components and effects and worsen the performance // and create weird warnings deps: (0, import_vue6.markRaw)(/* @__PURE__ */ new Set()), gcTimeout: void 0, // eslint-disable-next-line ts/ban-ts-comment // @ts-ignore: some plugins are adding properties to the entry type ext: START_EXT, options, get stale() { return !this.when || Date.now() >= this.when + this.options.staleTime; }, get active() { return this.deps.size > 0; } }); }) ); let currentDefineQueryEntry; const defineQueryMap = /* @__PURE__ */ new WeakMap(); const ensureDefinedQuery = action((fn) => { let defineQueryEntry = defineQueryMap.get(fn); if (!defineQueryEntry) { currentDefineQueryEntry = defineQueryEntry = [[], null, scope.run(() => (0, import_vue6.effectScope)())]; defineQueryEntry[1] = app.runWithContext(() => defineQueryEntry[2].run(fn)); currentDefineQueryEntry = null; defineQueryMap.set(fn, defineQueryEntry); } else { defineQueryEntry[2].resume(); defineQueryEntry[0] = defineQueryEntry[0].map( (oldEntry) => ( // the entries' key might have changed (e.g. Nuxt navigation) // so we need to ensure them again oldEntry.options ? ensure(oldEntry.options, oldEntry) : oldEntry ) ); } return defineQueryEntry; }); function track(entry, effect) { if (!effect) return; entry.deps.add(effect); clearTimeout(entry.gcTimeout); entry.gcTimeout = void 0; triggerCache(); } function untrack(entry, effect) { if (!effect || !entry.deps.has(effect)) return; entry.deps.delete(effect); triggerCache(); if (entry.deps.size > 0 || !entry.options) return; clearTimeout(entry.gcTimeout); if (Number.isFinite(entry.options.gcTime)) { entry.gcTimeout = setTimeout(() => { remove(entry); }, entry.options.gcTime); } } const invalidateQueries = action((filters) => { return Promise.all( getEntries({ active: true, ...filters }).map((entry) => { invalidate(entry); return (0, import_vue6.toValue)(entry.options?.enabled) && fetch(entry); }) ); }); const getEntries = action((filters = {}) => { const node = filters.key ? caches.value.find(toCacheKey(filters.key)) : caches.value; if (!node) return []; return (filters.exact ? node.value ? [node.value] : [] : [...node]).filter( (entry) => (filters.stale == null || entry.stale === filters.stale) && (filters.active == null || entry.active === filters.active) && (!filters.status || entry.state.value.status === filters.status) && (!filters.predicate || filters.predicate(entry)) ); }); const ensure = action( (opts, previousEntry) => { const options = { ...optionDefaults, ...opts }; const key = toCacheKey((0, import_vue6.toValue)(options.key)); if (process.env.NODE_ENV !== "production" && key.length === 0) { throw new Error( `useQuery() was called with an empty array as the key. It must have at least one element.` ); } let entry = cachesRaw.get(key); if (!entry) { cachesRaw.set(key, entry = create(key, options, options.initialData?.())); if (options.placeholderData && entry.state.value.status === "pending") { entry.placeholderData = toValueWithArgs( options.placeholderData, // pass the previous entry placeholder data if it was in placeholder state // NOTE: the build needs a cast or it thinks it's never isEntryUsingPlaceholderData(previousEntry) ? previousEntry.placeholderData : previousEntry?.state.value.data ); } triggerCache(); } if (process.env.NODE_ENV !== "production") { const currentInstance = (0, import_vue6.getCurrentInstance)(); if (currentInstance) { entry.__hmr ??= {}; entry.__hmr.deps ??= /* @__PURE__ */ new Set(); entry.__hmr.id = currentInstance.type?.__hmrId ?? currentInstance.proxy?._uid; if (entry.__hmr.id == null && process.env.NODE_ENV !== "test" && typeof document !== "undefined") { warnOnce( `Found a nullish hmr id. This is probably a bug. Please report it to pinia-colada with a boiled down reproduction. Thank you!` ); } } } entry.options = options; if (entry.ext === START_EXT) { entry.ext = {}; extend(entry); } currentDefineQueryEntry?.[0].push(entry); return entry; } ); const extend = action( (_entry) => { } ); const invalidate = action((entry) => { entry.when = 0; cancel(entry); }); const refresh = action( async (entry, options = entry.options) => { if (process.env.NODE_ENV !== "production" && !options) { throw new Error( `"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!` ); } if (entry.state.value.error || entry.stale) { return entry.pending?.refreshCall ?? fetch(entry, options); } return entry.state.value; } ); const fetch = action( async (entry, options = entry.options) => { if (process.env.NODE_ENV !== "production" && !options) { throw new Error( `"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!` ); } entry.asyncStatus.value = "loading"; const abortController = new AbortController(); const { signal } = abortController; entry.pending?.abortController.abort(); const pendingCall = entry.pending = { abortController, // wrapping with async allows us to catch synchronous errors too refreshCall: (async () => options.query({ signal }))().then((data) => { if (pendingCall === entry.pending) { setEntryState(entry, { data, error: null, status: "success" }); } return entry.state.value; }).catch((error) => { if (pendingCall === entry.pending && error && error.name !== "AbortError") { setEntryState(entry, { status: "error", data: entry.state.value.data, error }); } throw error; }).finally(() => { entry.asyncStatus.value = "idle"; if (pendingCall === entry.pending) { entry.pending = null; if (entry.state.value.status !== "pending") { entry.placeholderData = null; } entry.when = Date.now(); } }), when: Date.now() }; return pendingCall.refreshCall; } ); const cancel = action((entry, reason) => { entry.pending?.abortController.abort(reason); entry.asyncStatus.value = "idle"; entry.pending = null; }); const cancelQueries = action((filters, reason) => { getEntries(filters).forEach((entry) => cancel(entry, reason)); }); const setEntryState = action( (entry, state) => { entry.state.value = state; entry.when = Date.now(); } ); const setQueryData = action( (key, data) => { const cacheKey = toCacheKey(key); let entry = cachesRaw.get(cacheKey); if (!entry) { cachesRaw.set(cacheKey, entry = create(cacheKey)); } setEntryState(entry, { // if we don't cast, this is not technically correct // the user is responsible for setting the data ...entry.state.value, data: toValueWithArgs(data, entry.state.value.data) }); triggerCache(); } ); function getQueryData(key) { return caches.value.get(toCacheKey(key))?.state.value.data; } const remove = action((entry) => { cachesRaw.delete(entry.key); triggerCache(); }); return { caches, ensureDefinedQuery, /** * Scope to track effects and components that use the query cache. * @internal */ _s: (0, import_vue6.markRaw)(scope), setQueryData, getQueryData, invalidateQueries, cancelQueries, // Actions for entries invalidate, fetch, refresh, ensure, extend, track, untrack, cancel, create, remove, setEntryState, getEntries }; }); function serializeTreeMap(root) { return root.children ? [...root.children.entries()].map(_serialize) : []; } function _serialize([key, tree]) { return [ key, tree.value && queryEntry_toJSON(tree.value), tree.children && [...tree.children.entries()].map(_serialize) ]; } function hydrateQueryCache(queryCache, serializedCache) { for (const entryData of serializedCache) { appendSerializedNodeToTree(queryCache.caches, entryData, queryCache.create); } } function serializeQueryCache(queryCache) { return serializeTreeMap(queryCache.caches); } // src/use-query.ts var import_vue7 = require("vue"); function useQuery(_options) { const queryCache = useQueryCache(); const optionDefaults = useQueryOptions(); const hasCurrentInstance = (0, import_vue7.getCurrentInstance)(); const currentEffect = getCurrentDefineQueryEffect() || (0, import_vue7.getCurrentScope)(); const options = { ...optionDefaults, ..._options }; const { refetchOnMount, refetchOnReconnect, refetchOnWindowFocus, enabled } = options; let lastEntry; const entry = (0, import_vue7.computed)( () => ( // NOTE: there should be a `paused` property on the effect later on // if the effect is paused, we don't want to compute the entry because its key // might be referencing unedfined values // https://github.com/posva/pinia-colada/issues/227 // @ts-expect-error: _isPaused is private currentEffect?._isPaused ? lastEntry : lastEntry = queryCache.ensure(options, lastEntry) ) ); lastEntry = entry.value; const errorCatcher = () => entry.value.state.value; const refresh = (throwOnError) => queryCache.refresh(entry.value, options).catch( // true is not allowed but it works per spec as only callable onRejected are used // https://tc39.es/ecma262/multipage/control-abstraction-objects.html#sec-performpromisethen // In other words `Promise.rejects('ok').catch(true)` still rejects // anything other than `true` falls back to the `errorCatcher` throwOnError || errorCatcher ); const refetch = (throwOnError) => queryCache.fetch(entry.value, options).catch( // same as above throwOnError || errorCatcher ); const isPlaceholderData = (0, import_vue7.computed)(() => isEntryUsingPlaceholderData(entry.value)); const state = (0, import_vue7.computed)( () => isPlaceholderData.value ? { status: "success", data: entry.value.placeholderData, error: null } : entry.value.state.value ); const extensions = {}; for (const key in lastEntry.ext) { extensions[key] = (0, import_vue7.computed)({ get: () => (0, import_vue7.toValue)(entry.value.ext[key]), set(value) { const target = entry.value.ext[key]; if ((0, import_vue7.isRef)(target)) { ; target.value = value; } else { ; entry.value.ext[key] = value; } } }); } const queryReturn = { ...extensions, state, status: (0, import_vue7.computed)(() => state.value.status), data: (0, import_vue7.computed)(() => state.value.data), error: (0, import_vue7.computed)(() => entry.value.state.value.error), asyncStatus: (0, import_vue7.computed)(() => entry.value.asyncStatus.value), isPlaceholderData, isPending: (0, import_vue7.computed)(() => state.value.status === "pending"), isLoading: (0, import_vue7.computed)(() => entry.value.asyncStatus.value === "loading"), refresh, refetch }; if (hasCurrentInstance) { (0, import_vue7.onServerPrefetch)(async () => { if ((0, import_vue7.toValue)(enabled)) await refresh(true); }); } let isActive = false; if (hasCurrentInstance) { (0, import_vue7.onMounted)(() => { isActive = true; queryCache.track(lastEntry, hasCurrentInstance); }); (0, import_vue7.onUnmounted)(() => { queryCache.untrack(lastEntry, hasCurrentInstance); }); } else { isActive = true; if (currentEffect) { queryCache.track(lastEntry, currentEffect); (0, import_vue7.onScopeDispose)(() => { queryCache.untrack(lastEntry, currentEffect); }); } } (0, import_vue7.watch)( entry, (entry2, previousEntry) => { if (!isActive) return; if (previousEntry) { queryCache.untrack(previousEntry, hasCurrentInstance); queryCache.untrack(previousEntry, currentEffect); } queryCache.track(entry2, hasCurrentInstance); queryCache.track(entry2, currentEffect); if ((0, import_vue7.toValue)(enabled)) refresh(); }, { immediate: true } ); if (typeof enabled !== "boolean") { (0, import_vue7.watch)(enabled, (newEnabled) => { if (newEnabled) refresh(); }); } if (hasCurrentInstance) { (0, import_vue7.onMounted)(() => { if ((refetchOnMount || queryReturn.status.value === "pending") && (0, import_vue7.toValue)(enabled)) { if (refetchOnMount === "always") { refetch(); } else { refresh(); } } }); } if (IS_CLIENT) { if (refetchOnWindowFocus) { useEventListener(document, "visibilitychange", () => { if (document.visibilityState === "visible" && (0, import_vue7.toValue)(enabled)) { if ((0, import_vue7.toValue)(refetchOnWindowFocus) === "always") { refetch(); } else { refresh(); } } }); } if (refetchOnReconnect) { useEventListener(window, "online", () => { if ((0, import_vue7.toValue)(enabled)) { if ((0, import_vue7.toValue)(refetchOnReconnect) === "always") { refetch(); } else { refresh(); } } }); } } return queryReturn; } // src/define-query.ts var currentDefineQueryEffect; function getCurrentDefineQueryEffect() { return currentDefineQueryEffect; } function defineQuery(optionsOrSetup) { const setupFn = typeof optionsOrSetup === "function" ? optionsOrSetup : () => useQuery(optionsOrSetup); let hasBeenEnsured; return () => { const queryCache = useQueryCache(); const previousEffect = currentDefineQueryEffect; const currentScope = (0, import_vue8.getCurrentInstance)() || (currentDefineQueryEffect = (0, import_vue8.getCurrentScope)()); const [entries, ret, scope] = queryCache.ensureDefinedQuery(setupFn); if (hasBeenEnsured) { entries.forEach((entry) => { if (entry.options?.refetchOnMount && (0, import_vue8.toValue)(entry.options.enabled)) { if ((0, import_vue8.toValue)(entry.options.refetchOnMount) === "always") { queryCache.fetch(entry); } else { queryCache.refresh(entry); } } }); } hasBeenEnsured = true; if (currentScope) { entries.forEach((entry) => { queryCache.track(entry, currentScope); if (process.env.NODE_ENV !== "production") { entry.__hmr ??= {}; entry.__hmr.skip = true; } }); (0, import_vue8.onScopeDispose)(() => { if (entries.every((entry) => { queryCache.untrack(entry, currentScope); return !entry.active; })) { scope.pause(); } }); } currentDefineQueryEffect = previousEffect; return ret; }; } // src/devtools/plugin.ts var import_devtools_api = require("@vue/devtools-api"); var import_vue9 = require("vue"); var QUERY_INSPECTOR_ID = "pinia-colada-queries"; var ID_SEPARATOR = "\0"; function debounce(fn, delay) { let timeout; return () => { clearTimeout(timeout); timeout = setTimeout(fn, delay); }; } function addDevtools(app, pinia) { const queryCache = useQueryCache(pinia); (0, import_devtools_api.setupDevtoolsPlugin)( { id: "dev.esm.pinia-colada", app, label: "Pinia Colada", packageName: "pinia-colada", homepage: "https://pinia-colada.esm.dev/", logo: "https://pinia-colada.esm.dev/logo.svg", componentStateTypes: [] }, (api) => { const updateQueryInspectorTree = debounce(() => { api.sendInspectorTree(QUERY_INSPECTOR_ID); api.sendInspectorState(QUERY_INSPECTOR_ID); }, 100); api.addInspector({ id: QUERY_INSPECTOR_ID, label: "Pinia Queries", icon: "storage", noSelectionText: "Select a query entry to inspect it", treeFilterPlaceholder: "Filter query entries", stateFilterPlaceholder: "Find within the query entry", actions: [ { icon: "refresh", action: updateQueryInspectorTree, tooltip: "Sync" } ] }); let stopWatcher = () => { }; api.on.getInspectorState((payload) => { if (payload.app !== app) return; if (payload.inspectorId === QUERY_INSPECTOR_ID) { const entry = queryCache.getEntries({ key: payload.nodeId.split(ID_SEPARATOR), exact: true })[0]; if (!entry) { payload.state = { Error: [ { key: "error", value: new Error(`Query entry ${payload.nodeId} not found`), editable: false } ] }; return; } stopWatcher(); stopWatcher = (0, import_vue9.watch)( () => [entry.state.value, entry.asyncStatus.value], () => { api.sendInspectorState(QUERY_INSPECTOR_ID); } ); const state = entry.state.value; payload.state = { state: [ { key: "data", value: state.data, editable: true }, { key: "error", value: state.error, editable: true }, { key: "status", value: state.status, editable: true }, { key: "asyncStatus", value: entry.asyncStatus.value, editable: true } ], entry: [ { key: "key", value: entry.key, editable: false }, { key: "options", value: entry.options, editable: true } ] }; } }); api.on.editInspectorState((payload) => { if (payload.app !== app) return; if (payload.inspectorId === QUERY_INSPECTOR_ID) { const entry = queryCache.getEntries({ key: payload.nodeId.split(ID_SEPARATOR), exact: true })[0]; if (!entry) return; const path = payload.path.slice(); payload.set(entry, path, payload.state.value); api.sendInspectorState(QUERY_INSPECTOR_ID); } }); const QUERY_FILTER_RE = /\b(active|inactive|stale|fresh|exact|loading|idle)\b/gi; api.on.getInspectorTree((payload) => { if (payload.app !== app) return; if (payload.inspectorId === QUERY_INSPECTOR_ID) { const filters = payload.filter.match(QUERY_FILTER_RE); const filter = (filters ? payload.filter.replace(QUERY_FILTER_RE, "") : payload.filter).trim(); const active = filters?.includes("active") ? true : filters?.includes("inactive") ? false : void 0; const stale = filters?.includes("stale") ? true : filters?.includes("fresh") ? false : void 0; const asyncStatus = filters?.includes("loading") ? "loading" : filters?.includes("idle") ? "idle" : void 0; payload.rootNodes = queryCache.getEntries({ active, stale, exact: filters?.includes("exact"), predicate(entry) { if (asyncStatus && entry.asyncStatus.value !== asyncStatus) return false; if (filter) { return entry.key.some((key) => String(key).includes(filter)); } return true; } }).map((entry) => { const id = entry.key.join(ID_SEPARATOR); const label = entry.key.join("/"); const asyncStatus2 = entry.asyncStatus.value; const state = entry.state.value; const tags = [ ASYNC_STATUS_TAG[asyncStatus2], STATUS_TAG[state.status] // useful for testing colors // ASYNC_STATUS_TAG.idle, // ASYNC_STATUS_TAG.fetching, // STATUS_TAG.pending, // STATUS_TAG.success, // STATUS_TAG.error, ]; if (!entry.active) { tags.push({ label: "inactive", textColor: 0, backgroundColor: 11184810, tooltip: "The query is not being used anywhere" }); } return { id, label, name: label, tags }; }); } }); queryCache.$onAction(({ name, after, onError }) => { if (name === "invalidate" || name === "fetch" || name === "setEntryState" || name === "remove" || name === "untrack" || name === "track" || name === "ensure") { updateQueryInspectorTree(); after(updateQueryInspectorTree); onError(updateQueryInspectorTree); } }); api.notifyComponentUpdate(); api.sendInspectorTree(QUERY_INSPECTOR_ID); api.sendInspectorState(QUERY_INSPECTOR_ID); } ); } var STATUS_TAG = { pending: { label: "pending", textColor: 0, backgroundColor: 16751907, tooltip: `The query hasn't resolved yet` }, success: { label: "success", textColor: 0, backgroundColor: 1492095, tooltip: "The query resolved successfully" }, error: { label: "error", textColor: 0, backgroundColor: 16332839, tooltip: "The query rejected with an error" } }; var ASYNC_STATUS_TAG = { idle: { label: "idle", textColor: 0, backgroundColor: 11184810, tooltip: "The query is not fetching" }, loading: { label: "fetching", textColor: 16777215, backgroundColor: 5738442, tooltip: "The query is currently fetching" } }; // src/pinia-colada.ts var PiniaColada = (app, options = {}) => { const { pinia = app.config.globalProperties.$pinia, plugins, queryOptions, mutationOptions = {} } = options; app.provide(USE_QUERY_OPTIONS_KEY, { ...USE_QUERY_DEFAULTS, ...queryOptions }); app.provide(USE_MUTATION_OPTIONS_KEY, mutationOptions); if (process.env.NODE_ENV !== "production" && !pinia) { throw new Error( '[@pinia/colada] root pinia plugin not detected. Make sure you install pinia before installing the "PiniaColada" plugin or to manually pass the pinia instance.' ); } if (typeof document !== "undefined" && process.env.NODE_ENV === "development") { addDevtools(app, pinia); } const queryCache = useQueryCache(pinia); plugins?.forEach( (plugin) => plugin({ scope: queryCache._s, queryCache, pinia }) ); }; // src/plugins/query-hooks.ts function PiniaColadaQueryHooksPlugin(options) { return ({ queryCache }) => { queryCache.$onAction(({ name, after, onError, args }) => { if (name === "fetch") { const [entry] = args; after(async ({ data }) => { await options.onSuccess?.(data, entry); options.onSettled?.(data, null, entry); }); onError(async (error) => { await options.onError?.(error, entry); options.onSettled?.(void 0, error, entry); }); } }); }; } // src/infinite-query.ts var import_vue10 = require("vue"); function useInfiniteQuery(options) { let pages = (0, import_vue10.toValue)(options.initialPage); const { refetch, refresh, ...query } = useQuery({ ...options, initialData: () => pages, // since we hijack the query function and augment the data, we cannot refetch the data // like usual staleTime: Infinity, async query(context) { const data = await options.query(pages, context); return pages = options.merge(pages, data); } }); return { ...query, loadMore: () => refetch() }; } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { PiniaColada, PiniaColadaQueryHooksPlugin, TreeMapNode, defineMutation, defineQuery, hydrateQueryCache, serializeQueryCache, serializeTreeMap, toCacheKey, useInfiniteQuery, useMutation, useQuery, useQueryCache }); //# sourceMappingURL=index.cjs.map