@geekmidas/client 0.0.1 → 0.1.0

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-fetcher.mjs","names":["options: AuthFetcherOptions<EndpointAuth, SecuritySchemes> & {\n baseURL: string;\n }","endpoint: T","config?: FilteredRequestConfig<Paths, T>","authHeaders: Record<string, string>","strategy: AuthStrategy","scheme: SecuritySchemeObject"],"sources":["../src/auth-fetcher.ts"],"sourcesContent":["import { TypedFetcher } from './fetcher';\nimport type {\n ExtractEndpointResponse,\n FetcherOptions,\n FilteredRequestConfig,\n TypedApiFunction,\n TypedEndpoint,\n} from './types';\n\n/**\n * Security scheme object matching OpenAPI 3.1 specification.\n */\nexport interface SecuritySchemeObject {\n type: 'apiKey' | 'http' | 'mutualTLS' | 'oauth2' | 'openIdConnect';\n description?: string;\n name?: string;\n in?: 'query' | 'header' | 'cookie';\n scheme?: string;\n bearerFormat?: string;\n flows?: Record<string, unknown>;\n openIdConnectUrl?: string;\n [key: string]: unknown;\n}\n\n/**\n * Extract all non-null security scheme IDs that are actually used in the API.\n * This gives us the union of scheme names that endpoints require.\n */\nexport type UsedSecuritySchemes<\n EndpointAuth extends Record<string, string | null>,\n> = NonNullable<EndpointAuth[keyof EndpointAuth]>;\n\n/**\n * Interface for token storage and retrieval.\n * Compatible with @geekmidas/auth TokenClient.\n */\nexport interface TokenProvider {\n /**\n * Get a valid access token, refreshing if necessary.\n */\n getValidAccessToken(): Promise<string | null>;\n\n /**\n * Create Authorization headers from the current token.\n */\n createValidAuthHeaders(): Promise<Record<string, string>>;\n}\n\n/**\n * Interface for API key providers.\n */\nexport interface ApiKeyProvider {\n /**\n * Get the API key value.\n */\n getApiKey(): Promise<string> | string;\n}\n\n/**\n * Interface for AWS SigV4 request signing.\n */\nexport interface AwsSigner {\n /**\n * Sign a request with AWS SigV4.\n * @param url - The request URL\n * @param init - The request init object\n * @returns Headers to add to the request\n */\n sign(url: string, init: RequestInit): Promise<Record<string, string>>;\n}\n\n/**\n * Auth strategy configuration for a specific security scheme type.\n */\nexport type AuthStrategy =\n | { type: 'bearer'; tokenProvider: TokenProvider }\n | { type: 'apiKey'; apiKeyProvider: ApiKeyProvider; headerName?: string }\n | { type: 'iam'; signer: AwsSigner }\n | { type: 'none' };\n\n/**\n * Options for creating an auth-aware fetcher.\n *\n * @template EndpointAuth - Map of endpoint strings to their auth scheme (or null for public)\n * @template SecuritySchemes - Available security scheme definitions\n */\nexport interface AuthFetcherOptions<\n EndpointAuth extends Record<string, string | null>,\n SecuritySchemes extends Record<string, SecuritySchemeObject>,\n> extends Omit<FetcherOptions, 'onRequest'> {\n /**\n * Runtime map of endpoints to their required auth scheme.\n * Generated by `gkm openapi --ts`.\n */\n endpointAuth: EndpointAuth;\n\n /**\n * Security scheme definitions.\n * Generated by `gkm openapi --ts`.\n */\n securitySchemes: SecuritySchemes;\n\n /**\n * Auth strategies for security schemes that are actually used.\n * Only schemes referenced in endpointAuth are required.\n *\n * @example\n * ```typescript\n * // If endpointAuth has: { 'GET /users': 'jwt', 'POST /data': 'iam', 'GET /public': null }\n * // Then authStrategies must include strategies for 'jwt' and 'iam'\n * authStrategies: {\n * jwt: { type: 'bearer', tokenProvider },\n * iam: { type: 'iam', signer: awsSigner },\n * }\n * ```\n */\n authStrategies: Record<UsedSecuritySchemes<EndpointAuth>, AuthStrategy>;\n\n /**\n * Optional request interceptor (runs after auth headers are added).\n */\n onRequest?: (config: RequestInit) => RequestInit | Promise<RequestInit>;\n}\n\n/**\n * Creates an auth-aware fetcher that automatically applies the correct\n * authentication based on the endpoint being called.\n *\n * @example\n * ```typescript\n * import { endpointAuth, securitySchemes, paths } from './openapi';\n * import { TokenClient } from '@geekmidas/auth/client';\n *\n * const tokenClient = new TokenClient({ ... });\n *\n * const api = createAuthAwareFetcher<paths>({\n * baseURL: 'https://api.example.com',\n * endpointAuth,\n * securitySchemes,\n * authStrategies: {\n * bearer: { type: 'bearer', tokenProvider: tokenClient },\n * iam: { type: 'iam', signer: awsSigner },\n * },\n * });\n *\n * // Bearer auth automatically applied\n * const user = await api('GET /users/{id}', { params: { id: '123' } });\n *\n * // IAM SigV4 auth automatically applied\n * const tenant = await api('POST /tenants', { body: { name: 'Acme' } });\n * ```\n */\nexport function createAuthAwareFetcher<\n Paths,\n EndpointAuth extends Record<string, string | null> = Record<\n string,\n string | null\n >,\n SecuritySchemes extends Record<string, SecuritySchemeObject> = Record<\n string,\n SecuritySchemeObject\n >,\n>(\n options: AuthFetcherOptions<EndpointAuth, SecuritySchemes> & {\n baseURL: string;\n },\n): TypedApiFunction<Paths> {\n const {\n endpointAuth,\n securitySchemes,\n authStrategies,\n onRequest: userOnRequest,\n ...fetcherOptions\n } = options;\n\n // Create base fetcher with user's onRequest if provided\n const baseFetcher = new TypedFetcher<Paths>({\n ...fetcherOptions,\n onRequest: userOnRequest,\n });\n\n const fetcher = async <T extends TypedEndpoint<Paths>>(\n endpoint: T,\n config?: FilteredRequestConfig<Paths, T>,\n ): Promise<ExtractEndpointResponse<Paths, T>> => {\n // Look up auth requirement for this endpoint\n const schemeName = endpointAuth[endpoint as keyof EndpointAuth] as\n | string\n | null;\n\n let authHeaders: Record<string, string> = {};\n\n if (schemeName) {\n const scheme = securitySchemes[schemeName as keyof SecuritySchemes];\n // Since authStrategies is now required to have all used schemes,\n // we can safely access it - TypeScript ensures the strategy exists\n const strategy =\n authStrategies[schemeName as UsedSecuritySchemes<EndpointAuth>];\n\n if (strategy) {\n authHeaders = await resolveAuthHeaders(strategy, scheme);\n }\n }\n\n // Merge auth headers with config headers\n const existingHeaders =\n config && 'headers' in config && config.headers\n ? (config.headers as Record<string, string>)\n : {};\n\n const mergedConfig = {\n ...config,\n headers: {\n ...authHeaders,\n ...existingHeaders,\n },\n } as unknown as FilteredRequestConfig<Paths, T>;\n\n return baseFetcher.request(endpoint, mergedConfig);\n };\n\n return fetcher as TypedApiFunction<Paths>;\n}\n\n/**\n * Resolves auth headers based on the strategy and scheme.\n */\nasync function resolveAuthHeaders(\n strategy: AuthStrategy,\n scheme: SecuritySchemeObject,\n): Promise<Record<string, string>> {\n switch (strategy.type) {\n case 'bearer': {\n return strategy.tokenProvider.createValidAuthHeaders();\n }\n\n case 'apiKey': {\n const apiKey = await strategy.apiKeyProvider.getApiKey();\n const headerName = strategy.headerName || scheme.name || 'X-API-Key';\n\n if (scheme.in === 'header' || !scheme.in) {\n return { [headerName]: apiKey };\n }\n // Note: query and cookie API keys are handled differently\n // For now, we only support header-based API keys\n return {};\n }\n\n case 'iam': {\n // IAM signing requires the full URL and request config\n // This is a simplified version - full implementation would need\n // access to the complete request\n // For now, return empty - the actual signing should be done\n // in a custom onRequest interceptor if needed\n return {};\n }\n\n case 'none':\n default:\n return {};\n }\n}\n\n/**\n * Type helper to extract the security scheme ID from an endpoint.\n */\nexport type GetEndpointAuth<\n EndpointAuth extends Record<string, string | null>,\n Endpoint extends keyof EndpointAuth,\n> = EndpointAuth[Endpoint];\n\n/**\n * Type helper to get all authenticated endpoints.\n */\nexport type AuthenticatedEndpoints<\n EndpointAuth extends Record<string, string | null>,\n> = {\n [K in keyof EndpointAuth]: EndpointAuth[K] extends null ? never : K;\n}[keyof EndpointAuth];\n\n/**\n * Type helper to get all public endpoints.\n */\nexport type PublicEndpoints<\n EndpointAuth extends Record<string, string | null>,\n> = {\n [K in keyof EndpointAuth]: EndpointAuth[K] extends null ? K : never;\n}[keyof EndpointAuth];\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwJA,SAAgB,uBAWdA,SAGyB;CACzB,MAAM,EACJ,cACA,iBACA,gBACA,WAAW,cACX,GAAG,gBACJ,GAAG;CAGJ,MAAM,cAAc,IAAI,aAAoB;EAC1C,GAAG;EACH,WAAW;CACZ;CAED,MAAM,UAAU,OACdC,UACAC,WAC+C;EAE/C,MAAM,aAAa,aAAa;EAIhC,IAAIC,cAAsC,CAAE;AAE5C,MAAI,YAAY;GACd,MAAM,SAAS,gBAAgB;GAG/B,MAAM,WACJ,eAAe;AAEjB,OAAI,SACF,eAAc,MAAM,mBAAmB,UAAU,OAAO;EAE3D;EAGD,MAAM,kBACJ,UAAU,aAAa,UAAU,OAAO,UACnC,OAAO,UACR,CAAE;EAER,MAAM,eAAe;GACnB,GAAG;GACH,SAAS;IACP,GAAG;IACH,GAAG;GACJ;EACF;AAED,SAAO,YAAY,QAAQ,UAAU,aAAa;CACnD;AAED,QAAO;AACR;;;;AAKD,eAAe,mBACbC,UACAC,QACiC;AACjC,SAAQ,SAAS,MAAjB;EACE,KAAK,SACH,QAAO,SAAS,cAAc,wBAAwB;EAGxD,KAAK,UAAU;GACb,MAAM,SAAS,MAAM,SAAS,eAAe,WAAW;GACxD,MAAM,aAAa,SAAS,cAAc,OAAO,QAAQ;AAEzD,OAAI,OAAO,OAAO,aAAa,OAAO,GACpC,QAAO,GAAG,aAAa,OAAQ;AAIjC,UAAO,CAAE;EACV;EAED,KAAK,MAMH,QAAO,CAAE;EAGX,KAAK;EACL,QACE,QAAO,CAAE;CACZ;AACF"}
@@ -0,0 +1,60 @@
1
+ const require_chunk = require('./chunk-CUT6urMc.cjs');
2
+ const __tanstack_react_query = require_chunk.__toESM(require("@tanstack/react-query"));
3
+ const react = require_chunk.__toESM(require("react"));
4
+
5
+ //#region src/endpoint-hooks.ts
6
+ /**
7
+ * Build query key from endpoint and config
8
+ */
9
+ function buildQueryKey(endpoint, config) {
10
+ const key = [endpoint];
11
+ if (config && "params" in config && config.params) key.push({ params: config.params });
12
+ if (config && "query" in config && config.query) key.push({ query: config.query });
13
+ return key;
14
+ }
15
+ /**
16
+ * Create endpoint-based React Query hooks from a typed fetcher.
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * const fetcher = createAuthAwareFetcher<paths>({ ... });
21
+ * const hooks = createEndpointHooks<paths>(fetcher);
22
+ *
23
+ * // In a component
24
+ * const { data } = hooks.useQuery('GET /users/{id}', { params: { id: '123' } });
25
+ *
26
+ * const mutation = hooks.useMutation('POST /users');
27
+ * await mutation.mutateAsync({ body: { name: 'John' } });
28
+ * ```
29
+ */
30
+ function createEndpointHooks(fetcher, options = {}) {
31
+ return {
32
+ useQuery: (endpoint, ...args) => {
33
+ const [config, queryOptions] = args;
34
+ const queryKey = buildQueryKey(endpoint, config);
35
+ const memoizedOptions = (0, react.useMemo)(() => ({
36
+ queryKey,
37
+ queryFn: () => fetcher(endpoint, config),
38
+ ...queryOptions
39
+ }), [
40
+ queryKey.join(","),
41
+ endpoint,
42
+ JSON.stringify(config),
43
+ JSON.stringify(queryOptions)
44
+ ]);
45
+ return (0, __tanstack_react_query.useQuery)(memoizedOptions);
46
+ },
47
+ useMutation: (endpoint, mutationOptions) => {
48
+ const memoizedOptions = (0, react.useMemo)(() => ({
49
+ mutationFn: (config) => fetcher(endpoint, config),
50
+ ...mutationOptions
51
+ }), [endpoint, JSON.stringify(mutationOptions)]);
52
+ return (0, __tanstack_react_query.useMutation)(memoizedOptions);
53
+ },
54
+ buildQueryKey
55
+ };
56
+ }
57
+
58
+ //#endregion
59
+ exports.createEndpointHooks = createEndpointHooks;
60
+ //# sourceMappingURL=endpoint-hooks.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"endpoint-hooks.cjs","names":["endpoint: T","config?: FilteredRequestConfig<Paths, T>","key: unknown[]","fetcher: TypedApiFunction<Paths>","options: CreateEndpointHooksOptions","mutationOptions?: Omit<\n UseMutationOptions<\n ExtractEndpointResponse<Paths, T>,\n Error,\n FilteredRequestConfig<Paths, T>\n >,\n 'mutationFn'\n >","config: FilteredRequestConfig<Paths, T>"],"sources":["../src/endpoint-hooks.ts"],"sourcesContent":["import type {\n QueryClient,\n UseMutationOptions,\n UseQueryOptions,\n} from '@tanstack/react-query';\nimport { useMutation, useQuery } from '@tanstack/react-query';\nimport { useMemo } from 'react';\nimport type {\n ExtractEndpointResponse,\n FilteredRequestConfig,\n IsConfigRequired,\n MutationEndpoint,\n QueryEndpoint,\n TypedApiFunction,\n} from './types';\n\n/**\n * Build query key from endpoint and config\n */\nfunction buildQueryKey<Paths, T extends QueryEndpoint<Paths>>(\n endpoint: T,\n config?: FilteredRequestConfig<Paths, T>,\n): unknown[] {\n const key: unknown[] = [endpoint];\n\n if (config && 'params' in config && config.params) {\n key.push({ params: config.params });\n }\n\n if (config && 'query' in config && config.query) {\n key.push({ query: config.query });\n }\n\n return key;\n}\n\n/**\n * Options for creating endpoint-based hooks\n */\nexport interface CreateEndpointHooksOptions {\n queryClient?: QueryClient;\n}\n\n/**\n * Hook options type that conditionally requires config\n */\ntype UseQueryArgs<Paths, T extends QueryEndpoint<Paths>> = IsConfigRequired<\n Paths,\n T\n> extends true\n ? [\n config: FilteredRequestConfig<Paths, T>,\n options?: Omit<\n UseQueryOptions<ExtractEndpointResponse<Paths, T>, Error>,\n 'queryKey' | 'queryFn'\n >,\n ]\n : [\n config?: FilteredRequestConfig<Paths, T>,\n options?: Omit<\n UseQueryOptions<ExtractEndpointResponse<Paths, T>, Error>,\n 'queryKey' | 'queryFn'\n >,\n ];\n\n/**\n * Endpoint-based React Query hooks\n */\nexport interface EndpointHooks<Paths> {\n /**\n * Use query hook for GET endpoints.\n * Config is required when endpoint has path params.\n */\n useQuery: <T extends QueryEndpoint<Paths>>(\n endpoint: T,\n ...args: UseQueryArgs<Paths, T>\n ) => ReturnType<typeof useQuery<ExtractEndpointResponse<Paths, T>, Error>>;\n\n /**\n * Use mutation hook for POST, PUT, PATCH, DELETE endpoints.\n * Config with params/body is passed to mutate().\n */\n useMutation: <T extends MutationEndpoint<Paths>>(\n endpoint: T,\n options?: Omit<\n UseMutationOptions<\n ExtractEndpointResponse<Paths, T>,\n Error,\n FilteredRequestConfig<Paths, T>\n >,\n 'mutationFn'\n >,\n ) => ReturnType<\n typeof useMutation<\n ExtractEndpointResponse<Paths, T>,\n Error,\n FilteredRequestConfig<Paths, T>\n >\n >;\n\n /**\n * Build a query key for manual cache operations\n */\n buildQueryKey: <T extends QueryEndpoint<Paths>>(\n endpoint: T,\n config?: FilteredRequestConfig<Paths, T>,\n ) => unknown[];\n}\n\n/**\n * Create endpoint-based React Query hooks from a typed fetcher.\n *\n * @example\n * ```typescript\n * const fetcher = createAuthAwareFetcher<paths>({ ... });\n * const hooks = createEndpointHooks<paths>(fetcher);\n *\n * // In a component\n * const { data } = hooks.useQuery('GET /users/{id}', { params: { id: '123' } });\n *\n * const mutation = hooks.useMutation('POST /users');\n * await mutation.mutateAsync({ body: { name: 'John' } });\n * ```\n */\nexport function createEndpointHooks<Paths>(\n fetcher: TypedApiFunction<Paths>,\n options: CreateEndpointHooksOptions = {},\n): EndpointHooks<Paths> {\n return {\n useQuery: <T extends QueryEndpoint<Paths>>(\n endpoint: T,\n ...args: UseQueryArgs<Paths, T>\n ) => {\n // Parse args - config is first, options is second\n const [config, queryOptions] = args as [\n FilteredRequestConfig<Paths, T> | undefined,\n (\n | Omit<\n UseQueryOptions<ExtractEndpointResponse<Paths, T>, Error>,\n 'queryKey' | 'queryFn'\n >\n | undefined\n ),\n ];\n\n const queryKey = buildQueryKey(endpoint, config);\n\n const memoizedOptions = useMemo(\n () => ({\n queryKey,\n queryFn: () =>\n fetcher(\n endpoint as Parameters<typeof fetcher>[0],\n config as Parameters<typeof fetcher>[1],\n ),\n ...queryOptions,\n }),\n [\n queryKey.join(','),\n endpoint,\n JSON.stringify(config),\n JSON.stringify(queryOptions),\n ],\n );\n\n return useQuery<ExtractEndpointResponse<Paths, T>, Error>(\n memoizedOptions,\n );\n },\n\n useMutation: <T extends MutationEndpoint<Paths>>(\n endpoint: T,\n mutationOptions?: Omit<\n UseMutationOptions<\n ExtractEndpointResponse<Paths, T>,\n Error,\n FilteredRequestConfig<Paths, T>\n >,\n 'mutationFn'\n >,\n ) => {\n const memoizedOptions = useMemo(\n () => ({\n mutationFn: (config: FilteredRequestConfig<Paths, T>) =>\n fetcher(\n endpoint as Parameters<typeof fetcher>[0],\n config as Parameters<typeof fetcher>[1],\n ),\n ...mutationOptions,\n }),\n [endpoint, JSON.stringify(mutationOptions)],\n );\n\n return useMutation<\n ExtractEndpointResponse<Paths, T>,\n Error,\n FilteredRequestConfig<Paths, T>\n >(memoizedOptions);\n },\n\n buildQueryKey,\n };\n}\n"],"mappings":";;;;;;;;AAmBA,SAAS,cACPA,UACAC,QACW;CACX,MAAMC,MAAiB,CAAC,QAAS;AAEjC,KAAI,UAAU,YAAY,UAAU,OAAO,OACzC,KAAI,KAAK,EAAE,QAAQ,OAAO,OAAQ,EAAC;AAGrC,KAAI,UAAU,WAAW,UAAU,OAAO,MACxC,KAAI,KAAK,EAAE,OAAO,OAAO,MAAO,EAAC;AAGnC,QAAO;AACR;;;;;;;;;;;;;;;;AA0FD,SAAgB,oBACdC,SACAC,UAAsC,CAAE,GAClB;AACtB,QAAO;EACL,UAAU,CACRJ,UACA,GAAG,SACA;GAEH,MAAM,CAAC,QAAQ,aAAa,GAAG;GAW/B,MAAM,WAAW,cAAc,UAAU,OAAO;GAEhD,MAAM,kBAAkB,mBACtB,OAAO;IACL;IACA,SAAS,MACP,QACE,UACA,OACD;IACH,GAAG;GACJ,IACD;IACE,SAAS,KAAK,IAAI;IAClB;IACA,KAAK,UAAU,OAAO;IACtB,KAAK,UAAU,aAAa;GAC7B,EACF;AAED,UAAO,qCACL,gBACD;EACF;EAED,aAAa,CACXA,UACAK,oBAQG;GACH,MAAM,kBAAkB,mBACtB,OAAO;IACL,YAAY,CAACC,WACX,QACE,UACA,OACD;IACH,GAAG;GACJ,IACD,CAAC,UAAU,KAAK,UAAU,gBAAgB,AAAC,EAC5C;AAED,UAAO,wCAIL,gBAAgB;EACnB;EAED;CACD;AACF"}
@@ -0,0 +1,53 @@
1
+ import { ExtractEndpointResponse, FilteredRequestConfig, IsConfigRequired, MutationEndpoint, QueryEndpoint, TypedApiFunction } from "./types-DVVUpwI4.cjs";
2
+ import { QueryClient, UseMutationOptions, UseQueryOptions, useMutation, useQuery } from "@tanstack/react-query";
3
+
4
+ //#region src/endpoint-hooks.d.ts
5
+
6
+ /**
7
+ * Options for creating endpoint-based hooks
8
+ */
9
+ interface CreateEndpointHooksOptions {
10
+ queryClient?: QueryClient;
11
+ }
12
+ /**
13
+ * Hook options type that conditionally requires config
14
+ */
15
+ type UseQueryArgs<Paths, T extends QueryEndpoint<Paths>> = IsConfigRequired<Paths, T> extends true ? [config: FilteredRequestConfig<Paths, T>, options?: Omit<UseQueryOptions<ExtractEndpointResponse<Paths, T>, Error>, 'queryKey' | 'queryFn'>] : [config?: FilteredRequestConfig<Paths, T>, options?: Omit<UseQueryOptions<ExtractEndpointResponse<Paths, T>, Error>, 'queryKey' | 'queryFn'>];
16
+ /**
17
+ * Endpoint-based React Query hooks
18
+ */
19
+ interface EndpointHooks<Paths> {
20
+ /**
21
+ * Use query hook for GET endpoints.
22
+ * Config is required when endpoint has path params.
23
+ */
24
+ useQuery: <T extends QueryEndpoint<Paths>>(endpoint: T, ...args: UseQueryArgs<Paths, T>) => ReturnType<typeof useQuery<ExtractEndpointResponse<Paths, T>, Error>>;
25
+ /**
26
+ * Use mutation hook for POST, PUT, PATCH, DELETE endpoints.
27
+ * Config with params/body is passed to mutate().
28
+ */
29
+ useMutation: <T extends MutationEndpoint<Paths>>(endpoint: T, options?: Omit<UseMutationOptions<ExtractEndpointResponse<Paths, T>, Error, FilteredRequestConfig<Paths, T>>, 'mutationFn'>) => ReturnType<typeof useMutation<ExtractEndpointResponse<Paths, T>, Error, FilteredRequestConfig<Paths, T>>>;
30
+ /**
31
+ * Build a query key for manual cache operations
32
+ */
33
+ buildQueryKey: <T extends QueryEndpoint<Paths>>(endpoint: T, config?: FilteredRequestConfig<Paths, T>) => unknown[];
34
+ }
35
+ /**
36
+ * Create endpoint-based React Query hooks from a typed fetcher.
37
+ *
38
+ * @example
39
+ * ```typescript
40
+ * const fetcher = createAuthAwareFetcher<paths>({ ... });
41
+ * const hooks = createEndpointHooks<paths>(fetcher);
42
+ *
43
+ * // In a component
44
+ * const { data } = hooks.useQuery('GET /users/{id}', { params: { id: '123' } });
45
+ *
46
+ * const mutation = hooks.useMutation('POST /users');
47
+ * await mutation.mutateAsync({ body: { name: 'John' } });
48
+ * ```
49
+ */
50
+ declare function createEndpointHooks<Paths>(fetcher: TypedApiFunction<Paths>, options?: CreateEndpointHooksOptions): EndpointHooks<Paths>;
51
+ //#endregion
52
+ export { CreateEndpointHooksOptions, EndpointHooks, createEndpointHooks };
53
+ //# sourceMappingURL=endpoint-hooks.d.cts.map
@@ -0,0 +1,53 @@
1
+ import { ExtractEndpointResponse, FilteredRequestConfig, IsConfigRequired, MutationEndpoint, QueryEndpoint, TypedApiFunction } from "./types-CyMkwV3g.mjs";
2
+ import { QueryClient, UseMutationOptions, UseQueryOptions, useMutation, useQuery } from "@tanstack/react-query";
3
+
4
+ //#region src/endpoint-hooks.d.ts
5
+
6
+ /**
7
+ * Options for creating endpoint-based hooks
8
+ */
9
+ interface CreateEndpointHooksOptions {
10
+ queryClient?: QueryClient;
11
+ }
12
+ /**
13
+ * Hook options type that conditionally requires config
14
+ */
15
+ type UseQueryArgs<Paths, T extends QueryEndpoint<Paths>> = IsConfigRequired<Paths, T> extends true ? [config: FilteredRequestConfig<Paths, T>, options?: Omit<UseQueryOptions<ExtractEndpointResponse<Paths, T>, Error>, 'queryKey' | 'queryFn'>] : [config?: FilteredRequestConfig<Paths, T>, options?: Omit<UseQueryOptions<ExtractEndpointResponse<Paths, T>, Error>, 'queryKey' | 'queryFn'>];
16
+ /**
17
+ * Endpoint-based React Query hooks
18
+ */
19
+ interface EndpointHooks<Paths> {
20
+ /**
21
+ * Use query hook for GET endpoints.
22
+ * Config is required when endpoint has path params.
23
+ */
24
+ useQuery: <T extends QueryEndpoint<Paths>>(endpoint: T, ...args: UseQueryArgs<Paths, T>) => ReturnType<typeof useQuery<ExtractEndpointResponse<Paths, T>, Error>>;
25
+ /**
26
+ * Use mutation hook for POST, PUT, PATCH, DELETE endpoints.
27
+ * Config with params/body is passed to mutate().
28
+ */
29
+ useMutation: <T extends MutationEndpoint<Paths>>(endpoint: T, options?: Omit<UseMutationOptions<ExtractEndpointResponse<Paths, T>, Error, FilteredRequestConfig<Paths, T>>, 'mutationFn'>) => ReturnType<typeof useMutation<ExtractEndpointResponse<Paths, T>, Error, FilteredRequestConfig<Paths, T>>>;
30
+ /**
31
+ * Build a query key for manual cache operations
32
+ */
33
+ buildQueryKey: <T extends QueryEndpoint<Paths>>(endpoint: T, config?: FilteredRequestConfig<Paths, T>) => unknown[];
34
+ }
35
+ /**
36
+ * Create endpoint-based React Query hooks from a typed fetcher.
37
+ *
38
+ * @example
39
+ * ```typescript
40
+ * const fetcher = createAuthAwareFetcher<paths>({ ... });
41
+ * const hooks = createEndpointHooks<paths>(fetcher);
42
+ *
43
+ * // In a component
44
+ * const { data } = hooks.useQuery('GET /users/{id}', { params: { id: '123' } });
45
+ *
46
+ * const mutation = hooks.useMutation('POST /users');
47
+ * await mutation.mutateAsync({ body: { name: 'John' } });
48
+ * ```
49
+ */
50
+ declare function createEndpointHooks<Paths>(fetcher: TypedApiFunction<Paths>, options?: CreateEndpointHooksOptions): EndpointHooks<Paths>;
51
+ //#endregion
52
+ export { CreateEndpointHooksOptions, EndpointHooks, createEndpointHooks };
53
+ //# sourceMappingURL=endpoint-hooks.d.mts.map
@@ -0,0 +1,59 @@
1
+ import { useMutation, useQuery } from "@tanstack/react-query";
2
+ import { useMemo } from "react";
3
+
4
+ //#region src/endpoint-hooks.ts
5
+ /**
6
+ * Build query key from endpoint and config
7
+ */
8
+ function buildQueryKey(endpoint, config) {
9
+ const key = [endpoint];
10
+ if (config && "params" in config && config.params) key.push({ params: config.params });
11
+ if (config && "query" in config && config.query) key.push({ query: config.query });
12
+ return key;
13
+ }
14
+ /**
15
+ * Create endpoint-based React Query hooks from a typed fetcher.
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * const fetcher = createAuthAwareFetcher<paths>({ ... });
20
+ * const hooks = createEndpointHooks<paths>(fetcher);
21
+ *
22
+ * // In a component
23
+ * const { data } = hooks.useQuery('GET /users/{id}', { params: { id: '123' } });
24
+ *
25
+ * const mutation = hooks.useMutation('POST /users');
26
+ * await mutation.mutateAsync({ body: { name: 'John' } });
27
+ * ```
28
+ */
29
+ function createEndpointHooks(fetcher, options = {}) {
30
+ return {
31
+ useQuery: (endpoint, ...args) => {
32
+ const [config, queryOptions] = args;
33
+ const queryKey = buildQueryKey(endpoint, config);
34
+ const memoizedOptions = useMemo(() => ({
35
+ queryKey,
36
+ queryFn: () => fetcher(endpoint, config),
37
+ ...queryOptions
38
+ }), [
39
+ queryKey.join(","),
40
+ endpoint,
41
+ JSON.stringify(config),
42
+ JSON.stringify(queryOptions)
43
+ ]);
44
+ return useQuery(memoizedOptions);
45
+ },
46
+ useMutation: (endpoint, mutationOptions) => {
47
+ const memoizedOptions = useMemo(() => ({
48
+ mutationFn: (config) => fetcher(endpoint, config),
49
+ ...mutationOptions
50
+ }), [endpoint, JSON.stringify(mutationOptions)]);
51
+ return useMutation(memoizedOptions);
52
+ },
53
+ buildQueryKey
54
+ };
55
+ }
56
+
57
+ //#endregion
58
+ export { createEndpointHooks };
59
+ //# sourceMappingURL=endpoint-hooks.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"endpoint-hooks.mjs","names":["endpoint: T","config?: FilteredRequestConfig<Paths, T>","key: unknown[]","fetcher: TypedApiFunction<Paths>","options: CreateEndpointHooksOptions","mutationOptions?: Omit<\n UseMutationOptions<\n ExtractEndpointResponse<Paths, T>,\n Error,\n FilteredRequestConfig<Paths, T>\n >,\n 'mutationFn'\n >","config: FilteredRequestConfig<Paths, T>"],"sources":["../src/endpoint-hooks.ts"],"sourcesContent":["import type {\n QueryClient,\n UseMutationOptions,\n UseQueryOptions,\n} from '@tanstack/react-query';\nimport { useMutation, useQuery } from '@tanstack/react-query';\nimport { useMemo } from 'react';\nimport type {\n ExtractEndpointResponse,\n FilteredRequestConfig,\n IsConfigRequired,\n MutationEndpoint,\n QueryEndpoint,\n TypedApiFunction,\n} from './types';\n\n/**\n * Build query key from endpoint and config\n */\nfunction buildQueryKey<Paths, T extends QueryEndpoint<Paths>>(\n endpoint: T,\n config?: FilteredRequestConfig<Paths, T>,\n): unknown[] {\n const key: unknown[] = [endpoint];\n\n if (config && 'params' in config && config.params) {\n key.push({ params: config.params });\n }\n\n if (config && 'query' in config && config.query) {\n key.push({ query: config.query });\n }\n\n return key;\n}\n\n/**\n * Options for creating endpoint-based hooks\n */\nexport interface CreateEndpointHooksOptions {\n queryClient?: QueryClient;\n}\n\n/**\n * Hook options type that conditionally requires config\n */\ntype UseQueryArgs<Paths, T extends QueryEndpoint<Paths>> = IsConfigRequired<\n Paths,\n T\n> extends true\n ? [\n config: FilteredRequestConfig<Paths, T>,\n options?: Omit<\n UseQueryOptions<ExtractEndpointResponse<Paths, T>, Error>,\n 'queryKey' | 'queryFn'\n >,\n ]\n : [\n config?: FilteredRequestConfig<Paths, T>,\n options?: Omit<\n UseQueryOptions<ExtractEndpointResponse<Paths, T>, Error>,\n 'queryKey' | 'queryFn'\n >,\n ];\n\n/**\n * Endpoint-based React Query hooks\n */\nexport interface EndpointHooks<Paths> {\n /**\n * Use query hook for GET endpoints.\n * Config is required when endpoint has path params.\n */\n useQuery: <T extends QueryEndpoint<Paths>>(\n endpoint: T,\n ...args: UseQueryArgs<Paths, T>\n ) => ReturnType<typeof useQuery<ExtractEndpointResponse<Paths, T>, Error>>;\n\n /**\n * Use mutation hook for POST, PUT, PATCH, DELETE endpoints.\n * Config with params/body is passed to mutate().\n */\n useMutation: <T extends MutationEndpoint<Paths>>(\n endpoint: T,\n options?: Omit<\n UseMutationOptions<\n ExtractEndpointResponse<Paths, T>,\n Error,\n FilteredRequestConfig<Paths, T>\n >,\n 'mutationFn'\n >,\n ) => ReturnType<\n typeof useMutation<\n ExtractEndpointResponse<Paths, T>,\n Error,\n FilteredRequestConfig<Paths, T>\n >\n >;\n\n /**\n * Build a query key for manual cache operations\n */\n buildQueryKey: <T extends QueryEndpoint<Paths>>(\n endpoint: T,\n config?: FilteredRequestConfig<Paths, T>,\n ) => unknown[];\n}\n\n/**\n * Create endpoint-based React Query hooks from a typed fetcher.\n *\n * @example\n * ```typescript\n * const fetcher = createAuthAwareFetcher<paths>({ ... });\n * const hooks = createEndpointHooks<paths>(fetcher);\n *\n * // In a component\n * const { data } = hooks.useQuery('GET /users/{id}', { params: { id: '123' } });\n *\n * const mutation = hooks.useMutation('POST /users');\n * await mutation.mutateAsync({ body: { name: 'John' } });\n * ```\n */\nexport function createEndpointHooks<Paths>(\n fetcher: TypedApiFunction<Paths>,\n options: CreateEndpointHooksOptions = {},\n): EndpointHooks<Paths> {\n return {\n useQuery: <T extends QueryEndpoint<Paths>>(\n endpoint: T,\n ...args: UseQueryArgs<Paths, T>\n ) => {\n // Parse args - config is first, options is second\n const [config, queryOptions] = args as [\n FilteredRequestConfig<Paths, T> | undefined,\n (\n | Omit<\n UseQueryOptions<ExtractEndpointResponse<Paths, T>, Error>,\n 'queryKey' | 'queryFn'\n >\n | undefined\n ),\n ];\n\n const queryKey = buildQueryKey(endpoint, config);\n\n const memoizedOptions = useMemo(\n () => ({\n queryKey,\n queryFn: () =>\n fetcher(\n endpoint as Parameters<typeof fetcher>[0],\n config as Parameters<typeof fetcher>[1],\n ),\n ...queryOptions,\n }),\n [\n queryKey.join(','),\n endpoint,\n JSON.stringify(config),\n JSON.stringify(queryOptions),\n ],\n );\n\n return useQuery<ExtractEndpointResponse<Paths, T>, Error>(\n memoizedOptions,\n );\n },\n\n useMutation: <T extends MutationEndpoint<Paths>>(\n endpoint: T,\n mutationOptions?: Omit<\n UseMutationOptions<\n ExtractEndpointResponse<Paths, T>,\n Error,\n FilteredRequestConfig<Paths, T>\n >,\n 'mutationFn'\n >,\n ) => {\n const memoizedOptions = useMemo(\n () => ({\n mutationFn: (config: FilteredRequestConfig<Paths, T>) =>\n fetcher(\n endpoint as Parameters<typeof fetcher>[0],\n config as Parameters<typeof fetcher>[1],\n ),\n ...mutationOptions,\n }),\n [endpoint, JSON.stringify(mutationOptions)],\n );\n\n return useMutation<\n ExtractEndpointResponse<Paths, T>,\n Error,\n FilteredRequestConfig<Paths, T>\n >(memoizedOptions);\n },\n\n buildQueryKey,\n };\n}\n"],"mappings":";;;;;;;AAmBA,SAAS,cACPA,UACAC,QACW;CACX,MAAMC,MAAiB,CAAC,QAAS;AAEjC,KAAI,UAAU,YAAY,UAAU,OAAO,OACzC,KAAI,KAAK,EAAE,QAAQ,OAAO,OAAQ,EAAC;AAGrC,KAAI,UAAU,WAAW,UAAU,OAAO,MACxC,KAAI,KAAK,EAAE,OAAO,OAAO,MAAO,EAAC;AAGnC,QAAO;AACR;;;;;;;;;;;;;;;;AA0FD,SAAgB,oBACdC,SACAC,UAAsC,CAAE,GAClB;AACtB,QAAO;EACL,UAAU,CACRJ,UACA,GAAG,SACA;GAEH,MAAM,CAAC,QAAQ,aAAa,GAAG;GAW/B,MAAM,WAAW,cAAc,UAAU,OAAO;GAEhD,MAAM,kBAAkB,QACtB,OAAO;IACL;IACA,SAAS,MACP,QACE,UACA,OACD;IACH,GAAG;GACJ,IACD;IACE,SAAS,KAAK,IAAI;IAClB;IACA,KAAK,UAAU,OAAO;IACtB,KAAK,UAAU,aAAa;GAC7B,EACF;AAED,UAAO,SACL,gBACD;EACF;EAED,aAAa,CACXA,UACAK,oBAQG;GACH,MAAM,kBAAkB,QACtB,OAAO;IACL,YAAY,CAACC,WACX,QACE,UACA,OACD;IACH,GAAG;GACJ,IACD,CAAC,UAAU,KAAK,UAAU,gBAAgB,AAAC,EAC5C;AAED,UAAO,YAIL,gBAAgB;EACnB;EAED;CACD;AACF"}
@@ -1,4 +1,4 @@
1
- import { ExtractEndpointResponse, FetcherOptions, FilteredRequestConfig, TypedEndpoint } from "./types-csACmD6U.cjs";
1
+ import { ExtractEndpointResponse, FetcherOptions, FilteredRequestConfig, TypedEndpoint } from "./types-DVVUpwI4.cjs";
2
2
 
3
3
  //#region src/fetcher.d.ts
4
4
  declare class TypedFetcher<Paths> {
@@ -1,4 +1,4 @@
1
- import { ExtractEndpointResponse, FetcherOptions, FilteredRequestConfig, TypedEndpoint } from "./types-tMp85Lt_.mjs";
1
+ import { ExtractEndpointResponse, FetcherOptions, FilteredRequestConfig, TypedEndpoint } from "./types-CyMkwV3g.mjs";
2
2
 
3
3
  //#region src/fetcher.d.ts
4
4
  declare class TypedFetcher<Paths> {
package/dist/infer.cjs ADDED
File without changes
@@ -0,0 +1,133 @@
1
+ import { Endpoint, EndpointSchemas } from "@geekmidas/constructs/endpoints";
2
+ import { HttpMethod } from "@geekmidas/constructs/types";
3
+ import { InferStandardSchema } from "@geekmidas/schema";
4
+ import { StandardSchemaV1 } from "@standard-schema/spec";
5
+
6
+ //#region src/infer.d.ts
7
+
8
+ /**
9
+ * Infers path parameters from a route string as an object
10
+ * @example '/users/{id}/posts/{postId}' -> { id: string, postId: string }
11
+ */
12
+ type InferPathParams<TRoute extends string> = TRoute extends `${string}{${infer Param}}${infer Rest}` ? { [K in Param]: string } & InferPathParams<Rest> : {};
13
+ /**
14
+ * Converts an HTTP method to lowercase for TypedFetcher compatibility
15
+ */
16
+ type LowercaseMethod<T extends HttpMethod> = Lowercase<T>;
17
+ /**
18
+ * Infers route-level parameters (path params)
19
+ */
20
+ type InferRouteParameters<TRoute extends string> = InferPathParams<TRoute> extends Record<string, never> ? {} : {
21
+ parameters: {
22
+ path: InferPathParams<TRoute>;
23
+ };
24
+ };
25
+ /**
26
+ * Infers operation-level parameters (query params)
27
+ */
28
+ type InferOperationParameters<TInput extends EndpointSchemas> = TInput extends {
29
+ query: infer Q;
30
+ } ? {
31
+ parameters: {
32
+ query: InferStandardSchema<Q>;
33
+ };
34
+ } : {};
35
+ /**
36
+ * Infers the operation object compatible with TypedFetcher
37
+ */
38
+ type InferOperation<TInput extends EndpointSchemas, TOutput extends StandardSchemaV1 | undefined> = InferOperationParameters<TInput> & {
39
+ requestBody?: TInput extends {
40
+ body: infer B;
41
+ } ? {
42
+ content: {
43
+ 'application/json': InferStandardSchema<B>;
44
+ };
45
+ } : never;
46
+ responses: {
47
+ 200: {
48
+ content: TOutput extends StandardSchemaV1 ? {
49
+ 'application/json': InferStandardSchema<TOutput>;
50
+ } : never;
51
+ };
52
+ };
53
+ };
54
+ /**
55
+ * Infers the TypedFetcher-compatible paths structure from a single endpoint
56
+ *
57
+ * This generates a structure compatible with @geekmidas/client TypedFetcher,
58
+ * allowing you to create a typed client directly from endpoint definitions
59
+ * without needing OpenAPI JSON + codegen.
60
+ *
61
+ * @example
62
+ * ```typescript
63
+ * import { e } from '@geekmidas/constructs';
64
+ * import { createTypedFetcher, type InferOpenApiFromEndpoint } from '@geekmidas/client';
65
+ * import { z } from 'zod';
66
+ *
67
+ * const endpoint = e
68
+ * .get('/users/{id}')
69
+ * .params(z.object({ id: z.string() }))
70
+ * .output(z.object({ id: z.string(), name: z.string() }))
71
+ * .handle(async ({ params }) => ({ id: params.id, name: 'John' }));
72
+ *
73
+ * type Paths = InferOpenApiFromEndpoint<typeof endpoint>['paths'];
74
+ * const client = createTypedFetcher<Paths>({ baseURL: 'http://localhost:3000' });
75
+ * const user = await client('GET /users/{id}', { params: { id: '123' } });
76
+ * ```
77
+ */
78
+ type InferOpenApiFromEndpoint<T> = T extends Endpoint<infer TRoute, infer TMethod, infer TInput, infer TOutput, any, any, any> ? {
79
+ paths: { [K in TRoute]: InferRouteParameters<TRoute> & { [M in LowercaseMethod<TMethod>]: InferOperation<TInput, TOutput> } };
80
+ } : never;
81
+ /**
82
+ * Infers TypedFetcher-compatible paths structure from multiple endpoints
83
+ *
84
+ * Merges multiple endpoint definitions into a single paths object that can be
85
+ * used with @geekmidas/client TypedFetcher for fully type-safe API calls.
86
+ *
87
+ * @example
88
+ * ```typescript
89
+ * import { e } from '@geekmidas/constructs';
90
+ * import { createTypedFetcher, type InferOpenApi } from '@geekmidas/client';
91
+ * import { z } from 'zod';
92
+ *
93
+ * // Define endpoints
94
+ * const getUserEndpoint = e
95
+ * .get('/users/{id}')
96
+ * .params(z.object({ id: z.string() }))
97
+ * .output(z.object({ id: z.string(), name: z.string() }))
98
+ * .handle(async ({ params }) => ({ id: params.id, name: 'John' }));
99
+ *
100
+ * const createUserEndpoint = e
101
+ * .post('/users')
102
+ * .body(z.object({ name: z.string() }))
103
+ * .output(z.object({ id: z.string(), name: z.string() }))
104
+ * .handle(async ({ body }) => ({ id: '123', name: body.name }));
105
+ *
106
+ * // Export for client
107
+ * export const endpoints = [getUserEndpoint, createUserEndpoint] as const;
108
+ * export type Paths = InferOpenApi<typeof endpoints>['paths'];
109
+ *
110
+ * // Client usage
111
+ * import type { Paths } from './endpoints';
112
+ * const client = createTypedFetcher<Paths>({ baseURL: 'http://localhost:3000' });
113
+ *
114
+ * const user = await client('GET /users/{id}', { params: { id: '123' } });
115
+ * const newUser = await client('POST /users', { body: { name: 'Jane' } });
116
+ * ```
117
+ */
118
+ type InferOpenApi<TEndpoints extends readonly any[]> = TEndpoints extends readonly [infer First, ...infer Rest] ? InferOpenApiFromEndpoint<First> extends {
119
+ paths: infer P1;
120
+ } ? Rest extends [] ? {
121
+ paths: P1;
122
+ } : InferOpenApi<Rest> extends {
123
+ paths: infer P2;
124
+ } ? {
125
+ paths: P1 & P2;
126
+ } : {
127
+ paths: P1;
128
+ } : InferOpenApi<Rest> : {
129
+ paths: {};
130
+ };
131
+ //#endregion
132
+ export { InferOpenApi, InferOpenApiFromEndpoint };
133
+ //# sourceMappingURL=infer.d.cts.map
@@ -0,0 +1,133 @@
1
+ import { Endpoint, EndpointSchemas } from "@geekmidas/constructs/endpoints";
2
+ import { HttpMethod } from "@geekmidas/constructs/types";
3
+ import { InferStandardSchema } from "@geekmidas/schema";
4
+ import { StandardSchemaV1 } from "@standard-schema/spec";
5
+
6
+ //#region src/infer.d.ts
7
+
8
+ /**
9
+ * Infers path parameters from a route string as an object
10
+ * @example '/users/{id}/posts/{postId}' -> { id: string, postId: string }
11
+ */
12
+ type InferPathParams<TRoute extends string> = TRoute extends `${string}{${infer Param}}${infer Rest}` ? { [K in Param]: string } & InferPathParams<Rest> : {};
13
+ /**
14
+ * Converts an HTTP method to lowercase for TypedFetcher compatibility
15
+ */
16
+ type LowercaseMethod<T extends HttpMethod> = Lowercase<T>;
17
+ /**
18
+ * Infers route-level parameters (path params)
19
+ */
20
+ type InferRouteParameters<TRoute extends string> = InferPathParams<TRoute> extends Record<string, never> ? {} : {
21
+ parameters: {
22
+ path: InferPathParams<TRoute>;
23
+ };
24
+ };
25
+ /**
26
+ * Infers operation-level parameters (query params)
27
+ */
28
+ type InferOperationParameters<TInput extends EndpointSchemas> = TInput extends {
29
+ query: infer Q;
30
+ } ? {
31
+ parameters: {
32
+ query: InferStandardSchema<Q>;
33
+ };
34
+ } : {};
35
+ /**
36
+ * Infers the operation object compatible with TypedFetcher
37
+ */
38
+ type InferOperation<TInput extends EndpointSchemas, TOutput extends StandardSchemaV1 | undefined> = InferOperationParameters<TInput> & {
39
+ requestBody?: TInput extends {
40
+ body: infer B;
41
+ } ? {
42
+ content: {
43
+ 'application/json': InferStandardSchema<B>;
44
+ };
45
+ } : never;
46
+ responses: {
47
+ 200: {
48
+ content: TOutput extends StandardSchemaV1 ? {
49
+ 'application/json': InferStandardSchema<TOutput>;
50
+ } : never;
51
+ };
52
+ };
53
+ };
54
+ /**
55
+ * Infers the TypedFetcher-compatible paths structure from a single endpoint
56
+ *
57
+ * This generates a structure compatible with @geekmidas/client TypedFetcher,
58
+ * allowing you to create a typed client directly from endpoint definitions
59
+ * without needing OpenAPI JSON + codegen.
60
+ *
61
+ * @example
62
+ * ```typescript
63
+ * import { e } from '@geekmidas/constructs';
64
+ * import { createTypedFetcher, type InferOpenApiFromEndpoint } from '@geekmidas/client';
65
+ * import { z } from 'zod';
66
+ *
67
+ * const endpoint = e
68
+ * .get('/users/{id}')
69
+ * .params(z.object({ id: z.string() }))
70
+ * .output(z.object({ id: z.string(), name: z.string() }))
71
+ * .handle(async ({ params }) => ({ id: params.id, name: 'John' }));
72
+ *
73
+ * type Paths = InferOpenApiFromEndpoint<typeof endpoint>['paths'];
74
+ * const client = createTypedFetcher<Paths>({ baseURL: 'http://localhost:3000' });
75
+ * const user = await client('GET /users/{id}', { params: { id: '123' } });
76
+ * ```
77
+ */
78
+ type InferOpenApiFromEndpoint<T> = T extends Endpoint<infer TRoute, infer TMethod, infer TInput, infer TOutput, any, any, any> ? {
79
+ paths: { [K in TRoute]: InferRouteParameters<TRoute> & { [M in LowercaseMethod<TMethod>]: InferOperation<TInput, TOutput> } };
80
+ } : never;
81
+ /**
82
+ * Infers TypedFetcher-compatible paths structure from multiple endpoints
83
+ *
84
+ * Merges multiple endpoint definitions into a single paths object that can be
85
+ * used with @geekmidas/client TypedFetcher for fully type-safe API calls.
86
+ *
87
+ * @example
88
+ * ```typescript
89
+ * import { e } from '@geekmidas/constructs';
90
+ * import { createTypedFetcher, type InferOpenApi } from '@geekmidas/client';
91
+ * import { z } from 'zod';
92
+ *
93
+ * // Define endpoints
94
+ * const getUserEndpoint = e
95
+ * .get('/users/{id}')
96
+ * .params(z.object({ id: z.string() }))
97
+ * .output(z.object({ id: z.string(), name: z.string() }))
98
+ * .handle(async ({ params }) => ({ id: params.id, name: 'John' }));
99
+ *
100
+ * const createUserEndpoint = e
101
+ * .post('/users')
102
+ * .body(z.object({ name: z.string() }))
103
+ * .output(z.object({ id: z.string(), name: z.string() }))
104
+ * .handle(async ({ body }) => ({ id: '123', name: body.name }));
105
+ *
106
+ * // Export for client
107
+ * export const endpoints = [getUserEndpoint, createUserEndpoint] as const;
108
+ * export type Paths = InferOpenApi<typeof endpoints>['paths'];
109
+ *
110
+ * // Client usage
111
+ * import type { Paths } from './endpoints';
112
+ * const client = createTypedFetcher<Paths>({ baseURL: 'http://localhost:3000' });
113
+ *
114
+ * const user = await client('GET /users/{id}', { params: { id: '123' } });
115
+ * const newUser = await client('POST /users', { body: { name: 'Jane' } });
116
+ * ```
117
+ */
118
+ type InferOpenApi<TEndpoints extends readonly any[]> = TEndpoints extends readonly [infer First, ...infer Rest] ? InferOpenApiFromEndpoint<First> extends {
119
+ paths: infer P1;
120
+ } ? Rest extends [] ? {
121
+ paths: P1;
122
+ } : InferOpenApi<Rest> extends {
123
+ paths: infer P2;
124
+ } ? {
125
+ paths: P1 & P2;
126
+ } : {
127
+ paths: P1;
128
+ } : InferOpenApi<Rest> : {
129
+ paths: {};
130
+ };
131
+ //#endregion
132
+ export { InferOpenApi, InferOpenApiFromEndpoint };
133
+ //# sourceMappingURL=infer.d.mts.map
package/dist/infer.mjs ADDED
File without changes
@@ -1,5 +1,5 @@
1
- import { FetcherOptions } from "./types-csACmD6U.cjs";
2
- import * as _tanstack_react_query8 from "@tanstack/react-query";
1
+ import { FetcherOptions } from "./types-DVVUpwI4.cjs";
2
+ import * as _tanstack_react_query0 from "@tanstack/react-query";
3
3
  import { UseMutationOptions, UseQueryOptions } from "@tanstack/react-query";
4
4
 
5
5
  //#region src/openapi-hooks.d.ts
@@ -91,8 +91,8 @@ interface OperationRegistry {
91
91
  declare function createOpenAPIHooks<Paths>(options?: FetcherOptions & {
92
92
  operations?: OperationRegistry;
93
93
  }): {
94
- useQuery: <OpId extends OperationsByMethod<Paths, "get">>(operationId: OpId, config?: RemoveNever<OperationParams<Paths, OpId>>, options?: Omit<UseQueryOptions<OperationResponse<Paths, OpId>, Error>, "queryKey" | "queryFn">) => _tanstack_react_query8.UseQueryResult<_tanstack_react_query8.NoInfer<OperationResponse<Paths, OpId>>, Error>;
95
- useMutation: <OpId extends Exclude<OperationId<Paths>, OperationsByMethod<Paths, "get">>>(operationId: OpId, options?: Omit<UseMutationOptions<OperationResponse<Paths, OpId>, Error, RemoveNever<OperationParams<Paths, OpId>>>, "mutationFn">) => _tanstack_react_query8.UseMutationResult<OperationResponse<Paths, OpId>, Error, RemoveNever<OperationParams<Paths, OpId>>, unknown>;
94
+ useQuery: <OpId extends OperationsByMethod<Paths, "get">>(operationId: OpId, config?: RemoveNever<OperationParams<Paths, OpId>>, options?: Omit<UseQueryOptions<OperationResponse<Paths, OpId>, Error>, "queryKey" | "queryFn">) => _tanstack_react_query0.UseQueryResult<_tanstack_react_query0.NoInfer<OperationResponse<Paths, OpId>>, Error>;
95
+ useMutation: <OpId extends Exclude<OperationId<Paths>, OperationsByMethod<Paths, "get">>>(operationId: OpId, options?: Omit<UseMutationOptions<OperationResponse<Paths, OpId>, Error, RemoveNever<OperationParams<Paths, OpId>>>, "mutationFn">) => _tanstack_react_query0.UseMutationResult<OperationResponse<Paths, OpId>, Error, RemoveNever<OperationParams<Paths, OpId>>, unknown>;
96
96
  };
97
97
  //#endregion
98
98
  export { createOpenAPIHooks };
@@ -1,4 +1,4 @@
1
- import { FetcherOptions } from "./types-tMp85Lt_.mjs";
1
+ import { FetcherOptions } from "./types-CyMkwV3g.mjs";
2
2
  import * as _tanstack_react_query0 from "@tanstack/react-query";
3
3
  import { UseMutationOptions, UseQueryOptions } from "@tanstack/react-query";
4
4