/**
* React Hooks for TanStack Query with postgres.do
*
* Provides React hooks that wrap TanStack Query v5 for seamless integration
* with postgres.do queries and mutations.
*
* @example
* ```typescript
* import { usePostgresQuery, usePostgresMutation } from '@dotdo/tanstack'
*
* function UserList() {
* const { data: users, isLoading } = usePostgresQuery({
* sql: 'SELECT * FROM users WHERE active = $1',
* params: [true],
* })
*
* const { mutate: createUser } = usePostgresMutation({
* onSuccess: () => {
* // Invalidate and refetch
* },
* })
*
* if (isLoading) return
Loading...
* return {users?.map(u => - {u.name}
)}
* }
* ```
*
* NOTE: This module requires React 18+ and @tanstack/react-query v5+
* The hooks are designed to work with TanStack Query's useQuery/useMutation
* but provide a simplified API specifically for postgres.do.
*/
import type { BaseRecord, Collection } from './types';
import type { QueryAdapter, QueryParams, MutationParams, PostgresQueryKey, QueryResult, MutationContext, InvalidationPattern, PostgresQueryOptions, PostgresMutationOptions } from './adapter';
/**
* Minimal QueryClient interface for type safety
* This represents the subset of QueryClient methods we use
*/
interface QueryClientLike {
invalidateQueries(options: {
queryKey: readonly unknown[];
}): Promise;
prefetchQuery(options: PostgresQueryOptions): Promise;
}
/**
* Query state returned by usePostgresQuery
*/
export interface PostgresQueryState {
data: T[] | undefined;
error: Error | null;
isLoading: boolean;
isError: boolean;
isSuccess: boolean;
isFetching: boolean;
refetch: () => Promise;
}
/**
* Mutation state returned by usePostgresMutation
*/
export interface PostgresMutationState {
data: QueryResult | undefined;
error: Error | null;
isLoading: boolean;
isPending: boolean;
isError: boolean;
isSuccess: boolean;
isIdle: boolean;
mutate: (variables: MutationParams) => void;
mutateAsync: (variables: MutationParams) => Promise>;
reset: () => void;
}
/**
* Options for usePostgresQuery hook
*/
export interface UsePostgresQueryOptions extends Omit {
/** Query adapter instance */
adapter: QueryAdapter;
/** Transform function for results */
select?: (data: T[]) => T[];
/** Placeholder data while loading */
placeholderData?: T[] | (() => T[]);
/** Keep previous data while refetching */
keepPreviousData?: boolean;
}
/**
* Options for usePostgresMutation hook
*/
export interface UsePostgresMutationOptions {
/** Query adapter instance */
adapter: QueryAdapter;
/** Invalidation pattern after mutation */
invalidate?: InvalidationPattern;
/** Called before mutation (for optimistic updates) */
onMutate?: (variables: MutationParams) => Promise | MutationContext | undefined;
/** Called on successful mutation */
onSuccess?: (data: QueryResult, variables: MutationParams, context: MutationContext | undefined) => void;
/** Called on mutation error */
onError?: (error: Error, variables: MutationParams, context: MutationContext | undefined) => void;
/** Called when mutation settles */
onSettled?: (data: QueryResult | undefined, error: Error | null, variables: MutationParams, context: MutationContext | undefined) => void;
}
/**
* Live query subscription options
*/
export interface UseLiveQueryOptions {
/** Collection to query */
collection: Collection;
/** Filter function or object */
where?: Partial | ((item: T) => boolean);
/** Sort configuration */
orderBy?: {
field: keyof T;
direction: 'asc' | 'desc';
};
/** Limit results */
limit?: number;
/** Offset results */
offset?: number;
/** Whether the query is enabled */
enabled?: boolean;
}
/**
* Live query state
*/
export interface LiveQueryState {
data: T[];
isLoading: boolean;
error: Error | null;
}
/**
* Result type for usePostgresQuery
* Compatible with TanStack Query's useQuery result
*/
export interface UsePostgresQueryResult {
data: T[] | undefined;
error: Error | null;
isLoading: boolean;
isError: boolean;
isSuccess: boolean;
isFetching: boolean;
isPending: boolean;
isStale: boolean;
status: 'pending' | 'error' | 'success';
fetchStatus: 'fetching' | 'paused' | 'idle';
refetch: () => Promise<{
data: T[] | undefined;
error: Error | null;
}>;
queryKey: PostgresQueryKey;
}
/**
* Result type for usePostgresMutation
* Compatible with TanStack Query's useMutation result
*/
export interface UsePostgresMutationResult {
data: QueryResult | undefined;
error: Error | null;
variables: MutationParams | undefined;
isLoading: boolean;
isPending: boolean;
isError: boolean;
isSuccess: boolean;
isIdle: boolean;
status: 'idle' | 'pending' | 'error' | 'success';
mutate: (variables: MutationParams) => void;
mutateAsync: (variables: MutationParams) => Promise>;
reset: () => void;
}
/**
* Create a usePostgresQuery hook factory.
* This pattern allows frameworks to provide their own TanStack Query integration.
*
* @param useQuery - The framework-specific useQuery hook (e.g., from @tanstack/react-query)
* @returns A configured usePostgresQuery hook
*
* @example
* ```typescript
* import { useQuery } from '@tanstack/react-query'
* import { createUsePostgresQuery } from '@dotdo/tanstack'
*
* const usePostgresQuery = createUsePostgresQuery(useQuery)
*
* // Now use it in components
* const { data } = usePostgresQuery(adapter, {
* sql: 'SELECT * FROM users',
* })
* ```
*/
export declare function createUsePostgresQuery) => unknown>(useQuery: TUseQuery): (adapter: QueryAdapter, params: QueryParams | string) => ReturnType;
/**
* Create a usePostgresMutation hook factory
*
* @example
* ```typescript
* import { useMutation, useQueryClient } from '@tanstack/react-query'
* import { createUsePostgresMutation } from '@dotdo/tanstack'
*
* const usePostgresMutation = createUsePostgresMutation(useMutation, useQueryClient)
*
* // Now use it in components
* const { mutate } = usePostgresMutation(adapter, {
* invalidate: { tables: ['users'] },
* })
* mutate({ sql: 'INSERT INTO users (name) VALUES ($1)', params: ['Alice'] })
* ```
*/
/**
* Extended invalidation pattern with advanced features
*/
interface ExtendedInvalidationPattern extends InvalidationPattern {
autoDetect?: boolean;
awaitInvalidation?: boolean;
shouldInvalidate?: (result: QueryResult) => boolean;
cascade?: Record;
}
export declare function createUsePostgresMutation, Error, MutationParams, MutationContext>) => unknown, TUseQueryClient extends () => QueryClientLike>(useMutation: TUseMutation, useQueryClient: TUseQueryClient): (adapter: QueryAdapter, options?: Omit, "adapter"> & {
invalidate?: ExtendedInvalidationPattern;
}) => ReturnType & {
getMutationHistory: () => {
sql: string;
params?: unknown[];
timestamp: number;
success: boolean;
}[];
getStats: () => {
totalMutations: number;
successCount: number;
errorCount: number;
averageDurationMs: number;
};
};
/**
* Create a useLiveQuery hook factory for reactive collection queries
*
* @example
* ```typescript
* import { useSyncExternalStore } from 'react'
* import { createUseLiveQuery } from '@dotdo/tanstack'
*
* const useLiveQuery = createUseLiveQuery(useSyncExternalStore)
*
* // Now use it in components
* const todos = useLiveQuery(collection, {
* where: { completed: false },
* orderBy: { field: 'createdAt', direction: 'desc' },
* })
* ```
*/
/**
* Extended live query options including advanced features
*/
interface ExtendedLiveQueryOptions extends Omit, 'collection'> {
select?: (items: T[]) => unknown;
groupBy?: keyof T;
distinct?: keyof T;
debounce?: number;
throttle?: number;
optimisticData?: T[];
onDiff?: (diff: {
added: T[];
removed: T[];
updated: Array<{
previous: T;
current: T;
}>;
}) => void;
onError?: (error: Error) => void;
cursor?: {
field: keyof T;
after?: unknown;
pageSize: number;
};
search?: {
query: string;
fields: Array;
fuzzy?: boolean;
};
}
export declare function createUseLiveQuery void) => () => void, getSnapshot: () => unknown, getServerSnapshot?: () => unknown) => unknown>(useSyncExternalStore: TUseSyncExternalStore): {
(collection: Collection, options?: ExtendedLiveQueryOptions): unknown;
connectionState(collection: Collection): unknown;
join(collection1: Collection, collection2: Collection, joinOptions: {
on: (item1: T1, item2: T2) => boolean;
select: (item1: T1, item2: T2) => unknown;
}): unknown[];
multiCollection(store: {
getCollection: (id: string) => Collection | undefined;
}, collectionIds: string[], multiOptions: {
combine: (...collections: unknown[][]) => unknown;
}): unknown;
aggregate(store: {
getCollection: (id: string) => Collection | undefined;
}, aggregateOptions: {
collections: string[];
aggregation: Record unknown>;
}): unknown;
} & {
connectionState: (collection: Collection) => unknown;
join: (collection1: Collection, collection2: Collection, joinOptions: {
on: (item1: T1, item2: T2) => boolean;
select: (item1: T1, item2: T2) => unknown;
}) => unknown[];
multiCollection: (store: {
getCollection: (id: string) => Collection | undefined;
}, collectionIds: string[], multiOptions: {
combine: (...collections: unknown[][]) => unknown;
}) => unknown;
aggregate: (store: {
getCollection: (id: string) => Collection | undefined;
}, aggregateOptions: {
collections: string[];
aggregation: Record unknown>;
}) => unknown;
};
/**
* Minimal mutation options for collection hooks
*/
interface CollectionMutationOptionsLike {
mutationKey: readonly unknown[];
mutationFn: (variables: TVariables) => Promise;
onSuccess?: ((data: TData) => void) | undefined;
onError?: ((error: Error) => void) | undefined;
}
/**
* Create hook for collection insert operations
*/
export declare function createUseCollectionInsert(options: CollectionMutationOptionsLike) => unknown>(useMutation: TUseMutation): (collection: Collection, options?: {
onSuccess?: (data: T) => void;
onError?: (error: Error) => void;
}) => ReturnType;
/**
* Create hook for collection update operations
*/
export declare function createUseCollectionUpdate(options: CollectionMutationOptionsLike) => unknown>(useMutation: TUseMutation): (collection: Collection, options?: {
onSuccess?: (data: T) => void;
onError?: (error: Error) => void;
}) => ReturnType;
/**
* Create hook for collection delete operations
*/
export declare function createUseCollectionDelete(options: CollectionMutationOptionsLike) => unknown>(useMutation: TUseMutation): (collection: Collection, options?: {
onSuccess?: () => void;
onError?: (error: Error) => void;
}) => ReturnType;
/**
* Create a hook for prefetching queries
*/
export declare function createUsePrefetchQuery QueryClientLike>(useQueryClient: TUseQueryClient): (adapter: QueryAdapter) => (params: QueryParams | string) => Promise;
/**
* Create a hook for invalidating queries
*/
export declare function createUseInvalidateQueries QueryClientLike>(useQueryClient: TUseQueryClient): (adapter: QueryAdapter) => {
/** Invalidate a specific query by its SQL and parameters */
invalidateQuery: (params: QueryParams | string) => Promise;
/** Invalidate queries involving specific tables */
invalidateTables: (tables: string[]) => Promise;
/** Invalidate queries matching a pattern */
invalidatePattern: (pattern: RegExp) => Promise;
/** Invalidate all queries for this database */
invalidateAll: () => Promise;
};
export { createQueryKey, normalizeSQL, normalizeQueryParams, extractTablesFromSQL, getMutationType, isReadOnlyQuery, filterByPartialMatch, sortByField, applyPagination, } from './adapter';
//# sourceMappingURL=hooks.d.ts.map