// src/define-query-options.ts
// @__NO_SIDE_EFFECTS__
function defineQueryOptions(setupOrOptions) {
  return setupOrOptions;
}

// src/define-query.ts
import { getCurrentInstance as getCurrentInstance3, getCurrentScope as getCurrentScope4, onScopeDispose as onScopeDispose3, toValue as toValue3 } from "vue";

// src/query-store.ts
import { defineStore, getActivePinia, skipHydrate } from "pinia";
import {
  customRef,
  effectScope,
  getCurrentInstance,
  getCurrentScope as getCurrentScope2,
  hasInjectionContext,
  markRaw,
  shallowRef,
  toValue
} from "vue";

// src/query-options.ts
import { inject } from "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 = () => inject(USE_QUERY_OPTIONS_KEY, USE_QUERY_DEFAULTS);

// src/entry-keys.ts
function toCacheKey(key) {
  return key && JSON.stringify(key, (_, val) => !val || typeof val !== "object" || Array.isArray(val) ? val : Object.keys(val).sort().reduce((result, key2) => {
    result[key2] = val[key2];
    return result;
  }, {}));
}
function isSubsetOf(subsetKey, fullsetKey) {
  return subsetKey === fullsetKey ? true : typeof subsetKey !== typeof fullsetKey ? false : subsetKey && fullsetKey && typeof subsetKey === "object" && typeof fullsetKey === "object" ? Object.keys(subsetKey).every(
    (key) => isSubsetOf(
      // NOTE: this or making them `any` in the function signature
      subsetKey[key],
      fullsetKey[key]
    )
  ) : false;
}
var ENTRY_DATA_TAG = Symbol("Pinia Colada data tag");
var ENTRY_ERROR_TAG = Symbol("Pinia Colada error tag");
var ENTRY_DATA_INITIAL_TAG = Symbol("Pinia Colada data initial tag");
function* find(map, partialKey) {
  for (const entry of map.values()) {
    if (!partialKey || entry.key && isSubsetOf(partialKey, entry.key)) {
      yield entry;
    }
  }
}

// src/utils.ts
import { computed, getCurrentScope, onScopeDispose } from "vue";
function useEventListener(target, event, listener, options) {
  target.addEventListener(event, listener, options);
  if (getCurrentScope()) {
    onScopeDispose(() => {
      target.removeEventListener(event, listener);
    });
  }
}
var IS_CLIENT = typeof window !== "undefined";
function toValueWithArgs(valFn, ...args) {
  return typeof valFn === "function" ? valFn(...args) : valFn;
}
var noop = () => {
};
var warnedMessages = /* @__PURE__ */ new Set();
function warnOnce(message, id = message) {
  if (warnedMessages.has(id)) return;
  warnedMessages.add(id);
  console.warn(`[@pinia/colada]: ${message}`);
}

