vue-api-kit 1.7.0 → 1.8.1

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 CHANGED
@@ -290,6 +290,133 @@ const api = createApiClient({
290
290
  });
291
291
  ```
292
292
 
293
+ ## 🎯 Per-Query and Per-Mutation Request Interceptors
294
+
295
+ In addition to global request interceptors, you can define `onBeforeRequest` hooks for individual queries and mutations. This is useful when you need to append specific headers or modify the request configuration for certain endpoints only.
296
+
297
+ ### Query-Level onBeforeRequest
298
+
299
+ You can define `onBeforeRequest` in two ways for queries:
300
+
301
+ **1. In the query definition:**
302
+
303
+ ```typescript
304
+ const api = createApiClient({
305
+ baseURL: 'https://api.example.com',
306
+ queries: {
307
+ getUser: {
308
+ path: '/users/{id}',
309
+ params: z.object({ id: z.number() }),
310
+ response: z.object({ id: z.number(), name: z.string() }),
311
+ // Query-level interceptor
312
+ onBeforeRequest: async (config) => {
313
+ config.headers['X-Custom-Query-Header'] = 'special-value';
314
+ return config;
315
+ }
316
+ }
317
+ }
318
+ });
319
+ ```
320
+
321
+ **2. In the query options when calling it:**
322
+
323
+ ```typescript
324
+ const { result, isLoading } = api.query.getUser({
325
+ params: { id: 1 },
326
+ // Runtime interceptor
327
+ onBeforeRequest: async (config) => {
328
+ const token = await getAuthToken();
329
+ config.headers.Authorization = `Bearer ${token}`;
330
+ return config;
331
+ }
332
+ });
333
+ ```
334
+
335
+ ### Mutation-Level onBeforeRequest
336
+
337
+ Similarly, you can define `onBeforeRequest` for mutations:
338
+
339
+ **1. In the mutation definition:**
340
+
341
+ ```typescript
342
+ const api = createApiClient({
343
+ baseURL: 'https://api.example.com',
344
+ mutations: {
345
+ createUser: {
346
+ method: 'POST',
347
+ path: '/users',
348
+ data: z.object({ name: z.string(), email: z.string() }),
349
+ response: z.object({ id: z.number(), name: z.string() }),
350
+ // Mutation-level interceptor
351
+ onBeforeRequest: async (config) => {
352
+ config.headers['X-Action'] = 'create-user';
353
+ return config;
354
+ }
355
+ }
356
+ }
357
+ });
358
+ ```
359
+
360
+ **2. In the mutation options when calling it:**
361
+
362
+ ```typescript
363
+ const { mutate } = api.mutation.createUser({
364
+ // Runtime interceptor
365
+ onBeforeRequest: async (config) => {
366
+ const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content;
367
+ if (csrfToken) {
368
+ config.headers['X-CSRF-Token'] = csrfToken;
369
+ }
370
+ return config;
371
+ }
372
+ });
373
+
374
+ await mutate({ data: { name: 'John', email: 'john@example.com' } });
375
+ ```
376
+
377
+ ### Execution Order
378
+
379
+ When multiple `onBeforeRequest` hooks are defined, they execute in the following order:
380
+
381
+ 1. **Global interceptor** (defined in `createApiClient` options) - Applied via axios interceptor
382
+ 2. **Query/Mutation definition interceptor** (defined in query/mutation object)
383
+ 3. **Options interceptor** (defined when calling the query/mutation)
384
+
385
+ Each hook can modify the config, and later hooks can see and override changes made by earlier hooks.
386
+
387
+ ### Use Cases
388
+
389
+ - **Authentication**: Add tokens for specific endpoints that require authentication
390
+ - **Custom Headers**: Append API keys, correlation IDs, or feature flags for specific requests
391
+ - **Request Transformation**: Modify request data or parameters before sending
392
+ - **Conditional Logic**: Apply different configurations based on runtime conditions
393
+ - **Debugging**: Add request IDs or trace headers for specific endpoints
394
+
395
+ ### Example: Dynamic Authorization
396
+
397
+ ```typescript
398
+ const api = createApiClient({
399
+ baseURL: 'https://api.example.com',
400
+ queries: {
401
+ getProtectedData: {
402
+ path: '/protected/data',
403
+ response: z.object({ data: z.string() }),
404
+ onBeforeRequest: async (config) => {
405
+ // This query always needs fresh token
406
+ const token = await refreshAndGetToken();
407
+ config.headers.Authorization = `Bearer ${token}`;
408
+ return config;
409
+ }
410
+ },
411
+ getPublicData: {
412
+ path: '/public/data',
413
+ response: z.object({ data: z.string() })
414
+ // No onBeforeRequest needed for public endpoint
415
+ }
416
+ }
417
+ });
418
+ ```
419
+
293
420
  ## 🧩 Modular API Definitions
294
421
 
295
422
  For large applications, you can organize your API definitions into separate files and merge them together with full type safety.
@@ -42,6 +42,7 @@ export interface ApiQuery<TParams extends ZodType<any> | undefined = ZodType<any
42
42
  params?: TParams;
43
43
  data?: TData;
44
44
  response?: TResponse;
45
+ onBeforeRequest?: (config: InternalAxiosRequestConfig<any>) => Promise<any> | void | any;
45
46
  }
46
47
  /**
47
48
  * Defines a mutation (POST, PUT, PATCH, DELETE) endpoint configuration
@@ -63,6 +64,7 @@ export interface ApiMutation<TData extends ZodType<any> | undefined = ZodType<an
63
64
  data?: TData;
64
65
  response?: TResponse;
65
66
  isMultipart?: boolean;
67
+ onBeforeRequest?: (config: InternalAxiosRequestConfig<any>) => Promise<any> | void | any;
66
68
  }
67
69
  /**
68
70
  * Configuration options for creating an API client
@@ -126,6 +128,7 @@ export interface UseQueryOptions<TParams = any, TData = any, TResult = any> {
126
128
  onResult?: (result: TResult) => void;
127
129
  onError?: (error: AxiosError | ZodError | Error) => void;
128
130
  onZodError?: (issues: Omit<$ZodIssue, "input">[]) => void;
131
+ onBeforeRequest?: (config: InternalAxiosRequestConfig<any>) => Promise<any> | void | any;
129
132
  }
130
133
  /**
131
134
  * Options for configuring a mutation hook
@@ -142,6 +145,7 @@ export interface UseMutationOptions<TResult = any> {
142
145
  onError?: (error: AxiosError | ZodError | Error) => void;
143
146
  onZodError?: (issues: Omit<$ZodIssue, "input">[]) => void;
144
147
  onUploadProgress?: (progress: number) => void;
148
+ onBeforeRequest?: (config: InternalAxiosRequestConfig<any>) => Promise<any> | void | any;
145
149
  }
146
150
  /**
147
151
  * Return type from a query hook
package/dist/index.js CHANGED
@@ -1,188 +1,204 @@
1
1
  import { ZodError as S } from "zod";
2
2
  import * as U from "zod";
3
3
  import L, { AxiosError as F } from "axios";
4
- import { AxiosError as H } from "axios";
5
- import { nextTick as T, ref as m, onMounted as k, watch as x, onBeforeUnmount as N } from "vue";
6
- import { debounce as Z } from "lodash-es";
7
- function z(r) {
8
- const h = L.create({
9
- baseURL: r.baseURL,
4
+ import { AxiosError as K } from "axios";
5
+ import { nextTick as T, ref as m, onMounted as k, watch as Z, onBeforeUnmount as x } from "vue";
6
+ import { debounce as N } from "lodash-es";
7
+ function V(s) {
8
+ const R = L.create({
9
+ baseURL: s.baseURL,
10
10
  headers: {
11
11
  "Content-Type": "application/json",
12
12
  Accept: "application/json",
13
- ...r.headers
13
+ ...s.headers
14
14
  },
15
- withCredentials: r.withCredentials ?? !1,
16
- withXSRFToken: r.withXSRFToken ?? !1
15
+ withCredentials: s.withCredentials ?? !1,
16
+ withXSRFToken: s.withXSRFToken ?? !1
17
17
  });
18
- let b = !1, q = null;
19
- r.onBeforeRequest && h.interceptors.request.use(
18
+ let C = !1, j = null;
19
+ s.onBeforeRequest && R.interceptors.request.use(
20
20
  async (e) => {
21
21
  try {
22
- return await r.onBeforeRequest(e) || e;
23
- } catch (s) {
24
- return Promise.reject(s);
22
+ return await s.onBeforeRequest(e) || e;
23
+ } catch (r) {
24
+ return Promise.reject(r);
25
25
  }
26
26
  },
27
27
  (e) => Promise.reject(e)
28
- ), r.onStartRequest && h.interceptors.request.use(
28
+ ), s.onStartRequest && R.interceptors.request.use(
29
29
  async (e) => {
30
30
  try {
31
- return await r.onStartRequest(), e;
32
- } catch (s) {
33
- return Promise.reject(s);
31
+ return await s.onStartRequest(), e;
32
+ } catch (r) {
33
+ return Promise.reject(r);
34
34
  }
35
35
  },
36
36
  (e) => Promise.reject(e)
37
- ), r.onFinishRequest && h.interceptors.response.use(
38
- (e) => (r.onFinishRequest(), e),
39
- (e) => (r.onFinishRequest(), Promise.reject(e))
40
- ), h.interceptors.request.use((e) => {
37
+ ), s.onFinishRequest && R.interceptors.response.use(
38
+ (e) => (s.onFinishRequest(), e),
39
+ (e) => (s.onFinishRequest(), Promise.reject(e))
40
+ ), R.interceptors.request.use((e) => {
41
41
  if (!e.url) return e;
42
- const s = (a) => {
42
+ const r = (a) => {
43
43
  if (a)
44
- for (const [t, v] of Object.entries(a)) {
45
- const l = `{${t}}`;
46
- e.url.includes(l) && (e.url = e.url.replace(
47
- l,
48
- encodeURIComponent(String(v))
44
+ for (const [t, p] of Object.entries(a)) {
45
+ const f = `{${t}}`;
46
+ e.url.includes(f) && (e.url = e.url.replace(
47
+ f,
48
+ encodeURIComponent(String(p))
49
49
  ), delete a[t]);
50
50
  }
51
51
  };
52
- return e.method !== "get" && e.data?.params && s(e.data.params), s(e.params), e;
53
- }), r.csrfRefreshEndpoint && h.interceptors.response.use(
52
+ return e.method !== "get" && e.data?.params && r(e.data.params), r(e.params), e;
53
+ }), s.csrfRefreshEndpoint && R.interceptors.response.use(
54
54
  (e) => e,
55
55
  async (e) => {
56
- const s = e.config;
57
- if (s?.url === r.csrfRefreshEndpoint)
56
+ const r = e.config;
57
+ if (r?.url === s.csrfRefreshEndpoint)
58
58
  return Promise.reject(e);
59
- if (e.response && (e.response.status === 403 || e.response.status === 419) && !s?._retry) {
60
- s._retry = !0;
59
+ if (e.response && (e.response.status === 403 || e.response.status === 419) && !r?._retry) {
60
+ r._retry = !0;
61
61
  try {
62
- return b && q ? await q : (b = !0, q = h.get(r.csrfRefreshEndpoint).then(() => {
63
- b = !1, q = null;
64
- }), await q), h.request(s);
62
+ return C && j ? await j : (C = !0, j = R.get(s.csrfRefreshEndpoint).then(() => {
63
+ C = !1, j = null;
64
+ }), await j), R.request(r);
65
65
  } catch (a) {
66
- return b = !1, q = null, Promise.reject(a);
66
+ return C = !1, j = null, Promise.reject(a);
67
67
  }
68
68
  }
69
69
  return Promise.reject(e);
70
70
  }
71
- ), h.interceptors.response.use(
71
+ ), R.interceptors.response.use(
72
72
  (e) => e,
73
73
  (e) => (T(() => {
74
74
  e.code;
75
75
  }), Promise.reject(e))
76
76
  );
77
- const C = r.queries ?? {}, M = {};
78
- for (const e in C) {
79
- const s = C[e];
80
- s && (M[e] = (a) => {
77
+ const P = s.queries ?? {}, M = {};
78
+ for (const e in P) {
79
+ const r = P[e];
80
+ r && (M[e] = (a) => {
81
81
  let t;
82
- a && typeof a == "object" && ("loadOnMount" in a || "debounce" in a || "onResult" in a || "onError" in a || "data" in a ? t = a : t = { params: a });
83
- const v = m(), l = m(), R = m(), g = m(!1), y = m(!1), P = m(!0);
84
- let j = new AbortController();
85
- const u = () => {
86
- j?.abort(), j = new AbortController();
87
- }, c = async () => {
88
- g.value && u(), g.value = !0, l.value = void 0;
82
+ a && typeof a == "object" && ("loadOnMount" in a || "debounce" in a || "onResult" in a || "onError" in a || "onZodError" in a || "onBeforeRequest" in a || "data" in a ? t = a : t = { params: a });
83
+ const p = m(), f = m(), g = m(), E = m(!1), y = m(!1), A = m(!0);
84
+ let b = new AbortController();
85
+ const i = () => {
86
+ b?.abort(), b = new AbortController();
87
+ }, l = async () => {
88
+ E.value && i(), E.value = !0, f.value = void 0;
89
89
  try {
90
- s.params && t?.params && s.params.parse(t.params);
90
+ r.params && t?.params && r.params.parse(t.params);
91
91
  let o = t?.data;
92
- s.data && o && s.data.parse(o);
93
- const f = {
94
- method: s.method ?? "GET",
95
- url: s.path,
92
+ r.data && o && r.data.parse(o);
93
+ const u = {
94
+ method: r.method ?? "GET",
95
+ url: r.path,
96
96
  params: t?.params,
97
- signal: j.signal
97
+ signal: b.signal
98
98
  };
99
- s.method === "POST" && o && (f.data = o);
100
- const n = await h.request(f), i = s.response ? s.response.parse(n.data) : n.data;
101
- v.value = i, t?.onResult?.(i);
99
+ if (r.method === "POST" && o && (u.data = o), r.onBeforeRequest) {
100
+ const c = await r.onBeforeRequest(u);
101
+ c !== void 0 && Object.assign(u, c);
102
+ }
103
+ if (t?.onBeforeRequest) {
104
+ const c = await t.onBeforeRequest(u);
105
+ c !== void 0 && Object.assign(u, c);
106
+ }
107
+ const h = await R.request(u), n = r.response ? r.response.parse(h.data) : h.data;
108
+ p.value = n, t?.onResult?.(n);
102
109
  } catch (o) {
103
110
  if (o instanceof F) {
104
111
  if (o.code !== "ERR_CANCELED") {
105
- const f = o.response?.data?.message || o.message || "An error occurred", n = o.response?.status, i = o.code, p = o.response?.data;
106
- l.value = f, t?.onError?.(o), r.onErrorRequest?.({ message: f, status: n, code: i, data: p });
112
+ const u = o.response?.data?.message || o.message || "An error occurred", h = o.response?.status, n = o.code, c = o.response?.data;
113
+ f.value = u, t?.onError?.(o), s.onErrorRequest?.({ message: u, status: h, code: n, data: c });
107
114
  }
108
115
  } else if (o instanceof S) {
109
- R.value = o.issues || [];
110
- const n = `Validation error: ${R.value.map(
111
- (i) => `${i.path.join(".")}: ${i.message}`
116
+ g.value = o.issues || [];
117
+ const h = `Validation error: ${g.value.map(
118
+ (n) => `${n.path.join(".")}: ${n.message}`
112
119
  ).join(", ")}`;
113
- l.value = n, t?.onError?.(o), t?.onZodError?.(R.value), r.onErrorRequest?.({ message: n, code: "VALIDATION_ERROR" }), r.onZodError && r.onZodError(R.value);
120
+ f.value = h, t?.onError?.(o), t?.onZodError?.(g.value), s.onErrorRequest?.({ message: h, code: "VALIDATION_ERROR" }), s.onZodError && s.onZodError(g.value);
114
121
  } else {
115
- const f = o.message || "An error occurred";
116
- l.value = f, t?.onError?.(f), r.onErrorRequest?.({ message: f });
122
+ const u = o.message || "An error occurred";
123
+ f.value = u, t?.onError?.(u), s.onErrorRequest?.({ message: u });
117
124
  }
118
125
  } finally {
119
- g.value = !1, y.value = !0;
126
+ E.value = !1, y.value = !0;
120
127
  }
