Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@pinia/colada

Package Overview
Dependencies
Maintainers
1
Versions
27
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@pinia/colada - npm Package Compare versions

Comparing version 0.11.1 to 0.12.0

589

dist/index.d.ts
import * as vue from 'vue';
import { ComputedRef, ShallowRef, EffectScope, ComponentInternalInstance, App, MaybeRefOrGetter, Ref } from 'vue';
import { MaybeRefOrGetter, ComputedRef, ShallowRef, EffectScope, ComponentInternalInstance, App } from 'vue';
import * as pinia from 'pinia';

@@ -52,2 +52,168 @@ import { Pinia } from 'pinia';

/**
* Allows you to extend the default types of the library.
*
* @example
* ```ts
* // types-extension.d.ts
* import '@pinia/colada'
* export {}
* declare module '@pinia/colada' {
* interface TypesConfig {
* defaultError: MyCustomError
* }
* }
* ```
*/
interface TypesConfig {
}
/**
* The default error type used.
* @internal
*/
type ErrorDefault = TypesConfig extends Record<'defaultError', infer E> ? E : Error;
/**
* `true` refetch if data is stale (refresh()), `false` never refetch, 'always' always refetch.
*/
type _RefetchOnControl = boolean | 'always';
/**
* Options for queries that can be globally overridden.
*/
interface UseQueryOptionsGlobal {
/**
* Whether the query should be enabled or not. If `false`, the query will not be executed until `refetch()` or
* `refresh()` is called. If it becomes `true`, the query will be refreshed.
*/
enabled?: MaybeRefOrGetter<boolean>;
/**
* Time in ms after which the data is considered stale and will be refreshed on next read.
* @default 5000 (5 seconds)
*/
staleTime?: number;
/**
* Time in ms after which, once the data is no longer being used, it will be garbage collected to free resources. Set to `false` to disable garbage collection.
* @default 300000 (5 minutes)
*/
gcTime?: number | false;
/**
* Whether to refetch the query when the component is mounted.
* @default true
*/
refetchOnMount?: MaybeRefOrGetter<_RefetchOnControl>;
/**
* Whether to refetch the query when the window regains focus.
* @default true
*/
refetchOnWindowFocus?: MaybeRefOrGetter<_RefetchOnControl>;
/**
* Whether to refetch the query when the network reconnects.
* @default true
*/
refetchOnReconnect?: MaybeRefOrGetter<_RefetchOnControl>;
}
/**
* Context object passed to the `query` function of `useQuery()`.
* @see {@link UseQueryOptions}
*/
interface UseQueryFnContext {
/**
* `AbortSignal` instance attached to the query call. If the call becomes outdated (e.g. due to a new call with the
* same key), the signal will be aborted.
*/
signal: AbortSignal;
}
/**
* Options for `useQuery()`. Can be extended by plugins.
*
* @example
* ```ts
* // use-query-plugin.d.ts
* export {} // needed
* declare module '@pinia/colada' {
* interface UseQueryOptions {
* // Whether to refresh the data when the component is mounted.
* refreshOnMount?: boolean
* }
* }
* ```
*/
interface UseQueryOptions<TResult = unknown, TError = ErrorDefault> {
/**
* The key used to identify the query. Array of primitives **without** reactive values or a reactive array or getter.
* It should be treaded as an array of dependencies of your queries, e.g. if you use the `route.params.id` property,
* it should also be part of the key:
*
* ```ts
* import { useRoute } from 'vue-router'
* import { useQuery } from '@pinia/colada'
*
* const route = useRoute()
* const { data } = useQuery({
* // pass a getter function (or computed, ref, etc.) to ensure reactivity
* key: () => ['user', route.params.id],
* query: () => fetchUser(route.params.id),
* })
* ```
*/
key: MaybeRefOrGetter<EntryKey>;
/**
* The function that will be called to fetch the data. It **must** be async.
*/
query: (context: UseQueryFnContext) => Promise<TResult>;
/**
* Whether the query should be enabled or not. If `false`, the query will not be executed until `refetch()` or
* `refresh()` is called. If it becomes `true`, the query will be refreshed.
*/
enabled?: MaybeRefOrGetter<boolean>;
/**
* Time in ms after which the data is considered stale and will be refreshed on next read.
* @default 5000 (5 seconds)
*/
staleTime?: number;
/**
* Time in ms after which, once the data is no longer being used, it will be garbage collected to free resources. Set to `false` to disable garbage collection.
* @default 300000 (5 minutes)
*/
gcTime?: number | false;
/**
* The data which is initially set to the query while the query is loading for the first time.
* Note: unlike with `placeholderData`, setting the initial data changes the state of the query (it will be set to `success`).
*/
initialData?: () => NoInfer<TResult>;
/**
* A placeholder data that is initially shown while the query is loading for the first time. This will also show the
* `status` as `success` until the query finishes loading (no matter the outcome of the query). Note: unlike with
* `initialData`, the placeholder does not change the cache state.
*/
placeholderData?: NoInfer<TResult> | (<T extends TResult>(previousData: T | undefined) => NoInfer<TResult> | null | undefined | void);
/**
* Whether to refetch the query when the component is mounted.
* @default true
*/
refetchOnMount?: MaybeRefOrGetter<_RefetchOnControl>;
/**
* Whether to refetch the query when the window regains focus.
* @default true
*/
refetchOnWindowFocus?: MaybeRefOrGetter<_RefetchOnControl>;
/**
* Whether to refetch the query when the network reconnects.
* @default true
*/
refetchOnReconnect?: MaybeRefOrGetter<_RefetchOnControl>;
}
/**
* Default options for `useQuery()`. Modifying this object will affect all the queries that don't override these
*/
declare const USE_QUERY_DEFAULTS: {
staleTime: number;
gcTime: NonNullable<UseQueryOptions["gcTime"]>;
refetchOnWindowFocus: NonNullable<UseQueryOptions["refetchOnWindowFocus"]>;
refetchOnReconnect: NonNullable<UseQueryOptions["refetchOnReconnect"]>;
refetchOnMount: NonNullable<UseQueryOptions["refetchOnMount"]>;
enabled: MaybeRefOrGetter<boolean>;
};
type UseQueryOptionsWithDefaults<TResult = unknown, TError = ErrorDefault> = UseQueryOptions<TResult, TError> & typeof USE_QUERY_DEFAULTS;
/**
* Key type for nodes in the tree map. Differently from {@link EntryKey}, this type is serializable to JSON.

@@ -95,2 +261,29 @@ */

}
/**
* Raw data of a query entry. Can be serialized from the server and used to hydrate the store.
* @internal
*/
type _UseQueryEntryNodeValueSerialized<TResult = unknown, TError = unknown> = [
/**
* The data returned by the query.
*/
data: TResult | undefined,
/**
* The error thrown by the query.
*/
error: TError | null,
/**
* When was this data fetched the last time in ms
*/
when?: number
];
/**
* Serialized version of a query entry node.
* @internal
*/
type UseQueryEntryNodeSerialized = [
key: EntryNodeKey,
value: undefined | _UseQueryEntryNodeValueSerialized,
children?: UseQueryEntryNodeSerialized[]
];

