@ryneex/api-client 0.0.85 → 1.0.0-beta.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.
- package/README.md +369 -7
- package/index.d.ts +37 -40
- package/index.js +81 -87
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,15 +1,377 @@
|
|
|
1
|
-
# api-client
|
|
1
|
+
# @ryneex/api-client
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Type-safe API client built on **Axios**, **Zod**, and **TanStack Query**. Define endpoints with request/response validation and get ready-to-use `call`, `queryOptions`, and `mutationOptions` for React Query.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
4
8
|
|
|
5
9
|
```bash
|
|
6
|
-
bun
|
|
10
|
+
bun add @ryneex/api-client axios zod @tanstack/react-query
|
|
11
|
+
# or
|
|
12
|
+
npm i @ryneex/api-client axios zod @tanstack/react-query
|
|
7
13
|
```
|
|
8
14
|
|
|
9
|
-
|
|
15
|
+
**Peer dependencies:** `axios` ^1.13.2, `zod` ^4, `@tanstack/react-query` ^5, `typescript` ^5.
|
|
10
16
|
|
|
11
|
-
|
|
12
|
-
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Quick start
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import axios from "axios";
|
|
23
|
+
import z from "zod";
|
|
24
|
+
import { createClient } from "@ryneex/api-client";
|
|
25
|
+
|
|
26
|
+
const axiosInstance = axios.create({
|
|
27
|
+
baseURL: "https://api.example.com",
|
|
28
|
+
headers: { "Content-Type": "application/json" },
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const client = createClient(axiosInstance);
|
|
32
|
+
|
|
33
|
+
// Define a GET endpoint with validated response
|
|
34
|
+
const getUsers = client.create({
|
|
35
|
+
method: "GET",
|
|
36
|
+
path: "/users",
|
|
37
|
+
outputSchema: z.object({
|
|
38
|
+
users: z.array(
|
|
39
|
+
z.object({ id: z.string(), name: z.string(), email: z.string() }),
|
|
40
|
+
),
|
|
41
|
+
}),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Call it (returns AxiosResponse<inferred output type>)
|
|
45
|
+
const res = await getUsers();
|
|
46
|
+
console.log(res.data.users);
|
|
47
|
+
|
|
48
|
+
// Or use with React Query
|
|
49
|
+
import { useQuery } from "@tanstack/react-query";
|
|
50
|
+
const { data } = useQuery(getUsers.queryOptions());
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Creating the client
|
|
56
|
+
|
|
57
|
+
Use any **Axios instance** (with base URL, auth, interceptors, etc.) with `createClient`:
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import axios from "axios";
|
|
61
|
+
import { createClient } from "@ryneex/api-client";
|
|
62
|
+
|
|
63
|
+
const axiosInstance = axios.create({
|
|
64
|
+
baseURL: "https://api.example.com/v1",
|
|
65
|
+
timeout: 10_000,
|
|
66
|
+
headers: {
|
|
67
|
+
"Content-Type": "application/json",
|
|
68
|
+
Authorization: `Bearer ${process.env.API_TOKEN}`,
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const client = createClient(axiosInstance);
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Defining endpoints: `client.create`
|
|
78
|
+
|
|
79
|
+
`client.create` takes:
|
|
80
|
+
|
|
81
|
+
| Option | Type | Required | Description |
|
|
82
|
+
| ----------------- | --------------------------------------- | -------- | -------------------------------------------------- |
|
|
83
|
+
| `method` | `GET \| POST \| PUT \| PATCH \| DELETE` | Yes | HTTP method. |
|
|
84
|
+
| `path` | `string \| (data) => string` | Yes | URL path (static or derived from input/variables). |
|
|
85
|
+
| `outputSchema` | `z.ZodType` | Yes | Zod schema for response body; type is inferred. |
|
|
86
|
+
| `inputSchema` | `z.ZodType` | No | Schema for `data.input` (e.g. body for POST). |
|
|
87
|
+
| `variablesSchema` | `z.ZodType` | No | Schema for `data.variables` (e.g. path/query). |
|
|
88
|
+
| `axiosOptions` | `(data) => AxiosRequestConfig` | No | Extra Axios config (headers, params, data, etc.). |
|
|
89
|
+
| `transform` | `(data, payload) => TOutput` | No | Optional post-processing of parsed response data. |
|
|
90
|
+
|
|
91
|
+
The returned endpoint is a **callable function** with helpers:
|
|
92
|
+
|
|
93
|
+
- **Direct call** — call `await endpoint(payload?)` to perform the request; returns `Promise<AxiosResponse<TOutput>>`.
|
|
94
|
+
- **`queryOptions(opts?)`** — `UseQueryOptions` for `useQuery`.
|
|
95
|
+
- **`mutationOptions(opts?)`** — `UseMutationOptions` for `useMutation`.
|
|
96
|
+
- **`config`** — `{ method, path, outputSchema, inputSchema?, variablesSchema? }`.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Example: GET with no input
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
const getProducts = client.create({
|
|
104
|
+
method: "GET",
|
|
105
|
+
path: "/products",
|
|
106
|
+
outputSchema: z.object({
|
|
107
|
+
products: z.array(
|
|
108
|
+
z.object({
|
|
109
|
+
id: z.string(),
|
|
110
|
+
title: z.string(),
|
|
111
|
+
price: z.number(),
|
|
112
|
+
}),
|
|
113
|
+
),
|
|
114
|
+
}),
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// Direct call
|
|
118
|
+
const { data } = await getProducts();
|
|
119
|
+
// data is { products: { id, title, price }[] }
|
|
120
|
+
|
|
121
|
+
// React Query
|
|
122
|
+
const { data } = useQuery(getProducts.queryOptions());
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Example: GET with variables (path/query)
|
|
128
|
+
|
|
129
|
+
Use `variablesSchema` and a **path function** when the URL or query depends on parameters:
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
const getUserById = client.create({
|
|
133
|
+
method: "GET",
|
|
134
|
+
path: (data) => `/users/${data.variables.userId}`,
|
|
135
|
+
variablesSchema: z.object({ userId: z.string() }),
|
|
136
|
+
outputSchema: z.object({
|
|
137
|
+
id: z.string(),
|
|
138
|
+
name: z.string(),
|
|
139
|
+
email: z.string(),
|
|
140
|
+
}),
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// Direct call — pass { variables: { userId: "123" } }
|
|
144
|
+
const { data } = await getUserById({ variables: { userId: "123" } });
|
|
145
|
+
|
|
146
|
+
// React Query — must pass data so queryKey and queryFn get userId
|
|
147
|
+
const { data } = useQuery(
|
|
148
|
+
getUserById.queryOptions({
|
|
149
|
+
data: { variables: { userId: "123" } },
|
|
150
|
+
staleTime: 60_000,
|
|
151
|
+
}),
|
|
152
|
+
);
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Example: POST with request body (input)
|
|
158
|
+
|
|
159
|
+
Use `inputSchema` for the body and optionally `axiosOptions` to pass it to Axios:
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
const createUser = client.create({
|
|
163
|
+
method: "POST",
|
|
164
|
+
path: "/users",
|
|
165
|
+
inputSchema: z.object({
|
|
166
|
+
name: z.string().min(1),
|
|
167
|
+
email: z.string().email(),
|
|
168
|
+
}),
|
|
169
|
+
outputSchema: z.object({
|
|
170
|
+
id: z.string(),
|
|
171
|
+
name: z.string(),
|
|
172
|
+
email: z.string(),
|
|
173
|
+
}),
|
|
174
|
+
axiosOptions: (data) => ({
|
|
175
|
+
data: data.input,
|
|
176
|
+
}),
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// Direct call
|
|
180
|
+
const { data } = await createUser({
|
|
181
|
+
input: { name: "Jane", email: "jane@example.com" },
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// React Query mutation
|
|
185
|
+
const mutation = useMutation(
|
|
186
|
+
createUser.mutationOptions({
|
|
187
|
+
onSuccess: (user) => console.log("Created", user),
|
|
188
|
+
}),
|
|
189
|
+
);
|
|
190
|
+
mutation.mutate({ input: { name: "Jane", email: "jane@example.com" } });
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## Example: Dynamic path and query params
|
|
196
|
+
|
|
197
|
+
Combine variables with a path function and `axiosOptions` for query params:
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
const listUsers = client.create({
|
|
201
|
+
method: "GET",
|
|
202
|
+
path: "/users",
|
|
203
|
+
variablesSchema: z.object({
|
|
204
|
+
page: z.number().optional(),
|
|
205
|
+
limit: z.number().optional(),
|
|
206
|
+
}),
|
|
207
|
+
outputSchema: z.object({
|
|
208
|
+
users: z.array(z.object({ id: z.string(), name: z.string() })),
|
|
209
|
+
total: z.number(),
|
|
210
|
+
}),
|
|
211
|
+
axiosOptions: (data) => ({
|
|
212
|
+
params: data.variables,
|
|
213
|
+
}),
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
const { data } = await listUsers({
|
|
217
|
+
variables: { page: 1, limit: 10 },
|
|
218
|
+
});
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## Example: PUT / PATCH / DELETE
|
|
224
|
+
|
|
225
|
+
Same pattern: use `inputSchema` for body and `axiosOptions` to pass it.
|
|
226
|
+
|
|
227
|
+
```ts
|
|
228
|
+
const updateUser = client.create({
|
|
229
|
+
method: "PATCH",
|
|
230
|
+
path: (data) => `/users/${data.variables.userId}`,
|
|
231
|
+
variablesSchema: z.object({ userId: z.string() }),
|
|
232
|
+
inputSchema: z.object({
|
|
233
|
+
name: z.string().optional(),
|
|
234
|
+
email: z.string().email().optional(),
|
|
235
|
+
}),
|
|
236
|
+
outputSchema: z.object({
|
|
237
|
+
id: z.string(),
|
|
238
|
+
name: z.string(),
|
|
239
|
+
email: z.string(),
|
|
240
|
+
}),
|
|
241
|
+
axiosOptions: (data) => ({
|
|
242
|
+
data: data.input,
|
|
243
|
+
}),
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
await updateUser({
|
|
247
|
+
variables: { userId: "123" },
|
|
248
|
+
input: { name: "New Name" },
|
|
249
|
+
});
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## Using with React Query
|
|
255
|
+
|
|
256
|
+
### Queries (GET)
|
|
257
|
+
|
|
258
|
+
- Use **`queryOptions({ data?, ...useQueryOptions })`**.
|
|
259
|
+
- If the endpoint has **input or variables**, pass **`data`** so both `queryKey` and `queryFn` receive it.
|
|
260
|
+
- You can pass any `useQuery` options (`staleTime`, `enabled`, etc.) and optional **`onSuccess`** / **`onError`** with the same payload shape.
|
|
261
|
+
|
|
262
|
+
```ts
|
|
263
|
+
// No input/variables
|
|
264
|
+
useQuery(getProducts.queryOptions({ staleTime: 60_000 }));
|
|
265
|
+
|
|
266
|
+
// With variables (e.g. GET by id)
|
|
267
|
+
useQuery(
|
|
268
|
+
getUserById.queryOptions({
|
|
269
|
+
data: { variables: { userId: "123" } },
|
|
270
|
+
enabled: !!userId,
|
|
271
|
+
onSuccess: (user) => {},
|
|
272
|
+
onError: (err, { variables }) => {},
|
|
273
|
+
}),
|
|
274
|
+
);
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Mutations (POST / PUT / PATCH / DELETE)
|
|
278
|
+
|
|
279
|
+
- Use **`mutationOptions(options?)`** with `useMutation`.
|
|
280
|
+
- **`mutation.mutate(data)`** must match the endpoint’s `input`/`variables` shape.
|
|
281
|
+
|
|
282
|
+
```ts
|
|
283
|
+
const mutation = useMutation(
|
|
284
|
+
createUser.mutationOptions({
|
|
285
|
+
onSuccess: (user) => {},
|
|
286
|
+
onError: (error, variables) => {},
|
|
287
|
+
}),
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
mutation.mutate({
|
|
291
|
+
input: { name: "Jane", email: "jane@example.com" },
|
|
292
|
+
});
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
## Validation and errors
|
|
298
|
+
|
|
299
|
+
- **Input** and **variables** are validated with Zod before the request; invalid data throws **`ZodError`**.
|
|
300
|
+
- **Response** is parsed with `outputSchema` after the request; invalid response throws **`ZodError`**.
|
|
301
|
+
- Network or server errors are **AxiosError**.
|
|
302
|
+
|
|
303
|
+
So the callable and React Query helpers can throw **`ZodError | AxiosError`**. Handle both in `onError` or in try/catch:
|
|
304
|
+
|
|
305
|
+
```ts
|
|
306
|
+
import { AxiosError } from "axios";
|
|
307
|
+
import { ZodError } from "zod";
|
|
308
|
+
|
|
309
|
+
const { data, error } = useQuery(
|
|
310
|
+
getUsers.queryOptions({
|
|
311
|
+
onError: (err) => {
|
|
312
|
+
if (err instanceof ZodError) {
|
|
313
|
+
console.error("Validation failed", err.flatten());
|
|
314
|
+
} else if (err instanceof AxiosError) {
|
|
315
|
+
console.error("Request failed", err.response?.status);
|
|
316
|
+
}
|
|
317
|
+
},
|
|
318
|
+
}),
|
|
319
|
+
);
|
|
13
320
|
```
|
|
14
321
|
|
|
15
|
-
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
## Full example: small API module
|
|
325
|
+
|
|
326
|
+
```ts
|
|
327
|
+
import axios from "axios";
|
|
328
|
+
import z from "zod";
|
|
329
|
+
import { createClient } from "@ryneex/api-client";
|
|
330
|
+
|
|
331
|
+
const axiosInstance = axios.create({
|
|
332
|
+
baseURL: "https://api.example.com",
|
|
333
|
+
headers: { "Content-Type": "application/json" },
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
const client = createClient(axiosInstance);
|
|
337
|
+
|
|
338
|
+
// Schemas
|
|
339
|
+
const userSchema = z.object({
|
|
340
|
+
id: z.string(),
|
|
341
|
+
name: z.string(),
|
|
342
|
+
email: z.string(),
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
// GET /users
|
|
346
|
+
export const getUsers = client.create({
|
|
347
|
+
method: "GET",
|
|
348
|
+
path: "/users",
|
|
349
|
+
outputSchema: z.object({ users: z.array(userSchema) }),
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
// GET /users/:id
|
|
353
|
+
export const getUser = client.create({
|
|
354
|
+
method: "GET",
|
|
355
|
+
path: (d) => `/users/${d.variables.id}`,
|
|
356
|
+
variablesSchema: z.object({ id: z.string() }),
|
|
357
|
+
outputSchema: userSchema,
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
// POST /users
|
|
361
|
+
export const createUser = client.create({
|
|
362
|
+
method: "POST",
|
|
363
|
+
path: "/users",
|
|
364
|
+
inputSchema: z.object({
|
|
365
|
+
name: z.string(),
|
|
366
|
+
email: z.string().email(),
|
|
367
|
+
}),
|
|
368
|
+
outputSchema: userSchema,
|
|
369
|
+
axiosOptions: (d) => ({ data: d.input }),
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
// Usage in a component
|
|
373
|
+
// const { data } = useQuery(getUsers.queryOptions());
|
|
374
|
+
// const { data } = useQuery(getUser.queryOptions({ data: { variables: { id: "1" } } }));
|
|
375
|
+
// const mutation = useMutation(createUser.mutationOptions());
|
|
376
|
+
// mutation.mutate({ input: { name: "Jane", email: "jane@example.com" } });
|
|
377
|
+
```
|
package/index.d.ts
CHANGED
|
@@ -1,52 +1,49 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import * as axios from 'axios';
|
|
2
|
+
import { AxiosRequestConfig, AxiosError, AxiosInstance } from 'axios';
|
|
3
3
|
import z, { ZodError } from 'zod';
|
|
4
|
+
import { UseQueryOptions, UseMutationOptions } from '@tanstack/react-query';
|
|
5
|
+
|
|
6
|
+
type ClientPayload<TInput, TVariables> = {} & (unknown extends TInput ? unknown : {
|
|
7
|
+
input: TInput;
|
|
8
|
+
}) & (unknown extends TVariables ? unknown : {
|
|
9
|
+
variables: TVariables;
|
|
10
|
+
});
|
|
11
|
+
type OptionalPayload<T> = object extends T ? void : T;
|
|
12
|
+
type ClientOptions<TOutputSchema extends z.ZodType, TInputSchema extends z.ZodType, TVariablesSchema extends z.ZodType, TOutput = z.infer<TOutputSchema>, TInput = z.infer<TInputSchema>, TVariables = z.infer<TVariablesSchema>> = {
|
|
13
|
+
method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
14
|
+
path: string | ((payload: ClientPayload<TInput, TVariables>) => string);
|
|
15
|
+
axiosOptions?: (payload: ClientPayload<TInput, TVariables>) => AxiosRequestConfig;
|
|
16
|
+
variablesSchema?: TVariablesSchema;
|
|
17
|
+
inputSchema?: TInputSchema;
|
|
18
|
+
outputSchema: TOutputSchema;
|
|
19
|
+
transform?: (data: z.infer<TOutputSchema>, payload: ClientPayload<TInput, TVariables>) => TOutput;
|
|
20
|
+
};
|
|
4
21
|
|
|
5
22
|
type ReactQueryOptions<TOutput, TInput, TVariables> = Omit<UseQueryOptions<TOutput, ZodError<TOutput> | AxiosError>, "queryFn" | "queryKey"> & {
|
|
6
23
|
queryKey?: unknown[];
|
|
7
|
-
} & (object extends
|
|
8
|
-
data
|
|
9
|
-
} : {
|
|
10
|
-
data: TData<TInput, TVariables>;
|
|
24
|
+
} & (object extends ClientPayload<TInput, TVariables> ? unknown : {
|
|
25
|
+
data: ClientPayload<TInput, TVariables>;
|
|
11
26
|
}) & {
|
|
12
|
-
onSuccess?: (data: TOutput, payload:
|
|
13
|
-
onError?: (error: ZodError<TOutput> | AxiosError, payload:
|
|
14
|
-
};
|
|
15
|
-
type ReactMutationOptions<TOutput, TInput, TVariables> = Omit<UseMutationOptions<TOutput, ZodError<TOutput> | AxiosError, OptionalTData<TInput, TVariables>>, "mutationFn" | "mutationKey"> & {
|
|
16
|
-
mutationKey?: unknown[];
|
|
27
|
+
onSuccess?: (data: TOutput, payload: ClientPayload<TInput, TVariables>) => void;
|
|
28
|
+
onError?: (error: ZodError<TOutput> | AxiosError, payload: ClientPayload<TInput, TVariables>) => void;
|
|
17
29
|
};
|
|
18
|
-
type
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
} : {
|
|
25
|
-
variables: TVariables;
|
|
26
|
-
});
|
|
27
|
-
type OptionalTData<TInput, TVariables> = object extends TData<TInput, TVariables> ? void : TData<TInput, TVariables>;
|
|
28
|
-
declare class BaseApiClient {
|
|
29
|
-
readonly axios: AxiosInstance;
|
|
30
|
-
constructor(axios: AxiosInstance);
|
|
31
|
-
createEndpoint<TOutputSchema extends z.ZodType, TInputSchema extends z.ZodType | undefined, TVariablesSchema extends z.ZodType | undefined, TOutput = z.infer<TOutputSchema>, TInput = undefined extends TInputSchema ? void : z.infer<TInputSchema>, TVariables = undefined extends TVariablesSchema ? void : z.infer<TVariablesSchema>>({ method, path, axiosOptions: axiosOptionsFn, variablesSchema, inputSchema, outputSchema, }: {
|
|
32
|
-
method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
33
|
-
path: string | ((data: TData<TInput, TVariables>) => string);
|
|
34
|
-
axiosOptions?: (data: TData<TInput, TVariables>) => AxiosRequestConfig;
|
|
35
|
-
variablesSchema?: TVariablesSchema;
|
|
36
|
-
inputSchema?: TInputSchema;
|
|
37
|
-
outputSchema: TOutputSchema;
|
|
38
|
-
}): ((opts: OptionalTData<TInput, TVariables>) => Promise<AxiosResponse<TOutput>>) & {
|
|
39
|
-
queryKey: (data: TInput | void) => (string | NonNullable<TInput>)[];
|
|
40
|
-
queryOptions: (opts: object extends TData<TInput, TVariables> ? ReactQueryOptions<TOutput, TInput, TVariables> | void : ReactQueryOptions<TOutput, TInput, TVariables>) => UseQueryOptions<TOutput, ZodError<TOutput> | AxiosError>;
|
|
41
|
-
mutationKey: () => string[];
|
|
42
|
-
mutationOptions: (opts: ReactMutationOptions<TOutput, TInput, TVariables> | void) => UseMutationOptions<TOutput, ZodError<TOutput> | AxiosError, OptionalTData<TInput, TVariables>>;
|
|
30
|
+
type ReactMutationOptions<TOutput, TInput, TVariables> = Omit<UseMutationOptions<TOutput, ZodError<TOutput> | AxiosError, object extends ClientPayload<TInput, TVariables> ? void : ClientPayload<TInput, TVariables>>, "mutationFn"> & {};
|
|
31
|
+
|
|
32
|
+
declare function createClient(axios: AxiosInstance): {
|
|
33
|
+
create: <TOutputSchema extends z.ZodType, TInputSchema extends z.ZodType, TVariablesSchema extends z.ZodType, TOutput = z.core.output<TOutputSchema>, TInput = z.core.output<TInputSchema>, TVariables = z.core.output<TVariablesSchema>>(opts: ClientOptions<TOutputSchema, TInputSchema, TVariablesSchema, TOutput, TInput, TVariables>) => ((_payload: OptionalPayload<ClientPayload<TInput, TVariables>>) => Promise<axios.AxiosResponse<TOutput, any, {}>>) & {
|
|
34
|
+
queryOptions: (_opts: object extends ClientPayload<TInput, TVariables> ? ReactQueryOptions<TOutput, TInput, TVariables> | void : ReactQueryOptions<TOutput, TInput, TVariables>) => UseQueryOptions<TOutput, ZodError<TOutput> | AxiosError>;
|
|
35
|
+
mutationOptions: (_opts: ReactMutationOptions<TOutput, TInput, TVariables> | void) => UseMutationOptions<TOutput, ZodError<TOutput> | AxiosError, object extends ClientPayload<TInput, TVariables> ? void : ClientPayload<TInput, TVariables>>;
|
|
43
36
|
config: {
|
|
44
37
|
inputSchema: undefined extends TInputSchema ? undefined : NonNullable<TInputSchema>;
|
|
45
|
-
|
|
38
|
+
variablesSchema: undefined extends TVariablesSchema ? undefined : NonNullable<TVariablesSchema>;
|
|
39
|
+
axios: AxiosInstance;
|
|
46
40
|
method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
47
|
-
path: string | ((
|
|
41
|
+
path: string | ((payload: ClientPayload<TInput, TVariables>) => string);
|
|
42
|
+
axiosOptions?: ((payload: ClientPayload<TInput, TVariables>) => axios.AxiosRequestConfig) | undefined;
|
|
43
|
+
outputSchema: TOutputSchema;
|
|
44
|
+
transform?: ((data: z.core.output<TOutputSchema>, payload: ClientPayload<TInput, TVariables>) => TOutput) | undefined;
|
|
48
45
|
};
|
|
49
46
|
};
|
|
50
|
-
}
|
|
47
|
+
};
|
|
51
48
|
|
|
52
|
-
export {
|
|
49
|
+
export { createClient };
|
package/index.js
CHANGED
|
@@ -1,89 +1,84 @@
|
|
|
1
|
-
// src/lib/
|
|
2
|
-
import "
|
|
1
|
+
// src/lib/api-client.ts
|
|
2
|
+
import "zod";
|
|
3
|
+
|
|
4
|
+
// src/lib/call-api.ts
|
|
3
5
|
import "zod";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
async function callApi(opts, data) {
|
|
7
|
+
if (typeof data !== "object")
|
|
8
|
+
throw new Error("API SDK: Data must be an object");
|
|
9
|
+
if (opts.inputSchema && "input" in data) {
|
|
10
|
+
opts.inputSchema.parse(data.input);
|
|
11
|
+
}
|
|
12
|
+
if (opts.variablesSchema && "variables" in data) {
|
|
13
|
+
opts.variablesSchema.parse(data.variables);
|
|
14
|
+
}
|
|
15
|
+
const axiosOptions = opts.axiosOptions?.(data);
|
|
16
|
+
const url = typeof opts.path === "function" ? opts.path(data) : opts.path;
|
|
17
|
+
if (opts.method === "GET") {
|
|
18
|
+
const response = await opts.axios.get(url, axiosOptions);
|
|
19
|
+
return getResponse(opts, data, response);
|
|
20
|
+
}
|
|
21
|
+
if (opts.method === "POST") {
|
|
22
|
+
const response = await opts.axios.post(
|
|
23
|
+
url,
|
|
24
|
+
axiosOptions?.data,
|
|
25
|
+
axiosOptions
|
|
26
|
+
);
|
|
27
|
+
return getResponse(opts, data, response);
|
|
28
|
+
}
|
|
29
|
+
if (opts.method === "PUT") {
|
|
30
|
+
const response = await opts.axios.put(
|
|
31
|
+
url,
|
|
32
|
+
axiosOptions?.data,
|
|
33
|
+
axiosOptions
|
|
34
|
+
);
|
|
35
|
+
return getResponse(opts, data, response);
|
|
36
|
+
}
|
|
37
|
+
if (opts.method === "PATCH") {
|
|
38
|
+
const response = await opts.axios.patch(
|
|
39
|
+
url,
|
|
40
|
+
axiosOptions?.data,
|
|
41
|
+
axiosOptions
|
|
42
|
+
);
|
|
43
|
+
return getResponse(opts, data, response);
|
|
44
|
+
}
|
|
45
|
+
if (opts.method === "DELETE") {
|
|
46
|
+
const response = await opts.axios.delete(url, axiosOptions);
|
|
47
|
+
return getResponse(opts, data, response);
|
|
48
|
+
}
|
|
49
|
+
throw new Error(`API SDK: Unsupported method: ${opts.method}`);
|
|
50
|
+
}
|
|
51
|
+
function getResponse(opts, payload, response) {
|
|
52
|
+
const parsedData = opts.outputSchema.parse(response.data);
|
|
53
|
+
if (opts.transform) {
|
|
54
|
+
return { ...response, data: opts.transform(parsedData, payload) };
|
|
8
55
|
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}) {
|
|
56
|
+
return { ...response, data: parsedData };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// src/lib/api-client.ts
|
|
60
|
+
import "@tanstack/react-query";
|
|
61
|
+
function createClient(axios) {
|
|
62
|
+
function create(opts) {
|
|
17
63
|
const uuid = crypto.randomUUID();
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
if (variablesSchema) {
|
|
24
|
-
variablesSchema.parse(data.variables);
|
|
25
|
-
}
|
|
26
|
-
const axiosOptions = axiosOptionsFn?.(data);
|
|
27
|
-
const url = typeof path === "function" ? path(data) : path;
|
|
28
|
-
if (method === "GET") {
|
|
29
|
-
const response = await this.axios.get(url, axiosOptions);
|
|
30
|
-
outputSchema.parse(response.data);
|
|
31
|
-
return response;
|
|
32
|
-
}
|
|
33
|
-
if (method === "POST") {
|
|
34
|
-
const response = await this.axios.post(
|
|
35
|
-
url,
|
|
36
|
-
axiosOptions?.data,
|
|
37
|
-
axiosOptions
|
|
38
|
-
);
|
|
39
|
-
outputSchema.parse(response.data);
|
|
40
|
-
return response;
|
|
41
|
-
}
|
|
42
|
-
if (method === "PUT") {
|
|
43
|
-
const response = await this.axios.put(
|
|
44
|
-
url,
|
|
45
|
-
axiosOptions?.data,
|
|
46
|
-
axiosOptions
|
|
47
|
-
);
|
|
48
|
-
outputSchema.parse(response.data);
|
|
49
|
-
return response;
|
|
50
|
-
}
|
|
51
|
-
if (method === "PATCH") {
|
|
52
|
-
const response = await this.axios.patch(
|
|
53
|
-
url,
|
|
54
|
-
axiosOptions?.data,
|
|
55
|
-
axiosOptions
|
|
56
|
-
);
|
|
57
|
-
outputSchema.parse(response.data);
|
|
58
|
-
return response;
|
|
59
|
-
}
|
|
60
|
-
if (method === "DELETE") {
|
|
61
|
-
const response = await this.axios.delete(url, axiosOptions);
|
|
62
|
-
outputSchema.parse(response.data);
|
|
63
|
-
return response;
|
|
64
|
-
}
|
|
65
|
-
throw new Error(`API SDK: Unsupported method: ${method}`);
|
|
64
|
+
const apiOptionas = { ...opts, axios };
|
|
65
|
+
const call = async (_payload) => {
|
|
66
|
+
const payload = _payload ?? {};
|
|
67
|
+
return callApi(apiOptionas, payload);
|
|
66
68
|
};
|
|
67
69
|
const queryKey = (data) => data ? ["api-call", "query", uuid, data] : ["api-call", "query", uuid];
|
|
68
70
|
const mutationKey = () => ["api-call", "mutation", uuid];
|
|
69
|
-
const queryOptions = (
|
|
70
|
-
const { data, ...options } =
|
|
71
|
+
const queryOptions = (_opts) => {
|
|
72
|
+
const { data: _data, ...options } = _opts ?? {};
|
|
73
|
+
const data = _data ?? {};
|
|
71
74
|
return {
|
|
72
75
|
queryFn: async () => {
|
|
73
76
|
try {
|
|
74
|
-
const response = await
|
|
75
|
-
|
|
76
|
-
);
|
|
77
|
-
options.onSuccess?.(
|
|
78
|
-
response.data,
|
|
79
|
-
data
|
|
80
|
-
);
|
|
77
|
+
const response = await callApi(apiOptionas, data);
|
|
78
|
+
options.onSuccess?.(response.data, data);
|
|
81
79
|
return response.data;
|
|
82
80
|
} catch (error) {
|
|
83
|
-
options.onError?.(
|
|
84
|
-
error,
|
|
85
|
-
data
|
|
86
|
-
);
|
|
81
|
+
options.onError?.(error, data);
|
|
87
82
|
throw error;
|
|
88
83
|
}
|
|
89
84
|
},
|
|
@@ -91,11 +86,12 @@ var BaseApiClient = class {
|
|
|
91
86
|
...options
|
|
92
87
|
};
|
|
93
88
|
};
|
|
94
|
-
const mutationOptions = (
|
|
95
|
-
const options =
|
|
89
|
+
const mutationOptions = (_opts) => {
|
|
90
|
+
const options = _opts ?? {};
|
|
96
91
|
return {
|
|
97
|
-
mutationFn: async (
|
|
98
|
-
const
|
|
92
|
+
mutationFn: async (_data) => {
|
|
93
|
+
const data = _data ?? {};
|
|
94
|
+
const response = await callApi(apiOptionas, data);
|
|
99
95
|
return response.data;
|
|
100
96
|
},
|
|
101
97
|
mutationKey: mutationKey(),
|
|
@@ -103,19 +99,17 @@ var BaseApiClient = class {
|
|
|
103
99
|
};
|
|
104
100
|
};
|
|
105
101
|
return Object.assign(call, {
|
|
106
|
-
queryKey,
|
|
107
102
|
queryOptions,
|
|
108
|
-
mutationKey,
|
|
109
103
|
mutationOptions,
|
|
110
104
|
config: {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
path
|
|
105
|
+
...apiOptionas,
|
|
106
|
+
inputSchema: opts.inputSchema,
|
|
107
|
+
variablesSchema: opts.variablesSchema
|
|
115
108
|
}
|
|
116
109
|
});
|
|
117
110
|
}
|
|
118
|
-
};
|
|
111
|
+
return { create };
|
|
112
|
+
}
|
|
119
113
|
export {
|
|
120
|
-
|
|
114
|
+
createClient
|
|
121
115
|
};
|