UNPKG

kitcn

Version:

kitcn - React Query integration and CLI tools for Convex

1,011 lines (1,010 loc) 62.8 kB
'use client'; import { ConvexProvider, ConvexProviderWithAuth as ConvexProviderWithAuth$1, ConvexReactClient, ConvexReactClient as ConvexReactClient$1, ConvexReactClientOptions, Watch, WatchQueryOptions, useConvex } from "convex/react"; import * as react from "react"; import { ReactNode } from "react"; import * as jotai_x0 from "jotai-x"; import * as react_jsx_runtime0 from "react/jsx-runtime"; import { FunctionArgs, FunctionReference, FunctionReturnType } from "convex/server"; import { DefaultError, QueryCache, QueryClient, QueryFilters, QueryFunction, QueryFunctionContext, QueryKey, SkipToken, UseMutationOptions, UseQueryOptions, UseQueryResult } from "@tanstack/react-query"; import { ConvexHttpClient } from "convex/browser"; import * as jotai_vanilla0 from "jotai/vanilla"; import { z } from "zod"; //#region src/crpc/auth-error.d.ts /** * Auth Mutation Error * * Framework-agnostic error class for Better Auth mutations. */ /** * Error thrown when a Better Auth mutation fails. * Contains the original error details from Better Auth. */ declare class AuthMutationError extends Error { /** Error code from Better Auth (e.g., 'INVALID_PASSWORD', 'EMAIL_ALREADY_REGISTERED') */ code?: string; /** HTTP status code */ status: number; /** HTTP status text */ statusText: string; constructor(authError: { message?: string; status: number; statusText: string; code?: string; }); } /** * Type guard to check if an error is an AuthMutationError. */ declare function isAuthMutationError(error: unknown): error is AuthMutationError; //#endregion //#region src/react/auth-mutations.d.ts type MutationOptionsHook<TData, TVariables = void> = (options?: Omit<UseMutationOptions<TData, DefaultError, TVariables>, 'mutationFn'>) => UseMutationOptions<TData, DefaultError, TVariables>; type AnyAuthFn = (...args: never[]) => unknown; type MutationArgsWithFetchOptions = { fetchOptions?: Record<string, unknown>; }; type AuthClient = { $store?: { atoms?: { session?: { get?: () => { data?: unknown; error?: unknown; isPending?: boolean; isRefetching?: boolean; refetch?: (queryParams?: { query?: Record<string, unknown>; }) => Promise<void>; }; set?: (value: { data: unknown; error: unknown; isPending: boolean; isRefetching: boolean; refetch: (queryParams?: { query?: Record<string, unknown>; }) => Promise<void>; }) => void; }; }; }; getSession?: AnyAuthFn; signOut?: AnyAuthFn; signIn?: { anonymous?: AnyAuthFn; social?: AnyAuthFn; email?: AnyAuthFn; }; signUp?: { email?: AnyAuthFn; }; }; /** * Create mutation option hooks from a better-auth client. * * @example * ```tsx * // lib/auth-client.ts * import { createAuthMutations } from 'kitcn/react'; * * export const authClient = createAuthClient({...}); * * export const { * useSignOutMutationOptions, * useSignInSocialMutationOptions, * useSignInMutationOptions, * useSignUpMutationOptions, * } = createAuthMutations(authClient); * * // components/header.tsx * const signOutMutation = useMutation(useSignOutMutationOptions({ * onSuccess: () => router.push('/login'), * })); * ``` */ type AuthMutationsResult = { useSignOutMutationOptions: MutationOptionsHook<unknown, MutationArgsWithFetchOptions | void>; useSignInSocialMutationOptions: MutationOptionsHook<unknown, unknown>; useSignInMutationOptions: MutationOptionsHook<unknown, unknown>; useSignUpMutationOptions: MutationOptionsHook<unknown, unknown>; }; declare function createAuthMutations(authClient: AuthClient): AuthMutationsResult; //#endregion //#region src/react/auth-store.d.ts type FetchAccessTokenFn = (args: { forceRefreshToken: boolean; }) => Promise<string | null>; declare const FetchAccessTokenContext: react.Context<FetchAccessTokenFn | null>; /** Get fetchAccessToken from context (available immediately, no race condition) */ declare const useFetchAccessToken: () => FetchAccessTokenFn | null; type ConvexAuthResult = { isAuthenticated: boolean; isLoading: boolean; }; /** Get auth from bridge context (null if no bridge configured) */ declare const useConvexAuthBridge: () => ConvexAuthResult | null; type AuthStoreState = { /** Callback when mutation/action called while unauthorized. Throws by default. */onMutationUnauthorized: () => void; /** Callback when query called while unauthorized. Noop by default. */ onQueryUnauthorized: (info: { queryName: string; }) => void; /** Custom function to detect UNAUTHORIZED errors. Default checks code or "auth" in message. */ isUnauthorized: (error: unknown) => boolean; /** Cached Convex JWT for HTTP requests */ token: string | null; /** JWT expiration timestamp (ms) */ expiresAt: number | null; /** Auth loading state (synced from useConvexAuth for class methods) */ isLoading: boolean; /** Auth state (synced from useConvexAuth for class methods) */ isAuthenticated: boolean; /** Grace window for freshly seeded auth tokens while session sync catches up */ sessionSyncGraceUntil: number | null; }; declare const AUTH_SESSION_SYNC_GRACE_MS = 10000; declare const isSessionSyncGraceActive: (sessionSyncGraceUntil: number | null) => boolean; /** Decode JWT expiration (ms timestamp) from token */ declare function decodeJwtExp(token: string): number | null; declare const AuthProvider: react.FC<jotai_x0.ProviderProps<{ onMutationUnauthorized: () => void; onQueryUnauthorized: (info: { queryName: string; }) => void; isUnauthorized: (error: unknown) => boolean; token: string | null; expiresAt: number | null; isLoading: boolean; isAuthenticated: boolean; sessionSyncGraceUntil: number | null; }>>, useAuthStore: jotai_x0.UseStoreApi<AuthStoreState, object>, useAuthState: <K extends keyof AuthStoreState>(key: K, options?: string | jotai_x0.UseAtomOptions) => ({ onMutationUnauthorized: jotai_x0.SimpleWritableAtom<() => void>; onQueryUnauthorized: jotai_x0.SimpleWritableAtom<(info: { queryName: string; }) => void>; isUnauthorized: jotai_x0.SimpleWritableAtom<(error: unknown) => boolean>; token: jotai_x0.SimpleWritableAtom<string | null>; expiresAt: jotai_x0.SimpleWritableAtom<number | null>; isLoading: jotai_x0.SimpleWritableAtom<boolean>; isAuthenticated: jotai_x0.SimpleWritableAtom<boolean>; sessionSyncGraceUntil: jotai_x0.SimpleWritableAtom<number | null>; } & object)[K] extends jotai_vanilla0.WritableAtom<infer V, infer A extends unknown[], infer R> ? [V, (...args: A) => R] : never, useAuthValue: <K extends keyof AuthStoreState, S = (({ onMutationUnauthorized: jotai_x0.SimpleWritableAtom<() => void>; onQueryUnauthorized: jotai_x0.SimpleWritableAtom<(info: { queryName: string; }) => void>; isUnauthorized: jotai_x0.SimpleWritableAtom<(error: unknown) => boolean>; token: jotai_x0.SimpleWritableAtom<string | null>; expiresAt: jotai_x0.SimpleWritableAtom<number | null>; isLoading: jotai_x0.SimpleWritableAtom<boolean>; isAuthenticated: jotai_x0.SimpleWritableAtom<boolean>; sessionSyncGraceUntil: jotai_x0.SimpleWritableAtom<number | null>; } & object)[K] extends jotai_vanilla0.Atom<infer V> ? V : never)>(key: K, options?: ({ selector?: ((v: ({ onMutationUnauthorized: jotai_x0.SimpleWritableAtom<() => void>; onQueryUnauthorized: jotai_x0.SimpleWritableAtom<(info: { queryName: string; }) => void>; isUnauthorized: jotai_x0.SimpleWritableAtom<(error: unknown) => boolean>; token: jotai_x0.SimpleWritableAtom<string | null>; expiresAt: jotai_x0.SimpleWritableAtom<number | null>; isLoading: jotai_x0.SimpleWritableAtom<boolean>; isAuthenticated: jotai_x0.SimpleWritableAtom<boolean>; sessionSyncGraceUntil: jotai_x0.SimpleWritableAtom<number | null>; } & object)[K] extends jotai_vanilla0.Atom<infer V_1> ? V_1 : never, prevSelectorOutput?: S | undefined) => S) | undefined; equalityFn?: ((prev: S, next: S) => boolean) | undefined; } & jotai_x0.UseAtomOptions) | undefined, deps?: unknown[]) => S; type AuthStore = ReturnType<typeof useAuthStore>; /** * Safe wrapper around useConvexAuth that doesn't throw when used outside auth provider. * Returns { isAuthenticated: false, isLoading: false } when no auth provider. * * Supports both: * - better-auth users (via AuthProvider) * - @convex-dev/auth users (via ConvexAuthBridge) */ declare function useSafeConvexAuth(): ConvexAuthResult; /** * Internal bridge component. Use `ConvexProviderWithAuth` instead. * @internal */ declare function ConvexAuthBridge({ children }: { children: React.ReactNode; }): react_jsx_runtime0.JSX.Element; /** * Convex provider with auth bridge for @convex-dev/auth users. * Automatically wraps children with ConvexAuthBridge. * * @example * ```tsx * import { ConvexProviderWithAuth } from 'kitcn/react'; * * <ConvexProviderWithAuth client={convex} useAuth={useAuthFromConvexDev}> * <App /> * </ConvexProviderWithAuth> * ``` */ declare function ConvexProviderWithAuth({ children, ...props }: React.ComponentProps<typeof ConvexProviderWithAuth$1>): react_jsx_runtime0.JSX.Element; declare const useAuth: () => { hasSession: boolean; isAuthenticated: boolean; isLoading: boolean; }; /** Check if user maybe has auth (optimistic, has token) */ declare const useMaybeAuth: () => boolean; /** Check if user is authenticated (server-verified) */ declare const useIsAuth: () => boolean; declare const useAuthGuard: () => (callback?: () => Promise<void> | void) => boolean | undefined; /** Render children only when maybe has auth (optimistic) */ declare function MaybeAuthenticated({ children }: { children: React.ReactNode; }): react.ReactNode; /** Render children only when authenticated (server-verified) */ declare function Authenticated({ children }: { children: React.ReactNode; }): react.ReactNode; /** Render children only when maybe not auth (optimistic) */ declare function MaybeUnauthenticated({ children }: { children: React.ReactNode; }): react.ReactNode; /** Render children only when not authenticated (server-verified) */ declare function Unauthenticated({ children }: { children: React.ReactNode; }): react.ReactNode; //#endregion //#region src/crpc/transformer.d.ts /** * Generic transformer contract (mirrors tRPC shape). */ interface DataTransformer { deserialize(object: any): any; serialize(object: any): any; } /** * Separate input/output transformers. */ interface CombinedDataTransformer { input: DataTransformer; output: DataTransformer; } /** * Transformer config accepted by cRPC. */ type DataTransformerOptions = CombinedDataTransformer | DataTransformer; //#endregion //#region src/react/client.d.ts interface ConvexQueryClientOptions extends ConvexReactClientOptions { /** Auth store for checking auth state in queryFn */ authStore?: AuthStore; /** * Opt out of consistent SSR queries for faster performance. * Trade-off: queries may return results from different timestamps. */ dangerouslyUseInconsistentQueriesDuringSSR?: boolean; /** TanStack QueryClient. Can also be set later via .connect(queryClient) */ queryClient?: QueryClient; /** Custom fetch for SSR. Avoid bundling on client. */ serverFetch?: typeof globalThis.fetch; /** Optional payload transformer (always composed with built-in Date support). */ transformer?: DataTransformerOptions; /** * Delay in ms before unsubscribing when a query has no observers. * Prevents wasteful unsubscribe/subscribe cycles from React StrictMode * and quick back/forward navigation. Set to 0 to unsubscribe immediately. * @default 3000 */ unsubscribeDelay?: number; } /** * Bridges TanStack Query with Convex real-time subscriptions. * * ## Setup * * ```ts * const convexQueryClient = new ConvexQueryClient(CONVEX_URL); * * const queryClient = new QueryClient({ * defaultOptions: { * queries: { * queryFn: convexQueryClient.queryFn(), * queryKeyHashFn: convexQueryClient.hashFn(), * }, * }, * }); * * convexQueryClient.connect(queryClient); * ``` * * ## How It Works * * 1. **Query Added**: When TanStack adds a Convex query to cache, * ConvexQueryClient creates a WebSocket subscription via `watchQuery`. * * 2. **Real-time Updates**: When Convex pushes an update, the subscription * callback updates the TanStack cache via `setQueryData`. * * 3. **Query Removed**: When TanStack removes a query (no observers), * ConvexQueryClient unsubscribes from the WebSocket. * * ## Auth Integration * * Auth is handled via TanStack Query `meta`: * - `convexQuery` includes `meta.authType` from generated Convex metadata * - `queryFn` checks `meta.authType` via `getAuthState()` and throws `CRPCClientError` if unauthorized * - `subscribeInner` respects `meta.subscribe === false` to skip WebSocket */ declare class ConvexQueryClient { /** Convex client for WebSocket subscriptions (client) and one-shot queries */ convexClient: ConvexReactClient$1; /** * Active WebSocket subscriptions, keyed by TanStack query hash. * Each subscription has: * - watch: Convex Watch object for the query * - unsubscribe: Cleanup function to remove the subscription * - queryKey: Original query key for cache updates */ subscriptions: Record<string, { watch: Watch<unknown>; unsubscribe: () => void; queryKey: QueryKey; }>; /** Cleanup function for QueryCache subscription */ unsubscribe: (() => void) | undefined; /** * Pending unsubscribes with timeout IDs. * Used to debounce unsubscribe/subscribe cycles from React StrictMode. */ private pendingUnsubscribes; /** HTTP client for SSR queries (no WebSocket on server) */ serverHttpClient?: ConvexHttpClient; /** TanStack QueryClient reference */ _queryClient: QueryClient | undefined; /** SSR query mode: 'consistent' guarantees same timestamp, 'inconsistent' is faster */ ssrQueryMode: 'consistent' | 'inconsistent'; /** Auth store for checking auth state */ private authStore?; /** Delay before unsubscribing when query has no observers */ private unsubscribeDelay; /** Payload transformer used across request/response boundaries. */ private transformer; /** Runtime-safe accessor for pending unsubscribe map (defensive for HMR edge cases) */ private getPendingUnsubscribesMap; /** Cancel a pending delayed unsubscribe for a query hash. */ private cancelPendingUnsubscribe; /** Unsubscribe a live Convex watch (if present) and remove it from the subscription map. */ private unsubscribeQueryByHash; private isAuthBoundQuery; private isQueryDisabled; private subscribeQuery; /** Update auth store (for HMR where jotai store may reset) */ updateAuthStore(authStore?: AuthStore): void; /** Get current auth state from store */ private getAuthState; /** * Check if subscription should be skipped due to auth state. * Needed for useSuspenseQuery which ignores enabled: false. * Regular useQuery already handles this via enabled: false in hooks. */ private shouldSkipSubscription; /** Get QueryClient, throwing if not connected */ get queryClient(): QueryClient; /** * Create a ConvexQueryClient. * * @param client - Convex URL string or existing ConvexReactClient * @param options - Configuration options */ constructor(client: ConvexReactClient$1 | string, options?: ConvexQueryClientOptions); /** * Connect to TanStack QueryClient. * Starts listening to cache events for subscription management. */ connect(queryClient: QueryClient): void; /** * Clean up all subscriptions. * Call this when the client is no longer needed. */ destroy(): void; /** * Unsubscribe from all auth-required queries. * Call before logout to prevent UNAUTHORIZED errors during session invalidation. */ unsubscribeAuthQueries(): void; resetAuthQueries(): Promise<void>; /** * Batch update all subscriptions. * Called internally when Convex client reconnects. */ onUpdate: () => void; /** * Handle Convex subscription update for a specific query. * Reads latest value from Watch and updates TanStack cache. * * @param queryHash - TanStack query hash identifying the query */ onUpdateQueryKeyHash(queryHash: string): void; /** * Subscribe to TanStack QueryCache events. * Creates/removes Convex WebSocket subscriptions as queries are added/removed. * * @param queryCache - TanStack QueryCache to subscribe to * @returns Cleanup function to unsubscribe */ subscribeInner(queryCache: QueryCache): () => void; /** * Create default queryFn for TanStack QueryClient. * * Handles: * - Convex queries and actions * - Auth checking (throws CRPCClientError if unauthorized) * - SSR via HTTP client * - Client via WebSocket client * * ## Usage * * ```ts * const queryClient = new QueryClient({ * defaultOptions: { * queries: { * queryFn: convexQueryClient.queryFn(), * }, * }, * }); * ``` * * @param otherFetch - Fallback queryFn for non-Convex queries * @returns QueryFunction compatible with TanStack Query */ queryFn(otherFetch?: QueryFunction<unknown, QueryKey>): <T extends FunctionReference<"query", "public">>(context: QueryFunctionContext<readonly unknown[]>) => Promise<FunctionReturnType<T>>; /** * Create hash function for TanStack QueryClient. * * Uses Convex-specific hashing for Convex queries to ensure * consistent cache keys across serialization. * * ## Usage * * ```ts * const queryClient = new QueryClient({ * defaultOptions: { * queries: { * queryKeyHashFn: convexQueryClient.hashFn(), * }, * }, * }); * ``` * * @param otherHashKey - Fallback hash function for non-Convex queries * @returns Hash function compatible with TanStack Query */ hashFn(fallback?: (queryKey: readonly unknown[]) => string): (queryKey: readonly unknown[]) => string; } //#endregion //#region src/internal/types.d.ts /** * Simplify complex type intersections for better IDE display. * Pattern from Drizzle ORM: The & {} intersection "seals" the type to prevent * distributive conditional behavior that can cause union widening. * @see https://github.com/drizzle-team/drizzle-orm/blob/main/drizzle-orm/src/utils.ts#L144-L149 */ type Simplify<TType> = TType extends any[] | Date ? TType : { [K in keyof TType]: TType[K] } & {}; /** * Omit keys without removing a potential union. * Unlike standard Omit, this preserves union types. */ type DistributiveOmit<TObj, TKey extends keyof any> = TObj extends any ? Omit<TObj, TKey> : never; /** Makes the object recursively optional */ type DeepPartial<TObject> = TObject extends object ? { [P in keyof TObject]?: DeepPartial<TObject[P]> } : TObject; //#endregion //#region src/server/types.d.ts /** Marker for unset values - branded type to distinguish "not set" from actual types */ type UnsetMarker = { readonly __brand: 'UnsetMarker'; }; //#endregion //#region src/server/http-types.d.ts type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'; interface HttpRouteDefinition<TMethod extends HttpMethod = HttpMethod> { method: TMethod; path: string; pathParamNames: string[]; usePathPrefix: boolean; } interface HttpActionHandler { isHttp: true; } interface HttpProcedure<TInput extends UnsetMarker | z.ZodTypeAny = any, TOutput extends UnsetMarker | z.ZodTypeAny = any, TParams extends UnsetMarker | z.ZodTypeAny = any, TQuery extends UnsetMarker | z.ZodTypeAny = any, TMethod extends HttpMethod = HttpMethod, TForm extends UnsetMarker | z.ZodTypeAny = any> extends HttpActionHandler { _crpcHttpRoute: HttpRouteDefinition<TMethod>; /** @internal Expose def for client-side type inference */ _def: { inputSchema?: TInput; outputSchema?: TOutput; paramsSchema?: TParams; querySchema?: TQuery; formSchema?: TForm; }; } //#endregion //#region src/server/http-router.d.ts /** * Recursive router record - can contain procedures or nested routers */ interface HttpRouterRecord { [key: string]: HttpProcedure | HttpRouterRecord | CRPCHttpRouter<any>; } /** * Router definition - stores both flat procedures and hierarchical record */ interface HttpRouterDef<TRecord extends HttpRouterRecord> { /** Flat map with dot-notation keys (e.g., "todos.get") for lookup */ procedures: Record<string, HttpProcedure>; /** Hierarchical structure for type inference */ record: TRecord; router: true; } /** * HTTP Router - like tRPC's BuiltRouter */ interface CRPCHttpRouter<TRecord extends HttpRouterRecord> { _def: HttpRouterDef<TRecord>; } //#endregion //#region src/crpc/http-types.d.ts /** Error codes that can be returned from HTTP endpoints */ type HttpErrorCode = 'BAD_REQUEST' | 'UNAUTHORIZED' | 'FORBIDDEN' | 'NOT_FOUND' | 'METHOD_NOT_SUPPORTED' | 'CONFLICT' | 'UNPROCESSABLE_CONTENT' | 'TOO_MANY_REQUESTS' | 'INTERNAL_SERVER_ERROR' | 'UNKNOWN'; /** HTTP client error */ declare class HttpClientError extends Error { readonly name = "HttpClientError"; readonly code: HttpErrorCode; readonly status: number; readonly procedureName: string; constructor(opts: { code: HttpErrorCode; status: number; procedureName: string; message?: string; }); } //#endregion //#region src/crpc/types.d.ts /** Symbol key for attaching FunctionReference to options (non-serializable) */ declare const FUNC_REF_SYMBOL: unique symbol; /** Options controlled by convexQuery/convexAction factories */ type ReservedQueryOptions$2 = 'queryKey' | 'queryFn' | 'staleTime'; /** Options controlled by mutation factories */ type ReservedMutationOptions$2 = 'mutationFn'; /** Reserved options controlled by infinite query factories */ type ReservedInfiniteQueryOptions = 'queryKey' | 'queryFn' | 'staleTime' | 'refetchInterval' | 'refetchOnMount' | 'refetchOnReconnect' | 'refetchOnWindowFocus' | 'persister' | 'placeholderData'; /** Metadata for a single Convex function */ type FnMeta$1 = { auth?: 'required' | 'optional'; role?: string; ratelimit?: string; type?: 'query' | 'mutation' | 'action'; limit?: number; [key: string]: unknown; }; /** Metadata for paginated functions (limit is required) */ type PaginatedFnMeta = Omit<FnMeta$1, 'limit'> & { limit: number; }; /** Metadata for all Convex functions by namespace.fnName, with _http for HTTP routes */ type Meta = Record<string, Record<string, FnMeta$1>> & { _http?: Record<string, { path: string; method: string; }>; }; /** Query key structure for Convex queries */ type ConvexQueryKey<T extends FunctionReference<'query'>> = readonly ['convexQuery', string, FunctionArgs<T>]; /** Query key structure for Convex actions */ type ConvexActionKey<T extends FunctionReference<'action'>> = readonly ['convexAction', string, FunctionArgs<T>]; /** Mutation key structure for Convex mutations/actions */ type ConvexMutationKey = ['convexMutation', string]; /** * Meta passed to TanStack Query for auth and subscription control. * Set by convexQuery, read by ConvexQueryClient.queryFn() and subscribeInner(). */ type ConvexQueryMeta = { /** Auth type from generated Convex metadata via getMeta() */authType?: 'required' | 'optional'; /** Skip query silently when unauthenticated (returns null) */ skipUnauth?: boolean; /** Whether to create WebSocket subscription (default: true) */ subscribe?: boolean; }; /** Hook options for Convex queries */ type ConvexQueryHookOptions = { /** Skip query silently when unauthenticated (default: false, calls onQueryUnauthorized) */skipUnauth?: boolean; /** Set to false to fetch once without subscribing (default: true) */ subscribe?: boolean; }; /** Extract input args without cursor/limit (user's filter args only) */ type InfiniteQueryInput<TInput> = Omit<TInput, 'cursor' | 'limit'>; /** Extract item type from PaginationResult<T> */ type ExtractPaginatedItem<TOutput> = TOutput extends { page: (infer T)[]; } ? T : never; /** Metadata for infinite query (extends ConvexQueryMeta) */ type ConvexInfiniteQueryMeta = ConvexQueryMeta & { /** The query function name (serializable for RSC) */queryName: string; /** Query args without cursor/limit (user's filter args only) */ args: Record<string, unknown>; /** Items per page (optional - server uses .paginated() default) */ limit?: number; }; type EmptyObject = Record<string, never>; /** Static query options parameter type (non-hook, for event handlers) */ type StaticQueryOptsParam = { skipUnauth?: boolean; }; /** Check if a type has cursor key (pagination detection) */ type IsPaginated<T> = 'cursor' extends keyof T ? true : false; /** Mutation variables type - undefined when no args required (allows mutateAsync() without args) */ type MutationVariables<T extends FunctionReference<'mutation' | 'action'>> = keyof FunctionArgs<T> extends never ? void : EmptyObject extends FunctionArgs<T> ? FunctionArgs<T> | undefined : FunctionArgs<T>; /** Vanilla mutation - direct .mutate() call without TanStack Query */ type VanillaMutation<T extends FunctionReference<'mutation'>> = { mutate: keyof FunctionArgs<T> extends never ? (args?: EmptyObject) => Promise<FunctionReturnType<T>> : EmptyObject extends FunctionArgs<T> ? (args?: FunctionArgs<T>) => Promise<FunctionReturnType<T>> : (args: FunctionArgs<T>) => Promise<FunctionReturnType<T>>; }; /** Vanilla action - both .query() and .mutate() for direct calls */ type VanillaAction<T extends FunctionReference<'action'>> = { query: keyof FunctionArgs<T> extends never ? (args?: EmptyObject) => Promise<FunctionReturnType<T>> : EmptyObject extends FunctionArgs<T> ? (args?: FunctionArgs<T>) => Promise<FunctionReturnType<T>> : (args: FunctionArgs<T>) => Promise<FunctionReturnType<T>>; mutate: keyof FunctionArgs<T> extends never ? (args?: EmptyObject) => Promise<FunctionReturnType<T>> : EmptyObject extends FunctionArgs<T> ? (args?: FunctionArgs<T>) => Promise<FunctionReturnType<T>> : (args: FunctionArgs<T>) => Promise<FunctionReturnType<T>>; }; //#endregion //#region src/react/crpc-types.d.ts /** Options returned by `convexQuery` factory */ type ConvexQueryOptions<T extends FunctionReference<'query'>> = Pick<UseQueryOptions<FunctionReturnType<T>, Error, FunctionReturnType<T>, ConvexQueryKey<T>>, 'queryKey' | 'staleTime' | 'enabled'>; /** Options returned by `convexAction` factory */ type ConvexActionOptions<T extends FunctionReference<'action'>> = Pick<UseQueryOptions<FunctionReturnType<T>, Error, FunctionReturnType<T>, ConvexActionKey<T>>, 'queryKey' | 'staleTime' | 'enabled'>; /** Query options parameter type */ type QueryOptsParam<T extends FunctionReference<'query'>> = Simplify<ConvexQueryHookOptions & DistributiveOmit<UseQueryOptions<FunctionReturnType<T>, DefaultError>, ReservedQueryOptions$2>>; /** Query options return type */ type QueryOptsReturn<T extends FunctionReference<'query'>> = ConvexQueryOptions<T> & { meta: ConvexQueryMeta; }; /** Action query options parameter type (actions don't support subscriptions) */ type ActionQueryOptsParam<T extends FunctionReference<'action'>> = DistributiveOmit<UseQueryOptions<FunctionReturnType<T>, DefaultError>, ReservedQueryOptions$2>; /** Action query options return type */ type ActionQueryOptsReturn<T extends FunctionReference<'action'>> = ConvexActionOptions<T>; /** * Decorated query procedure with queryOptions, queryKey, and queryFilter methods. * Args are optional when the function has no required parameters. * Supports skipToken for type-safe conditional queries. */ type DecorateQuery<T extends FunctionReference<'query'>> = { queryOptions: keyof FunctionArgs<T> extends never ? (args?: EmptyObject | SkipToken, opts?: QueryOptsParam<T>) => QueryOptsReturn<T> : EmptyObject extends FunctionArgs<T> ? (args?: FunctionArgs<T> | SkipToken, opts?: QueryOptsParam<T>) => QueryOptsReturn<T> : (args: FunctionArgs<T> | SkipToken, opts?: QueryOptsParam<T>) => QueryOptsReturn<T>; /** Static (non-hook) query options for event handlers and prefetching */ staticQueryOptions: keyof FunctionArgs<T> extends never ? (args?: EmptyObject | SkipToken, opts?: StaticQueryOptsParam) => ConvexQueryOptions<T> & { meta: ConvexQueryMeta; } : EmptyObject extends FunctionArgs<T> ? (args?: FunctionArgs<T> | SkipToken, opts?: StaticQueryOptsParam) => ConvexQueryOptions<T> & { meta: ConvexQueryMeta; } : (args: FunctionArgs<T> | SkipToken, opts?: StaticQueryOptsParam) => ConvexQueryOptions<T> & { meta: ConvexQueryMeta; }; /** Get query key for QueryClient methods (setQueryData, getQueryData, etc.) */ queryKey: (args?: DeepPartial<FunctionArgs<T>>) => ConvexQueryKey<T>; /** Get query filter for QueryClient methods (invalidateQueries, removeQueries, etc.) */ queryFilter: (args?: DeepPartial<FunctionArgs<T>>, filters?: DistributiveOmit<QueryFilters, 'queryKey'>) => QueryFilters; }; /** Options for infinite query - extends TanStack Query options */ type InfiniteQueryOptsParam<T extends FunctionReference<'query'> = FunctionReference<'query'>> = { /** Items per page. Optional - server uses .paginated() default if not provided. */limit?: number; /** Skip query silently when unauthenticated */ skipUnauth?: boolean; /** Placeholder data shown while loading (item array, not pagination result) */ placeholderData?: ExtractPaginatedItem<FunctionReturnType<T>>[]; } & DistributiveOmit<UseQueryOptions<FunctionReturnType<T>, DefaultError>, ReservedInfiniteQueryOptions>; /** Return type of infiniteQueryOptions - compatible with TanStack prefetch */ type ConvexInfiniteQueryOptions<T extends FunctionReference<'query'>> = Pick<UseQueryOptions<FunctionReturnType<T>, Error, FunctionReturnType<T>, ConvexQueryKey<T>>, 'queryKey' | 'staleTime' | 'enabled'> & { meta: ConvexInfiniteQueryMeta; refetchInterval: false; refetchOnMount: false; refetchOnReconnect: false; refetchOnWindowFocus: false; /** Placeholder data shown while loading (item array, not pagination result) */ placeholderData?: ExtractPaginatedItem<FunctionReturnType<T>>[]; } & DistributiveOmit<UseQueryOptions<FunctionReturnType<T>, DefaultError>, ReservedInfiniteQueryOptions>; /** Infinite query options with attached function reference (client-only) */ type ConvexInfiniteQueryOptionsWithRef<T extends FunctionReference<'query'>> = ConvexInfiniteQueryOptions<T> & { [FUNC_REF_SYMBOL]: T; }; /** Infinite query options return type */ type InfiniteQueryOptsReturn<T extends FunctionReference<'query'>> = ConvexInfiniteQueryOptionsWithRef<T>; /** * Decorated infinite query procedure. * Only available on queries that have cursor/limit in their input (paginated). * Supports skipToken for conditional queries. * Args are optional when the function has no required parameters (besides cursor/limit). */ type DecorateInfiniteQuery<T extends FunctionReference<'query'>> = { /** Create infinite query options for useInfiniteQuery and prefetch */infiniteQueryOptions: keyof InfiniteQueryInput<FunctionArgs<T>> extends never ? (args?: EmptyObject | SkipToken, opts?: InfiniteQueryOptsParam<T>) => InfiniteQueryOptsReturn<T> : EmptyObject extends InfiniteQueryInput<FunctionArgs<T>> ? (args?: InfiniteQueryInput<FunctionArgs<T>> | SkipToken, opts?: InfiniteQueryOptsParam<T>) => InfiniteQueryOptsReturn<T> : (args: InfiniteQueryInput<FunctionArgs<T>> | SkipToken, opts?: InfiniteQueryOptsParam<T>) => InfiniteQueryOptsReturn<T>; /** Get query key for infinite query (QueryClient methods like setQueryData, getQueryData) */ infiniteQueryKey: (args?: DeepPartial<InfiniteQueryInput<FunctionArgs<T>>>) => ConvexQueryKey<T>; /** Function metadata from server (auth, limit, rateLimit, role, type) */ meta: PaginatedFnMeta; }; /** * Decorated mutation procedure with mutationOptions and mutationKey methods. */ type DecorateMutation<T extends FunctionReference<'mutation'>> = { mutationOptions: (opts?: DistributiveOmit<UseMutationOptions<FunctionReturnType<T>, DefaultError, MutationVariables<T>>, ReservedMutationOptions$2>) => UseMutationOptions<FunctionReturnType<T>, DefaultError, MutationVariables<T>>; /** Get mutation key for QueryClient methods */ mutationKey: () => ConvexMutationKey; }; /** * Decorated action procedure with queryOptions, mutationOptions, and key methods. * Actions can be used as one-shot queries (no subscription) or as mutations. * Supports skipToken for conditional queries. */ type DecorateAction<T extends FunctionReference<'action'>> = { /** Use action as a one-shot query (no WebSocket subscription) */queryOptions: keyof FunctionArgs<T> extends never ? (args?: EmptyObject | SkipToken, opts?: ActionQueryOptsParam<T>) => ActionQueryOptsReturn<T> : EmptyObject extends FunctionArgs<T> ? (args?: FunctionArgs<T> | SkipToken, opts?: ActionQueryOptsParam<T>) => ActionQueryOptsReturn<T> : (args: FunctionArgs<T> | SkipToken, opts?: ActionQueryOptsParam<T>) => ActionQueryOptsReturn<T>; /** Static (non-hook) action query options for event handlers and prefetching */ staticQueryOptions: keyof FunctionArgs<T> extends never ? (args?: EmptyObject | SkipToken, opts?: StaticQueryOptsParam) => ConvexActionOptions<T> & { meta: ConvexQueryMeta; } : EmptyObject extends FunctionArgs<T> ? (args?: FunctionArgs<T> | SkipToken, opts?: StaticQueryOptsParam) => ConvexActionOptions<T> & { meta: ConvexQueryMeta; } : (args: FunctionArgs<T> | SkipToken, opts?: StaticQueryOptsParam) => ConvexActionOptions<T> & { meta: ConvexQueryMeta; }; /** Use action as a mutation */ mutationOptions: (opts?: DistributiveOmit<UseMutationOptions<FunctionReturnType<T>, DefaultError, MutationVariables<T>>, ReservedMutationOptions$2>) => UseMutationOptions<FunctionReturnType<T>, DefaultError, MutationVariables<T>>; /** Get mutation key for QueryClient methods */ mutationKey: () => ConvexMutationKey; /** Get query key for QueryClient methods */ queryKey: (args?: DeepPartial<FunctionArgs<T>>) => ConvexActionKey<T>; /** Get query filter for QueryClient methods */ queryFilter: (args?: DeepPartial<FunctionArgs<T>>, filters?: DistributiveOmit<QueryFilters, 'queryKey'>) => QueryFilters; }; /** Vanilla query - direct .query() and .watchQuery() calls without React Query */ type VanillaQuery<T extends FunctionReference<'query'>> = { query: keyof FunctionArgs<T> extends never ? (args?: EmptyObject) => Promise<FunctionReturnType<T>> : EmptyObject extends FunctionArgs<T> ? (args?: FunctionArgs<T>) => Promise<FunctionReturnType<T>> : (args: FunctionArgs<T>) => Promise<FunctionReturnType<T>>; watchQuery: keyof FunctionArgs<T> extends never ? (args?: EmptyObject, opts?: WatchQueryOptions) => Watch<FunctionReturnType<T>> : EmptyObject extends FunctionArgs<T> ? (args?: FunctionArgs<T>, opts?: WatchQueryOptions) => Watch<FunctionReturnType<T>> : (args: FunctionArgs<T>, opts?: WatchQueryOptions) => Watch<FunctionReturnType<T>>; }; /** * Recursively creates vanilla client type for direct procedural calls. */ type VanillaCRPCClient<TApi> = { [K in keyof TApi as K extends string ? K extends `_${string}` ? never : K : K]: TApi[K] extends FunctionReference<'query'> ? VanillaQuery<TApi[K]> : TApi[K] extends FunctionReference<'mutation'> ? VanillaMutation<TApi[K]> : TApi[K] extends FunctionReference<'action'> ? VanillaAction<TApi[K]> : TApi[K] extends Record<string, unknown> ? VanillaCRPCClient<TApi[K]> : never }; /** * Recursively decorates all procedures in a Convex API object. */ type CRPCClient<TApi> = { [K in keyof TApi as K extends string ? K extends `_${string}` ? never : K : K]: TApi[K] extends FunctionReference<'query'> ? IsPaginated<FunctionArgs<TApi[K]>> extends true ? DecorateQuery<TApi[K]> & DecorateInfiniteQuery<TApi[K]> : DecorateQuery<TApi[K]> : TApi[K] extends FunctionReference<'mutation'> ? DecorateMutation<TApi[K]> : TApi[K] extends FunctionReference<'action'> ? DecorateAction<TApi[K]> : TApi[K] extends Record<string, unknown> ? CRPCClient<TApi[K]> : never }; //#endregion //#region src/crpc/http-client.d.ts /** Form value types (matches Hono's FormValue) */ type HttpFormValue = string | Blob; /** * Hybrid input args: JSON body fields at root, explicit params/searchParams/form. * - JSON body: spread at root level (tRPC-style) * - Path params: { params: { id: '123' } } * - Query params: { searchParams: { limit: '10' } } * - Form data: { form: { file: blob } } - typed via .form() builder * - Client options: { headers, fetch, init } - for per-call customization */ type HttpInputArgs = { /** Path parameters (e.g., :id in /users/:id) */params?: Record<string, string>; /** Query string parameters */ searchParams?: Record<string, string | string[]>; /** Form data body (Content-Type: multipart/form-data) - typed via .form() builder */ form?: Record<string, HttpFormValue | HttpFormValue[]>; /** Custom fetch function (per-call override) */ fetch?: typeof fetch; /** Standard RequestInit (per-call override) */ init?: RequestInit; /** Additional headers (per-call override) */ headers?: Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>); /** Any other properties are JSON body fields */ [key: string]: unknown; }; /** * Client request options (matches Hono's ClientRequestOptions). * Standard RequestInit in `init` takes highest priority and can override * things that are set automatically like body, method, headers. */ type HttpClientOptions = { /** Custom fetch function */fetch?: typeof fetch; /** * Standard RequestInit - takes highest priority. * Can override body, method, headers if needed. */ init?: RequestInit; /** Additional headers (or async function returning headers) */ headers?: Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>); }; //#endregion //#region src/react/http-proxy.d.ts type HttpRouteInfo = { path: string; method: string; }; type HttpRouteMap = Record<string, HttpRouteInfo>; /** Infer schema type or return empty object if UnsetMarker */ type InferSchemaOrEmpty<T> = T extends UnsetMarker ? object : T extends z.ZodTypeAny ? z.infer<T> : object; /** Infer merged input from HttpProcedure (flat - used internally) */ type InferHttpInput<T> = T extends HttpProcedure<infer TInput, infer _TOutput, infer TParams, infer TQuery> ? Simplify<InferSchemaOrEmpty<TParams> & InferSchemaOrEmpty<TQuery> & InferSchemaOrEmpty<TInput>> : object; /** * Extract string keys from a Zod object schema. * Used for param/query which are always strings in URLs. */ type ZodObjectKeys<T> = T extends z.ZodObject<infer Shape> ? { [K in keyof Shape]: string } : Record<string, string>; /** * Extract string or string[] keys from a Zod object schema. * Query params can have array values. */ type ZodQueryKeys<T> = T extends z.ZodObject<infer Shape> ? { [K in keyof Shape]?: string | string[] } : Record<string, string | string[]>; /** * Infer client-side args from HttpProcedure with proper nesting. * - params: only present if TParams is defined, always strings (URL path params) * - searchParams: only present if TQuery is defined, always strings (URL query params) * - form: only present if TForm is defined, typed from schema * - JSON body fields spread at root level (typed from schema) * - Client options (fetch, init, headers) always optional for per-call overrides */ type InferHttpClientArgs<T> = T extends HttpProcedure<infer TInput, infer _TOutput, infer TParams, infer TQuery, infer _TMethod, infer TForm> ? Simplify<(TParams extends UnsetMarker ? object : { params: ZodObjectKeys<TParams>; }) & (TQuery extends UnsetMarker ? object : { searchParams: ZodQueryKeys<TQuery>; }) & (TForm extends UnsetMarker ? object : TForm extends z.ZodTypeAny ? { form: z.infer<TForm>; } : object) & (TInput extends UnsetMarker ? object : TInput extends z.ZodTypeAny ? z.infer<TInput> : object) & { fetch?: typeof fetch; init?: RequestInit; headers?: Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>); }> : HttpInputArgs; /** Infer output type from HttpProcedure */ type InferHttpOutput<T> = T extends HttpProcedure<infer _TInput, infer TOutput, infer _TParams, infer _TQuery> ? TOutput extends UnsetMarker ? unknown : TOutput extends z.ZodTypeAny ? z.infer<TOutput> : unknown : unknown; /** Query key with args (3-element) or prefix key without args (2-element) for invalidation */ type HttpQueryKey = readonly ['httpQuery', string, unknown] | readonly ['httpQuery', string]; type HttpMutationKey = readonly ['httpMutation', string]; type ReservedQueryOptions$1 = 'queryKey' | 'queryFn'; type ReservedMutationOptions$1 = 'mutationFn'; /** Query options for GET HTTP endpoints - compatible with both useQuery and useSuspenseQuery */ type HttpQueryOptsReturn<T extends HttpProcedure> = Omit<UseQueryOptions<InferHttpOutput<T>, Error, InferHttpOutput<T>, HttpQueryKey>, 'queryFn'> & { queryFn: () => Promise<InferHttpOutput<T>>; }; /** Mutation options for POST/PUT/PATCH/DELETE HTTP endpoints - typed variables */ type HttpMutationOptsReturn<T extends HttpProcedure> = UseMutationOptions<InferHttpOutput<T>, DefaultError, InferHttpClientArgs<T>>; /** Query options (TanStack Query only - client opts go in args) */ type HttpQueryOptions<T extends HttpProcedure> = DistributiveOmit<HttpQueryOptsReturn<T>, ReservedQueryOptions$1>; /** Mutation options (TanStack Query only - client opts go in mutate args) */ type HttpMutationOptions<T extends HttpProcedure> = DistributiveOmit<HttpMutationOptsReturn<T>, ReservedMutationOptions$1>; /** * Decorated GET procedure with queryOptions and mutationOptions. * - queryOptions: For cached data fetching (useQuery/useSuspenseQuery) * - mutationOptions: For one-time actions like exports (useMutation) */ type DecorateHttpQuery<T extends HttpProcedure> = { queryOptions: keyof InferHttpInput<T> extends never ? (args?: InferHttpClientArgs<T>, opts?: HttpQueryOptions<T>) => HttpQueryOptsReturn<T> : object extends InferHttpInput<T> ? (args?: InferHttpClientArgs<T>, opts?: HttpQueryOptions<T>) => HttpQueryOptsReturn<T> : (args: InferHttpClientArgs<T>, opts?: HttpQueryOptions<T>) => HttpQueryOptsReturn<T>; /** Get query key for QueryClient methods (with args = exact match, without = prefix) */ queryKey: (args?: InferHttpClientArgs<T>) => HttpQueryKey; /** Get query filter for QueryClient methods (e.g., invalidateQueries) */ queryFilter: (args?: InferHttpClientArgs<T>, filters?: DistributiveOmit<QueryFilters, 'queryKey'>) => QueryFilters; /** Mutation options for GET endpoints (exports, downloads - no caching) */ mutationOptions: (opts?: HttpMutationOptions<T>) => HttpMutationOptsReturn<T>; /** Get mutation key for QueryClient methods */ mutationKey: () => HttpMutationKey; }; /** * Decorated POST/PUT/PATCH/DELETE procedure with mutationOptions. * The mutationFn receives typed args inferred from server schemas. */ type DecorateHttpMutation<T extends HttpProcedure> = { mutationOptions: (opts?: HttpMutationOptions<T>) => HttpMutationOptsReturn<T>; /** Get mutation key for QueryClient methods */ mutationKey: () => HttpMutationKey; }; /** Vanilla HTTP query - only direct call, no React Query */ type VanillaHttpQuery<T extends HttpProcedure> = { query: keyof InferHttpInput<T> extends never ? (args?: InferHttpClientArgs<T>) => Promise<InferHttpOutput<T>> : object extends InferHttpInput<T> ? (args?: InferHttpClientArgs<T>) => Promise<InferHttpOutput<T>> : (args: InferHttpClientArgs<T>) => Promise<InferHttpOutput<T>>; }; /** Vanilla HTTP mutation - only direct call, no React Query */ type VanillaHttpMutation<T extends HttpProcedure> = { mutate: keyof InferHttpInput<T> extends never ? (args?: InferHttpClientArgs<T>) => Promise<InferHttpOutput<T>> : object extends InferHttpInput<T> ? (args?: InferHttpClientArgs<T>) => Promise<InferHttpOutput<T>> : (args: InferHttpClientArgs<T>) => Promise<InferHttpOutput<T>>; }; /** Vanilla HTTP client type - only query/mutate methods */ type VanillaHttpCRPCClient<T extends HttpRouterRecord> = { [K in keyof T]: T[K] extends HttpProcedure<infer _TInput, infer _TOutput, infer _TParams, infer _TQuery, infer TMethod, infer _TForm> ? TMethod extends 'GET' ? VanillaHttpQuery<T[K]> : VanillaHttpMutation<T[K]> : T[K] extends CRPCHttpRouter<infer R> ? VanillaHttpCRPCClient<R> : T[K] extends HttpRouterRecord ? VanillaHttpCRPCClient<T[K]> : never }; /** Extract vanilla HTTP client from router */ type VanillaHttpCRPCClientFromRouter<T> = T extends CRPCHttpRouter<infer R> ? VanillaHttpCRPCClient<R> : never; /** * HTTP Client type from router record. * Maps each procedure to queryOptions (GET) or mutationOptions (POST/etc). * Uses infer to extract the method type literal for proper GET/non-GET distinction. */ type HttpCRPCClient<T extends HttpRouterRecord> = { [K in keyof T]: T[K] extends HttpProcedure<infer _TInput, infer _TOutput, infer _TParams, infer _TQuery, infer TMethod, infer _TForm> ? TMethod extends 'GET' ? DecorateHttpQuery<T[K]> : DecorateHttpMutation<T[K]> : T[K] extends CRPCHttpRouter<infer R> ? HttpCRPCClient<R> : T[K] extends HttpRouterRecord ? HttpCRPCClient<T[K]> : never }; /** * HTTP Client type from a CRPCHttpRouter. * Use this when your type is the router object (with _def). */ type HttpCRPCClientFromRouter<TRouter extends CRPCHttpRouter<any>> = HttpCRPCClient<TRouter['_def']['record']>; interface HttpProxyOptions<TRoutes extends HttpRouteMap> { /** Base URL for the Convex HTTP API (e.g., https://your-site.convex.site) */ convexSiteUrl: string; /** Custom fetch function (defaults to global fetch) */ fetch?: typeof fetch; /** Default headers or async function returning headers (for auth tokens) */ headers?: { [key: string]: string | undefined; } | (() => { [key: string]: string | undefined; } | Promise<{ [key: string]: string | undefined; }>); /** Error handler called on HTTP errors */ onError?: (error: HttpClientError) => void; /** Runtime route definitions (from codegen httpRoutes) */ routes: TRoutes; /** Optional payload transformer (always composed with built-in Date support). */ transformer?: DataTransformerOptions; } /** * Create an HTTP proxy with TanStack Query integration. * * Returns a proxy that provides: * - `queryOptions` for GET endpoints (no subscription) * - `mutationOptions` for POST/PUT/PATCH/DELETE endpoints * * @example * ```ts * const httpProxy = createHttpProxy<AppRouter>({ * convexSiteUrl: process.env.NEXT_PUBLIC_CONVEX_SITE_URL!, * routes: httpRoutes, * }); * * // GET endpoint * const opts = httpProxy.todos.get.queryOptions({ id: '123' }); * const { data } = useQuery(opts); * * // POST endpoint * const mutation = useMutation(httpProxy.todos.create.mutationOptions()); * await mutation.mutateAsync({ title: 'New todo' }); * ``` */ declare function createHttpProxy<TRouter extends CRPCHttpRouter<any>, TRoutes extends HttpRouteMap = HttpRouteMap>(opts: HttpProxyOptions<TRoutes>): HttpCRPCClientFromRouter<TRouter>; //#endregion //#region src/react/context.d.ts /** Access ConvexQueryClient (e.g., for logout cleanup) */ declare const useConvexQueryClient: () => ConvexQueryClient | null; /** * Hook to access the meta object from context. * Returns undefined if meta was not provided to createCRPCContext. */ declare function useMeta(): Meta | undefined; /** * Hook to get auth type for a function from meta. */ declare function useFnMeta(): (namespace: string, fnName: string) => FnMeta$1 | undefined; /** Headers record that allows empty objects and optional properties */ type HeadersInput = { [key: string]: string | undefined; }; type CRPCHttpOptions = { /** Base URL for the Convex HTTP API (e.g., https://your-site.convex.site) */convexSiteUrl: string; /** Default headers or async function returning headers (for auth tokens) */ headers?: HeadersInput | (() => HeadersInput | Promise<HeadersInput>); /** Custom fetch function (defaults to global fetch) */ fetch?: typeof fetch; /** Error handler called on HTTP errors */ onError?: (error: HttpClientError) => void; }; type CreateCRPCContextOptions<TApi> = { api: TApi; /** Optional payload transformer (always composed with built-in Date support). */ transformer?: DataTransformerOptions; } & Partial<CRPCHttpOptions>; /** * Extract HTTP router from TApi['http'] if present (optional). * Uses NonNullable to handle optional http property. */ type ExtractHttpRouter<TApi> = TApi extends { http?: infer R; } ? NonNullable<R> extends CRPCHttpRouter<HttpRouterRecord> ? NonNullable<R> : undefined : undefined; /** * Create CRPC context, provider, and hooks for a Convex API. * * @param options - Configuration object containing api and optional HTTP settings * @returns Object with CRPCProvider, useCRPC, and useCRPCClient * * @example * ```tsx * // lib/crpc.ts * import { api } from '@convex/api'; * import { createCRPCContext } from 'kitcn/react'; * * // Works for both regular Convex functions and generated HTTP router types * export const { useCRPC } = createCRPCContext({ * api, * convexSiteUrl: process.env.NEXT_PUBLIC_CONVEX_SITE_URL!, * }); * * // components/user-profile.tsx * function UserProfile({ id }) { * const crpc = useCRPC(); * const { data } = useQuery(crpc.user.get.queryOptions({ id })); * * // HTTP endpoints (if configured) * const { data: httpData } = useQuery(crpc.http.todos.get.queryOptions({ id })); * } * ``` */ declare function createCRPCContext<TApi extends Record<string, unknown>>(options: CreateCRPCContextOptions<TApi>): { CRPCProvider: ({ children, convexClient, convexQueryClient }: { children: ReactNode; convexClient: ConvexReactClient$1; convexQueryClient: ConvexQueryClient; }) => react_jsx_runtime0.JSX.Element; useCRPC: () => Extract