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 +16 -12
- package/dist/core/types.d.ts +1 -0
- package/dist/index.js +103 -105
- package/package.json +1 -1
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.
|
|
463
|
-
2.
|
|
464
|
-
3.
|
|
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,
|
|
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,
|
|
495
|
-
|
|
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
|
-
- ✅ **
|
|
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. **
|
|
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
|
package/dist/core/types.d.ts
CHANGED
|
@@ -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
|
|
2
|
+
import * as U from "zod";
|
|
3
3
|
import L, { AxiosError as F } from "axios";
|
|
4
|
-
import { AxiosError as
|
|
5
|
-
import { nextTick as
|
|
6
|
-
import { debounce as
|
|
7
|
-
function z(
|
|
8
|
-
const
|
|
9
|
-
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
|
-
...
|
|
13
|
+
...r.headers
|
|
14
14
|
},
|
|
15
|
-
withCredentials:
|
|
15
|
+
withCredentials: r.withCredentials ?? !1,
|
|
16
|
+
withXSRFToken: r.withXSRFToken ?? !1
|
|
16
17
|
});
|
|
17
|
-
let
|
|
18
|
-
|
|
18
|
+
let b = !1, q = null;
|
|
19
|
+
r.onBeforeRequest && h.interceptors.request.use(
|
|
19
20
|
async (e) => {
|
|
20
21
|
try {
|
|
21
|
-
return await
|
|
22
|
-
} catch (
|
|
23
|
-
return Promise.reject(
|
|
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
|
-
),
|
|
28
|
+
), r.onStartRequest && h.interceptors.request.use(
|
|
28
29
|
async (e) => {
|
|
29
30
|
try {
|
|
30
|
-
return await
|
|
31
|
-
} catch (
|
|
32
|
-
return Promise.reject(
|
|
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
|
-
),
|
|
37
|
-
(e) => (
|
|
38
|
-
(e) => (
|
|
39
|
-
),
|
|
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
|
|
42
|
+
const s = (a) => {
|
|
42
43
|
if (a)
|
|
43
|
-
for (const [
|
|
44
|
-
const l = `{${
|
|
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(
|
|
48
|
-
), delete a[
|
|
48
|
+
encodeURIComponent(String(v))
|
|
49
|
+
), delete a[t]);
|
|
49
50
|
}
|
|
50
51
|
};
|
|
51
|
-
return e.method !== "get" && e.data?.params &&
|
|
52
|
-
}),
|
|
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
|
|
59
|
-
if (
|
|
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) && !
|
|
62
|
-
|
|
59
|
+
if (e.response && (e.response.status === 403 || e.response.status === 419) && !s?._retry) {
|
|
60
|
+
s._retry = !0;
|
|
63
61
|
try {
|
|
64
|
-
return
|
|
65
|
-
|
|
66
|
-
}), await q),
|
|
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
|
|
66
|
+
return b = !1, q = null, Promise.reject(a);
|
|
69
67
|
}
|
|
70
68
|
}
|
|
71
69
|
return Promise.reject(e);
|
|
72
70
|
}
|
|
73
|
-
),
|
|
71
|
+
), h.interceptors.response.use(
|
|
74
72
|
(e) => e,
|
|
75
|
-
(e) => (
|
|
73
|
+
(e) => (T(() => {
|
|
76
74
|
e.code;
|
|
77
75
|
}), Promise.reject(e))
|
|
78
76
|
);
|
|
79
|
-
const
|
|
80
|
-
for (const e in
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
let
|
|
84
|
-
a && typeof a == "object" && ("loadOnMount" in a || "debounce" in a || "onResult" in a || "onError" in a || "data" in a ?
|
|
85
|
-
const
|
|
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
|
-
|
|
93
|
-
let o =
|
|
94
|
-
|
|
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:
|
|
97
|
-
url:
|
|
98
|
-
params:
|
|
94
|
+
method: s.method ?? "GET",
|
|
95
|
+
url: s.path,
|
|
96
|
+
params: t?.params,
|
|
99
97
|
signal: j.signal
|
|
100
98
|
};
|
|
101
|
-
|
|
102
|
-
const n = await
|
|
103
|
-
|
|
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,
|
|
108
|
-
l.value = f,
|
|
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
|
-
|
|
112
|
-
const n = `Validation error: ${
|
|
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,
|
|
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,
|
|
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 =
|
|
121
|
+
}, E = t?.debounce ? Z(c, t.debounce) : c;
|
|
124
122
|
let d = null;
|
|
125
|
-
return (
|
|
126
|
-
d && d(), d =
|
|
127
|
-
() => JSON.stringify({ params:
|
|
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
|
-
}),
|
|
131
|
+
}), N(() => {
|
|
134
132
|
d && d(), j?.abort();
|
|
135
|
-
})), (
|
|
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
|
|
139
|
-
for (const e in
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
const
|
|
143
|
-
return { result:
|
|
144
|
-
if (!
|
|
145
|
-
|
|
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 (
|
|
147
|
+
if (s.isMultipart) {
|
|
150
148
|
const n = new FormData();
|
|
151
|
-
for (const [i,
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}) : typeof
|
|
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
|
|
157
|
-
|
|
158
|
-
const o = await
|
|
159
|
-
method:
|
|
160
|
-
url:
|
|
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 =
|
|
171
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
182
|
+
v.value = c, a?.onError?.(u), r.onErrorRequest?.({ message: c });
|
|
185
183
|
}
|
|
186
184
|
} finally {
|
|
187
|
-
|
|
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:
|
|
193
|
+
mutation: D
|
|
196
194
|
};
|
|
197
195
|
}
|
|
198
|
-
function V(
|
|
199
|
-
return
|
|
196
|
+
function V(r) {
|
|
197
|
+
return r;
|
|
200
198
|
}
|
|
201
|
-
function J(
|
|
202
|
-
return
|
|
199
|
+
function J(r) {
|
|
200
|
+
return r;
|
|
203
201
|
}
|
|
204
|
-
function Q(...
|
|
205
|
-
return Object.assign({}, ...
|
|
202
|
+
function Q(...r) {
|
|
203
|
+
return Object.assign({}, ...r);
|
|
206
204
|
}
|
|
207
|
-
function W(...
|
|
208
|
-
return Object.assign({}, ...
|
|
205
|
+
function W(...r) {
|
|
206
|
+
return Object.assign({}, ...r);
|
|
209
207
|
}
|
|
210
208
|
export {
|
|
211
|
-
|
|
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
|
-
|
|
215
|
+
U as z
|
|
218
216
|
};
|
package/package.json
CHANGED