@@ -121,2 +314,9 @@ /**

}
/**
* Removes the `MaybeRefOrGetter` wrapper from all fields of an object.
* @internal
*/
type _RemoveMaybeRef<T> = {
[K in keyof T]: T[K] extends MaybeRefOrGetter<infer U> ? MaybeRefOrGetter<U> extends T[K] ? U : T[K] : T[K];
};

@@ -129,25 +329,2 @@ /**

/**
* Allows you to extend the default types of the library.
*
* @example
* ```ts
* // types-extension.d.ts
* import '@pinia/colada'
* export {}
* declare module '@pinia/colada' {
* interface TypesConfig {
* Error: MyCustomError
* }
* }
* ```
*/
interface TypesConfig {
}
/**
* The default error type used.
* @internal
*/
type ErrorDefault = TypesConfig extends Record<'Error', infer E> ? E : Error;
/**
* Valid keys for a mutation. Similar to query keys.

@@ -185,7 +362,2 @@ * @see {@link EntryKey}

/**
* Context object passed to the different hooks of a mutation like `onMutate`, `onSuccess`, `onError` and `onSettled`.
*/
interface UseMutationHooksContext {
}
/**
* Options to create a mutation.

@@ -229,4 +401,3 @@ */

*/
vars: NoInfer<TVars>, context: // always defined
UseMutationHooksContext & UseMutationGlobalContext) => _Awaitable<TContext | undefined | void | null>;
vars: NoInfer<TVars>, context: UseMutationGlobalContext) => _Awaitable<TContext | undefined | void | null>;
/**

@@ -247,3 +418,3 @@ * Runs if the mutation is successful.

*/
context: UseMutationGlobalContext & UseMutationHooksContext & _ReduceContext<NoInfer<TContext>>) => unknown;
context: UseMutationGlobalContext & _ReduceContext<NoInfer<TContext>>) => unknown;
/**

@@ -265,3 +436,4 @@ * Runs if the mutation encounters an error.

*/
context: (UseMutationHooksContext & Partial<Record<keyof UseMutationGlobalContext, never>> & Partial<Record<keyof _ReduceContext<NoInfer<TContext>>, never>>) | (UseMutationHooksContext & UseMutationGlobalContext & _ReduceContext<NoInfer<TContext>>)) => unknown;
context: // undefined if global onMutate throws, makes type narrowing easier for the user
(Partial<Record<keyof UseMutationGlobalContext, never>> & Partial<Record<keyof _ReduceContext<NoInfer<TContext>>, never>>) | (UseMutationGlobalContext & _ReduceContext<NoInfer<TContext>>)) => unknown;
/**

@@ -287,3 +459,4 @@ * Runs after the mutation is settled, regardless of the result.

*/
context: (UseMutationHooksContext & Partial<Record<keyof UseMutationGlobalContext, never>> & Partial<Record<keyof _ReduceContext<NoInfer<TContext>>, never>>) | (UseMutationHooksContext & UseMutationGlobalContext & _ReduceContext<NoInfer<TContext>>)) => unknown;
context: // undefined if global onMutate throws, makes type narrowing easier for the user
(Partial<Record<keyof UseMutationGlobalContext, never>> & Partial<Record<keyof _ReduceContext<NoInfer<TContext>>, never>>) | (UseMutationGlobalContext & _ReduceContext<NoInfer<TContext>>)) => unknown;
}

