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