/** * TanStack Query v5 Adapter * * Provides integration between postgres.do queries and TanStack Query v5, * including query key generation, query options factories, and mutation patterns. * * @example * ```typescript * import { createQueryAdapter } from '@dotdo/tanstack' * * const adapter = createQueryAdapter({ * baseUrl: 'https://db.postgres.do', * database: 'mydb', * }) * * // Use with TanStack Query * const { data } = useQuery(adapter.queryOptions('SELECT * FROM users')) * * // Mutations * const { mutate } = useMutation(adapter.mutationOptions()) * mutate({ sql: 'INSERT INTO users (name) VALUES ($1)', params: ['Alice'] }) * ``` */ import type { BaseRecord, Collection } from './types'; /** * Query key structure for postgres.do queries */ export type PostgresQueryKey = readonly [ 'postgres', string, string, ...unknown[] ]; /** * Configuration for the TanStack Query adapter */ export interface QueryAdapterConfig { /** Base URL for postgres.do API */ baseUrl?: string; /** Database identifier */ database: string; /** Default stale time in milliseconds */ defaultStaleTime?: number; /** Default cache time (gcTime) in milliseconds */ defaultGcTime?: number; /** Custom fetch function */ fetch?: typeof globalThis.fetch; /** Key prefix for multi-tenant isolation */ keyPrefix?: string; } /** * Query parameters for SELECT queries */ export interface QueryParams { /** SQL query string */ sql: string; /** Query parameters */ params?: unknown[]; /** Override stale time for this query */ staleTime?: number; /** Override gc time for this query */ gcTime?: number; /** Refetch interval */ refetchInterval?: number | false; /** Whether to enable the query */ enabled?: boolean; /** Timeout in milliseconds */ timeout?: number; /** AbortSignal for query cancellation */ signal?: AbortSignal; /** Number of retries on transient failures */ retry?: number; /** Delay between retries in milliseconds */ retryDelay?: number; /** Fetch policy: cache-first or network-only */ fetchPolicy?: 'cache-first' | 'network-only'; /** Refetch on window focus */ refetchOnWindowFocus?: boolean; /** Refetch on reconnect */ refetchOnReconnect?: boolean; /** Whether to refetch in background when tab is hidden */ refetchIntervalInBackground?: boolean; /** Retry backoff strategy */ retryBackoff?: 'exponential' | 'linear'; /** Maximum retry delay in milliseconds */ maxRetryDelay?: number; /** Placeholder data while loading */ placeholderData?: unknown[] | (() => unknown[]); /** Keep previous data while refetching */ keepPreviousData?: boolean; /** Transform/select function for results */ select?: (data: unknown[]) => unknown[]; } /** * Mutation parameters for INSERT/UPDATE/DELETE */ export interface MutationParams { /** SQL mutation string */ sql: string; /** Mutation parameters */ params?: unknown[]; } /** * Mutation context for optimistic updates */ export interface MutationContext { /** Previous data before mutation */ previousData?: T; /** Query keys that will be invalidated */ invalidateKeys?: readonly PostgresQueryKey[]; } /** * Result from a postgres.do query */ export interface QueryResult { rows: T[]; fields?: Array<{ name: string; dataTypeID: number; }>; rowCount?: number; command?: string; } /** * Query options compatible with TanStack Query v5 * @see https://tanstack.com/query/v5/docs/framework/react/reference/queryOptions */ export interface PostgresQueryOptions { queryKey: PostgresQueryKey; queryFn: () => Promise; staleTime?: number; gcTime?: number; refetchInterval?: number | false; enabled?: boolean; refetchOnWindowFocus?: boolean; refetchOnReconnect?: boolean; refetchIntervalInBackground?: boolean; placeholderData?: TData | (() => TData); select?: (data: TData) => unknown; } /** * Mutation options compatible with TanStack Query v5 * @see https://tanstack.com/query/v5/docs/framework/react/reference/useMutation */ export interface PostgresMutationOptions { mutationKey?: readonly unknown[]; mutationFn: (variables: TVariables) => Promise; onMutate?: (variables: TVariables) => Promise | TContext | undefined; onSuccess?: (data: TData, variables: TVariables, context: TContext | undefined) => Promise | void; onError?: (error: TError, variables: TVariables, context: TContext | undefined) => Promise | void; onSettled?: (data: TData | undefined, error: TError | null, variables: TVariables, context: TContext | undefined) => Promise | void; } /** * Cache invalidation pattern */ export interface InvalidationPattern { /** Tables to invalidate (extracts from SQL) */ tables?: string[]; /** Exact query keys to invalidate */ queryKeys?: readonly PostgresQueryKey[]; /** Whether to invalidate all queries for the database */ all?: boolean; } /** * Normalize query parameters: accepts either a SQL string or a QueryParams object. * Always returns a QueryParams object. */ export declare function normalizeQueryParams(params: QueryParams | string): QueryParams; /** * Normalize SQL for consistent query key generation. * Removes extra whitespace and ensures consistent formatting of * parentheses and commas for reliable cache key matching. */ export declare function normalizeSQL(sql: string): string; /** * Generate a query key from SQL and parameters * Follows TanStack Query best practices for key structure */ export declare function createQueryKey(database: string, sql: string, params?: unknown[]): PostgresQueryKey; /** * Extract table names from SQL query for invalidation * Supports SELECT, INSERT, UPDATE, DELETE, FROM, JOIN */ export declare function extractTablesFromSQL(sql: string): string[]; /** * Determine mutation type from SQL */ export declare function getMutationType(sql: string): 'insert' | 'update' | 'delete' | 'unknown'; /** * Check if SQL is a read-only query */ export declare function isReadOnlyQuery(sql: string): boolean; /** * Structured error with query context */ export declare class PostgresQueryError extends Error { sql?: string; code?: string; params?: unknown[]; constraint?: string; detail?: string; isRetryable?: boolean; constructor(message: string, context?: { sql?: string; code?: string; params?: unknown[]; constraint?: string; detail?: string; isRetryable?: boolean; status?: number; }); } /** * Circuit breaker state */ export interface CircuitState { state: 'closed' | 'open' | 'half-open'; consecutiveFailures: number; lastFailureAt?: number; } /** * TanStack Query adapter for postgres.do */ export declare class QueryAdapter { private config; private fetchFn; private errorHandlers; private circuitState; private queryCache; private inflightQueries; private seedCacheMap; constructor(config: QueryAdapterConfig); /** * Register an error event handler */ onError(handler: (event: { error: Error; sql: string; params?: unknown[]; }) => void): void; /** * Get the circuit breaker state */ getCircuitState(): CircuitState; /** * Seed the cache with known data */ seedCache(sql: string, data: T[]): void; /** * Create a mutation queue for sequential execution */ createMutationQueue(options: { concurrency: number; }): { add: (params: MutationParams) => Promise; }; private getCacheKey; private emitError; private resetCircuit; /** * Execute a query against postgres.do */ query(sql: string, params?: unknown[], options?: { signal?: AbortSignal; timeout?: number; }): Promise>; /** * Create query options for TanStack Query v5 * * @example * ```typescript * const queryOptions = adapter.queryOptions({ * sql: 'SELECT * FROM users WHERE active = $1', * params: [true], * staleTime: 30000, * }) * * // Use with useQuery * const { data } = useQuery(queryOptions) * ``` */ queryOptions(params: QueryParams | string): PostgresQueryOptions; /** * Create mutation options for TanStack Query v5 * * @example * ```typescript * const mutationOptions = adapter.mutationOptions({ * invalidate: { tables: ['users'] }, * onSuccess: (data) => console.log('Inserted:', data), * }) * * // Use with useMutation * const { mutate } = useMutation(mutationOptions) * mutate({ sql: 'INSERT INTO users (name) VALUES ($1)', params: ['Alice'] }) * ``` */ mutationOptions(options?: { /** 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 (success or error) */ onSettled?: (data: QueryResult | undefined, error: Error | null, variables: MutationParams, context: MutationContext | undefined) => void; /** Called with progress for bulk operations */ onProgress?: (progress: { processed: number; total: number; }) => void; /** Called when optimistic update is applied */ onOptimisticUpdate?: (event: { type: string; variables: MutationParams; }) => void; /** Deduplication: prevent duplicate concurrent mutations */ deduplicate?: boolean; }): PostgresMutationOptions, Error, MutationParams, MutationContext>; /** * Create query key for a SQL query */ getQueryKey(sql: string, params?: unknown[]): PostgresQueryKey; /** * Get invalidation keys for tables */ getInvalidationKeysForTables(_tables: string[]): Array; /** * Get the database name */ get database(): string; } /** * Create a TanStack Query adapter for postgres.do */ export declare function createQueryAdapter(config: QueryAdapterConfig): QueryAdapter; /** * Filter items by a partial match object. * Each defined key in the filter must match the corresponding value in the item. */ export declare function filterByPartialMatch(items: T[], where: Partial): T[]; /** * Sort items by a field and direction. * Returns a new sorted array (does not mutate the input). */ export declare function sortByField(items: T[], field: keyof T, direction: 'asc' | 'desc'): T[]; /** * Apply offset and limit pagination to an array of items. * Returns a sliced copy of the array. */ export declare function applyPagination(items: T[], offset?: number, limit?: number): T[]; /** * Options for creating collection query options */ export interface CollectionQueryOptions { /** The collection to query */ collection: Collection; /** Stale time in milliseconds */ staleTime?: number; /** GC time in milliseconds */ gcTime?: number; /** Whether the query is enabled */ enabled?: boolean; /** Filter conditions */ where?: Partial; /** Sort configuration */ orderBy?: { field: keyof T; direction: 'asc' | 'desc'; }; /** Limit results */ limit?: number; /** Offset results */ offset?: number; } /** * Create query options from a collection * Bridges the Collection API with TanStack Query */ export declare function createCollectionQueryOptions(options: CollectionQueryOptions): PostgresQueryOptions; /** * Collection mutation variable types */ export type CollectionMutationVariables = { type: 'insert' | 'update' | 'delete' | 'batch' | 'upsert'; data: Partial; id?: string | number; operations?: Array<{ type: 'insert' | 'update' | 'delete'; data: Partial; id?: string | number; }>; }; /** * Create mutation options from a collection */ export declare function createCollectionMutationOptions(collection: Collection, options?: { onMutate?: (variables: CollectionMutationVariables) => MutationContext | undefined; onSuccess?: (data: T | void, variables: CollectionMutationVariables) => void; onError?: (error: Error, variables: CollectionMutationVariables) => void; transactional?: boolean; }): PostgresMutationOptions, MutationContext>; //# sourceMappingURL=adapter.d.ts.map