@@ -344,6 +517,7 @@ interface UseMutationReturn<TResult, TVars, TError> {

* ```ts
* const queryCache = useQueryCache()
* const { mutate, status, error } = useMutation({
* mutation: (id: number) => fetch(`/api/todos/${id}`),
* onSuccess({ queryClient }) {
* queryClient.invalidateQueries('todos')
* onSuccess() {
* queryCache.invalidateQueries('todos')
* },

@@ -396,2 +570,7 @@ * })

/**
* Allows defining extensions to the query entry that are returned by `useQuery()`.
*/
interface UseQueryEntryExtensions<TResult, TError> {
}
/**
* NOTE: Entries could be classes but the point of having all functions within the store is to allow plugins to hook

@@ -433,5 +612,17 @@ * into actions.

gcTimeout: ReturnType<typeof setTimeout> | undefined;
/**
* The current pending request.
*/
pending: null | {
/**
* The abort controller used to cancel the request and which `signal` is passed to the query function.
*/
abortController: AbortController;
/**
* The promise created by `queryCache.fetch` that is currently pending.
*/
refreshCall: Promise<DataState<TResult, TError>>;
/**
* When was this `pending` object created.
*/
when: number;

@@ -454,2 +645,6 @@ };

/**
* Extensions to the query entry added by plugins.
*/
ext: UseQueryEntryExtensions<TResult, TError>;
/**
* Component `__hmrId` to track wrong usage of `useQuery` and warn the user.

@@ -485,3 +680,3 @@ * @internal

/**
* If true or false, it will only return entries that match the stale status.
* If true or false, it will only return entries that match the stale status. Requires `entry.options` to be set.
*/

@@ -508,5 +703,14 @@ stale?: boolean;

type DefineQueryEntry = [entries: UseQueryEntry[], returnValue: unknown];
/**
* Composable to get the cache of the queries. As any other composable, it can be used inside the `setup` function of a
* component, within another composable, or in injectable contexts like stores and navigation guards.
*/
declare const useQueryCache: pinia.StoreDefinition<"_pc_query", Pick<{
caches: vue.ShallowReactive<TreeMapNode<UseQueryEntry<unknown, unknown>>>;
ensureDefinedQuery: <T>(fn: () => T) => DefineQueryEntry;
/**
* Scope to track effects and components that use the query cache.
* @internal
*/
_s: vue.Raw<EffectScope>;
setQueryData: <TResult = unknown>(key: EntryKey, data: TResult | ((oldData: TResult | undefined) => TResult)) => void;

@@ -519,10 +723,19 @@ getQueryData: <TResult = unknown>(key: EntryKey) => TResult | undefined;

refresh: <TResult, TError>(entry: UseQueryEntry<TResult, TError>) => Promise<DataState<TResult, TError>>;
ensure: <TResult = unknown, TError = Error>(options: UseQueryOptionsWithDefaults<TResult, TError>) => UseQueryEntry<TResult, TError>;
ensure: <TResult = unknown, TError = Error>(opts: UseQueryOptions<TResult, TError>) => UseQueryEntry<TResult, TError>;
extend: <TResult = unknown, TError = Error>(_entry: UseQueryEntry<TResult, TError>) => void;
track: (entry: UseQueryEntry, effect: EffectScope | ComponentInternalInstance | null | undefined) => void;
untrack: (entry: UseQueryEntry, effect: EffectScope | ComponentInternalInstance | undefined | null, store: ReturnType<typeof useQueryCache>) => void;
cancel: (entry: UseQueryEntry, reason?: unknown) => void;
create: <TResult, TError>(key: EntryNodeKey[], options?: UseQueryOptionsWithDefaults<TResult, TError> | null, initialData?: TResult, error?: TError | null, when?: number) => UseQueryEntry<TResult, TError>;
remove: (entry: UseQueryEntry) => void;
setEntryState: <TResult, TError>(entry: UseQueryEntry<TResult, TError>, state: DataState<NoInfer<TResult>, NoInfer<TError>>) => void;
getEntries: (filters?: UseQueryEntryFilter) => UseQueryEntry[];
}, "caches">, Pick<{
}, "caches" | "_s">, Pick<{
caches: vue.ShallowReactive<TreeMapNode<UseQueryEntry<unknown, unknown>>>;
ensureDefinedQuery: <T>(fn: () => T) => DefineQueryEntry;
/**
* Scope to track effects and components that use the query cache.
* @internal
*/
_s: vue.Raw<EffectScope>;
setQueryData: <TResult = unknown>(key: EntryKey, data: TResult | ((oldData: TResult | undefined) => TResult)) => void;

@@ -535,4 +748,8 @@ getQueryData: <TResult = unknown>(key: EntryKey) => TResult | undefined;

refresh: <TResult, TError>(entry: UseQueryEntry<TResult, TError>) => Promise<DataState<TResult, TError>>;
ensure: <TResult = unknown, TError = Error>(options: UseQueryOptionsWithDefaults<TResult, TError>) => UseQueryEntry<TResult, TError>;
ensure: <TResult = unknown, TError = Error>(opts: UseQueryOptions<TResult, TError>) => UseQueryEntry<TResult, TError>;
extend: <TResult = unknown, TError = Error>(_entry: UseQueryEntry<TResult, TError>) => void;
track: (entry: UseQueryEntry, effect: EffectScope | ComponentInternalInstance | null | undefined) => void;
untrack: (entry: UseQueryEntry, effect: EffectScope | ComponentInternalInstance | undefined | null, store: ReturnType<typeof useQueryCache>) => void;
cancel: (entry: UseQueryEntry, reason?: unknown) => void;
create: <TResult, TError>(key: EntryNodeKey[], options?: UseQueryOptionsWithDefaults<TResult, TError> | null, initialData?: TResult, error?: TError | null, when?: number) => UseQueryEntry<TResult, TError>;
remove: (entry: UseQueryEntry) => void;

@@ -544,2 +761,7 @@ setEntryState: <TResult, TError>(entry: UseQueryEntry<TResult, TError>, state: DataState<NoInfer<TResult>, NoInfer<TError>>) => void;

ensureDefinedQuery: <T>(fn: () => T) => DefineQueryEntry;
/**
* Scope to track effects and components that use the query cache.
* @internal
*/
_s: vue.Raw<EffectScope>;
setQueryData: <TResult = unknown>(key: EntryKey, data: TResult | ((oldData: TResult | undefined) => TResult)) => void;

@@ -552,8 +774,12 @@ getQueryData: <TResult = unknown>(key: EntryKey) => TResult | undefined;

refresh: <TResult, TError>(entry: UseQueryEntry<TResult, TError>) => Promise<DataState<TResult, TError>>;
ensure: <TResult = unknown, TError = Error>(options: UseQueryOptionsWithDefaults<TResult, TError>) => UseQueryEntry<TResult, TError>;
ensure: <TResult = unknown, TError = Error>(opts: UseQueryOptions<TResult, TError>) => UseQueryEntry<TResult, TError>;
extend: <TResult = unknown, TError = Error>(_entry: UseQueryEntry<TResult, TError>) => void;
track: (entry: UseQueryEntry, effect: EffectScope | ComponentInternalInstance | null | undefined) => void;
untrack: (entry: UseQueryEntry, effect: EffectScope | ComponentInternalInstance | undefined | null, store: ReturnType<typeof useQueryCache>) => void;
cancel: (entry: UseQueryEntry, reason?: unknown) => void;
create: <TResult, TError>(key: EntryNodeKey[], options?: UseQueryOptionsWithDefaults<TResult, TError> | null, initialData?: TResult, error?: TError | null, when?: number) => UseQueryEntry<TResult, TError>;
remove: (entry: UseQueryEntry) => void;
setEntryState: <TResult, TError>(entry: UseQueryEntry<TResult, TError>, state: DataState<NoInfer<TResult>, NoInfer<TError>>) => void;
getEntries: (filters?: UseQueryEntryFilter) => UseQueryEntry[];
}, "cancel" | "ensureDefinedQuery" | "setQueryData" | "getQueryData" | "invalidateQueries" | "cancelQueries" | "invalidate" | "fetch" | "refresh" | "ensure" | "remove" | "setEntryState" | "getEntries">>;
}, "cancel" | "ensure" | "ensureDefinedQuery" | "setQueryData" | "getQueryData" | "invalidateQueries" | "cancelQueries" | "invalidate" | "fetch" | "refresh" | "extend" | "track" | "untrack" | "create" | "remove" | "setEntryState" | "getEntries">>;
/**

@@ -564,29 +790,2 @@ * The cache of the queries. It's the store returned by {@link useQueryCache}.

/**
* Raw data of a query entry. Can be serialized from the server and used to hydrate the store.
* @internal
*/
type _UseQueryEntryNodeValueSerialized<TResult = unknown, TError = unknown> = [
/**
* The data returned by the query.
*/
data: TResult | undefined,
/**
* The error thrown by the query.
*/
error: TError | null,
/**
* When was this data fetched the last time in ms
*/
when?: number
];
/**
* Serialized version of a query entry node.
* @internal
*/
type UseQueryEntryNodeSerialized = [
key: EntryNodeKey,
value: undefined | _UseQueryEntryNodeValueSerialized,
children?: UseQueryEntryNodeSerialized[]
];
/**
* Transform a tree into a compressed array.

@@ -605,180 +804,21 @@ * @param root - root node of the tree

* @param raw - array af values created with {@link serializeTreeMap}
* @deprecated Not needed anymore the query cache handles reviving the map, only the serialization is needed.
*/
declare function reviveTreeMap(raw?: UseQueryEntryNodeSerialized[]): TreeMapNode<UseQueryEntry>;
/**
* Context passed to a Pinia Colada plugin.
* Hydrates the query cache with the serialized cache. Used during SSR.
* @param queryCache - query cache
* @param serializedCache - serialized cache
*/
interface PiniaColadaPluginContext {
/**
* The query cache used by the application.
*/
queryCache: QueryCache;
/**
* The Pinia instance used by the application.
*/
pinia: Pinia;
}
declare function hydrateQueryCache(queryCache: QueryCache, serializedCache: UseQueryEntryNodeSerialized[]): void;
/**
* A Pinia Colada plugin.
* Serializes the query cache to a compressed array. Used during SSR.
* @param queryCache - query cache
*/
interface PiniaColadaPlugin {
(context: PiniaColadaPluginContext): void;
}
declare function serializeQueryCache(queryCache: QueryCache): UseQueryEntryNodeSerialized[];
/**
* Options for the Pinia Colada plugin.
*/
interface PiniaColadaOptions extends Omit<UseQueryOptions, 'key' | 'query' | 'initialData' | 'transformError' | 'placeholderData'> {
/**
* Pinia instance to use. This is only needed if installing before the Pinia plugin.
*/
pinia?: Pinia;
/**
* Pinia Colada plugins to install.
*/
plugins?: PiniaColadaPlugin[];
/**
* Function to ensure the `error` property is always an instance of the default global type error. Defaults to the
* identity function.
*
* @param error - error thrown
*/
transformError?: (error: unknown) => ErrorDefault;
/**
* Executes setup code inside `useQuery()` to add custom behavior to all queries. **Must be synchronous**.
* @experimental still going through testing to see what is needed
*
* @param context - properties of the `useQuery` return value and the options
*/
setup?: <TResult = unknown, TError = unknown>(useQueryReturn: UseQueryReturn<TResult, TError>, options: UseQueryOptionsWithDefaults<TResult, TError>) => UseQueryReturn<TResult, TError>;
}
/**
* Plugin that installs the Query and Mutation plugins alongside some extra plugins.
*
* @see {@link QueryPlugin} to only install the Query plugin.
* @see {@link MutationPlugin} to only install the Query plugin.
*
* @param app - Vue App
* @param options - Pinia Colada options
*/
declare function PiniaColada(app: App, options?: PiniaColadaOptions): void;
/**
* `true` refetch if data is stale (refresh()), `false` never refetch, 'always' always refetch.
*/
type _RefetchOnControl = boolean | 'always';
/**
* Context object passed to the `query` function of `useQuery()`.
* @see {@link UseQueryOptions}
*/
interface UseQueryFnContext {
/**
* `AbortSignal` instance attached to the query call. If the call becomes outdated (e.g. due to a new call with the
* same key), the signal will be aborted.
*/
signal: AbortSignal;
}
/**
* Options for `useQuery()`. Can be extended by plugins.
*
* @example
* ```ts
* // use-query-plugin.d.ts
* export {} // needed
* declare module '@pinia/colada' {
* interface UseQueryOptions {
* // Whether to refresh the data when the component is mounted.
* refreshOnMount?: boolean
* }
* }
* ```
*/
interface UseQueryOptions<TResult = unknown, TError = ErrorDefault> {
/**
* The key used to identify the query. Array of primitives **without** reactive values or a reactive array or getter.
* It should be treaded as an array of dependencies of your queries, e.g. if you use the `route.params.id` property,
* it should also be part of the key:
*
* ```ts
* import { useRoute } from 'vue-router'
* import { useQuery } from '@pinia/colada'
*
* const route = useRoute()
* const { data } = useQuery({
* // pass a getter function (or computed, ref, etc.) to ensure reactivity
* key: () => ['user', route.params.id],
* query: () => fetchUser(route.params.id),
* })
* ```
*/
key: MaybeRefOrGetter<EntryKey>;
/**
* The function that will be called to fetch the data. It **must** be async.
*/
query: (context: UseQueryFnContext) => Promise<TResult>;
/**
* Whether the query should be enabled or not. If `false`, the query will not be executed until `refetch()` or
* `refresh()` is called. If it becomes `true`, the query will be refreshed.
*/
enabled?: MaybeRefOrGetter<boolean>;
/**
* Time in ms after which the data is considered stale and will be refreshed on next read.
* @default 5000 (5 seconds)
*/
staleTime?: number;
/**
* Time in ms after which, once the data is no longer being used, it will be garbage collected to free resources. Set to `false` to disable garbage collection.
* @default 300000 (5 minutes)
*/
gcTime?: number | false;
/**
* The data which is initially set to the query while the query is loading for the first time.
* Note: unlike with `placeholderData`, setting the initial data changes the state of the query (it will be set to `success`).
*/
initialData?: () => NoInfer<TResult>;
/**
* A placeholder data that is initially shown while the query is loading for the first time. This will also show the
* `status` as `success` until the query finishes loading (no matter the outcome of the query). Note: unlike with
* `initialData`, the placeholder does not change the cache state.
*/
placeholderData?: NoInfer<TResult> | (<T extends TResult>(previousData: T | undefined) => NoInfer<TResult> | null | undefined | void);
/**
* Function to type and ensure the `error` property is always an instance of `TError`.
*
* @param error - error thrown
* @example
* ```ts
* useQuery({
* key: ['user', id],
* query: () => fetchUser(id),
* transformError: (error): MyCustomError | UnexpectedError =>
* // this assumes both `MyCustomError` and a `UnexpectedError` are valid error classes
* error instanceof MyCustomError ? error : new UnexpectedError(error),
* })
* ```
*/
transformError?: (error: unknown) => TError;
refetchOnMount?: _RefetchOnControl;
refetchOnWindowFocus?: _RefetchOnControl;
refetchOnReconnect?: _RefetchOnControl;
}
/**
* Default options for `useQuery()`. Modifying this object will affect all the queries that don't override these
*/
declare const USE_QUERY_DEFAULTS: {
staleTime: number;
gcTime: NonNullable<UseQueryOptions["gcTime"]>;
refetchOnWindowFocus: _RefetchOnControl;
refetchOnReconnect: _RefetchOnControl;
refetchOnMount: _RefetchOnControl;
enabled: MaybeRefOrGetter<boolean>;
transformError: (error: unknown) => any;
};
type UseQueryOptionsWithDefaults<TResult = unknown, TError = ErrorDefault> = UseQueryOptions<TResult, TError> & typeof USE_QUERY_DEFAULTS;
/**
* Return type of `useQuery()`.
*/
interface UseQueryReturn<TResult = unknown, TError = ErrorDefault> {
interface UseQueryReturn<TResult = unknown, TError = ErrorDefault> extends UseQueryEntryExtensions<TResult, TError> {
/**

@@ -838,4 +878,10 @@ * The state of the query. Contains its data, error, and status.

/**
* Options to define a query with `defineQuery()`. Similar to {@link UseQueryOptions} but disallows reactive values as
* `defineQuery()` is used outside of an effect scope.
*/
interface DefineQueryOptions<TResult = unknown, TError = ErrorDefault> extends _RemoveMaybeRef<UseQueryOptions<TResult, TError>> {
}
/**
* Define a query with the given options. Similar to `useQuery(options)` but allows you to reuse the query in multiple
* places.
* places. It only allow static values in options. If you need dynamic values, use the function version.
*

@@ -851,3 +897,3 @@ * @param options - the options to define the query

*/
declare function defineQuery<TResult, TError = ErrorDefault>(options: UseQueryOptions<TResult, TError>): () => UseQueryReturn<TResult, TError>;
declare function defineQuery<TResult, TError = ErrorDefault>(options: DefineQueryOptions<TResult, TError>): () => UseQueryReturn<TResult, TError>;
/**

@@ -876,2 +922,51 @@ * Define a query with a setup function. Allows to return arbitrary values from the query function, create contextual

/**
* Context passed to a Pinia Colada plugin.
*/
interface PiniaColadaPluginContext {
/**
* The query cache used by the application.
*/
queryCache: QueryCache;
/**
* The Pinia instance used by the application.
*/
pinia: Pinia;
/**
* An effect scope to collect effects. It should be used if you use any reactivity API like `ref()`, `watch()`, `computed()`, etc.
* @see https://vuejs.org/api/reactivity-advanced.html#effectscope
*/
scope: EffectScope;
}
/**
* A Pinia Colada plugin.
*/
interface PiniaColadaPlugin {
(context: PiniaColadaPluginContext): void;
}
/**
* Options for the Pinia Colada plugin.
*/
interface PiniaColadaOptions extends UseQueryOptionsGlobal {
/**
* Pinia instance to use. This is only needed if installing before the Pinia plugin.
*/
pinia?: Pinia;
/**
* Pinia Colada plugins to install.
*/
plugins?: PiniaColadaPlugin[];
}
/**
* Plugin that installs the Query and Mutation plugins alongside some extra plugins.
*
* @see {@link QueryPlugin} to only install the Query plugin.
* @see {@link MutationPlugin} to only install the Query plugin.
*
* @param app - Vue App
* @param options - Pinia Colada options
*/
declare function PiniaColada(app: App, options?: PiniaColadaOptions): void;
/**
* Options for {@link PiniaColadaQueryHooksPlugin}.

@@ -927,16 +1022,2 @@ */

/**
* Creates a delayed computed ref from an existing ref, computed, or getter. Use this to delay a loading state (`isLoading`, `isLoading`) to avoid flickering.
*
* @example
* ```ts
* const { isLoading: _isLoading } = useQuery({ queryKey: 'todos', queryFn: fetchTodos })
* const isLoading = delayLoadingRef(_isLoading, 300)
* ```
*
* @param refOrGetter - ref or getter to delay
* @param delay - delay in ms
*/
declare function delayLoadingRef(refOrGetter: Ref<boolean> | ComputedRef<boolean> | (() => boolean), delay?: number): ComputedRef<boolean>;
export { type AsyncStatus, type DataState, type DataStateStatus, type DataState_Error, type DataState_Pending, type DataState_Success, type EntryKey, type EntryNodeKey, PiniaColada, type PiniaColadaOptions, type PiniaColadaPlugin, type PiniaColadaPluginContext, PiniaColadaQueryHooksPlugin, type PiniaColadaQueryHooksPluginOptions, TreeMapNode, type TypesConfig, type UseMutationOptions, type UseMutationReturn, type UseQueryEntry, type UseQueryOptions, type UseQueryOptionsWithDefaults, type UseQueryReturn, type _Awaitable, type _DataState_Base, type _EmptyObject, type _MaybeArray, type _ReduceContext, defineMutation, defineQuery, delayLoadingRef, reviveTreeMap, serialize, serializeTreeMap, useMutation, useQuery, useQueryCache };
export { type AsyncStatus, type DataState, type DataStateStatus, type DataState_Error, type DataState_Pending, type DataState_Success, type EntryKey, type EntryNodeKey, PiniaColada, type PiniaColadaOptions, type PiniaColadaPlugin, type PiniaColadaPluginContext, PiniaColadaQueryHooksPlugin, type PiniaColadaQueryHooksPluginOptions, TreeMapNode, type TypesConfig, type UseMutationOptions, type UseMutationReturn, type UseQueryEntry, type UseQueryEntryExtensions, type UseQueryEntryFilter, type UseQueryOptions, type UseQueryOptionsWithDefaults, type UseQueryReturn, type _Awaitable, type _DataState_Base, type _EmptyObject, type _MaybeArray, type _ReduceContext, defineMutation, defineQuery, hydrateQueryCache, reviveTreeMap, serialize, serializeQueryCache, serializeTreeMap, useMutation, useQuery, useQueryCache };
// src/use-mutation.ts
import { computed as computed2, shallowRef as shallowRef3 } from "vue";
import { computed as computed2, shallowRef as shallowRef2 } from "vue";
// src/mutation-store.ts
import { defineStore as defineStore2 } from "pinia";
import { getCurrentScope as getCurrentScope3, shallowReactive as shallowReactive2, shallowRef as shallowRef2 } from "vue";
import { defineStore } from "pinia";
import { getCurrentScope as getCurrentScope2, shallowReactive, shallowRef } from "vue";

@@ -85,2 +85,18 @@ // src/tree-map.ts

};
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);
}
}
}

@@ -124,58 +140,221 @@ // src/utils.ts

// src/mutation-store.ts
function createMutationEntry(options, key, vars) {
return {
state: shallowRef({
status: "pending",
data: void 0,
error: null
}),
asyncStatus: shallowRef("idle"),
when: 0,
vars: shallowRef(vars),
key,
options,
pending: null
};
}
var useMutationCache = /* @__PURE__ */ defineStore(
"_pc_mutation",
({ action }) => {
const cachesRaw = new TreeMapNode();
const caches = shallowReactive(cachesRaw);
function ensure(options, entry, vars) {
const key = vars && toValueWithArgs(options.key, vars)?.map(stringifyFlatObject);
if (!entry) {
entry = createMutationEntry(options, key);
if (key) {
caches.set(
key,
// @ts-expect-error: function types with generics are incompatible
entry
);
}
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
);
caches.set(
key,
// @ts-expect-error: function types with generics are incompatible
entry
);
}
}
return entry;
}
const scope = getCurrentScope2();
const defineMutationMap = /* @__PURE__ */ new WeakMap();
const ensureDefinedMutation = action((fn) => {
let defineMutationResult = defineMutationMap.get(fn);
if (!defineMutationResult) {
defineMutationMap.set(fn, defineMutationResult = scope.run(fn));
}
return defineMutationResult;
});
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 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 = {
// ...globalOnMutateContext!,
...onMutateContext
// NOTE: needed for onSuccess cast
};
const newData = currentData = await options.mutation(
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) {
currentEntry.state.value = {
status: "success",
data: newData,
error: null
};
}
} catch (newError) {
currentError = newError;
await options.onError?.(newError, vars, context);
if (currentEntry.pending === currentCall) {
currentEntry.state.value = {
status: "error",
data: currentEntry.state.value.data,
error: newError
};
}
throw newError;
} finally {
await options.onSettled?.(currentData, currentError, vars, context);
currentEntry.asyncStatus.value = "idle";
}
return currentData;
}
return { ensure, ensureDefinedMutation, caches, mutate };
}
);
// src/use-mutation.ts
function useMutation(options) {
const mutationCache = useMutationCache();
const entry = shallowRef2(
mutationCache.ensure(options)
);
const state = computed2(() => entry.value.state.value);
const status = computed2(() => state.value.status);
const data = computed2(() => state.value.data);
const error = computed2(() => state.value.error);
const asyncStatus = computed2(() => entry.value.asyncStatus.value);
const variables = computed2(() => 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() {
entry.value.state.value = {
status: "pending",
data: void 0,
error: null
};
entry.value.asyncStatus.value = "idle";
}
return {
state,
data,
isLoading: computed2(() => 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/use-query.ts
import {
computed as computed3,
getCurrentInstance as getCurrentInstance3,
getCurrentScope as getCurrentScope5,
isRef,
onMounted,
onScopeDispose as onScopeDispose3,
onServerPrefetch,
onUnmounted,
toValue as toValue2,
watch
} from "vue";
// src/query-store.ts
import { defineStore } from "pinia";
import { defineStore as defineStore2, skipHydrate } from "pinia";
import {
getCurrentInstance,
getCurrentScope as getCurrentScope2,
getCurrentScope as getCurrentScope3,
hasInjectionContext,
markRaw,
shallowReactive,
shallowRef,
shallowReactive as shallowReactive2,
shallowRef as shallowRef3,
toValue
} from "vue";
function queryEntry_addDep(entry, effect) {
if (!effect) return;
entry.deps.add(effect);
clearTimeout(entry.gcTimeout);
}
function queryEntry_removeDep(entry, effect, store) {
if (!effect) return;
entry.deps.delete(effect);
if (entry.deps.size > 0 || !entry.options) return;
clearTimeout(entry.gcTimeout);
if (Number.isFinite(entry.options.gcTime)) {
entry.gcTimeout = setTimeout(() => {
store.remove(entry);
}, entry.options.gcTime);
}
}
function createQueryEntry(key, initialData, error = null, when = 0) {
const state = shallowRef(
// @ts-expect-error: to make the code shorter we are using one declaration instead of multiple ternaries
{
data: initialData,
error,
status: error ? "error" : initialData !== void 0 ? "success" : "pending"
}
);
const asyncStatus = shallowRef("idle");
return 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: markRaw(/* @__PURE__ */ new Set()),
gcTimeout: void 0,
options: null,
get stale() {
return Date.now() >= this.when + this.options.staleTime;
},
get active() {
return this.deps.size > 0;
}
});
}
// 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/query-store.ts
var START_EXT = {};
var queryEntry_toJSON = ({

@@ -186,6 +365,50 @@ state: { value },

var QUERY_STORE_ID = "_pc_query";
var useQueryCache = /* @__PURE__ */ defineStore(QUERY_STORE_ID, ({ action }) => {
var useQueryCache = /* @__PURE__ */ defineStore2(QUERY_STORE_ID, ({ action }) => {
const cachesRaw = new TreeMapNode();
const caches = shallowReactive(cachesRaw);
const scope = getCurrentScope2();
const caches = skipHydrate(shallowReactive2(cachesRaw));
const scope = getCurrentScope3();
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 = shallowRef3(
// @ts-expect-error: to make the code shorter we are using one declaration instead of multiple ternaries
{
data: initialData,
error,
status: error ? "error" : initialData !== void 0 ? "success" : "pending"
}
);
const asyncStatus = shallowRef3("idle");
return 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: 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 Date.now() >= this.when + this.options.staleTime;
},
get active() {
return this.deps.size > 0;
}
});
})
);
let currentDefineQueryEntry;

@@ -203,3 +426,3 @@ const defineQueryMap = /* @__PURE__ */ new WeakMap();

if (queryEntry.options?.refetchOnMount) {
if (queryEntry.options.refetchOnMount === "always") {
if (toValue(queryEntry.options.refetchOnMount) === "always") {
fetch(queryEntry);

@@ -214,2 +437,18 @@ } else {

});
function track(entry, effect) {
if (!effect) return;
entry.deps.add(effect);
clearTimeout(entry.gcTimeout);
}
function untrack(entry, effect, store) {
if (!effect) return;
entry.deps.delete(effect);
if (entry.deps.size > 0 || !entry.options) return;
clearTimeout(entry.gcTimeout);
if (Number.isFinite(entry.options.gcTime)) {
entry.gcTimeout = setTimeout(() => {
store.remove(entry);
}, entry.options.gcTime);
}
}
const invalidateQueries = action(

@@ -241,3 +480,7 @@ (filters) => {

const ensure = action(
(options) => {
(opts) => {
const options = {
...optionDefaults,
...opts
};
const key = toValue(options.key).map(stringifyFlatObject);

@@ -251,8 +494,3 @@ if (process.env.NODE_ENV !== "production" && key.length === 0) {

if (!entry) {
cachesRaw.set(
key,
entry = scope.run(
() => createQueryEntry(key, options.initialData?.())
)
);
cachesRaw.set(key, entry = create(key, options, options.initialData?.()));
}

@@ -265,3 +503,3 @@ if (process.env.NODE_ENV !== "production") {

entry.__hmr.id = currentInstance.type.__hmrId;
if (entry.__hmr.id == null && process.env.NODE_ENV !== "test") {
if (entry.__hmr.id == null && process.env.NODE_ENV !== "test" && typeof document !== "undefined") {
warnOnce(

@@ -274,2 +512,6 @@ `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);

