vue-api-kit 1.10.6 → 1.10.8

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
@@ -235,7 +235,7 @@ async function handleSubmit() {
235
235
  - ✅ **POST Queries**: Support for both GET and POST methods in queries for complex data retrieval
236
236
  - ✅ **Modular APIs**: Merge queries and mutations from separate files with full type safety
237
237
  - ✅ **Multi-Level Nesting**: Organize queries and mutations in nested structures with full type safety
238
- - ✅ **File Upload**: Support for multipart/form-data in mutations
238
+ - ✅ **File Upload**: Support for multipart/form-data with nested objects in mutations
239
239
  - ✅ **Path Parameters**: Automatic path parameter replacement
240
240
  - ✅ **Debouncing**: Built-in request debouncing
241
241
  - ✅ **CSRF Protection**: Automatic CSRF token refresh on 403/419 errors
@@ -434,7 +434,7 @@ const api = createApiClient({
434
434
  console.log('Request finished');
435
435
  },
436
436
 
437
- onErrorRequest: (error) => {
437
+ onError: (error) => {
438
438
  // Global error handler
439
439
  console.error('API Error:', error.message);
440
440
  },
@@ -832,6 +832,66 @@ async function handleUpload(file: File) {
832
832
  }
833
833
  ```
834
834
 
835
+ ### Nested Objects in Multipart
836
+
837
+ The multipart feature supports nested objects with automatic bracket notation flattening:
838
+
839
+ ```typescript
840
+ const api = createApiClient({
841
+ baseURL: 'https://api.example.com',
842
+ mutations: {
843
+ createProduct: {
844
+ method: 'POST',
845
+ path: '/products',
846
+ isMultipart: true,
847
+ response: z.object({
848
+ id: z.number(),
849
+ success: z.boolean()
850
+ })
851
+ }
852
+ }
853
+ });
854
+
855
+ // In component
856
+ const { mutate } = api.mutation.createProduct();
857
+
858
+ async function handleSubmit(file: File) {
859
+ await mutate({
860
+ data: {
861
+ code: 'PROD001',
862
+ name: 'Product Name',
863
+ description: 'Product description',
864
+ // Nested objects are automatically flattened with bracket notation
865
+ image: {
866
+ file_url: 'https://example.com/existing.jpg',
867
+ file: file
868
+ }
869
+ }
870
+ });
871
+ }
872
+
873
+ // The FormData will be sent as:
874
+ // code=PROD001
875
+ // name=Product Name
876
+ // description=Product description
877
+ // image[file_url]=https://example.com/existing.jpg
878
+ // image[file]=<File>
879
+ ```
880
+
881
+ You can also use flat bracket notation directly:
882
+
883
+ ```typescript
884
+ await mutate({
885
+ data: {
886
+ 'image[file]': file,
887
+ 'image[file_url]': 'https://example.com/existing.jpg',
888
+ code: 'PROD001'
889
+ }
890
+ });
891
+ ```
892
+
893
+ Both approaches work seamlessly together, giving you flexibility in how you structure your data.
894
+
835
895
  ## 🔒 CSRF Token Protection
836
896
 
837
897
  The client includes built-in CSRF token protection, perfect for Laravel Sanctum or similar CSRF-based authentication systems.
@@ -104,7 +104,22 @@ export type MutationHooksFromDefinitions<M> = {
104
104
  * csrfRefreshEndpoint: "/auth/refresh-csrf",
105
105
  * queries: { getUsers: { path: "/users" } },
106
106
  * mutations: { createUser: { method: "POST", path: "/users" } },
107
- * onErrorRequest: (error) => console.error(error.message)
107
+ * onBeforeRequest: async (config) => {
108
+ * // Modify config before request
109
+ * return config;
110
+ * },
111
+ * onStartRequest: async () => {
112
+ * console.log("Request started");
113
+ * },
114
+ * onFinishRequest: async () => {
115
+ * console.log("Request finished");
116
+ * },
117
+ * onError: ({ err, message }) => {
118
+ * console.error("API Error:", message);
119
+ * },
120
+ * onZodError: (zodError) => {
121
+ * console.error("Validation Error:", zodError);
122
+ * }
108
123
  * };
109
124
  * @example
110
125
  * // Nested structure
package/dist/index.d.ts CHANGED
@@ -2,5 +2,5 @@ export * as z from 'zod';
2
2
  export { createApiClient } from './core/client';
3
3
  export { mergeQueries, mergeMutations, defineQuery, defineMutation } from './core/merge';
4
4
  export type * from './core/types';
5
- export type { ZodError } from 'zod';
5
+ export { ZodError } from 'zod';
6
6
  export { AxiosError } from 'axios';
package/dist/index.js CHANGED
@@ -1,17 +1,33 @@
1
- import A, { ZodError as F } from "zod";
2
- import * as G from "zod";
3
- import U, { AxiosError as Z } from "axios";
4
- import { AxiosError as ae } from "axios";
5
- import { nextTick as _, ref as h, onMounted as N, watch as Q, onBeforeUnmount as z } from "vue";
6
- import { debounce as J } from "lodash-es";
7
- function H(e) {
1
+ import A, { ZodError as Z } from "zod";
2
+ import * as I from "zod";
3
+ import { ZodError as ne } from "zod";
4
+ import U, { AxiosError as $ } from "axios";
5
+ import { AxiosError as ue } from "axios";
6
+ import { nextTick as _, ref as g, onMounted as Q, watch as z, onBeforeUnmount as N } from "vue";
7
+ import { debounce as H } from "lodash-es";
8
+ function W(e) {
8
9
  return e && typeof e == "object" && e !== null && typeof e.path == "string";
9
10
  }
10
- function W(e) {
11
+ function X(e) {
11
12
  return e && typeof e == "object" && e !== null && typeof e.path == "string" && typeof e.method == "string";
12
13
  }
13
- function Y(e) {
14
- const p = U.create({
14
+ function F(e, a, h = "") {
15
+ if (a instanceof File || a instanceof Blob)
16
+ e.append(h, a);
17
+ else if (Array.isArray(a))
18
+ a.forEach((l) => {
19
+ l instanceof File || l instanceof Blob ? e.append(h, l) : typeof l == "object" && l !== null ? F(e, l, h) : e.append(h, String(l));
20
+ });
21
+ else if (typeof a == "object" && a !== null)
22
+ for (const [l, B] of Object.entries(a)) {
23
+ const k = h ? `${h}[${l}]` : l;
24
+ F(e, B, k);
25
+ }
26
+ else
27
+ e.append(h, String(a));
28
+ }
29
+ function K(e) {
30
+ const a = U.create({
15
31
  baseURL: e.baseURL,
16
32
  headers: {
17
33
  "Content-Type": "application/json",
@@ -21,212 +37,210 @@ function Y(e) {
21
37
  withCredentials: e.withCredentials ?? !1,
22
38
  withXSRFToken: e.withXSRFToken ?? !1
23
39
  });
24
- let B = !1, w = null;
25
- e.onBeforeRequest && p.interceptors.request.use(
40
+ let h = !1, l = null;
41
+ e.onBeforeRequest && a.interceptors.request.use(
26
42
  async (r) => {
27
43
  try {
28
44
  return await e.onBeforeRequest(r) || r;
29
- } catch (n) {
30
- return Promise.reject(n);
45
+ } catch (u) {
46
+ return Promise.reject(u);
31
47
  }
32
48
  },
33
49
  (r) => Promise.reject(r)
34
- ), e.onStartRequest && p.interceptors.request.use(
50
+ ), e.onStartRequest && a.interceptors.request.use(
35
51
  async (r) => {
36
52
  try {
37
53
  return await e.onStartRequest(), r;
38
- } catch (n) {
39
- return Promise.reject(n);
54
+ } catch (u) {
55
+ return Promise.reject(u);
40
56
  }
41
57
  },
42
58
  (r) => Promise.reject(r)
43
- ), e.onFinishRequest && p.interceptors.response.use(
59
+ ), e.onFinishRequest && a.interceptors.response.use(
44
60
  (r) => (e.onFinishRequest(), r),
45
61
  (r) => (e.onFinishRequest(), Promise.reject(r))
46
- ), p.interceptors.request.use((r) => {
62
+ ), a.interceptors.request.use((r) => {
47
63
  if (!r.url) return r;
48
- const n = (m) => {
49
- if (m)
50
- for (const [c, s] of Object.entries(m)) {
51
- const o = `{${c}}`;
52
- r.url.includes(o) && (r.url = r.url.replace(
53
- o,
54
- encodeURIComponent(String(s))
55
- ), delete m[c]);
64
+ const u = (v) => {
65
+ if (v)
66
+ for (const [m, o] of Object.entries(v)) {
67
+ const s = `{${m}}`;
68
+ r.url.includes(s) && (r.url = r.url.replace(
69
+ s,
70
+ encodeURIComponent(String(o))
71
+ ), delete v[m]);
56
72
  }
57
73
  };
58
- return r.method !== "get" && r.data?.params && n(r.data.params), n(r.params), r;
59
- }), e.csrfRefreshEndpoint && p.interceptors.response.use(
74
+ return r.method !== "get" && r.data?.params && u(r.data.params), u(r.params), r;
75
+ }), e.csrfRefreshEndpoint && a.interceptors.response.use(
60
76
  (r) => r,
61
77
  async (r) => {
62
- const n = r.config;
63
- if (n?.url === e.csrfRefreshEndpoint)
78
+ const u = r.config;
79
+ if (u?.url === e.csrfRefreshEndpoint)
64
80
  return Promise.reject(r);
65
- if (r.response && (r.response.status === 403 || r.response.status === 419) && !n?._retry) {
66
- n._retry = !0;
81
+ if (r.response && (r.response.status === 403 || r.response.status === 419) && !u?._retry) {
82
+ u._retry = !0;
67
83
  try {
68
- return B && w ? await w : (B = !0, w = p.get(e.csrfRefreshEndpoint).then(() => {
69
- B = !1, w = null;
70
- }), await w), p.request(n);
71
- } catch (m) {
72
- return B = !1, w = null, Promise.reject(m);
84
+ return h && l ? await l : (h = !0, l = a.get(e.csrfRefreshEndpoint).then(() => {
85
+ h = !1, l = null;
86
+ }), await l), a.request(u);
87
+ } catch (v) {
88
+ return h = !1, l = null, Promise.reject(v);
73
89
  }
74
90
  }
75
91
  return Promise.reject(r);
76
92
  }
77
- ), p.interceptors.response.use(
93
+ ), a.interceptors.response.use(
78
94
  (r) => r,
79
95
  (r) => (_(() => {
80
- r.code;
96
+ r?.code;
81
97
  }), Promise.reject(r))
82
98
  );
83
- function D(r) {
84
- const n = {};
85
- for (const m in r) {
86
- const c = r[m];
87
- if (c)
88
- if (H(c)) {
89
- const s = c;
90
- n[m] = (o) => {
91
- let a;
92
- o && typeof o == "object" && ("loadOnMount" in o || "debounce" in o || "onResult" in o || "onError" in o || "onZodError" in o || "onBeforeRequest" in o || "params" in o || "data" in o ? a = o : a = { params: o });
93
- const g = h(), v = h(), E = h(), C = h(!1), j = h(!1), k = h(!0);
99
+ function B(r) {
100
+ const u = {};
101
+ for (const v in r) {
102
+ const m = r[v];
103
+ if (m)
104
+ if (W(m)) {
105
+ const o = m;
106
+ u[v] = (s) => {
107
+ let n;
108
+ s && typeof s == "object" && ("loadOnMount" in s || "debounce" in s || "onResult" in s || "onError" in s || "onZodError" in s || "onBeforeRequest" in s || "params" in s || "data" in s ? n = s : n = { params: s });
109
+ const R = g(), E = g(), p = g(), C = g(!1), w = g(!1), M = g(!0);
94
110
  let P = new AbortController();
95
111
  const i = () => {
96
112
  P?.abort(), P = new AbortController();
97
- }, l = async () => {
98
- C.value && i(), C.value = !0, v.value = void 0;
113
+ }, f = async () => {
114
+ C.value && i(), C.value = !0, E.value = void 0;
99
115
  try {
100
- s.params && a?.params && s.params.parse(a.params);
101
- let t = a?.data;
102
- s.data && t && s.data.parse(t);
103
- const u = {
104
- method: s.method ?? "GET",
105
- url: s.path,
106
- params: a?.params,
116
+ o.params && n?.params && o.params.parse(n.params);
117
+ let t = n?.data;
118
+ o.data && t && o.data.parse(t);
119
+ const c = {
120
+ method: o.method ?? "GET",
121
+ url: o.path,
122
+ params: n?.params,
107
123
  signal: P.signal
108
124
  };
109
- if (s.method === "POST" && t && (u.data = t), s.onBeforeRequest) {
110
- const d = await s.onBeforeRequest(u);
111
- d !== void 0 && Object.assign(u, d);
125
+ if (o.method === "POST" && t && (c.data = t), o.onBeforeRequest) {
126
+ const y = await o.onBeforeRequest(c);
127
+ y !== void 0 && Object.assign(c, y);
112
128
  }
113
- if (a?.onBeforeRequest) {
114
- const d = await a.onBeforeRequest(u);
115
- d !== void 0 && Object.assign(u, d);
129
+ if (n?.onBeforeRequest) {
130
+ const y = await n.onBeforeRequest(c);
131
+ y !== void 0 && Object.assign(c, y);
116
132
  }
117
- v.value = void 0, E.value = void 0;
118
- const b = await p.request(u), f = s.response ? s.response.parse(b.data) : b.data;
119
- g.value = f, a?.onResult?.(f);
133
+ E.value = void 0, p.value = void 0;
134
+ const j = await a.request(c), d = o.response ? o.response.parse(j.data) : j.data;
135
+ R.value = d, n?.onResult?.(d);
120
136
  } catch (t) {
121
- if (t instanceof Z) {
137
+ if (t instanceof $) {
122
138
  if (t.code !== "ERR_CANCELED") {
123
- const u = t.response?.data?.message || t.message || "An error occurred";
124
- v.value = u, a?.onError?.(t), e.onError?.({ err: t, message: u });
139
+ const c = t.response?.data?.message || t.message || "An error occurred";
140
+ E.value = c, n?.onError?.(t), e.onError?.({ err: t, message: c });
125
141
  }
126
- } else if (t instanceof F) {
127
- E.value = A.flattenError(t);
128
- const u = Object.keys(E.value.fieldErrors).length, b = `${Object.values(E.value.fieldErrors).at(0)}.${u > 1 ? ` (and ${u - 1} more errors)` : ""}`;
129
- v.value = b, a?.onError?.(t), a?.onZodError?.(A.flattenError(t)), e.onError?.({ err: t, message: b }), e.onZodError && e.onZodError(A.flattenError(t));
142
+ } else if (t instanceof Z) {
143
+ p.value = A.flattenError(t);
144
+ const c = Object.keys(p.value.fieldErrors).length, j = `${Object.values(p.value.fieldErrors).at(0)}.${c > 1 ? ` (and ${c - 1} more errors)` : ""}`;
145
+ E.value = j, n?.onError?.(t), n?.onZodError?.(A.flattenError(t)), e.onError?.({ err: t, message: j }), e.onZodError && e.onZodError(A.flattenError(t));
130
146
  } else {
131
- const u = t.message || "An error occurred";
132
- v.value = u, a?.onError?.(u), e.onError?.({ err: t, message: u });
147
+ const c = t.message || "An error occurred";
148
+ E.value = c, n?.onError?.(c), e.onError?.({ err: t, message: c });
133
149
  }
134
150
  } finally {
135
- C.value = !1, j.value = !0;
151
+ C.value = !1, w.value = !0;
136
152
  }
137
- }, R = a?.debounce ? J(l, a.debounce) : l;
138
- let q = null;
139
- return (a?.params || a?.data) && (N(() => {
140
- q && q(), q = Q(
141
- () => JSON.stringify({ params: a.params, data: a.data }),
153
+ }, q = n?.debounce ? H(f, n.debounce) : f;
154
+ let b = null;
155
+ return (n?.params || n?.data) && (Q(() => {
156
+ b && b(), b = z(
157
+ () => JSON.stringify({ params: n.params, data: n.data }),
142
158
  () => {
143
- R();
159
+ q();
144
160
  },
145
161
  { immediate: !1 }
146
162
  );
147
- }), z(() => {
148
- q && q(), P?.abort();
149
- })), (a?.loadOnMount === void 0 || a.loadOnMount) && !j.value && (k.value ? (k.value = !1, l()) : R()), { result: g, errorMessage: v, zodError: E, isLoading: C, isDone: j, refetch: l };
163
+ }), N(() => {
164
+ b && b(), P?.abort();
165
+ })), (n?.loadOnMount === void 0 || n.loadOnMount) && !w.value && (M.value ? (M.value = !1, f()) : q()), { result: R, errorMessage: E, zodError: p, isLoading: C, isDone: w, refetch: f };
150
166
  };
151
- } else typeof c == "object" && (n[m] = D(c));
167
+ } else typeof m == "object" && (u[v] = B(m));
152
168
  }
153
- return n;
169
+ return u;
154
170
  }
155
- const x = e.queries ?? {}, L = D(x);
171
+ const k = e.queries ?? {}, x = B(k);
156
172
  function S(r) {
157
- const n = {};
158
- for (const m in r) {
159
- const c = r[m];
160
- if (c)
161
- if (W(c)) {
162
- const s = c;
163
- n[m] = (o) => {
164
- const a = h(), g = h(), v = h(), E = h(!1), C = h(!1), j = h(0);
165
- return { result: a, errorMessage: g, zodError: v, isLoading: E, isDone: C, uploadProgress: j, mutate: async (P) => {
166
- if (!E.value) {
167
- E.value = !0, g.value = void 0, j.value = 0;
173
+ const u = {};
174
+ for (const v in r) {
175
+ const m = r[v];
176
+ if (m)
177
+ if (X(m)) {
178
+ const o = m;
179
+ u[v] = (s) => {
180
+ const n = g(), R = g(), E = g(), p = g(!1), C = g(!1), w = g(0);
181
+ return { result: n, errorMessage: R, zodError: E, isLoading: p, isDone: C, uploadProgress: w, mutate: async (P) => {
182
+ if (!p.value) {
183
+ p.value = !0, R.value = void 0, w.value = 0;
168
184
  try {
169
- const { data: i = {}, params: l } = P ?? {};
170
- let R = i ?? {}, q = {};
171
- if (s.isMultipart) {
172
- const f = new FormData();
173
- for (const [d, y] of Object.entries(i))
174
- y instanceof File || y instanceof Blob ? f.append(d, y) : Array.isArray(y) ? y.forEach((M) => {
175
- M instanceof File || M instanceof Blob ? f.append(d, M) : f.append(d, JSON.stringify(M));
176
- }) : typeof y == "object" && y !== null ? f.append(d, JSON.stringify(y)) : f.append(d, String(y));
177
- R = f, q["Content-Type"] = "multipart/form-data";
178
- } else s.data && s.data.parse(i);
179
- s.params && l && s.params.parse(l);
185
+ const { data: i = {}, params: f } = P ?? {};
186
+ let q = i ?? {}, b = {};
187
+ if (o.isMultipart) {
188
+ const d = new FormData();
189
+ for (const [y, D] of Object.entries(i))
190
+ F(d, D, y);
191
+ q = d, b["Content-Type"] = "multipart/form-data";
192
+ } else o.data && o.data.parse(i);
193
+ o.params && f && o.params.parse(f);
180
194
  const t = {
181
- method: s.method,
182
- url: s.path,
183
- data: R,
184
- params: l,
185
- headers: q,
186
- onUploadProgress: (f) => {
187
- if (f.total) {
188
- const d = Math.round(f.loaded * 100 / f.total);
189
- j.value = d, o?.onUploadProgress?.(d);
195
+ method: o.method,
196
+ url: o.path,
197
+ data: q,
198
+ params: f,
199
+ headers: b,
200
+ onUploadProgress: (d) => {
201
+ if (d.total) {
202
+ const y = Math.round(d.loaded * 100 / d.total);
203
+ w.value = y, s?.onUploadProgress?.(y);
190
204
  }
191
205
  }
192
206
  };
193
- if (s.onBeforeRequest) {
194
- const f = await s.onBeforeRequest(t);
195
- f !== void 0 && Object.assign(t, f);
207
+ if (o.onBeforeRequest) {
208
+ const d = await o.onBeforeRequest(t);
209
+ d !== void 0 && Object.assign(t, d);
196
210
  }
197
- if (o?.onBeforeRequest) {
198
- const f = await o.onBeforeRequest(t);
199
- f !== void 0 && Object.assign(t, f);
211
+ if (s?.onBeforeRequest) {
212
+ const d = await s.onBeforeRequest(t);
213
+ d !== void 0 && Object.assign(t, d);
200
214
  }
201
- g.value = void 0, v.value = void 0;
202
- const u = await p.request(t), b = s.response ? s.response.parse(u.data) : u.data;
203
- a.value = b, o?.onResult?.(b);
215
+ R.value = void 0, E.value = void 0;
216
+ const c = await a.request(t), j = o.response ? o.response.parse(c.data) : c.data;
217
+ n.value = j, s?.onResult?.(j);
204
218
  } catch (i) {
205
- if (i instanceof Z) {
206
- const l = i.response?.data?.message || i.message || "An error occurred";
207
- g.value = l, o?.onError?.(i), e.onError?.({ err: i, message: l });
208
- } else if (i instanceof F) {
209
- v.value = A.flattenError(i);
210
- const l = Object.keys(v.value.fieldErrors).length, R = `${Object.values(v.value.fieldErrors).at(0)}.${l > 1 ? ` (and ${l - 1} more errors)` : ""}`;
211
- g.value = R, o?.onError?.(i), o?.onZodError?.(A.flattenError(i)), e.onError?.({ err: i, message: R }), e.onZodError && e.onZodError(A.flattenError(i));
219
+ if (i instanceof $) {
220
+ const f = i.response?.data?.message || i.message || "An error occurred";
221
+ R.value = f, s?.onError?.(i), e.onError?.({ err: i, message: f });
222
+ } else if (i instanceof Z) {
223
+ E.value = A.flattenError(i);
224
+ const f = Object.keys(E.value.fieldErrors).length, q = `${Object.values(E.value.fieldErrors).at(0)}.${f > 1 ? ` (and ${f - 1} more errors)` : ""}`;
225
+ R.value = q, s?.onError?.(i), s?.onZodError?.(A.flattenError(i)), e.onError?.({ err: i, message: q }), e.onZodError && e.onZodError(A.flattenError(i));
212
226
  } else {
213
- const l = i.message || "An error occurred";
214
- g.value = l, o?.onError?.(i), e.onError?.({ err: i, message: l });
227
+ const f = i.message || "An error occurred";
228
+ R.value = f, s?.onError?.(i), e.onError?.({ err: i, message: f });
215
229
  }
216
230
  } finally {
217
- E.value = !1, C.value = !0;
231
+ p.value = !1, C.value = !0;
218
232
  }
219
233
  }
220
234
  } };
221
235
  };
222
- } else typeof c == "object" && (n[m] = S(c));
236
+ } else typeof m == "object" && (u[v] = S(m));
223
237
  }
224
- return n;
238
+ return u;
225
239
  }
226
- const T = e.mutations ?? {}, $ = S(T);
240
+ const T = e.mutations ?? {}, L = S(T);
227
241
  return {
228
- query: L,
229
- mutation: $
242
+ query: x,
243
+ mutation: L
230
244
  };
231
245
  }
232
246
  function O(e) {
@@ -242,11 +256,12 @@ function te(...e) {
242
256
  return Object.assign({}, ...e);
243
257
  }
244
258
  export {
245
- ae as AxiosError,
246
- Y as createApiClient,
259
+ ue as AxiosError,
260
+ ne as ZodError,
261
+ K as createApiClient,
247
262
  ee as defineMutation,
248
263
  O as defineQuery,
249
264
  te as mergeMutations,
250
265
  re as mergeQueries,
251
- G as z
266
+ I as z
252
267
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "vue-api-kit",
3
3
  "type": "module",
4
- "version": "1.10.6",
4
+ "version": "1.10.8",
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",