diff --git a/packages/postgrest-core/src/mutate-item.ts b/packages/postgrest-core/src/mutate-item.ts index b9025ce5..18361f1b 100644 --- a/packages/postgrest-core/src/mutate-item.ts +++ b/packages/postgrest-core/src/mutate-item.ts @@ -23,6 +23,7 @@ export const mutateOperation = >( primaryKeys: (keyof Type)[], filter: Pick, 'apply'>, orderBy?: OrderDefinition[], + transformer?: (v: Type) => Type, ) => { // find item const itemIdx = currentData.findIndex((oldItem) => @@ -52,7 +53,11 @@ export const mutateOperation = >( // check that new item is still a valid member of the list and has all required paths if (filter.apply(newItem)) { - currentData.splice(newItemIdx, 0, newItem); + currentData.splice( + newItemIdx, + 0, + transformer ? transformer(newItem) : newItem, + ); } return currentData; @@ -64,6 +69,7 @@ export type MutateItemOperation> = { input: Partial; mutate: (current: Type) => Type; primaryKeys: (keyof Type)[]; + transformer?: (v: Type) => Type; } & RevalidateOpts; export type MutateItemCache> = { @@ -113,6 +119,7 @@ export const mutateItem = async >( schema, table, primaryKeys, + transformer, } = op; const { cacheKeys, decode, getPostgrestFilter, mutate, revalidate } = cache; @@ -165,6 +172,7 @@ export const mutateItem = async >( primaryKeys, filter, orderBy, + transformer, ), currentData, limit, @@ -178,6 +186,7 @@ export const mutateItem = async >( primaryKeys, filter, orderBy, + transformer, ), limit, ); @@ -207,6 +216,7 @@ export const mutateItem = async >( primaryKeys, filter, orderBy, + transformer, ); return { diff --git a/packages/postgrest-core/src/upsert-item.ts b/packages/postgrest-core/src/upsert-item.ts index dcc57a20..3567e071 100644 --- a/packages/postgrest-core/src/upsert-item.ts +++ b/packages/postgrest-core/src/upsert-item.ts @@ -30,6 +30,7 @@ export const upsert = >( filter: Pick, 'apply'>, mergeFn?: MergeFn, orderBy?: OrderDefinition[], + transformer?: (v: Type) => Type, ) => { const merge = mergeFn ?? (mergeAnything as MergeFn); @@ -59,7 +60,11 @@ export const upsert = >( // check that new item is still a valid member of the list and has all required paths if (filter.apply(newItem)) { - currentData.splice(newItemIdx, 0, newItem); + currentData.splice( + newItemIdx, + 0, + transformer ? transformer(newItem) : newItem, + ); } return currentData; @@ -71,6 +76,7 @@ export type UpsertItemOperation> = { input: Type; primaryKeys: (keyof Type)[]; merge?: (current: Type, input: Type) => Type; + transformer?: (v: Type) => Type; } & RevalidateOpts; export type UpsertItemCache> = { @@ -118,6 +124,7 @@ export const upsertItem = async >( schema, table, primaryKeys, + transformer, } = op; const { cacheKeys, decode, getPostgrestFilter, mutate, revalidate } = cache; @@ -167,6 +174,7 @@ export const upsertItem = async >( filter, merge, orderBy, + transformer, ), currentData, limit, @@ -180,6 +188,7 @@ export const upsertItem = async >( filter, merge, orderBy, + transformer, ), limit, ); @@ -209,6 +218,7 @@ export const upsertItem = async >( filter, merge, orderBy, + transformer, ); return { diff --git a/packages/postgrest-react-query/src/mutate/use-insert-mutation.ts b/packages/postgrest-react-query/src/mutate/use-insert-mutation.ts index 4de12f06..bd993a2f 100644 --- a/packages/postgrest-react-query/src/mutate/use-insert-mutation.ts +++ b/packages/postgrest-react-query/src/mutate/use-insert-mutation.ts @@ -37,7 +37,7 @@ function useInsertMutation< opts?: Omit< UsePostgrestMutationOpts, 'mutationFn' - >, + > & { transformer?: (v: T['Row']) => Record }, ) { const queriesForTable = useQueriesForTableLoader(getTable(qb)); const upsertItem = useUpsertItem({ diff --git a/packages/postgrest-react-query/src/mutate/use-update-mutation.ts b/packages/postgrest-react-query/src/mutate/use-update-mutation.ts index 419743c1..e14106c5 100644 --- a/packages/postgrest-react-query/src/mutate/use-update-mutation.ts +++ b/packages/postgrest-react-query/src/mutate/use-update-mutation.ts @@ -36,7 +36,7 @@ function useUpdateMutation< opts?: Omit< UsePostgrestMutationOpts, 'mutationFn' - >, + > & { transformer?: (v: T['Row']) => Record }, ) { const queriesForTable = useQueriesForTableLoader(getTable(qb)); const upsertItem = useUpsertItem({ diff --git a/packages/postgrest-react-query/src/mutate/use-upsert-mutation.ts b/packages/postgrest-react-query/src/mutate/use-upsert-mutation.ts index 8501e2fa..f3c64e45 100644 --- a/packages/postgrest-react-query/src/mutate/use-upsert-mutation.ts +++ b/packages/postgrest-react-query/src/mutate/use-upsert-mutation.ts @@ -37,7 +37,7 @@ function useUpsertMutation< opts?: Omit< UsePostgrestMutationOpts, 'mutationFn' - >, + > & { transformer?: (v: T['Row']) => Record }, ) { const queriesForTable = useQueriesForTableLoader(getTable(qb)); const upsertItem = useUpsertItem({ diff --git a/packages/postgrest-react-query/src/query/build-query-opts.ts b/packages/postgrest-react-query/src/query/build-query-opts.ts index 1aee911a..bbc3089e 100644 --- a/packages/postgrest-react-query/src/query/build-query-opts.ts +++ b/packages/postgrest-react-query/src/query/build-query-opts.ts @@ -8,13 +8,23 @@ import type { UseQueryOptions as UseReactQueryOptions } from '@tanstack/react-qu import { encode } from '../lib/key'; -export function buildQueryOpts( +export function buildQueryOpts( query: PromiseLike>, config?: Omit< - UseReactQueryOptions, PostgrestError>, + UseReactQueryOptions< + AnyPostgrestResponse, + PostgrestError + >, 'queryKey' | 'queryFn' - >, -): UseReactQueryOptions, PostgrestError> { + > & { + transformer?: ( + data: AnyPostgrestResponse['data'], + ) => TransformedResult; + }, +): UseReactQueryOptions< + AnyPostgrestResponse, + PostgrestError +> { return { queryKey: encode(query, false), queryFn: async ({ signal }) => { @@ -24,7 +34,14 @@ export function buildQueryOpts( if (isPostgrestBuilder(query)) { query = query.throwOnError(); } - return await query; + const result = await query; + if (config?.transformer && result.error === null) { + return { + ...result, + data: config.transformer(result.data), + }; + } + return result as AnyPostgrestResponse; }, ...config, }; diff --git a/packages/postgrest-react-query/src/query/use-query.ts b/packages/postgrest-react-query/src/query/use-query.ts index e83aa7a5..7dcfb87e 100644 --- a/packages/postgrest-react-query/src/query/use-query.ts +++ b/packages/postgrest-react-query/src/query/use-query.ts @@ -76,65 +76,90 @@ export type UseQueryAnyReturn = Omit< * React hook to execute a PostgREST query and return a single item response. * * @param {PromiseLike>} query A promise that resolves to a PostgREST single item response. - * @param {Omit, PostgrestError>, 'queryKey' | 'queryFn'>} [config] The React Query options. - * @returns {UseQuerySingleReturn} The hook result containing the single item response data. + * @param {Omit, PostgrestError>, 'queryKey' | 'queryFn'>} [config] The React Query options. + * @returns {UseQuerySingleReturn} The hook result containing the single item response data. */ -function useQuery( +function useQuery( query: PromiseLike>, config?: Omit< - UseReactQueryOptions, PostgrestError>, + UseReactQueryOptions< + PostgrestSingleResponse, + PostgrestError + >, 'queryKey' | 'queryFn' - >, -): UseQuerySingleReturn; + > & { + transformer?: ( + data: PostgrestSingleResponse['data'], + ) => TransformedResult; + }, +): UseQuerySingleReturn; /** * React hook to execute a PostgREST query and return a maybe single item response. * * @param {PromiseLike>} query A promise that resolves to a PostgREST maybe single item response. - * @param {Omit, PostgrestError>, 'queryKey' | 'queryFn'>} [config] The React Query options. - * @returns {UseQueryMaybeSingleReturn} The hook result containing the maybe single item response data. + * @param {Omit, PostgrestError>, 'queryKey' | 'queryFn'>} [config] The React Query options. + * @returns {UseQueryMaybeSingleReturn} The hook result containing the maybe single item response data. */ -function useQuery( +function useQuery( query: PromiseLike>, config?: Omit< - UseReactQueryOptions, PostgrestError>, + UseReactQueryOptions< + PostgrestMaybeSingleResponse, + PostgrestError + >, 'queryKey' | 'queryFn' - >, -): UseQueryMaybeSingleReturn; + > & { + transformer?: ( + data: PostgrestMaybeSingleResponse['data'], + ) => TransformedResult; + }, +): UseQueryMaybeSingleReturn; /** * React hook to execute a PostgREST query. * * @template Result The expected response data type. * @param {PromiseLike>} query A promise that resolves to a PostgREST response. - * @param {Omit, PostgrestError>, 'queryKey' | 'queryFn'>} [config] The React Query options. - * @returns {UseQueryReturn} The hook result containing the response data. + * @param {Omit, PostgrestError>, 'queryKey' | 'queryFn'>} [config] The React Query options. + * @returns {UseQueryReturn} The hook result containing the response data. */ -function useQuery( +function useQuery( query: PromiseLike>, config?: Omit< - UseReactQueryOptions, PostgrestError>, + UseReactQueryOptions, PostgrestError>, 'queryKey' | 'queryFn' - >, -): UseQueryReturn; + > & { + transformer?: ( + data: PostgrestResponse['data'], + ) => TransformedResult; + }, +): UseQueryReturn; /** * React hook to execute a PostgREST query. * * @template Result The expected response data type. * @param {PromiseLike>} query A promise that resolves to a PostgREST response of any kind. - * @param {Omit, PostgrestError>, 'queryKey' | 'queryFn'>} [config] The React Query options. - * @returns {UseQueryAnyReturn} The hook result containing the response data. + * @param {Omit, PostgrestError>, 'queryKey' | 'queryFn'>} [config] The React Query options. + * @returns {UseQueryAnyReturn} The hook result containing the response data. */ -function useQuery( +function useQuery( query: PromiseLike>, config?: Omit< - UseReactQueryOptions, PostgrestError>, + UseReactQueryOptions< + AnyPostgrestResponse, + PostgrestError + >, 'queryKey' | 'queryFn' - >, -): UseQueryAnyReturn { + > & { + transformer?: ( + data: AnyPostgrestResponse['data'], + ) => TransformedResult; + }, +): UseQueryAnyReturn { const { data, ...rest } = useReactQuery< - AnyPostgrestResponse, + AnyPostgrestResponse, PostgrestError - >(buildQueryOpts(query, config)); + >(buildQueryOpts(query, config)); return { data: data?.data, count: data?.count ?? null, ...rest }; }