// src/query-store.ts
var currentDefineQueryEntry;
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,
  // because of time zones, we create a relative time
  when ? Date.now() - when : -1
];
var QUERY_STORE_ID = "_pc_query";
var useQueryCache = /* @__PURE__ */ defineStore(QUERY_STORE_ID, ({ action }) => {
  const cachesRaw = /* @__PURE__ */ new Map();
  let triggerCache;
  const caches = skipHydrate(
    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 = getCurrentScope2();
  const app = getActivePinia()._a;
  if (process.env.NODE_ENV !== "production") {
    if (!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 = 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 = shallowRef("idle");
      return markRaw({
        key,
        keyHash: toCacheKey(key),
        state,
        placeholderData: null,
        when: initialData === void 0 ? 0 : Date.now() - when,
        asyncStatus,
        pending: null,
        // this set can contain components and effects and worsen the performance
        // and create weird warnings
        deps: 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.options || !this.when || Date.now() >= this.when + this.options.staleTime;
        },
        get active() {
          return this.deps.size > 0;
        }
      });
    })
  );
  const defineQueryMap = /* @__PURE__ */ new WeakMap();
  const ensureDefinedQuery = action((fn) => {
    let defineQueryEntry = defineQueryMap.get(fn);
    if (!defineQueryEntry) {
      currentDefineQueryEntry = defineQueryEntry = scope.run(() => [
        [],
        null,
        effectScope(),
        shallowRef(false)
      ]);
      defineQueryEntry[1] = app.runWithContext(() => defineQueryEntry[2].run(fn));
      currentDefineQueryEntry = null;
      defineQueryMap.set(fn, defineQueryEntry);
    } else {
      defineQueryEntry[2].resume();
      defineQueryEntry[3].value = false;
      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;
    if (process.env.NODE_ENV !== "production") {
      if ("type" in effect && "__hmrId" in effect.type && entry.__hmr) {
        const count = (entry.__hmr.ids.get(effect.type.__hmrId) ?? 1) - 1;
        if (count > 0) {
          entry.__hmr.ids.set(effect.type.__hmrId, count);
        } else {
          entry.__hmr.ids.delete(effect.type.__hmrId);
        }
      }
    }
    entry.deps.delete(effect);
    triggerCache();
    scheduleGarbageCollection(entry);
  }
  function scheduleGarbageCollection(entry) {
    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, refetchActive = true) => {
      return Promise.all(
        getEntries(filters).map((entry) => {
          invalidate(entry);
          return (refetchActive === "all" || entry.active && refetchActive) && toValue(entry.options?.enabled) && fetch(entry);
        })
      );
    }
  );
  const getEntries = action((filters = {}) => {
    return (filters.exact ? filters.key ? [caches.value.get(toCacheKey(filters.key))].filter((v) => !!v) : [] : [...find(caches.value, filters.key)]).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 = toValue(options.key);
      const keyHash = toCacheKey(key);
      if (process.env.NODE_ENV !== "production" && keyHash === "[]") {
        throw new Error(
          `useQuery() was called with an empty array as the key. It must have at least one element.`
        );
      }
      if (previousEntry && keyHash === previousEntry.keyHash) {
        return previousEntry;
      }
      let entry = cachesRaw.get(keyHash);
      if (!entry) {
        cachesRaw.set(keyHash, 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
            isEntryUsingPlaceholderData(previousEntry) ? previousEntry.placeholderData : previousEntry?.state.value.data
          );
        }
        triggerCache();
      }
      if (process.env.NODE_ENV !== "production") {
        const currentInstance = getCurrentInstance();
        if (currentInstance) {
          entry.__hmr ??= { ids: /* @__PURE__ */ new Map() };
          const id = currentInstance.type?.__hmrId;
          if (id) {
            if (entry.__hmr.ids.has(id)) {
              invalidate(entry);
            }
            const count = (entry.__hmr.ids.get(id) ?? 0) + 1;
            entry.__hmr.ids.set(id, count);
          }
        }
      }
      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();
    }
  );
  function get(key) {
    return caches.value.get(toCacheKey(key));
  }
  const setQueryData = action(
    (key, data) => {
      const keyHash = toCacheKey(key);
      let entry = cachesRaw.get(keyHash);
      if (!entry) {
        cachesRaw.set(keyHash, entry = create(key));
      }
      setEntryState(entry, {
        // we assume the data accounts for a successful state
        error: null,
        status: "success",
        data: toValueWithArgs(data, entry.state.value.data)
      });
      scheduleGarbageCollection(entry);
      triggerCache();
    }
  );
  function setQueriesData(filters, updater) {
    for (const entry of getEntries(filters)) {
      setEntryState(entry, {
        error: null,
        status: "success",
        data: updater(entry.state.value.data)
      });
      scheduleGarbageCollection(entry);
    }
    triggerCache();
  }
  function getQueryData(key) {
    return caches.value.get(toCacheKey(key))?.state.value.data;
  }
  const remove = action((entry) => {
    cachesRaw.delete(entry.keyHash);
    triggerCache();
  });
  return {
    caches,
    ensureDefinedQuery,
    /**
     * Scope to track effects and components that use the query cache.
     * @internal
     */
    _s: markRaw(scope),
    setQueryData,
    setQueriesData,
    getQueryData,
    invalidateQueries,
    cancelQueries,
    // Actions for entries
    invalidate,
    fetch,
    refresh,
    ensure,
    extend,
    track,
    untrack,
    cancel,
    create,
    remove,
    get,
    setEntryState,
    getEntries
  };
});
function isQueryCache(cache) {
  return typeof cache === "object" && !!cache && cache.$id === QUERY_STORE_ID;
}
function hydrateQueryCache(queryCache, serializedCache) {
  for (const keyHash in serializedCache) {
    queryCache.caches.set(
      keyHash,
      queryCache.create(JSON.parse(keyHash), void 0, ...serializedCache[keyHash] ?? [])
    );
  }
}
function serializeQueryCache(queryCache) {
  return Object.fromEntries(
    // TODO: 2028: directly use .map on the iterator
    [...queryCache.caches.entries()].map(([keyHash, entry]) => [keyHash, queryEntry_toJSON(entry)])
  );
}

