vue-api-kit 1.6.0 → 1.7.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 CHANGED
@@ -251,7 +251,8 @@ const api = createApiClient({
251
251
  headers: {
252
252
  'Authorization': 'Bearer token'
253
253
  },
254
- withCredentials: true,
254
+ withCredentials: true, // Enable cookies
255
+ withXSRFToken: true, // Enable automatic XSRF token handling
255
256
 
256
257
  // CSRF Token Protection
257
258
  csrfRefreshEndpoint: '/sanctum/csrf-cookie', // Auto-refresh CSRF token on 403/419 errors
@@ -459,9 +460,10 @@ The client includes built-in CSRF token protection, perfect for Laravel Sanctum
459
460
  ### How it works
460
461
 
461
462
  **Automatic XSRF Token Handling:**
462
- 1. When `withCredentials: true`, the client automatically reads the `XSRF-TOKEN` cookie
463
- 2. Decodes and sends it as `X-XSRF-TOKEN` header with every request
464
- 3. This satisfies Laravel Sanctum's CSRF protection requirements
463
+ 1. Set `withCredentials: true` to enable cookie-based authentication
464
+ 2. Set `withXSRFToken: true` to enable automatic XSRF token handling
465
+ 3. Axios automatically reads `XSRF-TOKEN` cookie and sends it as `X-XSRF-TOKEN` header
466
+ 4. This satisfies Laravel Sanctum's CSRF protection requirements
465
467
 
466
468
  **Automatic CSRF Refresh:**
467
469
  1. Detects CSRF errors (403 or 419 status codes)
@@ -474,9 +476,9 @@ The client includes built-in CSRF token protection, perfect for Laravel Sanctum
474
476
  ```typescript
475
477
  const api = createApiClient({
476
478
  baseURL: 'https://api.example.com',
477
- withCredentials: true, // Required! Enables cookies AND auto XSRF header
479
+ withCredentials: true, // Enable cookies for authentication
480
+ withXSRFToken: true, // Enable automatic XSRF token handling
478
481
  csrfRefreshEndpoint: '/sanctum/csrf-cookie', // Laravel Sanctum endpoint
479
-
480
482
  queries: { /* ... */ },
481
483
  mutations: { /* ... */ }
482
484
  });
@@ -491,10 +493,9 @@ import { z } from 'zod';
491
493
 
492
494
  export const api = createApiClient({
493
495
  baseURL: 'https://api.example.com',
494
- withCredentials: true, // Enables cookies AND automatic XSRF-TOKEN header
495
- csrfRefreshEndpoint: '/sanctum/csrf-cookie', // Laravel's CSRF endpoint
496
-
497
- mutations: {
496
+ withCredentials: true, // Enables cookies
497
+ withXSRFToken: true, // Enables automatic XSRF-TOKEN header
498
+ csrfRefreshEndpoint: '/sanctum/csrf-cookie', // Laravel's CSRF endpoint mutations: {
498
499
  login: {
499
500
  method: 'POST',
500
501
  path: '/login',
@@ -524,7 +525,8 @@ export const api = createApiClient({
524
525
 
525
526
  ### Benefits
526
527
 
527
- - ✅ **Automatic XSRF Header**: Cookie automatically sent as `X-XSRF-TOKEN` header
528
+ - ✅ **Separate Options**: `withCredentials` and `withXSRFToken` can be configured independently
529
+ - ✅ **Built-in XSRF Support**: Axios `withXSRFToken` handles token automatically
528
530
  - ✅ **Automatic Recovery**: No manual token refresh needed
529
531
  - ✅ **Seamless UX**: Users don't experience authentication errors
530
532
  - ✅ **Race Condition Safe**: Multiple simultaneous requests share the same refresh
@@ -533,7 +535,9 @@ export const api = createApiClient({
533
535
 
534
536
  ### Important Notes
535
537
 
536
- 1. **withCredentials is required**: Set `withCredentials: true` to enable both cookies and automatic XSRF token handling
538
+ 1. **Two separate options**:
539
+ - `withCredentials: true` - Enables sending cookies with requests
540
+ - `withXSRFToken: true` - Enables automatic XSRF token header handling
537
541
  2. **Cookie Domain**: Ensure your API sets cookies with the correct domain (e.g., `.localhost` for local development)
538
542
  3. **CORS Configuration**: Your Laravel backend must allow credentials:
539
543
  ```php
@@ -83,6 +83,7 @@ export interface ApiClientOptions<Q extends Record<string, ApiQuery> = Record<st
83
83
  baseURL: string;
84
84
  headers?: Record<string, string>;
85
85
  withCredentials?: boolean;
86
+ withXSRFToken?: boolean;
86
87
  csrfRefreshEndpoint?: string;
87
88
  queries?: Q;
88
89
  mutations?: M;
package/dist/index.js CHANGED
@@ -1,163 +1,161 @@
1
1
  import { ZodError as S } from "zod";
2
- import * as $ from "zod";
2
+ import * as U from "zod";
3
3
  import L, { AxiosError as F } from "axios";
4
- import { AxiosError as G } from "axios";
5
- import { nextTick as N, ref as m, onMounted as T, watch as k, onBeforeUnmount as x } from "vue";
6
- import { debounce as U } from "lodash-es";
7
- function z(t) {
8
- const p = L.create({
9
- baseURL: t.baseURL,
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,
10
10
  headers: {
11
11
  "Content-Type": "application/json",
12
12
  Accept: "application/json",
13
- ...t.headers
13
+ ...r.headers
14
14
  },
15
- withCredentials: t.withCredentials ?? !1
15
+ withCredentials: r.withCredentials ?? !1,
16
+ withXSRFToken: r.withXSRFToken ?? !1
16
17
  });
17
- let C = !1, q = null;
18
- t.onBeforeRequest && p.interceptors.request.use(
18
+ let b = !1, q = null;
19
+ r.onBeforeRequest && h.interceptors.request.use(
19
20
  async (e) => {
20
21
  try {
21
- return await t.onBeforeRequest(e) || e;
22
- } catch (r) {
23
- return Promise.reject(r);
22
+ return await r.onBeforeRequest(e) || e;
23
+ } catch (s) {
24
+ return Promise.reject(s);
24
25
  }
25
26
  },
26
27
  (e) => Promise.reject(e)
27
- ), t.onStartRequest && p.interceptors.request.use(
28
+ ), r.onStartRequest && h.interceptors.request.use(
28
29
  async (e) => {
29
30
  try {
30
- return await t.onStartRequest(), e;
31
- } catch (r) {
32
- return Promise.reject(r);
31
+ return await r.onStartRequest(), e;
32
+ } catch (s) {
33
+ return Promise.reject(s);
33
34
  }
34
35
  },
35
36
  (e) => Promise.reject(e)
36
- ), t.onFinishRequest && p.interceptors.response.use(
37
- (e) => (t.onFinishRequest(), e),
38
- (e) => (t.onFinishRequest(), Promise.reject(e))
39
- ), p.interceptors.request.use((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) => {
40
41
  if (!e.url) return e;
41
- const r = (a) => {
42
+ const s = (a) => {
42
43
  if (a)
43
- for (const [s, R] of Object.entries(a)) {
44
- const l = `{${s}}`;
44
+ for (const [t, v] of Object.entries(a)) {
45
+ const l = `{${t}}`;
45
46
  e.url.includes(l) && (e.url = e.url.replace(
46
47
  l,
47
- encodeURIComponent(String(R))
48
- ), delete a[s]);
48
+ encodeURIComponent(String(v))
49
+ ), delete a[t]);
49
50
  }
50
51
  };
51
- return e.method !== "get" && e.data?.params && r(e.data.params), r(e.params), e;
52
- }), t.withCredentials && p.interceptors.request.use((e) => {
53
- const r = document.cookie.split("; ").find((a) => a.startsWith("XSRF-TOKEN="))?.split("=")[1];
54
- return r && (e.headers["X-XSRF-TOKEN"] = decodeURIComponent(r)), e;
55
- }), t.csrfRefreshEndpoint && p.interceptors.response.use(
52
+ return e.method !== "get" && e.data?.params && s(e.data.params), s(e.params), e;
53
+ }), r.csrfRefreshEndpoint && h.interceptors.response.use(
56
54
  (e) => e,
57
55
  async (e) => {
58
- const r = e.config;
59
- if (r?.url === t.csrfRefreshEndpoint)
56
+ const s = e.config;
57
+ if (s?.url === r.csrfRefreshEndpoint)
60
58
  return Promise.reject(e);
61
- if (e.response && (e.response.status === 403 || e.response.status === 419) && !r?._retry) {
62
- r._retry = !0;
59
+ if (e.response && (e.response.status === 403 || e.response.status === 419) && !s?._retry) {
60
+ s._retry = !0;
63
61
  try {
64
- return C && q ? await q : (C = !0, q = p.get(t.csrfRefreshEndpoint).then(() => {
65
- C = !1, q = null;
66
- }), await q), p.request(r);
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);
67
65
  } catch (a) {
68
- return C = !1, q = null, Promise.reject(a);
66
+ return b = !1, q = null, Promise.reject(a);
69
67
  }
70
68
  }
71
69
  return Promise.reject(e);
72
70
  }
73
- ), p.interceptors.response.use(
71
+ ), h.interceptors.response.use(
74
72
  (e) => e,
75
- (e) => (N(() => {
73
+ (e) => (T(() => {
76
74
  e.code;
77
75
  }), Promise.reject(e))
78
76
  );
79
- const P = t.queries ?? {}, M = {};
80
- for (const e in P) {
81
- const r = P[e];
82
- r && (M[e] = (a) => {
83
- let s;
84
- a && typeof a == "object" && ("loadOnMount" in a || "debounce" in a || "onResult" in a || "onError" in a || "data" in a ? s = a : s = { params: a });
85
- const R = m(), l = m(), v = m(), g = m(!1), y = m(!1), A = m(!0);
77
+ const C = r.queries ?? {}, M = {};
78
+ for (const e in C) {
79
+ const s = C[e];
80
+ s && (M[e] = (a) => {
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);
86
84
  let j = new AbortController();
87
85
  const u = () => {
88
86
  j?.abort(), j = new AbortController();
89
87
  }, c = async () => {
90
88
  g.value && u(), g.value = !0, l.value = void 0;
91
89
  try {
92
- r.params && s?.params && r.params.parse(s.params);
93
- let o = s?.data;
94
- r.data && o && r.data.parse(o);
90
+ s.params && t?.params && s.params.parse(t.params);
91
+ let o = t?.data;
92
+ s.data && o && s.data.parse(o);
95
93
  const f = {
96
- method: r.method ?? "GET",
97
- url: r.path,
98
- params: s?.params,
94
+ method: s.method ?? "GET",
95
+ url: s.path,
96
+ params: t?.params,
99
97
  signal: j.signal
100
98
  };
101
- r.method === "POST" && o && (f.data = o);
102
- const n = await p.request(f), i = r.response ? r.response.parse(n.data) : n.data;
103
- R.value = i, s?.onResult?.(i);
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);
104
102
  } catch (o) {
105
103
  if (o instanceof F) {
106
104
  if (o.code !== "ERR_CANCELED") {
107
- const f = o.response?.data?.message || o.message || "An error occurred", n = o.response?.status, i = o.code, h = o.response?.data;
108
- l.value = f, s?.onError?.(o), t.onErrorRequest?.({ message: f, status: n, code: i, data: h });
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 });
109
107
  }
110
108
  } else if (o instanceof S) {
111
- v.value = o.issues || [];
112
- const n = `Validation error: ${v.value.map(
109
+ R.value = o.issues || [];
110
+ const n = `Validation error: ${R.value.map(
113
111
  (i) => `${i.path.join(".")}: ${i.message}`
114
112
  ).join(", ")}`;
115
- l.value = n, s?.onError?.(o), s?.onZodError?.(v.value), t.onErrorRequest?.({ message: n, code: "VALIDATION_ERROR" }), t.onZodError && t.onZodError(v.value);
113
+ l.value = n, t?.onError?.(o), t?.onZodError?.(R.value), r.onErrorRequest?.({ message: n, code: "VALIDATION_ERROR" }), r.onZodError && r.onZodError(R.value);
116
114
  } else {
117
115
  const f = o.message || "An error occurred";
118
- l.value = f, s?.onError?.(f), t.onErrorRequest?.({ message: f });
116
+ l.value = f, t?.onError?.(f), r.onErrorRequest?.({ message: f });
119
117
  }
120
118
  } finally {
121
119
  g.value = !1, y.value = !0;
122
120
  }
123
- }, E = s?.debounce ? U(c, s.debounce) : c;
121
+ }, E = t?.debounce ? Z(c, t.debounce) : c;
124
122
  let d = null;
125
- return (s?.params || s?.data) && (T(() => {
126
- d && d(), d = k(
127
- () => JSON.stringify({ params: s.params, data: s.data }),
123
+ return (t?.params || t?.data) && (k(() => {
124
+ d && d(), d = x(
125
+ () => JSON.stringify({ params: t.params, data: t.data }),
128
126
  () => {
129
127
  E();
130
128
  },
131
129
  { immediate: !1 }
132
130
  );
133
- }), x(() => {
131
+ }), N(() => {
134
132
  d && d(), j?.abort();
135
- })), (s?.loadOnMount === void 0 || s.loadOnMount) && !y.value && (A.value ? (A.value = !1, c()) : E()), { result: R, errorMessage: l, zodErrors: v, isLoading: g, isDone: y, refetch: c };
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 };
136
134
  });
137
135
  }
138
- const D = t.mutations ?? {}, w = {};
139
- for (const e in D) {
140
- const r = D[e];
141
- r && (w[e] = (a) => {
142
- const s = m(), R = m(), l = m(), v = m(!1), g = m(!1), y = m(0);
143
- return { result: s, errorMessage: R, zodErrors: l, isLoading: v, isDone: g, uploadProgress: y, mutate: async (j) => {
144
- if (!v.value) {
145
- v.value = !0, R.value = void 0, y.value = 0;
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;
146
144
  try {
147
145
  const { data: u = {}, params: c } = j ?? {};
148
146
  let E = u ?? {}, d = {};
149
- if (r.isMultipart) {
147
+ if (s.isMultipart) {
150
148
  const n = new FormData();
151
- for (const [i, h] of Object.entries(u))
152
- h instanceof File || h instanceof Blob ? n.append(i, h) : Array.isArray(h) ? h.forEach((b) => {
153
- b instanceof File || b instanceof Blob ? n.append(i, b) : n.append(i, JSON.stringify(b));
154
- }) : typeof h == "object" && h !== null ? n.append(i, JSON.stringify(h)) : n.append(i, String(h));
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));
155
153
  E = n, d["Content-Type"] = "multipart/form-data";
156
- } else r.data && r.data.parse(u);
157
- r.params && c && r.params.parse(c);
158
- const o = await p.request({
159
- method: r.method,
160
- url: r.path,
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,
161
159
  data: E,
162
160
  params: c,
163
161
  headers: d,
@@ -167,24 +165,24 @@ function z(t) {
167
165
  y.value = i, a?.onUploadProgress?.(i);
168
166
  }
169
167
  }
170
- }), f = r.response ? r.response.parse(o.data) : o.data;
171
- s.value = f, a?.onResult?.(f);
168
+ }), f = s.response ? s.response.parse(o.data) : o.data;
169
+ t.value = f, a?.onResult?.(f);
172
170
  } catch (u) {
173
171
  if (u instanceof F) {
174
172
  const c = u.response?.data?.message || u.message || "An error occurred", E = u.response?.status, d = u.code;
175
- R.value = c, a?.onError?.(u), t.onErrorRequest?.({ message: c, status: E, code: d });
173
+ v.value = c, a?.onError?.(u), r.onErrorRequest?.({ message: c, status: E, code: d });
176
174
  } else if (u instanceof S) {
177
175
  l.value = u.issues || [];
178
176
  const E = `Validation error: ${l.value.map(
179
177
  (d) => `${d.path.join(".")}: ${d.message}`
180
178
  ).join(", ")}`;
181
- R.value = E, a?.onError?.(u), a?.onZodError?.(l.value), t.onErrorRequest?.({ message: E, code: "VALIDATION_ERROR" }), t.onZodError && t.onZodError(l.value);
179
+ v.value = E, a?.onError?.(u), a?.onZodError?.(l.value), r.onErrorRequest?.({ message: E, code: "VALIDATION_ERROR" }), r.onZodError && r.onZodError(l.value);
182
180
  } else {
183
181
  const c = u.message || "An error occurred";
184
- R.value = c, a?.onError?.(u), t.onErrorRequest?.({ message: c });
182
+ v.value = c, a?.onError?.(u), r.onErrorRequest?.({ message: c });
185
183
  }
186
184
  } finally {
187
- v.value = !1, g.value = !0;
185
+ R.value = !1, g.value = !0;
188
186
  }
189
187
  }
190
188
  } };
@@ -192,27 +190,27 @@ function z(t) {
192
190
  }
193
191
  return {
194
192
  query: M,
195
- mutation: w
193
+ mutation: D
196
194
  };
197
195
  }
198
- function V(t) {
199
- return t;
196
+ function V(r) {
197
+ return r;
200
198
  }
201
- function J(t) {
202
- return t;
199
+ function J(r) {
200
+ return r;
203
201
  }
204
- function Q(...t) {
205
- return Object.assign({}, ...t);
202
+ function Q(...r) {
203
+ return Object.assign({}, ...r);
206
204
  }
207
- function W(...t) {
208
- return Object.assign({}, ...t);
205
+ function W(...r) {
206
+ return Object.assign({}, ...r);
209
207
  }
210
208
  export {
211
- G as AxiosError,
209
+ H as AxiosError,
212
210
  z as createApiClient,
213
211
  J as defineMutation,
214
212
  V as defineQuery,
215
213
  W as mergeMutations,
216
214
  Q as mergeQueries,
217
- $ as z
215
+ U as z
218
216
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "vue-api-kit",
3
3
  "type": "module",
4
- "version": "1.6.0",
4
+ "version": "1.7.0",
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",