@@ -279,2 +521,6 @@ return entry;

);
const extend = action(
(_entry) => {
}
);
const invalidate = action((entry) => {

@@ -312,3 +558,3 @@ entry.when = 0;

refreshCall: (async () => entry.options.query({ signal }))().then((data) => {
if (pendingCall === entry.pending && !signal.aborted) {
if (pendingCall === entry.pending) {
setEntryState(entry, {

@@ -366,3 +612,3 @@ data,

cacheKey,
entry = scope.run(() => createQueryEntry(cacheKey))
entry = create(cacheKey)
);

@@ -378,8 +624,5 @@ }

);
const getQueryData = action(
(key) => {
const entry = caches.get(key.map(stringifyFlatObject));
return entry?.state.value.data;
}
);
function getQueryData(key) {
return caches.get(key.map(stringifyFlatObject))?.state.value.data;
}
const remove = action(

@@ -391,7 +634,10 @@ /**

);
const _preload = action((_useQueryFn) => {
});
return {
caches,
ensureDefinedQuery,
/**
* Scope to track effects and components that use the query cache.
* @internal
*/
_s: markRaw(scope),
setQueryData,

@@ -406,3 +652,7 @@ getQueryData,

ensure,
extend,
track,
untrack,
cancel,
create,
remove,

@@ -446,214 +696,44 @@ setEntryState,

}
// src/mutation-store.ts
function createMutationEntry(options, key, vars) {
return {
state: shallowRef2({
status: "pending",
data: void 0,
error: null
}),
asyncStatus: shallowRef2("idle"),
when: 0,
vars: shallowRef2(vars),
key,
options,
pending: null
};
}
var useMutationCache = /* @__PURE__ */ defineStore2(
"_pc_mutation",
({ action }) => {
const cachesRaw = new TreeMapNode();
const caches = shallowReactive2(cachesRaw);
const queryCache = useQueryCache();
function ensure(options, entry, vars) {
const key = vars && toValueWithArgs(options.key, vars)?.map(stringifyFlatObject);
if (!entry) {
entry = createMutationEntry(options, key);
if (key) {
caches.set(
key,
// @ts-expect-error: function types with generics are incompatible
entry
);
}
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
);
caches.set(
key,
// @ts-expect-error: function types with generics are incompatible
entry
);
}
}
return entry;
function createQueryEntry(key, initialData, error = null, when = 0, options = null) {
const state = shallowRef3(
// @ts-expect-error: to make the code shorter we are using one declaration instead of multiple ternaries
{
data: initialData,
error,
status: error ? "error" : initialData !== void 0 ? "success" : "pending"
}
const scope = getCurrentScope3();
const defineMutationMap = /* @__PURE__ */ new WeakMap();
const ensureDefinedMutation = action((fn) => {
let defineMutationResult = defineMutationMap.get(fn);
if (!defineMutationResult) {
defineMutationMap.set(fn, defineMutationResult = scope.run(fn));
}
return defineMutationResult;
});
async function mutate(currentEntry, vars) {
currentEntry.asyncStatus.value = "loading";
currentEntry.vars.value = vars;
let currentData;
let currentError;
const { options } = currentEntry;
let context = {
queryCache
};
const currentCall = currentEntry.pending = Symbol();
try {
let 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>
);
const newData = currentData = await options.mutation(
vars,
onMutateContext
);
context = {
...globalOnMutateContext,
queryCache,
...onMutateContext
// NOTE: needed for onSuccess cast
};
await options.onSuccess?.(
newData,
vars,
// NOTE: cast is safe because of the satisfies above
// using a spread also works
context
);
if (currentEntry.pending === currentCall) {
currentEntry.state.value = {
status: "success",
data: newData,
error: null
};
}
} catch (newError) {
currentError = newError;
await options.onError?.(newError, vars, context);
if (currentEntry.pending === currentCall) {
currentEntry.state.value = {
status: "error",
data: currentEntry.state.value.data,
error: newError
};
}
throw newError;
} finally {
await options.onSettled?.(currentData, currentError, vars, context);
currentEntry.asyncStatus.value = "idle";
}
return currentData;
}
return { ensure, ensureDefinedMutation, caches, mutate };
}
);
// src/use-mutation.ts
function useMutation(options) {
const mutationCache = useMutationCache();
const entry = shallowRef3(
mutationCache.ensure(options)
);
const state = computed2(() => entry.value.state.value);
const status = computed2(() => state.value.status);
const data = computed2(() => state.value.data);
const error = computed2(() => state.value.error);
const asyncStatus = computed2(() => entry.value.asyncStatus.value);
const variables = computed2(() => 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() {
entry.value.state.value = {
status: "pending",
data: void 0,
error: null
};
entry.value.asyncStatus.value = "idle";
}
return {
const asyncStatus = shallowRef3("idle");
return markRaw({
key,
state,
data,
isLoading: computed2(() => asyncStatus.value === "loading"),
status,
variables,
placeholderData: null,
when,
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
};
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 Date.now() >= this.when + this.options.staleTime;
},
get active() {
return this.deps.size > 0;
}
});
}
// src/define-mutation.ts
function defineMutation(optionsOrSetup) {
const setupFn = typeof optionsOrSetup === "function" ? optionsOrSetup : () => useMutation(optionsOrSetup);
return () => {
const mutationCache = useMutationCache();
return mutationCache.ensureDefinedMutation(setupFn);
};
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
import {
computed as computed3,
getCurrentInstance as getCurrentInstance3,
getCurrentScope as getCurrentScope5,
onMounted,
onScopeDispose as onScopeDispose3,
onServerPrefetch,
onUnmounted,
toValue as toValue2,
watch
} 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,
// as any to simplify the typing with generics
transformError: (error) => error
};
var USE_QUERY_OPTIONS_KEY = process.env.NODE_ENV !== "production" ? Symbol("useQueryOptions") : Symbol();
var useQueryOptions = () => inject(USE_QUERY_OPTIONS_KEY);
// src/define-query.ts

@@ -672,9 +752,9 @@ import {

return () => {
const store = useQueryCache();
const queryCache = useQueryCache();
const previousEffect = currentDefineQueryEffect;
const currentScope = getCurrentInstance2() || (currentDefineQueryEffect = getCurrentScope4());
const [entries, ret] = store.ensureDefinedQuery(setupFn);
const [entries, ret] = queryCache.ensureDefinedQuery(setupFn);
if (currentScope) {
entries.forEach((entry) => {
queryEntry_addDep(entry, currentScope);
queryCache.track(entry, currentScope);
if (process.env.NODE_ENV !== "production") {

@@ -687,3 +767,3 @@ entry.__hmr ??= {};

entries.forEach((entry) => {
queryEntry_removeDep(entry, currentScope, store);
queryCache.untrack(entry, currentScope, queryCache);
});

@@ -699,6 +779,6 @@ });

function useQuery(_options) {
const cacheEntries = useQueryCache();
const USE_QUERY_DEFAULTS2 = useQueryOptions();
const queryCache = useQueryCache();
const optionDefaults = useQueryOptions();
const options = {
...USE_QUERY_DEFAULTS2,
...optionDefaults,
..._options

@@ -710,3 +790,3 @@ };

if (currentInstance) {
const entry2 = cacheEntries.getEntries({
const entry2 = queryCache.getEntries({
exact: true,

@@ -729,5 +809,5 @@ key: toValue2(options.key)

}
const entry = computed3(() => cacheEntries.ensure(options));
const entry = computed3(() => queryCache.ensure(options));
const errorCatcher = () => entry.value.state.value;
const refresh = (throwOnError) => cacheEntries.refresh(entry.value).catch(
const refresh = (throwOnError) => queryCache.refresh(entry.value).catch(
// true is not allowed but it works per spec as only callable onRejected are used

@@ -739,3 +819,3 @@ // https://tc39.es/ecma262/multipage/control-abstraction-objects.html#sec-performpromisethen

);
const refetch = (throwOnError) => cacheEntries.fetch(entry.value).catch(
const refetch = (throwOnError) => queryCache.fetch(entry.value).catch(
// same as above

@@ -754,3 +834,18 @@ throwOnError || errorCatcher

);
const extensions = {};
for (const key in entry.value.ext) {
extensions[key] = computed3({
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,

@@ -771,3 +866,3 @@ status: computed3(() => state.value.status),

onServerPrefetch(async () => {
if (toValue2(enabled)) await refresh();
if (toValue2(enabled)) await refresh(true);
});

@@ -779,6 +874,6 @@ }

isActive = true;
queryEntry_addDep(entry.value, hasCurrentInstance);
queryCache.track(entry.value, hasCurrentInstance);
});
onUnmounted(() => {
queryEntry_removeDep(entry.value, hasCurrentInstance, cacheEntries);
queryCache.untrack(entry.value, hasCurrentInstance, queryCache);
});

@@ -788,5 +883,5 @@ } else {

if (currentEffect) {
queryEntry_addDep(entry.value, currentEffect);
queryCache.track(entry.value, currentEffect);
onScopeDispose3(() => {
queryEntry_removeDep(entry.value, currentEffect, cacheEntries);
queryCache.untrack(entry.value, currentEffect, queryCache);
});

@@ -807,7 +902,7 @@ }

if (previousEntry) {
queryEntry_removeDep(previousEntry, hasCurrentInstance, cacheEntries);
queryEntry_removeDep(previousEntry, currentEffect, cacheEntries);
queryCache.untrack(previousEntry, hasCurrentInstance, queryCache);
queryCache.untrack(previousEntry, currentEffect, queryCache);
}
queryEntry_addDep(entry2, hasCurrentInstance);
queryEntry_addDep(entry2, currentEffect);
queryCache.track(entry2, hasCurrentInstance);
queryCache.track(entry2, currentEffect);
if (toValue2(enabled)) refresh();

@@ -837,3 +932,3 @@ },

if (document.visibilityState === "visible" && toValue2(enabled)) {
if (refetchOnWindowFocus === "always") {
if (toValue2(refetchOnWindowFocus) === "always") {
refetch();

@@ -849,3 +944,3 @@ } else {

if (toValue2(enabled)) {
if (refetchOnReconnect === "always") {
if (toValue2(refetchOnReconnect) === "always") {
refetch();

@@ -859,3 +954,3 @@ } else {

}
return options.setup?.(queryReturn, options) || queryReturn;
return queryReturn;
}

@@ -879,3 +974,10 @@

}
plugins?.forEach((plugin) => plugin({ queryCache: useQueryCache(pinia), pinia }));
const queryCache = useQueryCache(pinia);
plugins?.forEach(
(plugin) => plugin({
scope: queryCache._s,
queryCache,
pinia
})
);
}

@@ -901,24 +1003,2 @@

}
// src/delay-loading.ts
import { computed as computed4, onScopeDispose as onScopeDispose4, ref, toValue as toValue3, watch as watch2 } from "vue";
function delayLoadingRef(refOrGetter, delay = 300) {
const isDelayElapsed = ref(toValue3(refOrGetter));
const newRef = computed4(() => toValue3(refOrGetter) && isDelayElapsed.value);
let timeout;
const stop = () => {
clearTimeout(timeout);
};
watch2(refOrGetter, (value) => {
stop();
if (value) {
isDelayElapsed.value = false;
timeout = setTimeout(() => {
isDelayElapsed.value = true;
}, delay);
}
});
onScopeDispose4(stop);
return newRef;
}
export {

@@ -930,5 +1010,6 @@ PiniaColada,

defineQuery,
delayLoadingRef,
hydrateQueryCache,
reviveTreeMap,
serialize,
serializeQueryCache,
serializeTreeMap,

@@ -935,0 +1016,0 @@ useMutation,

{
"name": "@pinia/colada",
"type": "module",
"version": "0.11.1",
"version": "0.12.0",
"description": "The smart data fetching layer for Pinia",

@@ -84,8 +84,8 @@ "publishConfig": {

"@antfu/eslint-config": "^3.8.0",
"@shikijs/vitepress-twoslash": "^1.22.1",
"@shikijs/vitepress-twoslash": "^1.22.2",
"@size-limit/preset-small-lib": "^11.1.6",
"@types/node": "^22.8.1",
"@types/node": "^22.8.7",
"@vitejs/plugin-vue": "^5.1.4",
"@vitest/coverage-v8": "^2.1.3",
"@vitest/ui": "^2.1.3",
"@vitest/coverage-v8": "^2.1.4",
"@vitest/ui": "^2.1.4",
"@vue/test-utils": "^2.4.6",

@@ -95,10 +95,10 @@ "chalk": "^5.3.0",

"enquirer": "^2.4.1",
"eslint": "^9.13.0",
"execa": "^9.5.0",
"eslint": "^9.14.0",
"execa": "^9.5.1",
"globby": "^14.0.2",
"happy-dom": "^15.7.4",
"happy-dom": "^15.8.3",
"lint-staged": "^15.2.10",
"minimist": "^1.2.8",
"p-series": "^3.0.0",
"pinia": "^2.2.4",
"pinia": "^2.2.6",
"prettier": "^3.3.3",

@@ -109,9 +109,9 @@ "semver": "^7.6.3",

"tsup": "^8.3.5",
"typedoc": "^0.26.10",
"typedoc-plugin-markdown": "^4.2.9",
"typedoc": "^0.26.11",
"typedoc-plugin-markdown": "^4.2.10",
"typescript": "~5.6.3",
"vitepress": "1.4.1",
"vitest": "^2.1.3",
"vitepress": "1.4.5",
"vitest": "^2.1.4",
"vue": "^3.5.12",
"@pinia/colada": "0.11.1"
"@pinia/colada": "0.12.0"
},

@@ -118,0 +118,0 @@ "simple-git-hooks": {

@@ -22,25 +22,31 @@ <p align="center">

This is a more complete and production-ready (not yet!) version of the exercises from [Mastering Pinia](https://masteringpinia.com/).
Pinia Colada makes data fetching in Vue applications a breeze. It's built on top of [Pinia](https://pinia.vuejs.org) and takes away all of the complexity and boilerplate that comes with fetching data. It's fully typed and tree-shakeable, and it's built with the same principles as Pinia and Vue: It's approachable, flexible, powerful and can be progressively adopted.
<a href="https://masteringpinia.com/?utm=pinia-colada-readme">
<img src="https://github.com/posva/pinia-colada/assets/664177/2f7081a5-90fe-467a-b021-7e709f71603e" width="320" alt="Mastering Pinia banner">
</a>
> [!TIP]
> This is a feature-complete version of the exercises from [Mastering Pinia](https://masteringpinia.com/?utm=pinia-colada-readme). If you would like to learn how it started and become an expert in Vue state management, check it out!
>
> <a href="https://masteringpinia.com/?utm=pinia-colada-readme">
> <img src="https://github.com/posva/pinia-colada/assets/664177/2f7081a5-90fe-467a-b021-7e709f71603e" width="200" alt="Mastering Pinia banner">
> </a>
> [!NOTE]
> Pinia Colada is in active development not ready for production. New versions might introduce breaking changes.
> Feedback regarding new and existing options and features is welcome!
> Documentation is a work in progress and **contributions are welcome**.
## Features
Pinia Colada is an opinionated yet flexible data fetching layer on top of Pinia. It's built as a set of **pinia plugins**, **stores** and **composables** to benefit from Pinia's features and ecosystem. Pinia Colada has:
- ⚡️ **Automatic caching**: Smart client-side caching with request deduplication
- 🗄️ **Async State**: Handle any async state
- 🔌 **Plugins**: Powerful plugin system
- ✨ **Optimistic Updates**: Optimistic updates with ease
- 💡 **Sensible defaults**: Sane defaults with full customization
- 🧩 **Out-of-the box plugins**: A set of composable functions to handle data fetching
- 📚 **Typescript Support**: Fully typed with Typescript
<!-- - 📡 **Network Status**: Handle network status and offline support -->
<!-- - 🛠 **Devtools**: Integration with the Vue devtools -->
- 💨 **Bundle Size**: Small bundle size (<2kb) and fully tree-shakeable
- 💨 **Small Bundle Size**: A baseline of ~2kb and fully tree-shakeable
- 📦 **Zero Dependencies**: No dependencies other than Pinia
- ⚙️ **SSR**: Server-side rendering support
- 🔌 **Plugins**: Powerful plugin system
- ⚙️ **SSR**: Out of the box server-side rendering support
> [!NOTE]
> Pinia Colada is always trying to improve and evolve.
> Feedback regarding new and existing options and features is very welcome!
> Contribution to documentation, issues, and pull requests are highly appreciated.
## Installation

@@ -54,3 +60,3 @@

```js
```ts
import { createPinia } from 'pinia'

@@ -68,2 +74,4 @@ import { PiniaColada } from '@pinia/colada'

The core of Pinia Colada is the `useQuery` and `useMutation` functions. They are used to read data and write it respectively. Here's a simple example:
```vue

@@ -73,9 +81,9 @@ <script lang="ts" setup>

import { useMutation, useQuery, useQueryCache } from '@pinia/colada'
import { updateContact as _updateContact, getContactById } from '~/api/contacts'
import { patchContact, getContactById } from '~/api/contacts'
const route = useRoute()
const caches = useQueryCache()
const queryCache = useQueryCache()
const { data: contact, isLoading } = useQuery({
// recognizes this query as ['contacts', id]
const { data: contact, isPending } = useQuery({
// unique key for the query in the cache
key: () => ['contacts', route.params.id],

@@ -85,6 +93,7 @@ query: () => getContactById(route.params.id),

const { mutate: updateContact } = useMutation({
mutation: _updateContact,
onSettled({ id }) {
caches.invalidateQueries({ key: ['contacts', id], exact: true })
const { mutate: updateContact, isLoading } = useMutation({
mutation: patchContact,
async onSettled({ id }) {
// invalidate the query to refetch the data of the query above
await queryCache.invalidateQueries({ key: ['contacts', id], exact: true })
},

@@ -96,3 +105,7 @@ })

<section>
<p v-if="isPending">
Loading...
</p>
<ContactCard
v-else
:key="contact.id"

@@ -107,4 +120,6 @@ :contact="contact"

Learn more about the core concepts and how to use them in the [documentation](https://pinia-colada.esm.dev).
## License
[MIT](http://opensource.org/licenses/MIT)

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc