@richie-rpc/react-query 1.0.6 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,9 +2,9 @@
2
2
  "version": 3,
3
3
  "sources": ["../../index.ts"],
4
4
  "sourcesContent": [
5
- "import type {\n Client,\n ClientMethod,\n EndpointRequestOptions,\n EndpointResponse,\n SSEClientMethod,\n StreamingClientMethod,\n} from '@richie-rpc/client';\nimport type {\n Contract,\n SSEEndpointDefinition,\n StandardEndpointDefinition,\n StreamingEndpointDefinition,\n} from '@richie-rpc/core';\nimport {\n type UseMutationOptions,\n type UseMutationResult,\n type UseQueryOptions,\n type UseQueryResult,\n type UseSuspenseQueryOptions,\n type UseSuspenseQueryResult,\n useMutation,\n useQuery,\n useSuspenseQuery,\n} from '@tanstack/react-query';\n\n// HTTP methods that should use query hooks (read operations)\ntype QueryMethods = 'GET' | 'HEAD';\n\n// HTTP methods that should use mutation hooks (write operations)\ntype MutationMethods = 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS';\n\n/**\n * Hook wrapper for query endpoints (GET, HEAD)\n * Provides useQuery and useSuspenseQuery methods\n */\nexport type QueryHook<T extends StandardEndpointDefinition> = {\n /**\n * Standard query hook that returns loading states\n */\n useQuery: (\n options: EndpointRequestOptions<T>,\n queryOptions?: Omit<UseQueryOptions<EndpointResponse<T>, Error>, 'queryKey' | 'queryFn'>,\n ) => UseQueryResult<EndpointResponse<T>, Error>;\n\n /**\n * Suspense-enabled query hook that throws promises for React Suspense\n */\n useSuspenseQuery: (\n options: EndpointRequestOptions<T>,\n queryOptions?: Omit<\n UseSuspenseQueryOptions<EndpointResponse<T>, Error>,\n 'queryKey' | 'queryFn'\n >,\n ) => UseSuspenseQueryResult<EndpointResponse<T>, Error>;\n};\n\n/**\n * Hook wrapper for mutation endpoints (POST, PUT, PATCH, DELETE)\n * Provides useMutation method\n */\nexport type MutationHook<T extends StandardEndpointDefinition> = {\n /**\n * Mutation hook for write operations\n */\n useMutation: (\n mutationOptions?: Omit<\n UseMutationOptions<EndpointResponse<T>, Error, EndpointRequestOptions<T>>,\n 'mutationFn'\n >,\n ) => UseMutationResult<EndpointResponse<T>, Error, EndpointRequestOptions<T>>;\n};\n\n/**\n * Conditionally apply hook type based on HTTP method\n */\nexport type EndpointHook<T extends StandardEndpointDefinition> = T['method'] extends QueryMethods\n ? QueryHook<T>\n : T['method'] extends MutationMethods\n ? MutationHook<T>\n : never;\n\n/**\n * Hook wrapper for streaming endpoints\n * Exposes the streaming client method directly since React Query\n * doesn't fit well with long-lived streaming connections\n */\nexport type StreamingHook<T extends StreamingEndpointDefinition> = {\n /**\n * Start a streaming request\n */\n stream: StreamingClientMethod<T>;\n};\n\n/**\n * Hook wrapper for SSE endpoints\n * Exposes the SSE client method directly since React Query\n * doesn't fit well with long-lived SSE connections\n */\nexport type SSEHook<T extends SSEEndpointDefinition> = {\n /**\n * Create an SSE connection\n */\n connect: SSEClientMethod<T>;\n};\n\n/**\n * Complete hooks object for a contract\n * Each endpoint gets appropriate hooks based on its type and HTTP method\n */\nexport type Hooks<T extends Contract> = {\n [K in keyof T]: T[K] extends StandardEndpointDefinition\n ? EndpointHook<T[K]>\n : T[K] extends StreamingEndpointDefinition\n ? StreamingHook<T[K]>\n : T[K] extends SSEEndpointDefinition\n ? SSEHook<T[K]>\n : never;\n};\n\n/**\n * Create typed React hooks for all endpoints in a contract\n *\n * Query endpoints (GET, HEAD) get useQuery and useSuspenseQuery methods\n * Mutation endpoints (POST, PUT, PATCH, DELETE) get useMutation method\n *\n * @param client - The typed client created with createClient()\n * @param contract - The contract definition\n * @returns Hooks object with methods for each endpoint\n *\n * @example\n * ```tsx\n * const client = createClient(contract, { baseUrl: 'http://localhost:3000' });\n * const hooks = createHooks(client, contract);\n *\n * // In a component - Query\n * function UserList() {\n * const { data, isLoading } = hooks.listUsers.useQuery({\n * query: { limit: \"10\" }\n * });\n * // ...\n * }\n *\n * // In a component - Mutation\n * function CreateUser() {\n * const mutation = hooks.createUser.useMutation();\n * return (\n * <button onClick={() => mutation.mutate({\n * body: { name: \"Alice\", email: \"alice@example.com\" }\n * })}>\n * Create User\n * </button>\n * );\n * }\n * ```\n */\nexport function createHooks<T extends Contract>(client: Client<T>, contract: T): Hooks<T> {\n const hooks: Record<string, unknown> = {};\n\n for (const [name, endpoint] of Object.entries(contract)) {\n // Handle streaming endpoints\n if (endpoint.type === 'streaming') {\n const streamMethod = client[\n name as keyof T\n ] as unknown as StreamingClientMethod<StreamingEndpointDefinition>;\n hooks[name] = {\n stream: streamMethod,\n };\n continue;\n }\n\n // Handle SSE endpoints\n if (endpoint.type === 'sse') {\n const connectMethod = client[\n name as keyof T\n ] as unknown as SSEClientMethod<SSEEndpointDefinition>;\n hooks[name] = {\n connect: connectMethod,\n };\n continue;\n }\n\n // Handle standard endpoints\n const method = endpoint.method;\n const clientMethod = client[\n name as keyof T\n ] as unknown as ClientMethod<StandardEndpointDefinition>;\n\n if (method === 'GET' || method === 'HEAD') {\n // Create query hooks for read operations\n hooks[name] = {\n useQuery: (\n options: EndpointRequestOptions<StandardEndpointDefinition>,\n queryOptions?: Omit<\n UseQueryOptions<EndpointResponse<StandardEndpointDefinition>, Error>,\n 'queryKey' | 'queryFn'\n >,\n ) => {\n return useQuery({\n queryKey: [\n name,\n options.params ?? null,\n options.query ?? null,\n options.headers ?? null,\n options.body ?? null,\n ],\n queryFn: () => clientMethod(options),\n ...queryOptions,\n });\n },\n useSuspenseQuery: (\n options: EndpointRequestOptions<StandardEndpointDefinition>,\n queryOptions?: Omit<\n UseSuspenseQueryOptions<EndpointResponse<StandardEndpointDefinition>, Error>,\n 'queryKey' | 'queryFn'\n >,\n ) => {\n return useSuspenseQuery({\n queryKey: [\n name,\n options.params ?? null,\n options.query ?? null,\n options.headers ?? null,\n options.body ?? null,\n ],\n queryFn: () => clientMethod(options),\n ...queryOptions,\n });\n },\n };\n } else {\n // Create mutation hooks for write operations\n hooks[name] = {\n useMutation: (\n mutationOptions?: Omit<\n UseMutationOptions<\n EndpointResponse<StandardEndpointDefinition>,\n Error,\n EndpointRequestOptions<StandardEndpointDefinition>\n >,\n 'mutationFn'\n >,\n ) => {\n return useMutation({\n mutationFn: (options: EndpointRequestOptions<StandardEndpointDefinition>) =>\n clientMethod(options),\n ...mutationOptions,\n });\n },\n };\n }\n }\n\n return hooks as Hooks<T>;\n}\n"
5
+ "/* eslint-disable @typescript-eslint/no-explicit-any */\nimport type {\n Client,\n ClientMethod,\n DownloadClientMethod,\n EndpointRequestOptions,\n EndpointResponse,\n SSEClientMethod,\n StreamingClientMethod,\n} from '@richie-rpc/client';\nimport type {\n Contract,\n DownloadEndpointDefinition,\n ExtractChunk,\n SSEEndpointDefinition,\n StandardEndpointDefinition,\n StreamingEndpointDefinition,\n} from '@richie-rpc/core';\nimport {\n type InfiniteData,\n type QueryClient,\n type QueryKey,\n type Updater,\n type UseInfiniteQueryOptions,\n type UseInfiniteQueryResult,\n type UseMutationOptions,\n type UseMutationResult,\n type UseQueryOptions,\n type UseQueryResult,\n type UseSuspenseInfiniteQueryOptions,\n type UseSuspenseInfiniteQueryResult,\n type UseSuspenseQueryOptions,\n type UseSuspenseQueryResult,\n experimental_streamedQuery as streamedQuery,\n useInfiniteQuery,\n useMutation,\n useQuery,\n useSuspenseInfiniteQuery,\n useSuspenseQuery,\n} from '@tanstack/react-query';\n\n// ============================================\n// HTTP Method Categories\n// ============================================\n\ntype QueryMethods = 'GET' | 'HEAD';\ntype MutationMethods = 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS';\n\n// ============================================\n// Query Options Types (ts-rest style)\n// ============================================\n\n/**\n * Unified query options - combines queryKey, queryData, and TanStack Query options\n */\nexport type TsrQueryOptions<T extends StandardEndpointDefinition> = Omit<\n UseQueryOptions<EndpointResponse<T>, Error>,\n 'queryKey' | 'queryFn'\n> & {\n queryKey: QueryKey;\n queryData: EndpointRequestOptions<T>;\n};\n\n/**\n * Suspense query options\n */\nexport type TsrSuspenseQueryOptions<T extends StandardEndpointDefinition> = Omit<\n UseSuspenseQueryOptions<EndpointResponse<T>, Error>,\n 'queryKey' | 'queryFn'\n> & {\n queryKey: QueryKey;\n queryData: EndpointRequestOptions<T>;\n};\n\n/**\n * Infinite query options - queryData is a function that receives pageParam\n */\nexport type TsrInfiniteQueryOptions<\n T extends StandardEndpointDefinition,\n TPageParam = unknown,\n> = Omit<\n UseInfiniteQueryOptions<EndpointResponse<T>, Error, unknown, QueryKey, TPageParam>,\n 'queryKey' | 'queryFn'\n> & {\n queryKey: QueryKey;\n queryData: (context: { pageParam: TPageParam }) => EndpointRequestOptions<T>;\n};\n\n/**\n * Suspense infinite query options\n */\nexport type TsrSuspenseInfiniteQueryOptions<\n T extends StandardEndpointDefinition,\n TPageParam = unknown,\n> = Omit<\n UseSuspenseInfiniteQueryOptions<EndpointResponse<T>, Error, unknown, QueryKey, TPageParam>,\n 'queryKey' | 'queryFn'\n> & {\n queryKey: QueryKey;\n queryData: (context: { pageParam: TPageParam }) => EndpointRequestOptions<T>;\n};\n\n/**\n * Mutation options - same as TanStack Query but typed\n */\nexport type TsrMutationOptions<T extends StandardEndpointDefinition> = Omit<\n UseMutationOptions<EndpointResponse<T>, Error, EndpointRequestOptions<T>>,\n 'mutationFn'\n>;\n\n/**\n * Stream query options for streaming endpoints\n */\nexport type TsrStreamQueryOptions<T extends StreamingEndpointDefinition> = Omit<\n UseQueryOptions<ExtractChunk<T>[], Error>,\n 'queryKey' | 'queryFn'\n> & {\n queryKey: QueryKey;\n queryData: EndpointRequestOptions<T>;\n refetchMode?: 'reset' | 'append' | 'replace';\n};\n\n// ============================================\n// Error Handling Utilities\n// ============================================\n\n/**\n * Response type from hooks - includes status and body like ts-rest\n */\nexport type TsrResponse<T extends StandardEndpointDefinition> = EndpointResponse<T>;\n\n/**\n * Error response type - either fetch error or typed response error\n */\nexport type TsrError =\n | Error\n | {\n status: number;\n data: unknown;\n };\n\n/**\n * Check if an error is a fetch/network error (Error instance, not a response)\n */\nexport function isFetchError(error: unknown): error is Error {\n return error instanceof Error && !('status' in error);\n}\n\n/**\n * Check if error is a response with a status code not defined in the contract\n */\nexport function isUnknownErrorResponse<T extends StandardEndpointDefinition>(\n error: unknown,\n endpoint: T,\n): error is { status: number; data: unknown } {\n if (!error || typeof error !== 'object' || !('status' in error)) {\n return false;\n }\n const status = (error as { status: number }).status;\n return !(status in endpoint.responses);\n}\n\n/**\n * Check if error is either a fetch error or unknown response error\n */\nexport function isNotKnownResponseError<T extends StandardEndpointDefinition>(\n error: unknown,\n endpoint: T,\n): error is Error | { status: number; data: unknown } {\n return isFetchError(error) || isUnknownErrorResponse(error, endpoint);\n}\n\n/**\n * Exhaustive guard for compile-time exhaustiveness checking\n * Use after handling all known error cases to ensure nothing is missed\n */\nexport function exhaustiveGuard(_value: never): never {\n throw new Error(`Unhandled case: ${JSON.stringify(_value)}`);\n}\n\n// ============================================\n// Typed QueryClient Types\n// ============================================\n\n/**\n * Typed query client methods for a single query endpoint\n */\nexport type TypedQueryEndpointClient<T extends StandardEndpointDefinition> = {\n getQueryData: (queryKey: QueryKey) => EndpointResponse<T> | undefined;\n setQueryData: (\n queryKey: QueryKey,\n updater: Updater<EndpointResponse<T> | undefined, EndpointResponse<T> | undefined>,\n ) => EndpointResponse<T> | undefined;\n getQueryState: (queryKey: QueryKey) => ReturnType<QueryClient['getQueryState']>;\n fetchQuery: (options: TsrQueryOptions<T>) => Promise<EndpointResponse<T>>;\n prefetchQuery: (options: TsrQueryOptions<T>) => Promise<void>;\n ensureQueryData: (options: TsrQueryOptions<T>) => Promise<EndpointResponse<T>>;\n};\n\n/**\n * Typed query client methods for a single mutation endpoint\n * Mutations don't have query-specific cache methods\n */\nexport type TypedMutationEndpointClient<_T extends StandardEndpointDefinition> = object;\n\n/**\n * Full typed query client - extends QueryClient with per-endpoint methods\n */\nexport type TypedQueryClient<T extends Contract> = QueryClient & {\n [K in keyof T]: T[K] extends StandardEndpointDefinition\n ? T[K]['method'] extends QueryMethods\n ? TypedQueryEndpointClient<T[K]>\n : TypedMutationEndpointClient<T[K]>\n : Record<string, never>;\n};\n\n// ============================================\n// Endpoint API Types\n// ============================================\n\n/**\n * API for query endpoints (GET, HEAD)\n */\nexport type QueryEndpointApi<T extends StandardEndpointDefinition> = {\n /** Standard query hook */\n useQuery: (options: TsrQueryOptions<T>) => UseQueryResult<EndpointResponse<T>, Error> & {\n contractEndpoint: T;\n };\n /** Suspense query hook */\n useSuspenseQuery: (options: TsrSuspenseQueryOptions<T>) => UseSuspenseQueryResult<EndpointResponse<T>, Error> & {\n contractEndpoint: T;\n };\n /** Infinite query hook */\n useInfiniteQuery: <TPageParam = unknown>(\n options: TsrInfiniteQueryOptions<T, TPageParam>,\n ) => UseInfiniteQueryResult<InfiniteData<EndpointResponse<T>, TPageParam>, Error> & {\n contractEndpoint: T;\n };\n /** Suspense infinite query hook */\n useSuspenseInfiniteQuery: <TPageParam = unknown>(\n options: TsrSuspenseInfiniteQueryOptions<T, TPageParam>,\n ) => UseSuspenseInfiniteQueryResult<InfiniteData<EndpointResponse<T>, TPageParam>, Error> & {\n contractEndpoint: T;\n };\n /** Direct fetch without React Query */\n query: (options: EndpointRequestOptions<T>) => Promise<EndpointResponse<T>>;\n};\n\n/**\n * API for mutation endpoints (POST, PUT, PATCH, DELETE)\n */\nexport type MutationEndpointApi<T extends StandardEndpointDefinition> = {\n /** Mutation hook */\n useMutation: (\n options?: TsrMutationOptions<T>,\n ) => UseMutationResult<EndpointResponse<T>, Error, EndpointRequestOptions<T>> & {\n contractEndpoint: T;\n };\n /** Direct mutate without React Query */\n mutate: (options: EndpointRequestOptions<T>) => Promise<EndpointResponse<T>>;\n};\n\n/**\n * API for streaming endpoints\n */\nexport type StreamingEndpointApi<T extends StreamingEndpointDefinition> = {\n /** Direct stream access (event-based) */\n stream: StreamingClientMethod<T>;\n /** Query hook using experimental streamedQuery */\n useStreamQuery: (options: TsrStreamQueryOptions<T>) => UseQueryResult<ExtractChunk<T>[], Error> & {\n contractEndpoint: T;\n };\n};\n\n/**\n * API for SSE endpoints\n */\nexport type SSEEndpointApi<T extends SSEEndpointDefinition> = {\n /** Direct SSE connection access (event-based) */\n connect: SSEClientMethod<T>;\n};\n\n/**\n * API for download endpoints\n */\nexport type DownloadEndpointApi<T extends DownloadEndpointDefinition> = {\n /** Direct download without React Query */\n download: DownloadClientMethod<T>;\n};\n\n/**\n * Select appropriate API type based on endpoint type\n */\nexport type EndpointApi<T> = T extends StandardEndpointDefinition\n ? T['method'] extends QueryMethods\n ? QueryEndpointApi<T>\n : T['method'] extends MutationMethods\n ? MutationEndpointApi<T>\n : never\n : T extends StreamingEndpointDefinition\n ? StreamingEndpointApi<T>\n : T extends SSEEndpointDefinition\n ? SSEEndpointApi<T>\n : T extends DownloadEndpointDefinition\n ? DownloadEndpointApi<T>\n : never;\n\n// ============================================\n// Main TanStack Query API Type\n// ============================================\n\n/**\n * Full TanStack Query API for a contract\n */\nexport type TanstackQueryApi<T extends Contract> = {\n [K in keyof T]: EndpointApi<T[K]>;\n};\n\n\n// ============================================\n// Async Iterator Adapter for Streaming\n// ============================================\n\n/**\n * Convert StreamingResult to an AsyncIterable for use with streamedQuery\n */\nfunction streamToAsyncIterable<T extends StreamingEndpointDefinition>(\n streamingMethod: StreamingClientMethod<T>,\n options: EndpointRequestOptions<T>,\n): () => Promise<AsyncIterable<ExtractChunk<T>>> {\n return async () => {\n const result = await streamingMethod(options);\n\n return {\n [Symbol.asyncIterator](): AsyncIterator<ExtractChunk<T>> {\n let resolveNext: ((value: IteratorResult<ExtractChunk<T>>) => void) | null = null;\n let rejectNext: ((error: Error) => void) | null = null;\n const queue: ExtractChunk<T>[] = [];\n let done = false;\n let error: Error | null = null;\n\n result.on('chunk', (chunk) => {\n if (resolveNext) {\n resolveNext({ value: chunk, done: false });\n resolveNext = null;\n rejectNext = null;\n } else {\n queue.push(chunk);\n }\n });\n\n result.on('close', () => {\n done = true;\n if (resolveNext) {\n resolveNext({ value: undefined as any, done: true });\n resolveNext = null;\n rejectNext = null;\n }\n });\n\n result.on('error', (err) => {\n error = err;\n done = true;\n if (rejectNext) {\n rejectNext(err);\n resolveNext = null;\n rejectNext = null;\n }\n });\n\n return {\n next(): Promise<IteratorResult<ExtractChunk<T>>> {\n if (error) {\n return Promise.reject(error);\n }\n if (queue.length > 0) {\n return Promise.resolve({ value: queue.shift()!, done: false });\n }\n if (done) {\n return Promise.resolve({ value: undefined as any, done: true });\n }\n return new Promise((resolve, reject) => {\n resolveNext = resolve;\n rejectNext = reject;\n });\n },\n };\n },\n };\n };\n}\n\n// ============================================\n// Create Typed QueryClient\n// ============================================\n\n/**\n * Create a typed QueryClient wrapper with per-endpoint cache methods\n *\n * @param queryClient - The TanStack QueryClient instance\n * @param client - The typed client created with createClient()\n * @param contract - The contract definition\n * @returns A typed QueryClient with per-endpoint methods\n *\n * @example\n * ```tsx\n * const typedQueryClient = createTypedQueryClient(queryClient, client, contract);\n *\n * // Type-safe cache operations\n * typedQueryClient.listUsers.getQueryData(['users']);\n * typedQueryClient.listUsers.setQueryData(['users'], newData);\n * await typedQueryClient.listUsers.prefetchQuery({\n * queryKey: ['users'],\n * queryData: { query: { limit: '10' } }\n * });\n * ```\n */\nexport function createTypedQueryClient<T extends Contract>(\n queryClient: QueryClient,\n client: Client<T>,\n contract: T,\n): TypedQueryClient<T> {\n const typed = queryClient as TypedQueryClient<T>;\n\n for (const [name, endpoint] of Object.entries(contract)) {\n if (endpoint.type !== 'standard') continue;\n\n const clientMethod = client[name as keyof T] as unknown as ClientMethod<StandardEndpointDefinition>;\n const typedEndpoint = endpoint as StandardEndpointDefinition;\n\n if (typedEndpoint.method === 'GET' || typedEndpoint.method === 'HEAD') {\n (typed as any)[name] = {\n getQueryData: (queryKey: QueryKey) => queryClient.getQueryData(queryKey),\n setQueryData: (queryKey: QueryKey, updater: any) => queryClient.setQueryData(queryKey, updater),\n getQueryState: (queryKey: QueryKey) => queryClient.getQueryState(queryKey),\n fetchQuery: ({ queryKey, queryData, ...rest }: TsrQueryOptions<StandardEndpointDefinition>) =>\n queryClient.fetchQuery({\n queryKey,\n queryFn: () => clientMethod(queryData),\n ...rest,\n }),\n prefetchQuery: ({ queryKey, queryData, ...rest }: TsrQueryOptions<StandardEndpointDefinition>) =>\n queryClient.prefetchQuery({\n queryKey,\n queryFn: () => clientMethod(queryData),\n ...rest,\n }),\n ensureQueryData: ({ queryKey, queryData, ...rest }: TsrQueryOptions<StandardEndpointDefinition>) =>\n queryClient.ensureQueryData({\n queryKey,\n queryFn: () => clientMethod(queryData),\n ...rest,\n }),\n } as TypedQueryEndpointClient<StandardEndpointDefinition>;\n } else {\n (typed as any)[name] = {} as TypedMutationEndpointClient<StandardEndpointDefinition>;\n }\n }\n\n return typed;\n}\n\n// ============================================\n// Main Factory Function\n// ============================================\n\n/**\n * Create typed TanStack Query API for a contract\n *\n * @param client - The typed client created with createClient()\n * @param contract - The contract definition\n * @returns API object with hooks for each endpoint\n *\n * @example\n * ```tsx\n * const client = createClient(contract, { baseUrl: 'http://localhost:3000' });\n * const api = createTanstackQueryApi(client, contract);\n *\n * // Use in components - Query\n * function UserList() {\n * const { data, isLoading } = api.listUsers.useQuery({\n * queryKey: ['users'],\n * queryData: { query: { limit: '10' } }\n * });\n * }\n *\n * // Use in components - Mutation\n * function CreateUser() {\n * const { mutate } = api.createUser.useMutation();\n * return (\n * <button onClick={() => mutate({ body: { name: 'Alice' } })}>\n * Create\n * </button>\n * );\n * }\n *\n * // Direct fetch (no hooks)\n * const users = await api.listUsers.query({ query: { limit: '10' } });\n *\n * // Streaming with React Query\n * const { data: chunks, isFetching } = api.streamChat.useStreamQuery({\n * queryKey: ['chat', prompt],\n * queryData: { body: { prompt } }\n * });\n * ```\n */\nexport function createTanstackQueryApi<T extends Contract>(\n client: Client<T>,\n contract: T,\n): TanstackQueryApi<T> {\n // Build endpoint APIs\n const endpoints: Record<string, unknown> = {};\n\n for (const [name, endpoint] of Object.entries(contract)) {\n // Handle streaming endpoints\n if (endpoint.type === 'streaming') {\n const streamMethod = client[name as keyof T] as unknown as StreamingClientMethod<StreamingEndpointDefinition>;\n\n endpoints[name] = {\n stream: streamMethod,\n useStreamQuery: ({ queryKey, queryData, refetchMode, ...rest }: TsrStreamQueryOptions<StreamingEndpointDefinition>) => {\n const result = useQuery({\n queryKey,\n queryFn: streamedQuery({\n streamFn: streamToAsyncIterable(streamMethod, queryData),\n refetchMode,\n }),\n ...rest,\n });\n return {\n ...result,\n contractEndpoint: endpoint,\n };\n },\n } as StreamingEndpointApi<StreamingEndpointDefinition>;\n continue;\n }\n\n // Handle SSE endpoints\n if (endpoint.type === 'sse') {\n const connectMethod = client[name as keyof T] as unknown as SSEClientMethod<SSEEndpointDefinition>;\n endpoints[name] = {\n connect: connectMethod,\n } as SSEEndpointApi<SSEEndpointDefinition>;\n continue;\n }\n\n // Handle download endpoints\n if (endpoint.type === 'download') {\n const downloadMethod = client[name as keyof T] as unknown as DownloadClientMethod<DownloadEndpointDefinition>;\n endpoints[name] = {\n download: downloadMethod,\n } as DownloadEndpointApi<DownloadEndpointDefinition>;\n continue;\n }\n\n // Handle standard endpoints\n const method = endpoint.method;\n const clientMethod = client[name as keyof T] as unknown as ClientMethod<StandardEndpointDefinition>;\n\n if (method === 'GET' || method === 'HEAD') {\n // Query endpoint\n endpoints[name] = {\n useQuery: ({ queryKey, queryData, ...rest }: TsrQueryOptions<StandardEndpointDefinition>) => {\n const result = useQuery({\n queryKey,\n queryFn: () => clientMethod(queryData),\n ...rest,\n });\n return {\n ...result,\n contractEndpoint: endpoint,\n };\n },\n useSuspenseQuery: ({ queryKey, queryData, ...rest }: TsrSuspenseQueryOptions<StandardEndpointDefinition>) => {\n const result = useSuspenseQuery({\n queryKey,\n queryFn: () => clientMethod(queryData),\n ...rest,\n });\n return {\n ...result,\n contractEndpoint: endpoint,\n };\n },\n useInfiniteQuery: <TPageParam = unknown>({\n queryKey,\n queryData,\n ...rest\n }: TsrInfiniteQueryOptions<StandardEndpointDefinition, TPageParam>) => {\n const result = useInfiniteQuery({\n queryKey,\n queryFn: (ctx: { pageParam: TPageParam }) =>\n clientMethod(queryData({ pageParam: ctx.pageParam })),\n ...rest,\n } as any);\n return {\n ...result,\n contractEndpoint: endpoint,\n };\n },\n useSuspenseInfiniteQuery: <TPageParam = unknown>({\n queryKey,\n queryData,\n ...rest\n }: TsrSuspenseInfiniteQueryOptions<StandardEndpointDefinition, TPageParam>) => {\n const result = useSuspenseInfiniteQuery({\n queryKey,\n queryFn: (ctx: { pageParam: TPageParam }) =>\n clientMethod(queryData({ pageParam: ctx.pageParam })),\n ...rest,\n } as any);\n return {\n ...result,\n contractEndpoint: endpoint,\n };\n },\n query: (options: EndpointRequestOptions<StandardEndpointDefinition>) => clientMethod(options),\n } as QueryEndpointApi<StandardEndpointDefinition>;\n } else {\n // Mutation endpoint\n endpoints[name] = {\n useMutation: (options?: TsrMutationOptions<StandardEndpointDefinition>) => {\n const result = useMutation({\n mutationFn: (data: EndpointRequestOptions<StandardEndpointDefinition>) => clientMethod(data),\n ...options,\n });\n return {\n ...result,\n contractEndpoint: endpoint,\n };\n },\n mutate: (options: EndpointRequestOptions<StandardEndpointDefinition>) => clientMethod(options),\n } as MutationEndpointApi<StandardEndpointDefinition>;\n }\n }\n\n return endpoints as TanstackQueryApi<T>;\n}\n"
6
6
  ],
7
- "mappings": ";;AAcA;AAAA;AAAA;AAAA;AAAA;AA8IO,SAAS,WAA+B,CAAC,QAAmB,UAAuB;AAAA,EACxF,MAAM,QAAiC,CAAC;AAAA,EAExC,YAAY,MAAM,aAAa,OAAO,QAAQ,QAAQ,GAAG;AAAA,IAEvD,IAAI,SAAS,SAAS,aAAa;AAAA,MACjC,MAAM,eAAe,OACnB;AAAA,MAEF,MAAM,QAAQ;AAAA,QACZ,QAAQ;AAAA,MACV;AAAA,MACA;AAAA,IACF;AAAA,IAGA,IAAI,SAAS,SAAS,OAAO;AAAA,MAC3B,MAAM,gBAAgB,OACpB;AAAA,MAEF,MAAM,QAAQ;AAAA,QACZ,SAAS;AAAA,MACX;AAAA,MACA;AAAA,IACF;AAAA,IAGA,MAAM,SAAS,SAAS;AAAA,IACxB,MAAM,eAAe,OACnB;AAAA,IAGF,IAAI,WAAW,SAAS,WAAW,QAAQ;AAAA,MAEzC,MAAM,QAAQ;AAAA,QACZ,UAAU,CACR,SACA,iBAIG;AAAA,UACH,OAAO,SAAS;AAAA,YACd,UAAU;AAAA,cACR;AAAA,cACA,QAAQ,UAAU;AAAA,cAClB,QAAQ,SAAS;AAAA,cACjB,QAAQ,WAAW;AAAA,cACnB,QAAQ,QAAQ;AAAA,YAClB;AAAA,YACA,SAAS,MAAM,aAAa,OAAO;AAAA,eAChC;AAAA,UACL,CAAC;AAAA;AAAA,QAEH,kBAAkB,CAChB,SACA,iBAIG;AAAA,UACH,OAAO,iBAAiB;AAAA,YACtB,UAAU;AAAA,cACR;AAAA,cACA,QAAQ,UAAU;AAAA,cAClB,QAAQ,SAAS;AAAA,cACjB,QAAQ,WAAW;AAAA,cACnB,QAAQ,QAAQ;AAAA,YAClB;AAAA,YACA,SAAS,MAAM,aAAa,OAAO;AAAA,eAChC;AAAA,UACL,CAAC;AAAA;AAAA,MAEL;AAAA,IACF,EAAO;AAAA,MAEL,MAAM,QAAQ;AAAA,QACZ,aAAa,CACX,oBAQG;AAAA,UACH,OAAO,YAAY;AAAA,YACjB,YAAY,CAAC,YACX,aAAa,OAAO;AAAA,eACnB;AAAA,UACL,CAAC;AAAA;AAAA,MAEL;AAAA;AAAA,EAEJ;AAAA,EAEA,OAAO;AAAA;",
8
- "debugId": "47BC57BE472723C264756E2164756E21",
7
+ "mappings": "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",
8
+ "debugId": "8C0C3CC1741EF8A064756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@richie-rpc/react-query",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "type": "module"
5
5
  }
