vue-api-kit 1.12.1 → 1.14.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 +2 -0
- package/dist/core/types.d.ts +3 -0
- package/dist/index.js +174 -161
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -436,6 +436,8 @@ mutations: {
|
|
|
436
436
|
method: 'POST',
|
|
437
437
|
path: '/upload',
|
|
438
438
|
isMultipart: true,
|
|
439
|
+
// Optional: Laravel-friendly boolean serialization in multipart (true => "1", false => "0")
|
|
440
|
+
multipartBooleanStyle: 'numeric',
|
|
439
441
|
response: z.object({ url: z.string() })
|
|
440
442
|
}
|
|
441
443
|
}
|
package/dist/core/types.d.ts
CHANGED
|
@@ -64,6 +64,7 @@ export interface ApiMutation<TData extends ZodType<any> | undefined = ZodType<an
|
|
|
64
64
|
data?: TData;
|
|
65
65
|
response?: TResponse;
|
|
66
66
|
isMultipart?: boolean;
|
|
67
|
+
multipartBooleanStyle?: "trueFalse" | "numeric";
|
|
67
68
|
onBeforeRequest?: (config: InternalAxiosRequestConfig<any>) => Promise<any> | void | any;
|
|
68
69
|
}
|
|
69
70
|
/**
|
|
@@ -215,6 +216,7 @@ export interface QueryResult<TResult> {
|
|
|
215
216
|
isLoading: Ref<boolean>;
|
|
216
217
|
isDone: Ref<boolean>;
|
|
217
218
|
refetch: () => Promise<void>;
|
|
219
|
+
cancel: () => void;
|
|
218
220
|
}
|
|
219
221
|
/**
|
|
220
222
|
* Return type from a mutation hook
|
|
@@ -239,4 +241,5 @@ export interface MutationResult<TResult, TData = any, TParams = any> {
|
|
|
239
241
|
data?: TData;
|
|
240
242
|
params?: TParams;
|
|
241
243
|
}) => Promise<void>;
|
|
244
|
+
cancel: () => void;
|
|
242
245
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,37 +1,42 @@
|
|
|
1
|
-
import
|
|
2
|
-
import * as
|
|
3
|
-
import { ZodError as
|
|
4
|
-
import
|
|
5
|
-
import { AxiosError as
|
|
6
|
-
import { nextTick as
|
|
7
|
-
import { debounce as
|
|
8
|
-
function
|
|
1
|
+
import B, { ZodError as T } from "zod";
|
|
2
|
+
import * as K from "zod";
|
|
3
|
+
import { ZodError as ie } from "zod";
|
|
4
|
+
import D, { AxiosError as L } from "axios";
|
|
5
|
+
import { AxiosError as fe } from "axios";
|
|
6
|
+
import { nextTick as N, ref as E, onMounted as H, watch as W, onBeforeUnmount as X } from "vue";
|
|
7
|
+
import { debounce as G } from "lodash-es";
|
|
8
|
+
function J(e) {
|
|
9
9
|
return e && typeof e == "object" && e !== null && typeof e.path == "string";
|
|
10
10
|
}
|
|
11
|
-
function
|
|
11
|
+
function V(e) {
|
|
12
12
|
return e && typeof e == "object" && e !== null && typeof e.path == "string" && typeof e.method == "string";
|
|
13
13
|
}
|
|
14
|
-
function
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
14
|
+
function U(e) {
|
|
15
|
+
return e?.code === "ERR_CANCELED" || e?.name === "CanceledError";
|
|
16
|
+
}
|
|
17
|
+
function Z(e, n, h = "", g = "trueFalse") {
|
|
18
|
+
if (n !== void 0)
|
|
19
|
+
if (n instanceof File || n instanceof Blob)
|
|
20
|
+
e.append(h, n);
|
|
21
|
+
else if (n instanceof Date)
|
|
22
|
+
e.append(h, n.toISOString());
|
|
23
|
+
else if (typeof n == "boolean")
|
|
24
|
+
e.append(h, g === "numeric" ? n ? "1" : "0" : String(n));
|
|
25
|
+
else if (Array.isArray(n))
|
|
26
|
+
n.forEach((i, M) => {
|
|
27
|
+
const b = `${h}[${M}]`;
|
|
28
|
+
i instanceof File || i instanceof Blob ? e.append(b, i) : i instanceof Date ? e.append(b, i.toISOString()) : typeof i == "boolean" ? e.append(b, g === "numeric" ? i ? "1" : "0" : String(i)) : typeof i == "object" && i !== null ? Z(e, i, b, g) : i !== void 0 && e.append(b, i);
|
|
24
29
|
});
|
|
25
|
-
else if (typeof
|
|
26
|
-
for (const [i,
|
|
27
|
-
const
|
|
28
|
-
|
|
30
|
+
else if (typeof n == "object" && n !== null)
|
|
31
|
+
for (const [i, M] of Object.entries(n)) {
|
|
32
|
+
const b = h ? `${h}[${i}]` : i;
|
|
33
|
+
Z(e, M, b, g);
|
|
29
34
|
}
|
|
30
35
|
else
|
|
31
|
-
e.append(
|
|
36
|
+
e.append(h, n);
|
|
32
37
|
}
|
|
33
|
-
function
|
|
34
|
-
const
|
|
38
|
+
function te(e) {
|
|
39
|
+
const n = D.create({
|
|
35
40
|
baseURL: e.baseURL,
|
|
36
41
|
headers: {
|
|
37
42
|
"Content-Type": "application/json",
|
|
@@ -41,171 +46,175 @@ function K(e) {
|
|
|
41
46
|
withCredentials: e.withCredentials ?? !1,
|
|
42
47
|
withXSRFToken: e.withXSRFToken ?? !1
|
|
43
48
|
});
|
|
44
|
-
let
|
|
45
|
-
e.onBeforeRequest &&
|
|
49
|
+
let h = !1, g = null;
|
|
50
|
+
e.onBeforeRequest && n.interceptors.request.use(
|
|
46
51
|
async (r) => {
|
|
47
52
|
try {
|
|
48
53
|
return await e.onBeforeRequest(r) || r;
|
|
49
|
-
} catch (
|
|
50
|
-
return Promise.reject(
|
|
54
|
+
} catch (l) {
|
|
55
|
+
return Promise.reject(l);
|
|
51
56
|
}
|
|
52
57
|
},
|
|
53
58
|
(r) => Promise.reject(r)
|
|
54
|
-
), e.onStartRequest &&
|
|
59
|
+
), e.onStartRequest && n.interceptors.request.use(
|
|
55
60
|
async (r) => {
|
|
56
61
|
try {
|
|
57
62
|
return await e.onStartRequest(), r;
|
|
58
|
-
} catch (
|
|
59
|
-
return Promise.reject(
|
|
63
|
+
} catch (l) {
|
|
64
|
+
return Promise.reject(l);
|
|
60
65
|
}
|
|
61
66
|
},
|
|
62
67
|
(r) => Promise.reject(r)
|
|
63
|
-
), e.onFinishRequest &&
|
|
68
|
+
), e.onFinishRequest && n.interceptors.response.use(
|
|
64
69
|
(r) => (e.onFinishRequest(), r),
|
|
65
70
|
(r) => (e.onFinishRequest(), Promise.reject(r))
|
|
66
|
-
),
|
|
71
|
+
), n.interceptors.request.use((r) => {
|
|
67
72
|
if (!r.url) return r;
|
|
68
|
-
const
|
|
69
|
-
if (
|
|
70
|
-
for (const [
|
|
71
|
-
const s = `{${
|
|
73
|
+
const l = (p) => {
|
|
74
|
+
if (p)
|
|
75
|
+
for (const [v, t] of Object.entries(p)) {
|
|
76
|
+
const s = `{${v}}`;
|
|
72
77
|
r.url.includes(s) && (r.url = r.url.replace(
|
|
73
78
|
s,
|
|
74
79
|
encodeURIComponent(String(t))
|
|
75
|
-
), delete v
|
|
80
|
+
), delete p[v]);
|
|
76
81
|
}
|
|
77
82
|
};
|
|
78
|
-
return r.method !== "get" && r.data?.params &&
|
|
79
|
-
}), e.csrfRefreshEndpoint &&
|
|
83
|
+
return r.method !== "get" && r.data?.params && l(r.data.params), l(r.params), r;
|
|
84
|
+
}), e.csrfRefreshEndpoint && n.interceptors.response.use(
|
|
80
85
|
(r) => r,
|
|
81
86
|
async (r) => {
|
|
82
|
-
const
|
|
83
|
-
if (
|
|
87
|
+
const l = r.config;
|
|
88
|
+
if (l?.url === e.csrfRefreshEndpoint)
|
|
84
89
|
return Promise.reject(r);
|
|
85
|
-
if (r.response && (r.response.status === 403 || r.response.status === 419) && !
|
|
86
|
-
|
|
90
|
+
if (r.response && (r.response.status === 403 || r.response.status === 419) && !l?._retry) {
|
|
91
|
+
l._retry = !0;
|
|
87
92
|
try {
|
|
88
|
-
return
|
|
89
|
-
|
|
90
|
-
}), await
|
|
91
|
-
} catch (
|
|
92
|
-
return
|
|
93
|
+
return h && g ? await g : (h = !0, g = n.get(e.csrfRefreshEndpoint).then(() => {
|
|
94
|
+
h = !1, g = null;
|
|
95
|
+
}), await g), n.request(l);
|
|
96
|
+
} catch (p) {
|
|
97
|
+
return h = !1, g = null, Promise.reject(p);
|
|
93
98
|
}
|
|
94
99
|
}
|
|
95
100
|
return Promise.reject(r);
|
|
96
101
|
}
|
|
97
|
-
),
|
|
102
|
+
), n.interceptors.response.use(
|
|
98
103
|
(r) => r,
|
|
99
|
-
(r) => (
|
|
104
|
+
(r) => (N(() => {
|
|
100
105
|
r?.code;
|
|
101
106
|
}), Promise.reject(r))
|
|
102
107
|
);
|
|
103
|
-
function
|
|
104
|
-
const
|
|
105
|
-
for (const
|
|
106
|
-
const
|
|
107
|
-
if (
|
|
108
|
-
if (
|
|
109
|
-
const t =
|
|
110
|
-
|
|
111
|
-
let
|
|
112
|
-
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 ?
|
|
113
|
-
const
|
|
114
|
-
let
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
},
|
|
118
|
-
|
|
108
|
+
function i(r) {
|
|
109
|
+
const l = {};
|
|
110
|
+
for (const p in r) {
|
|
111
|
+
const v = r[p];
|
|
112
|
+
if (v)
|
|
113
|
+
if (J(v)) {
|
|
114
|
+
const t = v;
|
|
115
|
+
l[p] = (s) => {
|
|
116
|
+
let a;
|
|
117
|
+
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 ? a = s : a = { params: s });
|
|
118
|
+
const C = E(), m = E(), y = E(), j = E(!1), A = E(!1), w = E(!0);
|
|
119
|
+
let k = null, $ = 0;
|
|
120
|
+
const F = () => {
|
|
121
|
+
k?.abort(), k = null, j.value = !1;
|
|
122
|
+
}, u = async () => {
|
|
123
|
+
j.value && F();
|
|
124
|
+
const S = ++$;
|
|
125
|
+
k = new AbortController(), j.value = !0, m.value = void 0;
|
|
119
126
|
try {
|
|
120
|
-
t.params &&
|
|
121
|
-
let o =
|
|
127
|
+
t.params && a?.params && t.params.parse(a.params);
|
|
128
|
+
let o = a?.data;
|
|
122
129
|
t.data && o && t.data.parse(o);
|
|
123
|
-
const
|
|
130
|
+
const c = {
|
|
124
131
|
method: t.method ?? "GET",
|
|
125
132
|
url: t.path,
|
|
126
|
-
params:
|
|
127
|
-
signal:
|
|
133
|
+
params: a?.params,
|
|
134
|
+
signal: k.signal,
|
|
128
135
|
headers: {}
|
|
129
136
|
};
|
|
130
|
-
if (t.method === "POST" && o && (
|
|
131
|
-
const
|
|
132
|
-
|
|
137
|
+
if (t.method === "POST" && o && (c.data = o), t.onBeforeRequest) {
|
|
138
|
+
const q = await t.onBeforeRequest(c);
|
|
139
|
+
q !== void 0 && Object.assign(c, q);
|
|
133
140
|
}
|
|
134
|
-
if (
|
|
135
|
-
const
|
|
136
|
-
|
|
141
|
+
if (a?.onBeforeRequest) {
|
|
142
|
+
const q = await a.onBeforeRequest(c);
|
|
143
|
+
q !== void 0 && Object.assign(c, q);
|
|
137
144
|
}
|
|
138
|
-
|
|
139
|
-
const
|
|
140
|
-
|
|
145
|
+
m.value = void 0, y.value = void 0;
|
|
146
|
+
const P = await n.request(c), d = t.response ? t.response.parse(P.data) : P.data;
|
|
147
|
+
C.value = d, a?.onResult?.(d);
|
|
141
148
|
} catch (o) {
|
|
142
|
-
if (o
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
}
|
|
147
|
-
} else if (o instanceof
|
|
148
|
-
console.warn("Zod validation error at:", t.method, t.path),
|
|
149
|
-
const
|
|
150
|
-
|
|
149
|
+
if (U(o))
|
|
150
|
+
return;
|
|
151
|
+
if (o instanceof L) {
|
|
152
|
+
const c = o.response?.data?.message || o.message || "An error occurred";
|
|
153
|
+
m.value = c, a?.onError?.(o), e.onError?.({ err: o, message: c });
|
|
154
|
+
} else if (o instanceof T) {
|
|
155
|
+
console.warn("Zod validation error at:", t.method, t.path), y.value = B.flattenError(o);
|
|
156
|
+
const c = Object.keys(y.value.fieldErrors).length, P = `${Object.values(y.value.fieldErrors).at(0)}.${c > 1 ? ` (and ${c - 1} more errors)` : ""}`;
|
|
157
|
+
m.value = P, a?.onError?.(o), a?.onZodError?.(B.flattenError(o)), e.onError?.({ err: o, message: P }), e.onZodError && e.onZodError(B.flattenError(o));
|
|
151
158
|
} else {
|
|
152
|
-
const
|
|
153
|
-
|
|
159
|
+
const c = o.message || "An error occurred";
|
|
160
|
+
m.value = c, a?.onError?.(c), e.onError?.({ err: o, message: c });
|
|
154
161
|
}
|
|
155
162
|
} finally {
|
|
156
|
-
|
|
163
|
+
S === $ && (j.value = !1, A.value = !0, k = null);
|
|
157
164
|
}
|
|
158
|
-
},
|
|
159
|
-
let
|
|
160
|
-
return (
|
|
161
|
-
|
|
162
|
-
() => JSON.stringify({ params:
|
|
165
|
+
}, f = a?.debounce ? G(u, a.debounce) : u;
|
|
166
|
+
let R = null;
|
|
167
|
+
return (a?.params || a?.data) && (H(() => {
|
|
168
|
+
R && R(), R = W(
|
|
169
|
+
() => JSON.stringify({ params: a.params, data: a.data }),
|
|
163
170
|
() => {
|
|
164
|
-
|
|
171
|
+
f();
|
|
165
172
|
},
|
|
166
173
|
{ immediate: !1 }
|
|
167
174
|
);
|
|
168
|
-
}),
|
|
169
|
-
|
|
170
|
-
})), (
|
|
175
|
+
}), X(() => {
|
|
176
|
+
R && R(), F();
|
|
177
|
+
})), (a?.loadOnMount === void 0 || a.loadOnMount) && !A.value && (w.value ? (w.value = !1, u()) : f()), { result: C, errorMessage: m, zodError: y, isLoading: j, isDone: A, refetch: u, cancel: F };
|
|
171
178
|
};
|
|
172
|
-
} else typeof
|
|
179
|
+
} else typeof v == "object" && (l[p] = i(v));
|
|
173
180
|
}
|
|
174
|
-
return
|
|
181
|
+
return l;
|
|
175
182
|
}
|
|
176
|
-
const
|
|
177
|
-
function
|
|
178
|
-
const
|
|
179
|
-
for (const
|
|
180
|
-
const
|
|
181
|
-
if (
|
|
182
|
-
if (
|
|
183
|
-
const t =
|
|
184
|
-
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
183
|
+
const M = e.queries ?? {}, b = i(M);
|
|
184
|
+
function x(r) {
|
|
185
|
+
const l = {};
|
|
186
|
+
for (const p in r) {
|
|
187
|
+
const v = r[p];
|
|
188
|
+
if (v)
|
|
189
|
+
if (V(v)) {
|
|
190
|
+
const t = v;
|
|
191
|
+
l[p] = (s) => {
|
|
192
|
+
const a = E(), C = E(), m = E(), y = E(!1), j = E(!1), A = E(0);
|
|
193
|
+
let w = null;
|
|
194
|
+
return { result: a, errorMessage: C, zodError: m, isLoading: y, isDone: j, uploadProgress: A, mutate: async (F) => {
|
|
195
|
+
if (!y.value) {
|
|
196
|
+
w = new AbortController(), y.value = !0, C.value = void 0, A.value = 0;
|
|
189
197
|
try {
|
|
190
|
-
const { data:
|
|
191
|
-
let
|
|
198
|
+
const { data: u = {}, params: f } = F ?? {};
|
|
199
|
+
let R = u ?? {}, S = {};
|
|
192
200
|
if (t.isMultipart) {
|
|
193
|
-
const d = new FormData();
|
|
194
|
-
for (const [
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
} else t.data && t.data.parse(
|
|
201
|
+
const d = new FormData(), q = t.multipartBooleanStyle ?? "trueFalse";
|
|
202
|
+
for (const [Q, z] of Object.entries(u))
|
|
203
|
+
Z(d, z, Q, q);
|
|
204
|
+
R = d, S["Content-Type"] = "multipart/form-data";
|
|
205
|
+
} else t.data && t.data.parse(u);
|
|
198
206
|
t.params && f && t.params.parse(f);
|
|
199
207
|
const o = {
|
|
200
208
|
method: t.method,
|
|
201
209
|
url: t.path,
|
|
202
|
-
data:
|
|
210
|
+
data: R,
|
|
203
211
|
params: f,
|
|
204
|
-
headers:
|
|
212
|
+
headers: S,
|
|
213
|
+
signal: w.signal,
|
|
205
214
|
onUploadProgress: (d) => {
|
|
206
215
|
if (d.total) {
|
|
207
|
-
const
|
|
208
|
-
|
|
216
|
+
const q = Math.round(d.loaded * 100 / d.total);
|
|
217
|
+
A.value = q, s?.onUploadProgress?.(q);
|
|
209
218
|
}
|
|
210
219
|
}
|
|
211
220
|
};
|
|
@@ -217,56 +226,60 @@ function K(e) {
|
|
|
217
226
|
const d = await s.onBeforeRequest(o);
|
|
218
227
|
d !== void 0 && Object.assign(o, d);
|
|
219
228
|
}
|
|
220
|
-
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
} catch (
|
|
224
|
-
if (
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
229
|
+
C.value = void 0, m.value = void 0;
|
|
230
|
+
const c = await n.request(o), P = t.response ? t.response.parse(c.data) : c.data;
|
|
231
|
+
a.value = P, s?.onResult?.(P);
|
|
232
|
+
} catch (u) {
|
|
233
|
+
if (U(u))
|
|
234
|
+
return;
|
|
235
|
+
if (u instanceof L) {
|
|
236
|
+
const f = u.response?.data?.message || u.message || "An error occurred";
|
|
237
|
+
C.value = f, s?.onError?.(u), e.onError?.({ err: u, message: f });
|
|
238
|
+
} else if (u instanceof T) {
|
|
239
|
+
m.value = B.flattenError(u);
|
|
240
|
+
const f = Object.keys(m.value.fieldErrors).length, R = `${Object.values(m.value.fieldErrors).at(0)}.${f > 1 ? ` (and ${f - 1} more errors)` : ""}`;
|
|
241
|
+
C.value = R, s?.onError?.(u), s?.onZodError?.(B.flattenError(u)), e.onError?.({ err: u, message: R }), e.onZodError && e.onZodError(B.flattenError(u));
|
|
231
242
|
} else {
|
|
232
|
-
const f =
|
|
233
|
-
|
|
243
|
+
const f = u.message || "An error occurred";
|
|
244
|
+
C.value = f, s?.onError?.(u), e.onError?.({ err: u, message: f });
|
|
234
245
|
}
|
|
235
246
|
} finally {
|
|
236
|
-
|
|
247
|
+
y.value = !1, j.value = !0, w = null;
|
|
237
248
|
}
|
|
238
249
|
}
|
|
250
|
+
}, cancel: () => {
|
|
251
|
+
w?.abort(), w = null, y.value = !1;
|
|
239
252
|
} };
|
|
240
253
|
};
|
|
241
|
-
} else typeof
|
|
254
|
+
} else typeof v == "object" && (l[p] = x(v));
|
|
242
255
|
}
|
|
243
|
-
return
|
|
256
|
+
return l;
|
|
244
257
|
}
|
|
245
|
-
const
|
|
258
|
+
const _ = e.mutations ?? {}, I = x(_);
|
|
246
259
|
return {
|
|
247
|
-
query:
|
|
248
|
-
mutation:
|
|
260
|
+
query: b,
|
|
261
|
+
mutation: I
|
|
249
262
|
};
|
|
250
263
|
}
|
|
251
|
-
function
|
|
264
|
+
function ne(e) {
|
|
252
265
|
return e;
|
|
253
266
|
}
|
|
254
|
-
function
|
|
267
|
+
function oe(e) {
|
|
255
268
|
return e;
|
|
256
269
|
}
|
|
257
|
-
function
|
|
270
|
+
function se(...e) {
|
|
258
271
|
return Object.assign({}, ...e);
|
|
259
272
|
}
|
|
260
|
-
function
|
|
273
|
+
function ae(...e) {
|
|
261
274
|
return Object.assign({}, ...e);
|
|
262
275
|
}
|
|
263
276
|
export {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
277
|
+
fe as AxiosError,
|
|
278
|
+
ie as ZodError,
|
|
279
|
+
te as createApiClient,
|
|
280
|
+
oe as defineMutation,
|
|
281
|
+
ne as defineQuery,
|
|
282
|
+
ae as mergeMutations,
|
|
283
|
+
se as mergeQueries,
|
|
284
|
+
K as z
|
|
272
285
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vue-api-kit",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.14.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",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
},
|
|
23
23
|
"repository": {
|
|
24
24
|
"type": "git",
|
|
25
|
-
"url": "https://github.com/
|
|
25
|
+
"url": "https://github.com/BuntuProject/vue-api-kit.git"
|
|
26
26
|
},
|
|
27
27
|
"scripts": {
|
|
28
28
|
"dev": "vite build --watch",
|