routesync 1.0.18 → 1.0.20

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.
package/dist/core.d.mts CHANGED
@@ -12,6 +12,14 @@ interface ServiceConfig {
12
12
  timeout?: number;
13
13
  retry?: RetryConfig;
14
14
  cache?: boolean;
15
+ validateResponse?: boolean;
16
+ onValidationError?: (error: unknown, context: {
17
+ endpoint: string;
18
+ method: string;
19
+ path: string;
20
+ request: unknown;
21
+ response: unknown;
22
+ }) => void;
15
23
  }
16
24
  interface RetryConfig {
17
25
  attempts: number;
@@ -27,6 +35,7 @@ interface AuthConfig {
27
35
 
28
36
  declare class HttpClient {
29
37
  private client;
38
+ readonly config: ServiceConfig;
30
39
  constructor(config: ServiceConfig);
31
40
  private setupInterceptors;
32
41
  setToken(token: string): void;
@@ -73,11 +82,15 @@ interface RequestOptions {
73
82
  timeout?: number;
74
83
  signal?: AbortSignal;
75
84
  }
76
- interface RouteDefinition {
85
+ interface ResponseSchema<T> {
86
+ parse(input: unknown): T;
87
+ }
88
+ interface RouteDefinition<TResponse = unknown, TParams = unknown, TBody = unknown> {
77
89
  method: HttpMethod;
78
90
  path: string;
79
91
  auth?: boolean;
80
92
  schema?: RouteSchema;
93
+ responseSchema?: ResponseSchema<TResponse>;
81
94
  mapper?: RouteMapper;
82
95
  headers?: Record<string, string>;
83
96
  cache?: unknown;
@@ -85,10 +98,13 @@ interface RouteDefinition {
85
98
  body?: Record<string, any>;
86
99
  params?: Record<string, any>;
87
100
  query?: Record<string, any>;
101
+ _typeResponse?: TResponse;
102
+ _typeParams?: TParams;
103
+ _typeBody?: TBody;
88
104
  }
89
105
  interface ApiDefinition {
90
106
  [group: string]: {
91
- [action: string]: RouteDefinition;
107
+ [action: string]: RouteDefinition<any, any, any>;
92
108
  };
93
109
  }
94
110
 
@@ -218,6 +234,10 @@ interface ParsedRoute {
218
234
  schema?: Record<string, any>;
219
235
  group?: string;
220
236
  action?: string;
237
+ response?: {
238
+ type: string;
239
+ collection: boolean;
240
+ };
221
241
  }
222
242
  interface ParsedColumn {
223
243
  name: string;
@@ -233,4 +253,4 @@ interface ParsedModel {
233
253
  casts?: Record<string, string>;
234
254
  }
235
255
 
236
- export { type ApiDefinition, ApiError, type ApiResponse, type AuthConfig, AuthMiddleware, ErrorHandler, HttpClient, type HttpMethod, Interceptor, type PaginationMeta, type ParsedChannel, type ParsedColumn, type ParsedModel, type ParsedRoute, PathResolver, QueryBuilder, Request, type RequestOptions, Response, type RetryConfig, type RouteDefinition, type RouteManifest, type RouteMapper, type RouteParserSchema, type RouteSchema, type RouteSchemaMap, type RouteSchemaValue, type RouteTransform, type RouteTransformMap, type ServiceConfig, TokenManager, camelCase, camelCaseKeys, snakeCase, snakeCaseKeys };
256
+ export { type ApiDefinition, ApiError, type ApiResponse, type AuthConfig, AuthMiddleware, ErrorHandler, HttpClient, type HttpMethod, Interceptor, type PaginationMeta, type ParsedChannel, type ParsedColumn, type ParsedModel, type ParsedRoute, PathResolver, QueryBuilder, Request, type RequestOptions, Response, type ResponseSchema, type RetryConfig, type RouteDefinition, type RouteManifest, type RouteMapper, type RouteParserSchema, type RouteSchema, type RouteSchemaMap, type RouteSchemaValue, type RouteTransform, type RouteTransformMap, type ServiceConfig, TokenManager, camelCase, camelCaseKeys, snakeCase, snakeCaseKeys };
package/dist/core.d.ts CHANGED
@@ -12,6 +12,14 @@ interface ServiceConfig {
12
12
  timeout?: number;
13
13
  retry?: RetryConfig;
14
14
  cache?: boolean;
15
+ validateResponse?: boolean;
16
+ onValidationError?: (error: unknown, context: {
17
+ endpoint: string;
18
+ method: string;
19
+ path: string;
20
+ request: unknown;
21
+ response: unknown;
22
+ }) => void;
15
23
  }
16
24
  interface RetryConfig {
17
25
  attempts: number;
@@ -27,6 +35,7 @@ interface AuthConfig {
27
35
 
28
36
  declare class HttpClient {
29
37
  private client;
38
+ readonly config: ServiceConfig;
30
39
  constructor(config: ServiceConfig);
31
40
  private setupInterceptors;
32
41
  setToken(token: string): void;
@@ -73,11 +82,15 @@ interface RequestOptions {
73
82
  timeout?: number;
74
83
  signal?: AbortSignal;
75
84
  }
76
- interface RouteDefinition {
85
+ interface ResponseSchema<T> {
86
+ parse(input: unknown): T;
87
+ }
88
+ interface RouteDefinition<TResponse = unknown, TParams = unknown, TBody = unknown> {
77
89
  method: HttpMethod;
78
90
  path: string;
79
91
  auth?: boolean;
80
92
  schema?: RouteSchema;
93
+ responseSchema?: ResponseSchema<TResponse>;
81
94
  mapper?: RouteMapper;
82
95
  headers?: Record<string, string>;
83
96
  cache?: unknown;
@@ -85,10 +98,13 @@ interface RouteDefinition {
85
98
  body?: Record<string, any>;
86
99
  params?: Record<string, any>;
87
100
  query?: Record<string, any>;
101
+ _typeResponse?: TResponse;
102
+ _typeParams?: TParams;
103
+ _typeBody?: TBody;
88
104
  }
89
105
  interface ApiDefinition {
90
106
  [group: string]: {
91
- [action: string]: RouteDefinition;
107
+ [action: string]: RouteDefinition<any, any, any>;
92
108
  };
93
109
  }
94
110
 
@@ -218,6 +234,10 @@ interface ParsedRoute {
218
234
  schema?: Record<string, any>;
219
235
  group?: string;
220
236
  action?: string;
237
+ response?: {
238
+ type: string;
239
+ collection: boolean;
240
+ };
221
241
  }
222
242
  interface ParsedColumn {
223
243
  name: string;
@@ -233,4 +253,4 @@ interface ParsedModel {
233
253
  casts?: Record<string, string>;
234
254
  }
235
255
 
236
- export { type ApiDefinition, ApiError, type ApiResponse, type AuthConfig, AuthMiddleware, ErrorHandler, HttpClient, type HttpMethod, Interceptor, type PaginationMeta, type ParsedChannel, type ParsedColumn, type ParsedModel, type ParsedRoute, PathResolver, QueryBuilder, Request, type RequestOptions, Response, type RetryConfig, type RouteDefinition, type RouteManifest, type RouteMapper, type RouteParserSchema, type RouteSchema, type RouteSchemaMap, type RouteSchemaValue, type RouteTransform, type RouteTransformMap, type ServiceConfig, TokenManager, camelCase, camelCaseKeys, snakeCase, snakeCaseKeys };
256
+ export { type ApiDefinition, ApiError, type ApiResponse, type AuthConfig, AuthMiddleware, ErrorHandler, HttpClient, type HttpMethod, Interceptor, type PaginationMeta, type ParsedChannel, type ParsedColumn, type ParsedModel, type ParsedRoute, PathResolver, QueryBuilder, Request, type RequestOptions, Response, type ResponseSchema, type RetryConfig, type RouteDefinition, type RouteManifest, type RouteMapper, type RouteParserSchema, type RouteSchema, type RouteSchemaMap, type RouteSchemaValue, type RouteTransform, type RouteTransformMap, type ServiceConfig, TokenManager, camelCase, camelCaseKeys, snakeCase, snakeCaseKeys };
package/dist/core.js CHANGED
@@ -87,6 +87,7 @@ function snakeCaseKeys(obj) {
87
87
  var import_axios = __toESM(require("axios"));
88
88
  var HttpClient = class {
89
89
  constructor(config) {
90
+ this.config = config;
90
91
  this.client = import_axios.default.create({
91
92
  baseURL: config.baseURL,
92
93
  timeout: config.timeout ?? 1e4,
package/dist/core.mjs CHANGED
@@ -38,6 +38,7 @@ function snakeCaseKeys(obj) {
38
38
  import axios from "axios";
39
39
  var HttpClient = class {
40
40
  constructor(config) {
41
+ this.config = config;
41
42
  this.client = axios.create({
42
43
  baseURL: config.baseURL,
43
44
  timeout: config.timeout ?? 1e4,
package/dist/react.d.mts CHANGED
@@ -24,11 +24,15 @@ interface RouteSchemaMap {
24
24
  }
25
25
  type RouteSchemaValue = RouteTransform | RouteParserSchema;
26
26
  type RouteSchema = RouteSchemaValue | RouteSchemaMap;
27
- interface RouteDefinition {
27
+ interface ResponseSchema<T> {
28
+ parse(input: unknown): T;
29
+ }
30
+ interface RouteDefinition<TResponse = unknown, TParams = unknown, TBody = unknown> {
28
31
  method: HttpMethod;
29
32
  path: string;
30
33
  auth?: boolean;
31
34
  schema?: RouteSchema;
35
+ responseSchema?: ResponseSchema<TResponse>;
32
36
  mapper?: RouteMapper;
33
37
  headers?: Record<string, string>;
34
38
  cache?: unknown;
@@ -36,20 +40,33 @@ interface RouteDefinition {
36
40
  body?: Record<string, any>;
37
41
  params?: Record<string, any>;
38
42
  query?: Record<string, any>;
43
+ _typeResponse?: TResponse;
44
+ _typeParams?: TParams;
45
+ _typeBody?: TBody;
39
46
  }
40
47
 
41
- type CallOptions = {
42
- params?: Record<string, any>;
48
+ type EndpointCallableOptions<TParams, TBody> = (unknown extends TParams ? {} : {
49
+ params: TParams;
50
+ }) & (unknown extends TBody ? {} : {
51
+ body: TBody;
52
+ }) & {
43
53
  query?: Record<string, any>;
44
- body?: Record<string, any>;
45
54
  headers?: Record<string, string>;
46
55
  };
47
- interface EndpointCallable {
48
- <TResponse = unknown>(options?: CallOptions): Promise<TResponse>;
56
+ type OptionalIfEmpty<T> = keyof Omit<T, 'query' | 'headers'> extends never ? [options?: T] : [options: T];
57
+ interface ApiError {
58
+ message: string;
59
+ status?: number;
60
+ errors?: Record<string, string[]>;
61
+ }
62
+ interface EndpointCallable<TResponse = unknown, TParams = unknown, TBody = unknown> {
63
+ (...args: OptionalIfEmpty<EndpointCallableOptions<TParams, TBody>>): Promise<TResponse>;
49
64
  /** Original RouteDefinition — used by useApiQuery / useApiMutation */
50
- $def: RouteDefinition;
65
+ $def: RouteDefinition<TResponse, TParams, TBody>;
51
66
  /** Stable TanStack query key: [group, action] */
52
67
  $key: string[];
68
+ /** Consistent query key builder that incorporates params/query if provided */
69
+ $queryKey: (...args: OptionalIfEmpty<EndpointCallableOptions<TParams, TBody>>) => unknown[];
53
70
  }
54
71
 
55
72
  /**
@@ -59,7 +76,10 @@ interface EndpointCallable {
59
76
  * const { data, isLoading } = useApiQuery(api.produk.list)
60
77
  * const { data } = useApiQuery(api.produk.detail, { params: { id: 10 } })
61
78
  */
62
- declare function useApiQuery<T = any>(endpoint: EndpointCallable, options?: CallOptions, queryOptions?: Omit<UseQueryOptions<T>, 'queryKey' | 'queryFn'>): _tanstack_react_query.UseQueryResult<NoInfer<T>, Error>;
79
+ declare function useApiQuery<TResponse = unknown, TParams = unknown, TBody = unknown, TError = ApiError, TData = TResponse>(endpoint: EndpointCallable<TResponse, TParams, TBody>, ...args: [
80
+ ...OptionalIfEmpty<EndpointCallableOptions<TParams, TBody>>,
81
+ queryOptions?: Omit<UseQueryOptions<TResponse, TError, TData>, 'queryKey' | 'queryFn'>
82
+ ]): _tanstack_react_query.UseQueryResult<NoInfer<TData>, TError>;
63
83
 
64
84
  /**
65
85
  * useApiMutation — accepts an endpoint callable directly.
@@ -74,11 +94,11 @@ declare function useApiQuery<T = any>(endpoint: EndpointCallable, options?: Call
74
94
  * invalidate: [api.cart.list, api.orders.index],
75
95
  * })
76
96
  */
77
- interface ApiMutationOptions<TData, TVariables> extends Omit<UseMutationOptions<TData, unknown, TVariables>, 'mutationFn'> {
97
+ interface ApiMutationOptions<TData, TError, TVariables> extends Omit<UseMutationOptions<TData, TError, TVariables>, 'mutationFn'> {
78
98
  /** Extra endpoints to invalidate on success (in addition to the auto group invalidation). */
79
- invalidate?: EndpointCallable[];
99
+ invalidate?: EndpointCallable<any, any, any>[];
80
100
  }
81
- declare function useApiMutation<TData = any, TVariables extends CallOptions = CallOptions>(endpoint: EndpointCallable, options?: ApiMutationOptions<TData, TVariables>): _tanstack_react_query.UseMutationResult<TData, unknown, TVariables, unknown>;
101
+ declare function useApiMutation<TResponse = unknown, TParams = unknown, TBody = unknown, TError = ApiError>(endpoint: EndpointCallable<TResponse, TParams, TBody>, options?: ApiMutationOptions<TResponse, TError, EndpointCallableOptions<TParams, TBody>>): _tanstack_react_query.UseMutationResult<TResponse, TError, EndpointCallableOptions<TParams, TBody>, unknown>;
82
102
 
83
103
  /**
84
104
  * createHooks — generate typed hooks from an api group.
package/dist/react.d.ts CHANGED
@@ -24,11 +24,15 @@ interface RouteSchemaMap {
24
24
  }
25
25
  type RouteSchemaValue = RouteTransform | RouteParserSchema;
26
26
  type RouteSchema = RouteSchemaValue | RouteSchemaMap;
27
- interface RouteDefinition {
27
+ interface ResponseSchema<T> {
28
+ parse(input: unknown): T;
29
+ }
30
+ interface RouteDefinition<TResponse = unknown, TParams = unknown, TBody = unknown> {
28
31
  method: HttpMethod;
29
32
  path: string;
30
33
  auth?: boolean;
31
34
  schema?: RouteSchema;
35
+ responseSchema?: ResponseSchema<TResponse>;
32
36
  mapper?: RouteMapper;
33
37
  headers?: Record<string, string>;
34
38
  cache?: unknown;
@@ -36,20 +40,33 @@ interface RouteDefinition {
36
40
  body?: Record<string, any>;
37
41
  params?: Record<string, any>;
38
42
  query?: Record<string, any>;
43
+ _typeResponse?: TResponse;
44
+ _typeParams?: TParams;
45
+ _typeBody?: TBody;
39
46
  }
40
47
 
41
- type CallOptions = {
42
- params?: Record<string, any>;
48
+ type EndpointCallableOptions<TParams, TBody> = (unknown extends TParams ? {} : {
49
+ params: TParams;
50
+ }) & (unknown extends TBody ? {} : {
51
+ body: TBody;
52
+ }) & {
43
53
  query?: Record<string, any>;
44
- body?: Record<string, any>;
45
54
  headers?: Record<string, string>;
46
55
  };
47
- interface EndpointCallable {
48
- <TResponse = unknown>(options?: CallOptions): Promise<TResponse>;
56
+ type OptionalIfEmpty<T> = keyof Omit<T, 'query' | 'headers'> extends never ? [options?: T] : [options: T];
57
+ interface ApiError {
58
+ message: string;
59
+ status?: number;
60
+ errors?: Record<string, string[]>;
61
+ }
62
+ interface EndpointCallable<TResponse = unknown, TParams = unknown, TBody = unknown> {
63
+ (...args: OptionalIfEmpty<EndpointCallableOptions<TParams, TBody>>): Promise<TResponse>;
49
64
  /** Original RouteDefinition — used by useApiQuery / useApiMutation */
50
- $def: RouteDefinition;
65
+ $def: RouteDefinition<TResponse, TParams, TBody>;
51
66
  /** Stable TanStack query key: [group, action] */
52
67
  $key: string[];
68
+ /** Consistent query key builder that incorporates params/query if provided */
69
+ $queryKey: (...args: OptionalIfEmpty<EndpointCallableOptions<TParams, TBody>>) => unknown[];
53
70
  }
54
71
 
55
72
  /**
@@ -59,7 +76,10 @@ interface EndpointCallable {
59
76
  * const { data, isLoading } = useApiQuery(api.produk.list)
60
77
  * const { data } = useApiQuery(api.produk.detail, { params: { id: 10 } })
61
78
  */
62
- declare function useApiQuery<T = any>(endpoint: EndpointCallable, options?: CallOptions, queryOptions?: Omit<UseQueryOptions<T>, 'queryKey' | 'queryFn'>): _tanstack_react_query.UseQueryResult<NoInfer<T>, Error>;
79
+ declare function useApiQuery<TResponse = unknown, TParams = unknown, TBody = unknown, TError = ApiError, TData = TResponse>(endpoint: EndpointCallable<TResponse, TParams, TBody>, ...args: [
80
+ ...OptionalIfEmpty<EndpointCallableOptions<TParams, TBody>>,
81
+ queryOptions?: Omit<UseQueryOptions<TResponse, TError, TData>, 'queryKey' | 'queryFn'>
82
+ ]): _tanstack_react_query.UseQueryResult<NoInfer<TData>, TError>;
63
83
 
64
84
  /**
65
85
  * useApiMutation — accepts an endpoint callable directly.
@@ -74,11 +94,11 @@ declare function useApiQuery<T = any>(endpoint: EndpointCallable, options?: Call
74
94
  * invalidate: [api.cart.list, api.orders.index],
75
95
  * })
76
96
  */
77
- interface ApiMutationOptions<TData, TVariables> extends Omit<UseMutationOptions<TData, unknown, TVariables>, 'mutationFn'> {
97
+ interface ApiMutationOptions<TData, TError, TVariables> extends Omit<UseMutationOptions<TData, TError, TVariables>, 'mutationFn'> {
78
98
  /** Extra endpoints to invalidate on success (in addition to the auto group invalidation). */
79
- invalidate?: EndpointCallable[];
99
+ invalidate?: EndpointCallable<any, any, any>[];
80
100
  }
81
- declare function useApiMutation<TData = any, TVariables extends CallOptions = CallOptions>(endpoint: EndpointCallable, options?: ApiMutationOptions<TData, TVariables>): _tanstack_react_query.UseMutationResult<TData, unknown, TVariables, unknown>;
101
+ declare function useApiMutation<TResponse = unknown, TParams = unknown, TBody = unknown, TError = ApiError>(endpoint: EndpointCallable<TResponse, TParams, TBody>, options?: ApiMutationOptions<TResponse, TError, EndpointCallableOptions<TParams, TBody>>): _tanstack_react_query.UseMutationResult<TResponse, TError, EndpointCallableOptions<TParams, TBody>, unknown>;
82
102
 
83
103
  /**
84
104
  * createHooks — generate typed hooks from an api group.
package/dist/react.js CHANGED
@@ -28,8 +28,10 @@ module.exports = __toCommonJS(src_exports);
28
28
 
29
29
  // packages/react/src/hooks/useQuery.ts
30
30
  var import_react_query = require("@tanstack/react-query");
31
- function useApiQuery(endpoint, options, queryOptions) {
32
- const queryKey = options ? [...endpoint.$key, options] : endpoint.$key;
31
+ function useApiQuery(endpoint, ...args) {
32
+ const options = args[0];
33
+ const queryOptions = args[1];
34
+ const queryKey = endpoint.$queryKey(options);
33
35
  return (0, import_react_query.useQuery)({
34
36
  queryKey,
35
37
  queryFn: () => endpoint(options),
package/dist/react.mjs CHANGED
@@ -1,7 +1,9 @@
1
1
  // packages/react/src/hooks/useQuery.ts
2
2
  import { useQuery } from "@tanstack/react-query";
3
- function useApiQuery(endpoint, options, queryOptions) {
4
- const queryKey = options ? [...endpoint.$key, options] : endpoint.$key;
3
+ function useApiQuery(endpoint, ...args) {
4
+ const options = args[0];
5
+ const queryOptions = args[1];
6
+ const queryKey = endpoint.$queryKey(options);
5
7
  return useQuery({
6
8
  queryKey,
7
9
  queryFn: () => endpoint(options),
package/dist/sdk.d.mts CHANGED
@@ -7,6 +7,14 @@ interface ServiceConfig {
7
7
  timeout?: number;
8
8
  retry?: RetryConfig;
9
9
  cache?: boolean;
10
+ validateResponse?: boolean;
11
+ onValidationError?: (error: unknown, context: {
12
+ endpoint: string;
13
+ method: string;
14
+ path: string;
15
+ request: unknown;
16
+ response: unknown;
17
+ }) => void;
10
18
  }
11
19
  interface RetryConfig {
12
20
  attempts: number;
@@ -16,6 +24,7 @@ interface RetryConfig {
16
24
 
17
25
  declare class HttpClient {
18
26
  private client;
27
+ readonly config: ServiceConfig;
19
28
  constructor(config: ServiceConfig);
20
29
  private setupInterceptors;
21
30
  setToken(token: string): void;
@@ -56,11 +65,15 @@ interface RouteSchemaMap {
56
65
  }
57
66
  type RouteSchemaValue = RouteTransform | RouteParserSchema;
58
67
  type RouteSchema = RouteSchemaValue | RouteSchemaMap;
59
- interface RouteDefinition {
68
+ interface ResponseSchema<T> {
69
+ parse(input: unknown): T;
70
+ }
71
+ interface RouteDefinition<TResponse = unknown, TParams = unknown, TBody = unknown> {
60
72
  method: HttpMethod;
61
73
  path: string;
62
74
  auth?: boolean;
63
75
  schema?: RouteSchema;
76
+ responseSchema?: ResponseSchema<TResponse>;
64
77
  mapper?: RouteMapper;
65
78
  headers?: Record<string, string>;
66
79
  cache?: unknown;
@@ -68,10 +81,13 @@ interface RouteDefinition {
68
81
  body?: Record<string, any>;
69
82
  params?: Record<string, any>;
70
83
  query?: Record<string, any>;
84
+ _typeResponse?: TResponse;
85
+ _typeParams?: TParams;
86
+ _typeBody?: TBody;
71
87
  }
72
88
  interface ApiDefinition {
73
89
  [group: string]: {
74
- [action: string]: RouteDefinition;
90
+ [action: string]: RouteDefinition<any, any, any>;
75
91
  };
76
92
  }
77
93
 
@@ -99,21 +115,37 @@ declare class TokenManager {
99
115
  exists(): boolean;
100
116
  }
101
117
 
102
- type CallOptions = {
103
- params?: Record<string, any>;
118
+ type CallOptions<TParams = unknown, TBody = unknown> = {
119
+ params?: TParams;
104
120
  query?: Record<string, any>;
105
- body?: Record<string, any>;
121
+ body?: TBody;
106
122
  headers?: Record<string, string>;
107
123
  };
108
- interface EndpointCallable {
109
- <TResponse = unknown>(options?: CallOptions): Promise<TResponse>;
124
+ type EndpointCallableOptions<TParams, TBody> = (unknown extends TParams ? {} : {
125
+ params: TParams;
126
+ }) & (unknown extends TBody ? {} : {
127
+ body: TBody;
128
+ }) & {
129
+ query?: Record<string, any>;
130
+ headers?: Record<string, string>;
131
+ };
132
+ type OptionalIfEmpty<T> = keyof Omit<T, 'query' | 'headers'> extends never ? [options?: T] : [options: T];
133
+ interface ApiError {
134
+ message: string;
135
+ status?: number;
136
+ errors?: Record<string, string[]>;
137
+ }
138
+ interface EndpointCallable<TResponse = unknown, TParams = unknown, TBody = unknown> {
139
+ (...args: OptionalIfEmpty<EndpointCallableOptions<TParams, TBody>>): Promise<TResponse>;
110
140
  /** Original RouteDefinition — used by useApiQuery / useApiMutation */
111
- $def: RouteDefinition;
141
+ $def: RouteDefinition<TResponse, TParams, TBody>;
112
142
  /** Stable TanStack query key: [group, action] */
113
143
  $key: string[];
144
+ /** Consistent query key builder that incorporates params/query if provided */
145
+ $queryKey: (...args: OptionalIfEmpty<EndpointCallableOptions<TParams, TBody>>) => unknown[];
114
146
  }
115
- type ApiGroupProxy<G extends Record<string, RouteDefinition>> = {
116
- [K in keyof G]: EndpointCallable;
147
+ type ApiGroupProxy<G extends Record<string, RouteDefinition<any, any, any>>> = {
148
+ [K in keyof G]: EndpointCallable<G[K] extends RouteDefinition<infer R, any, any> ? R : unknown, G[K] extends RouteDefinition<any, infer P, any> ? P : unknown, G[K] extends RouteDefinition<any, any, infer B> ? B : unknown>;
117
149
  };
118
150
  type ApiProxy<T extends ApiDefinition> = {
119
151
  [G in keyof T]: ApiGroupProxy<T[G]>;
@@ -139,7 +171,7 @@ type EndpointDefinition$1 = RouteDefinition;
139
171
  * const { data } = useApiQuery(api.cart.list)
140
172
  * const mutation = useApiMutation(api.cart.create)
141
173
  */
142
- declare function endpoint(def: RouteDefinition): RouteDefinition;
174
+ declare function endpoint<TResponse = unknown, TParams = unknown, TBody = unknown>(def: RouteDefinition<TResponse, TParams, TBody>): RouteDefinition<TResponse, TParams, TBody>;
143
175
 
144
176
  type EndpointDefinition = RouteDefinition;
145
177
  interface ResourceConfig<TEndpoints extends Record<string, EndpointDefinition>> {
@@ -271,4 +303,4 @@ declare function mapKeysDeep<T>(value: T, keyCase: KeyCase): T;
271
303
  declare function toCamelCase<T>(value: T): T;
272
304
  declare function toSnakeCase<T>(value: T): T;
273
305
 
274
- export { type ApiDefinition, type ApiResponse, type CallOptions, type CamelCasedPropertiesDeep, type CamelToSnake, type EndpointCallable, type EndpointDefinition$1 as EndpointDefinition, GenericService, type GenericServiceOptions, type HttpMethod, type Id, type KeyCase, type ParseResult, type ParserSchema, type QueryParams, type ResourceConfig, type ResourceDefinition, type RouteDefinition, type RouteMapper, type RouteParserSchema, type RouteSchema, type RouteSchemaMap, type RouteSchemaValue, type RouteTransform, type RouteTransformMap, type SchemaLike, type ServiceConfig, type SnakeCasedPropertiesDeep, type SnakeToCamel, type UnknownRecord, camelToSnakeKey, createClient$1 as createClient, createClient as createHttpClient, createService, defineApi, endpoint, generateHooks, mapKeysDeep, parseWithSchema, resource, snakeToCamelKey, toCamelCase, toSnakeCase };
306
+ export { type ApiDefinition, type ApiError, type ApiResponse, type CallOptions, type CamelCasedPropertiesDeep, type CamelToSnake, type EndpointCallable, type EndpointCallableOptions, type EndpointDefinition$1 as EndpointDefinition, GenericService, type GenericServiceOptions, type HttpMethod, type Id, type KeyCase, type OptionalIfEmpty, type ParseResult, type ParserSchema, type QueryParams, type ResourceConfig, type ResourceDefinition, type RouteDefinition, type RouteMapper, type RouteParserSchema, type RouteSchema, type RouteSchemaMap, type RouteSchemaValue, type RouteTransform, type RouteTransformMap, type SchemaLike, type ServiceConfig, type SnakeCasedPropertiesDeep, type SnakeToCamel, type UnknownRecord, camelToSnakeKey, createClient$1 as createClient, createClient as createHttpClient, createService, defineApi, endpoint, generateHooks, mapKeysDeep, parseWithSchema, resource, snakeToCamelKey, toCamelCase, toSnakeCase };
package/dist/sdk.d.ts CHANGED
@@ -7,6 +7,14 @@ interface ServiceConfig {
7
7
  timeout?: number;
8
8
  retry?: RetryConfig;
9
9
  cache?: boolean;
10
+ validateResponse?: boolean;
11
+ onValidationError?: (error: unknown, context: {
12
+ endpoint: string;
13
+ method: string;
14
+ path: string;
15
+ request: unknown;
16
+ response: unknown;
17
+ }) => void;
10
18
  }
11
19
  interface RetryConfig {
12
20
  attempts: number;
@@ -16,6 +24,7 @@ interface RetryConfig {
16
24
 
17
25
  declare class HttpClient {
18
26
  private client;
27
+ readonly config: ServiceConfig;
19
28
  constructor(config: ServiceConfig);
20
29
  private setupInterceptors;
21
30
  setToken(token: string): void;
@@ -56,11 +65,15 @@ interface RouteSchemaMap {
56
65
  }
57
66
  type RouteSchemaValue = RouteTransform | RouteParserSchema;
58
67
  type RouteSchema = RouteSchemaValue | RouteSchemaMap;
59
- interface RouteDefinition {
68
+ interface ResponseSchema<T> {
69
+ parse(input: unknown): T;
70
+ }
71
+ interface RouteDefinition<TResponse = unknown, TParams = unknown, TBody = unknown> {
60
72
  method: HttpMethod;
61
73
  path: string;
62
74
  auth?: boolean;
63
75
  schema?: RouteSchema;
76
+ responseSchema?: ResponseSchema<TResponse>;
64
77
  mapper?: RouteMapper;
65
78
  headers?: Record<string, string>;
66
79
  cache?: unknown;
@@ -68,10 +81,13 @@ interface RouteDefinition {
68
81
  body?: Record<string, any>;
69
82
  params?: Record<string, any>;
70
83
  query?: Record<string, any>;
84
+ _typeResponse?: TResponse;
85
+ _typeParams?: TParams;
86
+ _typeBody?: TBody;
71
87
  }
72
88
  interface ApiDefinition {
73
89
  [group: string]: {
74
- [action: string]: RouteDefinition;
90
+ [action: string]: RouteDefinition<any, any, any>;
75
91
  };
76
92
  }
77
93
 
@@ -99,21 +115,37 @@ declare class TokenManager {
99
115
  exists(): boolean;
100
116
  }
101
117
 
102
- type CallOptions = {
103
- params?: Record<string, any>;
118
+ type CallOptions<TParams = unknown, TBody = unknown> = {
119
+ params?: TParams;
104
120
  query?: Record<string, any>;
105
- body?: Record<string, any>;
121
+ body?: TBody;
106
122
  headers?: Record<string, string>;
107
123
  };
108
- interface EndpointCallable {
109
- <TResponse = unknown>(options?: CallOptions): Promise<TResponse>;
124
+ type EndpointCallableOptions<TParams, TBody> = (unknown extends TParams ? {} : {
125
+ params: TParams;
126
+ }) & (unknown extends TBody ? {} : {
127
+ body: TBody;
128
+ }) & {
129
+ query?: Record<string, any>;
130
+ headers?: Record<string, string>;
131
+ };
132
+ type OptionalIfEmpty<T> = keyof Omit<T, 'query' | 'headers'> extends never ? [options?: T] : [options: T];
133
+ interface ApiError {
134
+ message: string;
135
+ status?: number;
136
+ errors?: Record<string, string[]>;
137
+ }
138
+ interface EndpointCallable<TResponse = unknown, TParams = unknown, TBody = unknown> {
139
+ (...args: OptionalIfEmpty<EndpointCallableOptions<TParams, TBody>>): Promise<TResponse>;
110
140
  /** Original RouteDefinition — used by useApiQuery / useApiMutation */
111
- $def: RouteDefinition;
141
+ $def: RouteDefinition<TResponse, TParams, TBody>;
112
142
  /** Stable TanStack query key: [group, action] */
113
143
  $key: string[];
144
+ /** Consistent query key builder that incorporates params/query if provided */
145
+ $queryKey: (...args: OptionalIfEmpty<EndpointCallableOptions<TParams, TBody>>) => unknown[];
114
146
  }
115
- type ApiGroupProxy<G extends Record<string, RouteDefinition>> = {
116
- [K in keyof G]: EndpointCallable;
147
+ type ApiGroupProxy<G extends Record<string, RouteDefinition<any, any, any>>> = {
148
+ [K in keyof G]: EndpointCallable<G[K] extends RouteDefinition<infer R, any, any> ? R : unknown, G[K] extends RouteDefinition<any, infer P, any> ? P : unknown, G[K] extends RouteDefinition<any, any, infer B> ? B : unknown>;
117
149
  };
118
150
  type ApiProxy<T extends ApiDefinition> = {
119
151
  [G in keyof T]: ApiGroupProxy<T[G]>;
@@ -139,7 +171,7 @@ type EndpointDefinition$1 = RouteDefinition;
139
171
  * const { data } = useApiQuery(api.cart.list)
140
172
  * const mutation = useApiMutation(api.cart.create)
141
173
  */
142
- declare function endpoint(def: RouteDefinition): RouteDefinition;
174
+ declare function endpoint<TResponse = unknown, TParams = unknown, TBody = unknown>(def: RouteDefinition<TResponse, TParams, TBody>): RouteDefinition<TResponse, TParams, TBody>;
143
175
 
144
176
  type EndpointDefinition = RouteDefinition;
145
177
  interface ResourceConfig<TEndpoints extends Record<string, EndpointDefinition>> {
@@ -271,4 +303,4 @@ declare function mapKeysDeep<T>(value: T, keyCase: KeyCase): T;
271
303
  declare function toCamelCase<T>(value: T): T;
272
304
  declare function toSnakeCase<T>(value: T): T;
273
305
 
274
- export { type ApiDefinition, type ApiResponse, type CallOptions, type CamelCasedPropertiesDeep, type CamelToSnake, type EndpointCallable, type EndpointDefinition$1 as EndpointDefinition, GenericService, type GenericServiceOptions, type HttpMethod, type Id, type KeyCase, type ParseResult, type ParserSchema, type QueryParams, type ResourceConfig, type ResourceDefinition, type RouteDefinition, type RouteMapper, type RouteParserSchema, type RouteSchema, type RouteSchemaMap, type RouteSchemaValue, type RouteTransform, type RouteTransformMap, type SchemaLike, type ServiceConfig, type SnakeCasedPropertiesDeep, type SnakeToCamel, type UnknownRecord, camelToSnakeKey, createClient$1 as createClient, createClient as createHttpClient, createService, defineApi, endpoint, generateHooks, mapKeysDeep, parseWithSchema, resource, snakeToCamelKey, toCamelCase, toSnakeCase };
306
+ export { type ApiDefinition, type ApiError, type ApiResponse, type CallOptions, type CamelCasedPropertiesDeep, type CamelToSnake, type EndpointCallable, type EndpointCallableOptions, type EndpointDefinition$1 as EndpointDefinition, GenericService, type GenericServiceOptions, type HttpMethod, type Id, type KeyCase, type OptionalIfEmpty, type ParseResult, type ParserSchema, type QueryParams, type ResourceConfig, type ResourceDefinition, type RouteDefinition, type RouteMapper, type RouteParserSchema, type RouteSchema, type RouteSchemaMap, type RouteSchemaValue, type RouteTransform, type RouteTransformMap, type SchemaLike, type ServiceConfig, type SnakeCasedPropertiesDeep, type SnakeToCamel, type UnknownRecord, camelToSnakeKey, createClient$1 as createClient, createClient as createHttpClient, createService, defineApi, endpoint, generateHooks, mapKeysDeep, parseWithSchema, resource, snakeToCamelKey, toCamelCase, toSnakeCase };
package/dist/sdk.js CHANGED
@@ -87,6 +87,7 @@ function snakeCaseKeys(obj) {
87
87
  var import_axios = __toESM(require("axios"));
88
88
  var HttpClient = class {
89
89
  constructor(config) {
90
+ this.config = config;
90
91
  this.client = import_axios.default.create({
91
92
  baseURL: config.baseURL,
92
93
  timeout: config.timeout ?? 1e4,
@@ -344,14 +345,35 @@ function defineApi(definition, config) {
344
345
  } else {
345
346
  response = await client[method](resolvedPath, body, requestConfig);
346
347
  }
348
+ if (client.config.validateResponse && route.responseSchema) {
349
+ try {
350
+ response = route.responseSchema.parse(response);
351
+ } catch (error) {
352
+ if (client.config.onValidationError) {
353
+ client.config.onValidationError(error, {
354
+ endpoint: action,
355
+ method: route.method,
356
+ path: resolvedPath,
357
+ request: { params, query, body, headers: requestConfig.headers },
358
+ response
359
+ });
360
+ }
361
+ throw error;
362
+ }
363
+ } else if (client.config.validateResponse) {
364
+ response = parseRouteSchema(route, "response", response);
365
+ }
347
366
  return applyMapper(
348
367
  route,
349
368
  "response",
350
- parseRouteSchema(route, "response", response)
369
+ response
351
370
  );
352
371
  };
353
372
  callable.$def = route;
354
373
  callable.$key = [group, action];
374
+ callable.$queryKey = (options) => {
375
+ return options ? [group, action, options] : [group, action];
376
+ };
355
377
  groupProxy[action] = callable;
356
378
  }
357
379
  ;