121
- }, E = t?.debounce ? Z(c, t.debounce) : c;
128
+ }, v = t?.debounce ? N(l, t.debounce) : l;
122
129
  let d = null;
123
130
  return (t?.params || t?.data) && (k(() => {
124
- d && d(), d = x(
131
+ d && d(), d = Z(
125
132
  () => JSON.stringify({ params: t.params, data: t.data }),
126
133
  () => {
127
- E();
134
+ v();
128
135
  },
129
136
  { immediate: !1 }
130
137
  );
131
- }), N(() => {
132
- d && d(), j?.abort();
133
- })), (t?.loadOnMount === void 0 || t.loadOnMount) && !y.value && (P.value ? (P.value = !1, c()) : E()), { result: v, errorMessage: l, zodErrors: R, isLoading: g, isDone: y, refetch: c };
138
+ }), x(() => {
139
+ d && d(), b?.abort();
140
+ })), (t?.loadOnMount === void 0 || t.loadOnMount) && !y.value && (A.value ? (A.value = !1, l()) : v()), { result: p, errorMessage: f, zodErrors: g, isLoading: E, isDone: y, refetch: l };
134
141
  });
135
142
  }
136
- const w = r.mutations ?? {}, D = {};
137
- for (const e in w) {
138
- const s = w[e];
139
- s && (D[e] = (a) => {
140
- const t = m(), v = m(), l = m(), R = m(!1), g = m(!1), y = m(0);
141
- return { result: t, errorMessage: v, zodErrors: l, isLoading: R, isDone: g, uploadProgress: y, mutate: async (j) => {
142
- if (!R.value) {
143
- R.value = !0, v.value = void 0, y.value = 0;
143
+ const B = s.mutations ?? {}, D = {};
144
+ for (const e in B) {
145
+ const r = B[e];
146
+ r && (D[e] = (a) => {
147
+ const t = m(), p = m(), f = m(), g = m(!1), E = m(!1), y = m(0);
148
+ return { result: t, errorMessage: p, zodErrors: f, isLoading: g, isDone: E, uploadProgress: y, mutate: async (b) => {
149
+ if (!g.value) {
150
+ g.value = !0, p.value = void 0, y.value = 0;
144
151
  try {
145
- const { data: u = {}, params: c } = j ?? {};
146
- let E = u ?? {}, d = {};
147
- if (s.isMultipart) {
152
+ const { data: i = {}, params: l } = b ?? {};
153
+ let v = i ?? {}, d = {};
154
+ if (r.isMultipart) {
148
155
  const n = new FormData();
149
- for (const [i, p] of Object.entries(u))
150
- p instanceof File || p instanceof Blob ? n.append(i, p) : Array.isArray(p) ? p.forEach((A) => {
151
- A instanceof File || A instanceof Blob ? n.append(i, A) : n.append(i, JSON.stringify(A));
152
- }) : typeof p == "object" && p !== null ? n.append(i, JSON.stringify(p)) : n.append(i, String(p));
153
- E = n, d["Content-Type"] = "multipart/form-data";
154
- } else s.data && s.data.parse(u);
155
- s.params && c && s.params.parse(c);
156
- const o = await h.request({
157
- method: s.method,
158
- url: s.path,
159
- data: E,
160
- params: c,
156
+ for (const [c, q] of Object.entries(i))
157
+ q instanceof File || q instanceof Blob ? n.append(c, q) : Array.isArray(q) ? q.forEach((w) => {
158
+ w instanceof File || w instanceof Blob ? n.append(c, w) : n.append(c, JSON.stringify(w));
159
+ }) : typeof q == "object" && q !== null ? n.append(c, JSON.stringify(q)) : n.append(c, String(q));
160
+ v = n, d["Content-Type"] = "multipart/form-data";
161
+ } else r.data && r.data.parse(i);
162
+ r.params && l && r.params.parse(l);
163
+ const o = {
164
+ method: r.method,
165
+ url: r.path,
166
+ data: v,
167
+ params: l,
161
168
  headers: d,
162
169
  onUploadProgress: (n) => {
163
170
  if (n.total) {
164
- const i = Math.round(n.loaded * 100 / n.total);
165
- y.value = i, a?.onUploadProgress?.(i);
171
+ const c = Math.round(n.loaded * 100 / n.total);
172
+ y.value = c, a?.onUploadProgress?.(c);
166
173
  }
167
174
  }
168
- }), f = s.response ? s.response.parse(o.data) : o.data;
169
- t.value = f, a?.onResult?.(f);
170
- } catch (u) {
171
- if (u instanceof F) {
172
- const c = u.response?.data?.message || u.message || "An error occurred", E = u.response?.status, d = u.code;
173
- v.value = c, a?.onError?.(u), r.onErrorRequest?.({ message: c, status: E, code: d });
174
- } else if (u instanceof S) {
175
- l.value = u.issues || [];
176
- const E = `Validation error: ${l.value.map(
175
+ };
176
+ if (r.onBeforeRequest) {
177
+ const n = await r.onBeforeRequest(o);
178
+ n !== void 0 && Object.assign(o, n);
179
+ }
180
+ if (a?.onBeforeRequest) {
181
+ const n = await a.onBeforeRequest(o);
182
+ n !== void 0 && Object.assign(o, n);
183
+ }
184
+ const u = await R.request(o), h = r.response ? r.response.parse(u.data) : u.data;
185
+ t.value = h, a?.onResult?.(h);
186
+ } catch (i) {
187
+ if (i instanceof F) {
188
+ const l = i.response?.data?.message || i.message || "An error occurred", v = i.response?.status, d = i.code;
189
+ p.value = l, a?.onError?.(i), s.onErrorRequest?.({ message: l, status: v, code: d });
190
+ } else if (i instanceof S) {
191
+ f.value = i.issues || [];
192
+ const v = `Validation error: ${f.value.map(
177
193
  (d) => `${d.path.join(".")}: ${d.message}`
178
194
  ).join(", ")}`;
179
- v.value = E, a?.onError?.(u), a?.onZodError?.(l.value), r.onErrorRequest?.({ message: E, code: "VALIDATION_ERROR" }), r.onZodError && r.onZodError(l.value);
195
+ p.value = v, a?.onError?.(i), a?.onZodError?.(f.value), s.onErrorRequest?.({ message: v, code: "VALIDATION_ERROR" }), s.onZodError && s.onZodError(f.value);
180
196
  } else {
181
- const c = u.message || "An error occurred";
182
- v.value = c, a?.onError?.(u), r.onErrorRequest?.({ message: c });
197
+ const l = i.message || "An error occurred";
198
+ p.value = l, a?.onError?.(i), s.onErrorRequest?.({ message: l });
183
199
  }
184
200
  } finally {
185
- R.value = !1, g.value = !0;
201
+ g.value = !1, E.value = !0;
186
202
  }
187
203
  }
188
204
  } };
@@ -193,24 +209,24 @@ function z(r) {
193
209
  mutation: D
194
210
  };
195
211
  }
196
- function V(r) {
197
- return r;
212
+ function J(s) {
213
+ return s;
198
214
  }
199
- function J(r) {
200
- return r;
215
+ function Q(s) {
216
+ return s;
201
217
  }
202
- function Q(...r) {
203
- return Object.assign({}, ...r);
218
+ function W(...s) {
219
+ return Object.assign({}, ...s);
204
220
  }
205
- function W(...r) {
206
- return Object.assign({}, ...r);
221
+ function X(...s) {
222
+ return Object.assign({}, ...s);
207
223
  }
208
224
  export {
209
- H as AxiosError,
210
- z as createApiClient,
211
- J as defineMutation,
212
- V as defineQuery,
213
- W as mergeMutations,
214
- Q as mergeQueries,
225
+ K as AxiosError,
226
+ V as createApiClient,
227
+ Q as defineMutation,
228
+ J as defineQuery,
229
+ X as mergeMutations,
230
+ W as mergeQueries,
215
231
  U as z
216
232
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "vue-api-kit",
3
3
  "type": "module",
4
- "version": "1.7.0",
4
+ "version": "1.8.1",
5
5
  "description": "A powerful and flexible API client for Vue 3 applications, built with TypeScript and Zod for type-safe API interactions.",
6
6
  "keywords": [
7
7
  "vue3",