// src/use-query.ts
import {
  computed as computed2,
  getCurrentInstance as getCurrentInstance2,
  getCurrentScope as getCurrentScope3,
  isRef,
  onMounted,
  onScopeDispose as onScopeDispose2,
  onServerPrefetch,
  onUnmounted,
  toValue as toValue2,
  watch
} from "vue";
function useQuery(...[_options, paramsGetter]) {
  if (paramsGetter != null) {
    return useQuery(
      () => (
        // NOTE: we manually type cast here because TS cannot infer correctly in overloads
        _options(
          toValue2(paramsGetter)
        )
      )
    );
  }
  const queryCache = useQueryCache();
  const optionDefaults = useQueryOptions();
  const hasCurrentInstance = getCurrentInstance2();
  const defineQueryEffect = currentDefineQueryEntry?.[2];
  const currentEffect = currentDefineQueryEffect || getCurrentScope3();
  const isPaused = currentDefineQueryEntry?.[3];
  const options = computed2(
    () => ({
      ...optionDefaults,
      ...toValue2(
        // NOTE: we manually type cast here because TS cannot infer correctly in overloads
        _options
      )
    })
  );
  const enabled = () => toValue2(options.value.enabled);
  let lastEntry;
  const entry = computed2(
    () => (
      // 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 undefined values
      // https://github.com/posva/pinia-colada/issues/227
      // NOTE: _isPaused isn't reactive which meant that reentering a component
      // would never recompute the entry, so _isPaused was replaced
      // this makes the computed depend on nothing initially, but the `watch` on the entry
      // with immediate: true will trigger it again
      // https://github.com/posva/pinia-colada/issues/290
      isPaused?.value ? lastEntry : lastEntry = queryCache.ensure(options.value, lastEntry)
    )
  );
  lastEntry = entry.value;
  const errorCatcher = () => entry.value.state.value;
  const refresh = (throwOnError) => queryCache.refresh(entry.value, options.value).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.value).catch(
    // same as above
    throwOnError || errorCatcher
  );
  const isPlaceholderData = computed2(() => isEntryUsingPlaceholderData(entry.value));
  const state = computed2(
    () => isPlaceholderData.value ? {
      status: "success",
      data: entry.value.placeholderData,
      error: null
    } : entry.value.state.value
  );
  const extensions = {};
  for (const key in lastEntry.ext) {
    extensions[key] = computed2({
      get: () => toValue2(entry.value.ext[key]),
      set(value) {
        const target = entry.value.ext[key];
        if (isRef(target)) {
          ;
          target.value = value;
        } else {
          ;
          entry.value.ext[key] = value;
        }
      }
    });
  }
  const queryReturn = {
    ...extensions,
    state,
    status: computed2(() => state.value.status),
    data: computed2(() => state.value.data),
    error: computed2(() => entry.value.state.value.error),
    asyncStatus: computed2(() => entry.value.asyncStatus.value),
    isPlaceholderData,
    isPending: computed2(() => state.value.status === "pending"),
    isLoading: computed2(() => entry.value.asyncStatus.value === "loading"),
    refresh,
    refetch
  };
  if (hasCurrentInstance) {
    onServerPrefetch(async () => {
      if (toValue2(enabled)) await refresh(true);
    });
  }
  let isActive = false;
  if (hasCurrentInstance) {
    onMounted(() => {
      isActive = true;
      queryCache.track(lastEntry, hasCurrentInstance);
    });
    onUnmounted(() => {
      queryCache.untrack(lastEntry, hasCurrentInstance);
    });
  } else {
    isActive = true;
    if (currentEffect !== defineQueryEffect) {
      queryCache.track(lastEntry, currentEffect);
      onScopeDispose2(() => {
        queryCache.untrack(lastEntry, currentEffect);
      });
    }
  }
  watch(
    entry,
    (entry2, previousEntry) => {
      if (!isActive) return;
      if (previousEntry) {
        queryCache.untrack(previousEntry, hasCurrentInstance);
        queryCache.untrack(previousEntry, currentEffect);
      }
      queryCache.track(entry2, hasCurrentInstance);
      if (currentEffect !== defineQueryEffect) {
        queryCache.track(entry2, currentEffect);
      }
      if (toValue2(enabled)) refresh();
    },
    {
      immediate: true
    }
  );
  if (typeof enabled !== "boolean") {
    watch(enabled, (newEnabled) => {
      if (newEnabled) refresh();
    });
  }
  if (hasCurrentInstance) {
    onMounted(() => {
      if (enabled()) {
        const refetchControl = toValue2(options.value.refetchOnMount);
        if (refetchControl === "always") {
          refetch();
        } else if (refetchControl || queryReturn.status.value === "pending") {
          refresh();
        }
      }
    });
  }
  if (IS_CLIENT) {
    useEventListener(document, "visibilitychange", () => {
      const refetchControl = toValue2(options.value.refetchOnWindowFocus);
      if (document.visibilityState === "visible" && toValue2(enabled)) {
        if (refetchControl === "always") {
          refetch();
        } else if (refetchControl) {
          refresh();
        }
      }
    });
    useEventListener(window, "online", () => {
      if (toValue2(enabled)) {
        const refetchControl = toValue2(options.value.refetchOnReconnect);
        if (refetchControl === "always") {
          refetch();
        } else if (refetchControl) {
          refresh();
        }
      }
    });
  }
  return queryReturn;
}