@@ -1,100 +1,238 @@
1
- import type { Client, EndpointRequestOptions, EndpointResponse, SSEClientMethod, StreamingClientMethod } from '@richie-rpc/client';
2
- import type { Contract, SSEEndpointDefinition, StandardEndpointDefinition, StreamingEndpointDefinition } from '@richie-rpc/core';
3
- import { type UseMutationOptions, type UseMutationResult, type UseQueryOptions, type UseQueryResult, type UseSuspenseQueryOptions, type UseSuspenseQueryResult } from '@tanstack/react-query';
1
+ import type { Client, DownloadClientMethod, EndpointRequestOptions, EndpointResponse, SSEClientMethod, StreamingClientMethod } from '@richie-rpc/client';
2
+ import type { Contract, DownloadEndpointDefinition, ExtractChunk, SSEEndpointDefinition, StandardEndpointDefinition, StreamingEndpointDefinition } from '@richie-rpc/core';
3
+ import { type InfiniteData, type QueryClient, type QueryKey, type Updater, type UseInfiniteQueryOptions, type UseInfiniteQueryResult, type UseMutationOptions, type UseMutationResult, type UseQueryOptions, type UseQueryResult, type UseSuspenseInfiniteQueryOptions, type UseSuspenseInfiniteQueryResult, type UseSuspenseQueryOptions, type UseSuspenseQueryResult } from '@tanstack/react-query';
4
4
  type QueryMethods = 'GET' | 'HEAD';
