Skip to content

Commit

Permalink
refactor: types and size
Browse files Browse the repository at this point in the history
  • Loading branch information
posva committed Mar 16, 2024
1 parent 2aeaf6f commit a812d84
Show file tree
Hide file tree
Showing 2 changed files with 28 additions and 31 deletions.
53 changes: 22 additions & 31 deletions src/use-mutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { ComputedRef, ShallowRef } from 'vue'
import { type UseQueryStatus, useQueryCache } from './query-store'
import type { UseQueryKey } from './query-options'
import type { ErrorDefault } from './types-extension'
import type { _Awaitable } from './utils'

type _MutationKeys<TParams extends readonly any[], TResult> =
| UseQueryKey[]
Expand All @@ -12,7 +13,7 @@ export interface UseMutationOptions<
TResult = unknown,
TParams extends readonly unknown[] = readonly [],
TError = ErrorDefault,
TContext = any, // TODO: type as `unknown`
TContext = unknown,
> {
/**
* The key of the mutation. If the mutation is successful, it will invalidate the query with the same key and refetch it
Expand All @@ -28,25 +29,23 @@ export interface UseMutationOptions<
/**
* Hook to execute a callback when the mutation is triggered
*/
onMutate?: (...args: TParams) => Promise<TContext | void> | TContext | void
// onMutate?: (...args: TParams) => TContext
onMutate?: (...args: TParams) => _Awaitable<TContext>

/**
* Hook to execute a callback in case of error
*/
onError?: (context: { error: TError, args: TParams, context: TContext }) => Promise<TContext | void> | TContext | void
// TODO: check that eh contact is well not obligatoire
onError?: (context: { error: TError, args: TParams, context: TContext }) => unknown
// onError?: (context: { error: TError, args: TParams } & TContext) => Promise<TContext | void> | TContext | void

/**
* Hook to execute a callback in case of error
*/
onSuccess?: (context: { result: TResult, args: TParams, context: TContext }) => Promise<TContext | void> | TContext | void
onSuccess?: (context: { data: TResult, args: TParams, context: TContext }) => unknown

/**
* Hook to execute a callback in case of error
*/
onSettled?: (context: { result: TResult, error: TError, args: TParams, context: TContext }) => void
onSettled?: (context: { data: TResult | undefined, error: TError | null, args: TParams, context: TContext }) => void

// TODO: invalidate options exact, refetch, etc
}
Expand Down Expand Up @@ -96,66 +95,58 @@ export function useMutation<
TResult,
TParams extends readonly unknown[] = readonly [],
TError = ErrorDefault,
TContext = unknown,
TContext = void,
>(
options: UseMutationOptions<TResult, TParams>,
options: UseMutationOptions<TResult, TParams, TError, TContext>,
): UseMutationReturn<TResult, TParams, TError> {
const store = useQueryCache()

const status = shallowRef<UseQueryStatus>('pending')
const data = shallowRef<TResult>()
const error = shallowRef<TError | null>(null)
let hookContext: TContext

// a pending promise allows us to discard previous ongoing requests
let pendingPromise: Promise<TResult> | null = null
// NOTE: do a mutation context?

async function mutate(...args: TParams) {
status.value = 'loading'

if (options.onMutate) {
hookContext = options.onMutate(...args)
}
// TODO: should this context be passed to mutation() and ...args transformed into one object?
const context = (await options.onMutate?.(...args)) as TContext

// TODO: AbortSignal that is aborted when the mutation is called again so we can throw in pending
const promise = (pendingPromise = options
.mutation(...args)
.then((_data) => {
if (options.onSuccess) {
options.onSuccess({ result: _data, args, context: hookContext })
}
.then(async (newData) => {
await options.onSuccess?.({ data: newData, args, context })

if (pendingPromise === promise) {
data.value = _data
data.value = newData
error.value = null
status.value = 'success'
if (options.keys) {
const keys
= typeof options.keys === 'function'
? options.keys(_data, ...args)
? options.keys(newData, ...args)
: options.keys
for (const key of keys) {
// TODO: find a way to pass a source of the invalidation, could be a symbol associated with the mutation, the parameters
store.invalidateEntry(key)
}
}
}
return _data
return newData
})
.catch((_error) => {
.catch(async (newError) => {
if (pendingPromise === promise) {
error.value = _error
error.value = newError
status.value = 'error'
}
if (options.onError) {
options.onError({ error: error.value, args, context: hookContext })
}
throw _error
await options.onError?.({ error: newError, args, context })
throw newError
})
.finally(async () => {
if (options.onSettled) {
// TODO: TS
options.onSettled({ result: data.value, error: error.value, args, context: hookContext })
}
await options.onSettled?.({ data: data.value, error: error.value, args, context })
}))

return promise
Expand Down
6 changes: 6 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ export const IS_CLIENT = typeof window !== 'undefined'
*/
export type _MaybeArray<T> = T | T[]

/**
* Type that represents a value that can be a promise or a single value.
* @internal
*/
export type _Awaitable<T> = T | Promise<T>

/**
* Flattens an object type for readability.
* @internal
Expand Down

0 comments on commit a812d84

Please sign in to comment.