// src/define-query.ts
var currentDefineQueryEffect;
function defineQuery(optionsOrSetup) {
  const setupFn = typeof optionsOrSetup === "function" ? optionsOrSetup : () => useQuery(optionsOrSetup);
  let hasBeenEnsured;
  let refCount = 0;
  return () => {
    const queryCache = useQueryCache();
    const previousEffect = currentDefineQueryEffect;
    const currentScope = getCurrentInstance3() || (currentDefineQueryEffect = getCurrentScope4());
    const [ensuredEntries, ret, scope, isPaused] = queryCache.ensureDefinedQuery(setupFn);
    if (hasBeenEnsured) {
      ensuredEntries.forEach((entry) => {
        if (entry.options?.refetchOnMount && toValue3(entry.options.enabled)) {
          if (toValue3(entry.options.refetchOnMount) === "always") {
            queryCache.fetch(entry).catch(noop);
          } else {
            queryCache.refresh(entry).catch(noop);
          }
        }
      });
    }
    hasBeenEnsured = true;
    if (currentScope) {
      refCount++;
      ensuredEntries.forEach((entry) => {
        queryCache.track(entry, currentScope);
      });
      onScopeDispose3(() => {
        ensuredEntries.forEach((entry) => {
          queryCache.untrack(entry, currentScope);
        });
        if (--refCount < 1) {
          scope.pause();
          isPaused.value = true;
        }
      });
    }
    currentDefineQueryEffect = previousEffect;
    return ret;
  };
}

// src/use-query-state.ts
import { computed as computed3, toValue as toValue4 } from "vue";
function useQueryState(...[_keyOrSetup, paramsGetter]) {
  const queryCache = useQueryCache();
  const key = paramsGetter ? computed3(
    () => (
      // NOTE: we manually type cast here because TS cannot infer correctly in overloads
      _keyOrSetup(
        toValue4(paramsGetter)
      ).key
    )
  ) : _keyOrSetup;
  const entry = computed3(() => queryCache.get(toValue4(key)));
  const state = computed3(() => entry.value?.state.value);
  const data = computed3(() => state.value?.data);
  const error = computed3(() => state.value?.error);
  const status = computed3(() => state.value?.status);
  const asyncStatus = computed3(() => entry.value?.asyncStatus.value);
  const isPending = computed3(() => !state.value || state.value.status === "pending");
  return {
    state,
    data,
    error,
    status,
    asyncStatus,
    isPending
  };
}