5
5
  type MutationMethods = 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS';
6
6
  /**
7
- * Hook wrapper for query endpoints (GET, HEAD)
8
- * Provides useQuery and useSuspenseQuery methods
7
+ * Unified query options - combines queryKey, queryData, and TanStack Query options
9
8
  */
10
- export type QueryHook<T extends StandardEndpointDefinition> = {
11
- /**
12
- * Standard query hook that returns loading states
13
- */
14
- useQuery: (options: EndpointRequestOptions<T>, queryOptions?: Omit<UseQueryOptions<EndpointResponse<T>, Error>, 'queryKey' | 'queryFn'>) => UseQueryResult<EndpointResponse<T>, Error>;
15
- /**
16
- * Suspense-enabled query hook that throws promises for React Suspense
17
- */
18
- useSuspenseQuery: (options: EndpointRequestOptions<T>, queryOptions?: Omit<UseSuspenseQueryOptions<EndpointResponse<T>, Error>, 'queryKey' | 'queryFn'>) => UseSuspenseQueryResult<EndpointResponse<T>, Error>;
9
+ export type TsrQueryOptions<T extends StandardEndpointDefinition> = Omit<UseQueryOptions<EndpointResponse<T>, Error>, 'queryKey' | 'queryFn'> & {
10
+ queryKey: QueryKey;
11
+ queryData: EndpointRequestOptions<T>;
19
12
  };
20
13
  /**
21
- * Hook wrapper for mutation endpoints (POST, PUT, PATCH, DELETE)
22
- * Provides useMutation method
14
+ * Suspense query options
23
15
  */
24
- export type MutationHook<T extends StandardEndpointDefinition> = {
25
- /**
26
- * Mutation hook for write operations
27
- */
28
- useMutation: (mutationOptions?: Omit<UseMutationOptions<EndpointResponse<T>, Error, EndpointRequestOptions<T>>, 'mutationFn'>) => UseMutationResult<EndpointResponse<T>, Error, EndpointRequestOptions<T>>;
16
+ export type TsrSuspenseQueryOptions<T extends StandardEndpointDefinition> = Omit<UseSuspenseQueryOptions<EndpointResponse<T>, Error>, 'queryKey' | 'queryFn'> & {
17
+ queryKey: QueryKey;
18
+ queryData: EndpointRequestOptions<T>;
29
19
  };
30
20
  /**
31
- * Conditionally apply hook type based on HTTP method
21
+ * Infinite query options - queryData is a function that receives pageParam
32
22
  */
33
- export type EndpointHook<T extends StandardEndpointDefinition> = T['method'] extends QueryMethods ? QueryHook<T> : T['method'] extends MutationMethods ? MutationHook<T> : never;
23
+ export type TsrInfiniteQueryOptions<T extends StandardEndpointDefinition, TPageParam = unknown> = Omit<UseInfiniteQueryOptions<EndpointResponse<T>, Error, unknown, QueryKey, TPageParam>, 'queryKey' | 'queryFn'> & {
24
+ queryKey: QueryKey;
25
+ queryData: (context: {
26
+ pageParam: TPageParam;
27
+ }) => EndpointRequestOptions<T>;
28
+ };
29
+ /**
30
+ * Suspense infinite query options
31
+ */
32
+ export type TsrSuspenseInfiniteQueryOptions<T extends StandardEndpointDefinition, TPageParam = unknown> = Omit<UseSuspenseInfiniteQueryOptions<EndpointResponse<T>, Error, unknown, QueryKey, TPageParam>, 'queryKey' | 'queryFn'> & {
33
+ queryKey: QueryKey;
34
+ queryData: (context: {
35
+ pageParam: TPageParam;
36
+ }) => EndpointRequestOptions<T>;
37
+ };
38
+ /**
39
+ * Mutation options - same as TanStack Query but typed
40
+ */
41
+ export type TsrMutationOptions<T extends StandardEndpointDefinition> = Omit<UseMutationOptions<EndpointResponse<T>, Error, EndpointRequestOptions<T>>, 'mutationFn'>;
42
+ /**
43
+ * Stream query options for streaming endpoints
44
+ */
45
+ export type TsrStreamQueryOptions<T extends StreamingEndpointDefinition> = Omit<UseQueryOptions<ExtractChunk<T>[], Error>, 'queryKey' | 'queryFn'> & {
46
+ queryKey: QueryKey;
47
+ queryData: EndpointRequestOptions<T>;
48
+ refetchMode?: 'reset' | 'append' | 'replace';
49
+ };
50
+ /**
51
+ * Response type from hooks - includes status and body like ts-rest
52
+ */
53
+ export type TsrResponse<T extends StandardEndpointDefinition> = EndpointResponse<T>;
54
+ /**
55
+ * Error response type - either fetch error or typed response error
56
+ */
57
+ export type TsrError = Error | {
58
+ status: number;
59
+ data: unknown;
60
+ };
61
+ /**
62
+ * Check if an error is a fetch/network error (Error instance, not a response)
63
+ */
64
+ export declare function isFetchError(error: unknown): error is Error;
65
+ /**
66
+ * Check if error is a response with a status code not defined in the contract
67
+ */
68
+ export declare function isUnknownErrorResponse<T extends StandardEndpointDefinition>(error: unknown, endpoint: T): error is {
69
+ status: number;
70
+ data: unknown;
71
+ };
72
+ /**
73
+ * Check if error is either a fetch error or unknown response error
74
+ */
75
+ export declare function isNotKnownResponseError<T extends StandardEndpointDefinition>(error: unknown, endpoint: T): error is Error | {
76
+ status: number;
77
+ data: unknown;
78
+ };
79
+ /**
80
+ * Exhaustive guard for compile-time exhaustiveness checking
81
+ * Use after handling all known error cases to ensure nothing is missed
82
+ */
83
+ export declare function exhaustiveGuard(_value: never): never;
84
+ /**
85
+ * Typed query client methods for a single query endpoint
86
+ */
87
+ export type TypedQueryEndpointClient<T extends StandardEndpointDefinition> = {
88
+ getQueryData: (queryKey: QueryKey) => EndpointResponse<T> | undefined;
89
+ setQueryData: (queryKey: QueryKey, updater: Updater<EndpointResponse<T> | undefined, EndpointResponse<T> | undefined>) => EndpointResponse<T> | undefined;
90
+ getQueryState: (queryKey: QueryKey) => ReturnType<QueryClient['getQueryState']>;
91
+ fetchQuery: (options: TsrQueryOptions<T>) => Promise<EndpointResponse<T>>;
92
+ prefetchQuery: (options: TsrQueryOptions<T>) => Promise<void>;
93
+ ensureQueryData: (options: TsrQueryOptions<T>) => Promise<EndpointResponse<T>>;
94
+ };
34
95
  /**
35
- * Hook wrapper for streaming endpoints
36
- * Exposes the streaming client method directly since React Query
37
- * doesn't fit well with long-lived streaming connections
96
+ * Typed query client methods for a single mutation endpoint
97
+ * Mutations don't have query-specific cache methods
38
98
  */
39
- export type StreamingHook<T extends StreamingEndpointDefinition> = {
40
- /**
41
- * Start a streaming request
42
- */
99
+ export type TypedMutationEndpointClient<_T extends StandardEndpointDefinition> = object;
100
+ /**
101
+ * Full typed query client - extends QueryClient with per-endpoint methods
102
+ */
103
+ export type TypedQueryClient<T extends Contract> = QueryClient & {
104
+ [K in keyof T]: T[K] extends StandardEndpointDefinition ? T[K]['method'] extends QueryMethods ? TypedQueryEndpointClient<T[K]> : TypedMutationEndpointClient<T[K]> : Record<string, never>;
105
+ };
106
+ /**
107
+ * API for query endpoints (GET, HEAD)
108
+ */
109
+ export type QueryEndpointApi<T extends StandardEndpointDefinition> = {
110
+ /** Standard query hook */
111
+ useQuery: (options: TsrQueryOptions<T>) => UseQueryResult<EndpointResponse<T>, Error> & {
112
+ contractEndpoint: T;
113
+ };
114
+ /** Suspense query hook */
115
+ useSuspenseQuery: (options: TsrSuspenseQueryOptions<T>) => UseSuspenseQueryResult<EndpointResponse<T>, Error> & {
116
+ contractEndpoint: T;
117
+ };
118
+ /** Infinite query hook */
119
+ useInfiniteQuery: <TPageParam = unknown>(options: TsrInfiniteQueryOptions<T, TPageParam>) => UseInfiniteQueryResult<InfiniteData<EndpointResponse<T>, TPageParam>, Error> & {
120
+ contractEndpoint: T;
121
+ };
122
+ /** Suspense infinite query hook */
123
+ useSuspenseInfiniteQuery: <TPageParam = unknown>(options: TsrSuspenseInfiniteQueryOptions<T, TPageParam>) => UseSuspenseInfiniteQueryResult<InfiniteData<EndpointResponse<T>, TPageParam>, Error> & {
124
+ contractEndpoint: T;
125
+ };
126
+ /** Direct fetch without React Query */
127
+ query: (options: EndpointRequestOptions<T>) => Promise<EndpointResponse<T>>;
128
+ };
129
+ /**
130
+ * API for mutation endpoints (POST, PUT, PATCH, DELETE)
131
+ */
132
+ export type MutationEndpointApi<T extends StandardEndpointDefinition> = {
133
+ /** Mutation hook */
134
+ useMutation: (options?: TsrMutationOptions<T>) => UseMutationResult<EndpointResponse<T>, Error, EndpointRequestOptions<T>> & {
135
+ contractEndpoint: T;
136
+ };
137
+ /** Direct mutate without React Query */
138
+ mutate: (options: EndpointRequestOptions<T>) => Promise<EndpointResponse<T>>;
139
+ };
140
+ /**
141
+ * API for streaming endpoints
142
+ */
143
+ export type StreamingEndpointApi<T extends StreamingEndpointDefinition> = {
144
+ /** Direct stream access (event-based) */
43
145
  stream: StreamingClientMethod<T>;
146
+ /** Query hook using experimental streamedQuery */
147
+ useStreamQuery: (options: TsrStreamQueryOptions<T>) => UseQueryResult<ExtractChunk<T>[], Error> & {
148
+ contractEndpoint: T;
149
+ };
44
150
  };
45
151
  /**
46
- * Hook wrapper for SSE endpoints
47
- * Exposes the SSE client method directly since React Query
48
- * doesn't fit well with long-lived SSE connections
152
+ * API for SSE endpoints
49
153
  */
50
- export type SSEHook<T extends SSEEndpointDefinition> = {
51
- /**
52
- * Create an SSE connection
53
- */
154
+ export type SSEEndpointApi<T extends SSEEndpointDefinition> = {
155
+ /** Direct SSE connection access (event-based) */
54
156
  connect: SSEClientMethod<T>;
55
157
  };
56
158
  /**
57
- * Complete hooks object for a contract
58
- * Each endpoint gets appropriate hooks based on its type and HTTP method
159
+ * API for download endpoints
59
160
  */
60
- export type Hooks<T extends Contract> = {
61
- [K in keyof T]: T[K] extends StandardEndpointDefinition ? EndpointHook<T[K]> : T[K] extends StreamingEndpointDefinition ? StreamingHook<T[K]> : T[K] extends SSEEndpointDefinition ? SSEHook<T[K]> : never;
161
+ export type DownloadEndpointApi<T extends DownloadEndpointDefinition> = {
162
+ /** Direct download without React Query */
163
+ download: DownloadClientMethod<T>;
62
164
  };
63
165
  /**
64
- * Create typed React hooks for all endpoints in a contract
166
+ * Select appropriate API type based on endpoint type
167
+ */
168
+ export type EndpointApi<T> = T extends StandardEndpointDefinition ? T['method'] extends QueryMethods ? QueryEndpointApi<T> : T['method'] extends MutationMethods ? MutationEndpointApi<T> : never : T extends StreamingEndpointDefinition ? StreamingEndpointApi<T> : T extends SSEEndpointDefinition ? SSEEndpointApi<T> : T extends DownloadEndpointDefinition ? DownloadEndpointApi<T> : never;
169
+ /**
170
+ * Full TanStack Query API for a contract
171
+ */
172
+ export type TanstackQueryApi<T extends Contract> = {
173
+ [K in keyof T]: EndpointApi<T[K]>;
174
+ };
175
+ /**
176
+ * Create a typed QueryClient wrapper with per-endpoint cache methods
65
177
  *
66
- * Query endpoints (GET, HEAD) get useQuery and useSuspenseQuery methods
67
- * Mutation endpoints (POST, PUT, PATCH, DELETE) get useMutation method
178
+ * @param queryClient - The TanStack QueryClient instance
179
+ * @param client - The typed client created with createClient()
180
+ * @param contract - The contract definition
181
+ * @returns A typed QueryClient with per-endpoint methods
182
+ *
183
+ * @example
184
+ * ```tsx
185
+ * const typedQueryClient = createTypedQueryClient(queryClient, client, contract);
186
+ *
187
+ * // Type-safe cache operations
188
+ * typedQueryClient.listUsers.getQueryData(['users']);
189
+ * typedQueryClient.listUsers.setQueryData(['users'], newData);
190
+ * await typedQueryClient.listUsers.prefetchQuery({
191
+ * queryKey: ['users'],
192
+ * queryData: { query: { limit: '10' } }
193
+ * });
194
+ * ```
195
+ */
196
+ export declare function createTypedQueryClient<T extends Contract>(queryClient: QueryClient, client: Client<T>, contract: T): TypedQueryClient<T>;
197
+ /**
198
+ * Create typed TanStack Query API for a contract
68
199
  *
69
200
  * @param client - The typed client created with createClient()
70
201
  * @param contract - The contract definition
71
- * @returns Hooks object with methods for each endpoint
202
+ * @returns API object with hooks for each endpoint
72
203
  *
73
204
  * @example
74
205
  * ```tsx
75
206
  * const client = createClient(contract, { baseUrl: 'http://localhost:3000' });
76
- * const hooks = createHooks(client, contract);
207
+ * const api = createTanstackQueryApi(client, contract);
77
208
  *
78
- * // In a component - Query
209
+ * // Use in components - Query
79
210
  * function UserList() {
80
- * const { data, isLoading } = hooks.listUsers.useQuery({
81
- * query: { limit: "10" }
211
+ * const { data, isLoading } = api.listUsers.useQuery({
212
+ * queryKey: ['users'],
213
+ * queryData: { query: { limit: '10' } }
82
214
  * });
83
- * // ...
84
215
  * }
85
216
  *
86
- * // In a component - Mutation
217
+ * // Use in components - Mutation
87
218
  * function CreateUser() {
88
- * const mutation = hooks.createUser.useMutation();
219
+ * const { mutate } = api.createUser.useMutation();
89
220
  * return (
90
- * <button onClick={() => mutation.mutate({
91
- * body: { name: "Alice", email: "alice@example.com" }
92
- * })}>
93
- * Create User
221
+ * <button onClick={() => mutate({ body: { name: 'Alice' } })}>
222
+ * Create
94
223
  * </button>
95
224
  * );
96
225
  * }
226
+ *
227
+ * // Direct fetch (no hooks)
228
+ * const users = await api.listUsers.query({ query: { limit: '10' } });
229
+ *
230
+ * // Streaming with React Query
231
+ * const { data: chunks, isFetching } = api.streamChat.useStreamQuery({
232
+ * queryKey: ['chat', prompt],
233
+ * queryData: { body: { prompt } }
234
+ * });
97
235
  * ```
98
236
  */
99
- export declare function createHooks<T extends Contract>(client: Client<T>, contract: T): Hooks<T>;
237
+ export declare function createTanstackQueryApi<T extends Contract>(client: Client<T>, contract: T): TanstackQueryApi<T>;
100
238
  export {};
package/package.json CHANGED
@@ -1,16 +1,9 @@
1
1
  {
2
2
  "name": "@richie-rpc/react-query",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "main": "./dist/cjs/index.cjs",
5
- "exports": {
6
- ".": {
7
- "types": "./dist/types/index.d.ts",
8
- "require": "./dist/cjs/index.cjs",
9
- "import": "./dist/mjs/index.mjs"
10
- }
11
- },
12
5
  "peerDependencies": {
13
- "@richie-rpc/client": "^1.2.5",
6
+ "@richie-rpc/client": "^1.2.6",
14
7
  "@richie-rpc/core": "^1.2.4",
15
8
  "@tanstack/react-query": "^5.0.0",
16
9
  "react": "^18.0.0 || ^19.0.0",
@@ -41,6 +34,13 @@
41
34
  "description": "A TypeScript-first, type-safe API contract library for Bun with Zod validation",
42
35
  "module": "./dist/mjs/index.mjs",
43
36
  "types": "./dist/types/index.d.ts",
37
+ "exports": {
38
+ ".": {
39
+ "types": "./dist/types/index.d.ts",
40
+ "require": "./dist/cjs/index.cjs",
41
+ "import": "./dist/mjs/index.mjs"
42
+ }
43
+ },
44
44
  "publishConfig": {
45
45
  "access": "public"
46
46
  },