// src/infinite-query.ts
import { toValue as toValue5 } from "vue";
function useInfiniteQuery(options) {
  let pages = toValue5(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()
  };
}

// src/mutation-store.ts
import { defineStore as defineStore2, skipHydrate as skipHydrate2 } from "pinia";
import { customRef as customRef2, getCurrentScope as getCurrentScope5, shallowRef as shallowRef2 } from "vue";

// src/mutation-options.ts
import { inject as inject2 } from "vue";
var USE_MUTATION_DEFAULTS = {
  gcTime: 1e3 * 60
  // 1 minute
};
var USE_MUTATION_OPTIONS_KEY = process.env.NODE_ENV !== "production" ? Symbol("useMutationOptions") : Symbol();
var useMutationOptions = () => inject2(USE_MUTATION_OPTIONS_KEY, USE_MUTATION_DEFAULTS);

// src/mutation-store.ts
var MUTATION_STORE_ID = "_pc_mutation";
var useMutationCache = /* @__PURE__ */ defineStore2(MUTATION_STORE_ID, ({ action }) => {
  const cachesRaw = /* @__PURE__ */ new Map();
  let triggerCache;
  const caches = skipHydrate2(
    customRef2(
      (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 = getCurrentScope5();
  const globalOptions = useMutationOptions();
  const defineMutationMap = /* @__PURE__ */ new WeakMap();
  let nextMutationId = 0;
  const generateMutationId = () => `$${nextMutationId++}`;
  const create = action(
    (options, key, vars) => scope.run(
      () => ({
        id: "",
        // not a real id yet, indicates that the entry is not in the cache
        state: shallowRef2({
          status: "pending",
          data: void 0,
          error: null
        }),
        gcTimeout: void 0,
        asyncStatus: shallowRef2("idle"),
        when: 0,
        vars,
        key,
        keyHash: toCacheKey(key),
        options
      })
    )
  );
  function ensure(entry, vars) {
    const options = entry.options;
    const id = generateMutationId();
    const key = [...toValueWithArgs(options.key || [], vars), id];
    const keyHash = toCacheKey(key);
    if (process.env.NODE_ENV !== "production") {
      const badKey = key?.slice(0, -1).find(
        (k) => typeof k === "string" && k.startsWith("$") && String(Number(k.slice(1))) === k.slice(1)
      );
      if (badKey) {
        console.warn(
          `[@pinia/colada] A mutation entry was created with a reserved key part "${badKey}". Do not name keys with "$<number>" as these are reserved in mutations.`
        );
      }
    }
    entry = entry.id ? (untrack(entry), create(options, key, vars)) : entry;
    entry.id = id;
    entry.key = key;
    entry.keyHash = keyHash;
    entry.vars = vars;
    cachesRaw.set(keyHash, 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.keyHash) {
        cachesRaw.delete(entry.keyHash);
        triggerCache();
      }
    }
  );
  const getEntries = action((filters = {}) => {
    return filters.exact ? filters.key ? [caches.value.get(toCacheKey(filters.key))].filter((v) => !!v) : [] : [...find(caches.value, filters.key)].filter(
      (entry) => (filters.status == null || entry.state.value.status === filters.status) && (!filters.predicate || filters.predicate(entry))
    );
  });
  const untrack = action(
    (entry) => {
      if (entry.gcTimeout) return;
      if (Number.isFinite(entry.options.gcTime)) {
        entry.gcTimeout = setTimeout(() => {
          remove(entry);
        }, entry.options.gcTime);
      }
    }
  );
  async function mutate(entry) {
    const { vars, options } = entry;
    if (process.env.NODE_ENV !== "production") {
      const key = entry.key?.join("/");
      const keyMessage = key ? `with key "${key}"` : "without a key";
      if (!entry.id) {
        console.error(
          `[@pinia/colada] A mutation entry ${keyMessage} was mutated before being ensured. If you are manually calling the "mutationCache.mutate()", you should always ensure the entry first If not, this is probably a bug. Please, open an issue on GitHub with a boiled down reproduction.`
        );
      }
      if (
        // the entry has already an ongoing request
        entry.state.value.status !== "pending" || entry.asyncStatus.value === "loading"
      ) {
        console.error(
          `[@pinia/colada] A mutation entry ${keyMessage} was reused. If you are manually calling the "mutationCache.mutate()", you should always ensure the entry first: "mutationCache.mutate(mutationCache.ensure(entry, vars))". If not this is probably a bug. Please, open an issue on GitHub with a boiled down reproduction.`
        );
      }
    }
    entry.asyncStatus.value = "loading";
    let currentData;
    let currentError;
    let context = {};
    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
      );
      setEntryState(entry, {
        status: "success",
        data: newData,
        error: null
      });
    } catch (newError) {
      currentError = newError;
      await globalOptions.onError?.(currentError, vars, context);
      await options.onError?.(currentError, vars, context);
      setEntryState(entry, {
        status: "error",
        data: entry.state.value.data,
        error: currentError
      });
      throw newError;
    } finally {
      await globalOptions.onSettled?.(currentData, currentError, vars, context);
      await options.onSettled?.(currentData, currentError, vars, context);
      entry.asyncStatus.value = "idle";
    }
    return currentData;
  }
  return {
    caches,
    create,
    ensure,
    ensureDefinedMutation,
    mutate,
    remove,
    setEntryState,
    getEntries,
    untrack
  };
});

// src/use-mutation.ts
import {
  computed as computed4,
  shallowRef as shallowRef3,
  getCurrentInstance as getCurrentInstance4,
  getCurrentScope as getCurrentScope6,
  onUnmounted as onUnmounted2,
  onScopeDispose as onScopeDispose4
} from "vue";
function useMutation(options) {
  const mutationCache = useMutationCache();
  const hasCurrentInstance = getCurrentInstance4();
  const currentEffect = getCurrentScope6();
  const entry = shallowRef3(
    mutationCache.create(options)
  );
  if (hasCurrentInstance) {
    onUnmounted2(() => {
      mutationCache.untrack(entry.value);
    });
  }
  if (currentEffect) {
    onScopeDispose4(() => {
      mutationCache.untrack(entry.value);
    });
  }
  const state = computed4(() => entry.value.state.value);
  const status = computed4(() => state.value.status);
  const data = computed4(() => state.value.data);
  const error = computed4(() => state.value.error);
  const asyncStatus = computed4(() => entry.value.asyncStatus.value);
  const variables = computed4(() => entry.value.vars);
  async function mutateAsync(vars) {
    return mutationCache.mutate(
      // ensures we reuse the initial empty entry and adapt it or create a new one
      entry.value = mutationCache.ensure(entry.value, vars)
    );
  }
  function mutate(vars) {
    mutateAsync(vars).catch(noop);
  }
  function reset() {
    entry.value = mutationCache.create(options);
  }
  return {
    state,
    data,
    isLoading: computed4(() => 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/devtools/plugin.ts
import { setupDevtoolsPlugin } from "@vue/devtools-api";
import { watch as watch2 } from "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);
  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 = watch2(
            () => [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 || payload.inspectorId !== QUERY_INSPECTOR_ID) return;
        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,
          // TODO: if there is an exact match, we should put it at the top
          exact: false,
          // we also filter many
          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, {
    ...USE_MUTATION_DEFAULTS,
    ...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);
        });
      }
    });
  };
}
export {
  PiniaColada,
  PiniaColadaQueryHooksPlugin,
  defineMutation,
  defineQuery,
  defineQueryOptions,
  hydrateQueryCache,
  isQueryCache,
  serializeQueryCache,
  useInfiniteQuery,
  useMutation,
  useQuery,
  useQueryCache,
  useQueryState
};
//# sourceMappingURL=index.js.map