voodoojs 0.4.6
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 +77 -0
- package/dist/chunk-234ZLC6W.js +401 -0
- package/dist/chunk-4HQEOXTK.js +10271 -0
- package/dist/chunk-5777LJVW.js +64 -0
- package/dist/chunk-5CKGDARU.js +1845 -0
- package/dist/chunk-A2UOVQBP.js +82 -0
- package/dist/chunk-E27NRARW.js +16 -0
- package/dist/chunk-JZIYRIY6.js +1196 -0
- package/dist/chunk-NNU6WOOU.js +641 -0
- package/dist/chunk-PQZEVFVZ.js +448 -0
- package/dist/chunk-RJUNPXQF.js +946 -0
- package/dist/chunk-U76IRJKH.js +72 -0
- package/dist/essential.cjs +13889 -0
- package/dist/essential.d.cts +24 -0
- package/dist/essential.d.ts +24 -0
- package/dist/essential.js +51 -0
- package/dist/gpu.cjs +2008 -0
- package/dist/gpu.d.cts +68 -0
- package/dist/gpu.d.ts +68 -0
- package/dist/gpu.js +273 -0
- package/dist/http.cjs +467 -0
- package/dist/http.d.cts +148 -0
- package/dist/http.d.ts +148 -0
- package/dist/http.js +7 -0
- package/dist/index-CaLD-0oh.d.cts +608 -0
- package/dist/index-CaLD-0oh.d.ts +608 -0
- package/dist/index-DTllqUtj.d.cts +261 -0
- package/dist/index-DTllqUtj.d.ts +261 -0
- package/dist/index.cjs +23063 -0
- package/dist/index.d.cts +1603 -0
- package/dist/index.d.ts +1603 -0
- package/dist/index.js +6924 -0
- package/dist/query-CKJ4oSpG.d.cts +1595 -0
- package/dist/query-DQFRmu3u.d.ts +1595 -0
- package/dist/reactivity.cjs +676 -0
- package/dist/reactivity.d.cts +188 -0
- package/dist/reactivity.d.ts +188 -0
- package/dist/reactivity.js +4 -0
- package/dist/socket.cjs +2685 -0
- package/dist/socket.d.cts +167 -0
- package/dist/socket.d.ts +167 -0
- package/dist/socket.js +238 -0
- package/dist/style-XEUAGGJK.js +5 -0
- package/dist/utils.cjs +397 -0
- package/dist/utils.d.cts +111 -0
- package/dist/utils.d.ts +111 -0
- package/dist/utils.js +4 -0
- package/dist/voodoo.core.js +8213 -0
- package/dist/voodoo.core.min.js +146 -0
- package/dist/voodoo.full.js +21193 -0
- package/dist/voodoo.full.min.js +1784 -0
- package/dist/voodoo.js +14185 -0
- package/dist/voodoo.min.js +420 -0
- package/package.json +127 -0
package/dist/http.cjs
ADDED
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Voodoo.js v0.4.6
|
|
5
|
+
* JavaScript feels like magic.
|
|
6
|
+
* (c) 2026 Voodoo.js contributors. MIT License.
|
|
7
|
+
*/
|
|
8
|
+
var __defProp = Object.defineProperty;
|
|
9
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
10
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
11
|
+
|
|
12
|
+
// src/utils/index.ts
|
|
13
|
+
function parseDuration(value, fallback = 0) {
|
|
14
|
+
if (value == null || value === "") return fallback;
|
|
15
|
+
if (typeof value === "number") return value;
|
|
16
|
+
const match = /^\s*([\d.]+)\s*(ms|s|m|h)?\s*$/i.exec(String(value));
|
|
17
|
+
if (!match) return fallback;
|
|
18
|
+
const amount = parseFloat(match[1]);
|
|
19
|
+
switch ((match[2] || "ms").toLowerCase()) {
|
|
20
|
+
case "s":
|
|
21
|
+
return amount * 1e3;
|
|
22
|
+
case "m":
|
|
23
|
+
return amount * 6e4;
|
|
24
|
+
case "h":
|
|
25
|
+
return amount * 36e5;
|
|
26
|
+
default:
|
|
27
|
+
return amount;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// src/http/index.ts
|
|
32
|
+
var HttpError = class extends Error {
|
|
33
|
+
constructor(message, response, config2, cause) {
|
|
34
|
+
super(message);
|
|
35
|
+
__publicField(this, "response", response);
|
|
36
|
+
__publicField(this, "config", config2);
|
|
37
|
+
__publicField(this, "cause", cause);
|
|
38
|
+
this.name = "HttpError";
|
|
39
|
+
}
|
|
40
|
+
get status() {
|
|
41
|
+
return this.response?.status ?? 0;
|
|
42
|
+
}
|
|
43
|
+
/** `true` when error is network, timeout, or cancellation. */
|
|
44
|
+
get isNetworkError() {
|
|
45
|
+
return !this.response;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
var defaults = {
|
|
49
|
+
baseURL: "",
|
|
50
|
+
headers: { Accept: "application/json, text/html, */*" },
|
|
51
|
+
timeout: 3e4,
|
|
52
|
+
retry: 0,
|
|
53
|
+
retryDelay: 500,
|
|
54
|
+
credentials: "same-origin",
|
|
55
|
+
csrfMeta: "csrf-token",
|
|
56
|
+
csrfHeader: "X-CSRF-TOKEN"
|
|
57
|
+
};
|
|
58
|
+
var requestInterceptors = [];
|
|
59
|
+
var responseInterceptors = [];
|
|
60
|
+
var errorInterceptors = [];
|
|
61
|
+
var responseCache = /* @__PURE__ */ new Map();
|
|
62
|
+
function cacheKey(config2) {
|
|
63
|
+
return `${config2.method ?? "GET"} ${buildURL(config2)}`;
|
|
64
|
+
}
|
|
65
|
+
function clearCache(pattern) {
|
|
66
|
+
if (!pattern) {
|
|
67
|
+
responseCache.clear();
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const test = typeof pattern === "string" ? (k) => k.includes(pattern) : (k) => pattern.test(k);
|
|
71
|
+
for (const key of [...responseCache.keys()]) if (test(key)) responseCache.delete(key);
|
|
72
|
+
}
|
|
73
|
+
var OFFLINE_KEY = "voodoo:offline-queue";
|
|
74
|
+
function readQueue() {
|
|
75
|
+
try {
|
|
76
|
+
return JSON.parse(localStorage.getItem(OFFLINE_KEY) || "[]");
|
|
77
|
+
} catch {
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function writeQueue(list) {
|
|
82
|
+
try {
|
|
83
|
+
localStorage.setItem(OFFLINE_KEY, JSON.stringify(list));
|
|
84
|
+
} catch {
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function enqueueOffline(config2) {
|
|
88
|
+
if (typeof localStorage === "undefined") return;
|
|
89
|
+
const list = readQueue();
|
|
90
|
+
list.push({
|
|
91
|
+
url: buildURL(config2),
|
|
92
|
+
method: config2.method ?? "POST",
|
|
93
|
+
body: config2.body,
|
|
94
|
+
headers: config2.headers ?? {},
|
|
95
|
+
at: Date.now()
|
|
96
|
+
});
|
|
97
|
+
writeQueue(list);
|
|
98
|
+
}
|
|
99
|
+
async function flushOfflineQueue() {
|
|
100
|
+
if (typeof localStorage === "undefined") return 0;
|
|
101
|
+
const list = readQueue();
|
|
102
|
+
if (!list.length) return 0;
|
|
103
|
+
writeQueue([]);
|
|
104
|
+
let sent = 0;
|
|
105
|
+
for (let index = 0; index < list.length; index++) {
|
|
106
|
+
const item = list[index];
|
|
107
|
+
try {
|
|
108
|
+
await request({
|
|
109
|
+
url: item.url,
|
|
110
|
+
method: item.method,
|
|
111
|
+
body: item.body,
|
|
112
|
+
headers: item.headers,
|
|
113
|
+
offlineQueue: false
|
|
114
|
+
});
|
|
115
|
+
sent++;
|
|
116
|
+
} catch {
|
|
117
|
+
const newItems = readQueue();
|
|
118
|
+
writeQueue([...list.slice(index), ...newItems]);
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return sent;
|
|
123
|
+
}
|
|
124
|
+
if (typeof window !== "undefined") {
|
|
125
|
+
window.addEventListener("online", () => {
|
|
126
|
+
void flushOfflineQueue();
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
function buildURL(config2) {
|
|
130
|
+
let url = config2.url;
|
|
131
|
+
const base = defaults.baseURL;
|
|
132
|
+
if (base && !/^https?:\/\//i.test(url) && !url.startsWith("//")) {
|
|
133
|
+
url = `${base.replace(/\/$/, "")}/${url.replace(/^\//, "")}`;
|
|
134
|
+
}
|
|
135
|
+
if (config2.params) {
|
|
136
|
+
const search = new URLSearchParams();
|
|
137
|
+
for (const [key, value] of Object.entries(config2.params)) {
|
|
138
|
+
if (value == null || value === "") continue;
|
|
139
|
+
search.append(key, String(value));
|
|
140
|
+
}
|
|
141
|
+
const query = search.toString();
|
|
142
|
+
if (query) url += (url.includes("?") ? "&" : "?") + query;
|
|
143
|
+
}
|
|
144
|
+
return url;
|
|
145
|
+
}
|
|
146
|
+
function csrfToken() {
|
|
147
|
+
if (typeof document === "undefined") return null;
|
|
148
|
+
const meta = document.querySelector(`meta[name="${defaults.csrfMeta}"]`);
|
|
149
|
+
return meta?.getAttribute("content") ?? null;
|
|
150
|
+
}
|
|
151
|
+
function prepareBody(body, headers) {
|
|
152
|
+
if (body == null) return void 0;
|
|
153
|
+
if (typeof FormData !== "undefined" && body instanceof FormData || typeof Blob !== "undefined" && body instanceof Blob || typeof URLSearchParams !== "undefined" && body instanceof URLSearchParams || typeof ArrayBuffer !== "undefined" && body instanceof ArrayBuffer || typeof body === "string") {
|
|
154
|
+
return body;
|
|
155
|
+
}
|
|
156
|
+
if (!headers["Content-Type"]) headers["Content-Type"] = "application/json";
|
|
157
|
+
return JSON.stringify(body);
|
|
158
|
+
}
|
|
159
|
+
async function parseResponse(response, type) {
|
|
160
|
+
if (response.status === 204 || response.status === 205) return null;
|
|
161
|
+
const contentType = response.headers.get("content-type") || "";
|
|
162
|
+
switch (type) {
|
|
163
|
+
case "json":
|
|
164
|
+
return response.json();
|
|
165
|
+
case "text":
|
|
166
|
+
return response.text();
|
|
167
|
+
case "blob":
|
|
168
|
+
return response.blob();
|
|
169
|
+
case "arrayBuffer":
|
|
170
|
+
return response.arrayBuffer();
|
|
171
|
+
case "formData":
|
|
172
|
+
return response.formData();
|
|
173
|
+
default:
|
|
174
|
+
if (contentType.includes("application/json") || contentType.includes("+json")) {
|
|
175
|
+
const text = await response.text();
|
|
176
|
+
return text ? JSON.parse(text) : null;
|
|
177
|
+
}
|
|
178
|
+
return response.text();
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
var METODOS_SEGUROS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
|
|
182
|
+
function temChaveDeIdempotencia(headers) {
|
|
183
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
184
|
+
if (name.toLowerCase() === "idempotency-key" && String(value).trim() !== "") return true;
|
|
185
|
+
}
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
function podeRepetir(method, config2, headers, url) {
|
|
189
|
+
if (METODOS_SEGUROS.has(method)) return true;
|
|
190
|
+
if (config2.retryUnsafe === true) return true;
|
|
191
|
+
if (temChaveDeIdempotencia(headers)) return true;
|
|
192
|
+
if ((config2.retry ?? 0) > 0) ;
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
async function request(input) {
|
|
196
|
+
let config2 = {
|
|
197
|
+
method: "GET",
|
|
198
|
+
timeout: defaults.timeout,
|
|
199
|
+
retry: defaults.retry,
|
|
200
|
+
retryDelay: defaults.retryDelay,
|
|
201
|
+
credentials: defaults.credentials,
|
|
202
|
+
responseType: "auto",
|
|
203
|
+
...input,
|
|
204
|
+
headers: { ...defaults.headers, ...input.headers }
|
|
205
|
+
};
|
|
206
|
+
for (const interceptor of requestInterceptors) {
|
|
207
|
+
config2 = await interceptor(config2);
|
|
208
|
+
}
|
|
209
|
+
const method = (config2.method ?? "GET").toUpperCase();
|
|
210
|
+
if (config2.cache && method === "GET") {
|
|
211
|
+
const entry = responseCache.get(cacheKey(config2));
|
|
212
|
+
if (entry && entry.expires > Date.now()) return entry.value;
|
|
213
|
+
}
|
|
214
|
+
if (config2.offlineQueue && typeof navigator !== "undefined" && navigator.onLine === false && method !== "GET") {
|
|
215
|
+
enqueueOffline(config2);
|
|
216
|
+
return {
|
|
217
|
+
data: null,
|
|
218
|
+
status: 0,
|
|
219
|
+
statusText: "offline-queued",
|
|
220
|
+
headers: new Headers(),
|
|
221
|
+
ok: true,
|
|
222
|
+
raw: new Response(null, { status: 202 }),
|
|
223
|
+
config: config2
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
const headers = { ...config2.headers };
|
|
227
|
+
if (method !== "GET" && method !== "HEAD") {
|
|
228
|
+
const token = csrfToken();
|
|
229
|
+
if (token && !headers[defaults.csrfHeader]) headers[defaults.csrfHeader] = token;
|
|
230
|
+
}
|
|
231
|
+
headers["X-Requested-With"] || (headers["X-Requested-With"] = "XMLHttpRequest");
|
|
232
|
+
const body = prepareBody(config2.body, headers);
|
|
233
|
+
const url = buildURL(config2);
|
|
234
|
+
const attempts = podeRepetir(method, config2, headers) ? (config2.retry ?? 0) + 1 : 1;
|
|
235
|
+
let lastError;
|
|
236
|
+
for (let attempt = 0; attempt < attempts; attempt++) {
|
|
237
|
+
const controller = new AbortController();
|
|
238
|
+
const externo = config2.signal;
|
|
239
|
+
let repassarAborto = null;
|
|
240
|
+
if (externo) {
|
|
241
|
+
if (externo.aborted) {
|
|
242
|
+
controller.abort(externo.reason);
|
|
243
|
+
} else {
|
|
244
|
+
repassarAborto = () => controller.abort(externo.reason);
|
|
245
|
+
externo.addEventListener("abort", repassarAborto, { once: true });
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
const soltarSinal = () => {
|
|
249
|
+
if (repassarAborto && externo) externo.removeEventListener("abort", repassarAborto);
|
|
250
|
+
repassarAborto = null;
|
|
251
|
+
};
|
|
252
|
+
const timeoutId = config2.timeout && config2.timeout > 0 ? setTimeout(() => controller.abort(new DOMException("timeout", "TimeoutError")), config2.timeout) : null;
|
|
253
|
+
try {
|
|
254
|
+
const response = await fetch(url, {
|
|
255
|
+
method,
|
|
256
|
+
headers,
|
|
257
|
+
body: method === "GET" || method === "HEAD" ? void 0 : body,
|
|
258
|
+
credentials: config2.credentials,
|
|
259
|
+
signal: controller.signal
|
|
260
|
+
});
|
|
261
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
262
|
+
soltarSinal();
|
|
263
|
+
const data = await parseResponse(response, config2.responseType);
|
|
264
|
+
let result = {
|
|
265
|
+
data,
|
|
266
|
+
status: response.status,
|
|
267
|
+
statusText: response.statusText,
|
|
268
|
+
headers: response.headers,
|
|
269
|
+
ok: response.ok,
|
|
270
|
+
raw: response,
|
|
271
|
+
config: config2
|
|
272
|
+
};
|
|
273
|
+
if (!response.ok) {
|
|
274
|
+
if (response.status >= 500 && attempt < attempts - 1) {
|
|
275
|
+
await wait((config2.retryDelay ?? 500) * 2 ** attempt);
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
const error2 = new HttpError(
|
|
279
|
+
`Request failed with status ${response.status}`,
|
|
280
|
+
result,
|
|
281
|
+
config2
|
|
282
|
+
);
|
|
283
|
+
for (const interceptor of errorInterceptors) interceptor(error2);
|
|
284
|
+
throw error2;
|
|
285
|
+
}
|
|
286
|
+
for (const interceptor of responseInterceptors) {
|
|
287
|
+
result = await interceptor(result);
|
|
288
|
+
}
|
|
289
|
+
if (config2.cache && method === "GET") {
|
|
290
|
+
responseCache.set(cacheKey(config2), {
|
|
291
|
+
expires: Date.now() + config2.cache,
|
|
292
|
+
value: result
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
return result;
|
|
296
|
+
} catch (err) {
|
|
297
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
298
|
+
soltarSinal();
|
|
299
|
+
if (err instanceof HttpError) throw err;
|
|
300
|
+
lastError = err;
|
|
301
|
+
const aborted = err?.name === "AbortError" && config2.signal?.aborted;
|
|
302
|
+
if (aborted) break;
|
|
303
|
+
if (attempt < attempts - 1) {
|
|
304
|
+
await wait((config2.retryDelay ?? 500) * 2 ** attempt);
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
const message = lastError?.name === "TimeoutError" ? `Timeout after ${config2.timeout}ms` : `Network failure accessing ${url}`;
|
|
310
|
+
const error = new HttpError(message, void 0, config2, lastError);
|
|
311
|
+
for (const interceptor of errorInterceptors) interceptor(error);
|
|
312
|
+
throw error;
|
|
313
|
+
}
|
|
314
|
+
function wait(ms) {
|
|
315
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
316
|
+
}
|
|
317
|
+
async function shortcut(config2) {
|
|
318
|
+
const response = await request(config2);
|
|
319
|
+
return response.data;
|
|
320
|
+
}
|
|
321
|
+
var http = {
|
|
322
|
+
defaults,
|
|
323
|
+
get(url, options = {}) {
|
|
324
|
+
return shortcut({ ...options, url, method: "GET" });
|
|
325
|
+
},
|
|
326
|
+
post(url, body, options = {}) {
|
|
327
|
+
return shortcut({ ...options, url, method: "POST", body });
|
|
328
|
+
},
|
|
329
|
+
put(url, body, options = {}) {
|
|
330
|
+
return shortcut({ ...options, url, method: "PUT", body });
|
|
331
|
+
},
|
|
332
|
+
patch(url, body, options = {}) {
|
|
333
|
+
return shortcut({ ...options, url, method: "PATCH", body });
|
|
334
|
+
},
|
|
335
|
+
delete(url, options = {}) {
|
|
336
|
+
return shortcut({ ...options, url, method: "DELETE" });
|
|
337
|
+
},
|
|
338
|
+
head(url, options = {}) {
|
|
339
|
+
return shortcut({ ...options, url, method: "HEAD" });
|
|
340
|
+
},
|
|
341
|
+
/** Full request with status and headers. */
|
|
342
|
+
request,
|
|
343
|
+
/** Upload files with real progress using XMLHttpRequest. */
|
|
344
|
+
upload(url, data, options = {}) {
|
|
345
|
+
return new Promise((resolve, reject) => {
|
|
346
|
+
const xhr = new XMLHttpRequest();
|
|
347
|
+
const finalUrl = buildURL({ url });
|
|
348
|
+
xhr.open(options.method ?? "POST", finalUrl);
|
|
349
|
+
for (const [key, value] of Object.entries({ ...defaults.headers, ...options.headers })) {
|
|
350
|
+
if (key.toLowerCase() === "content-type") continue;
|
|
351
|
+
xhr.setRequestHeader(key, value);
|
|
352
|
+
}
|
|
353
|
+
const token = csrfToken();
|
|
354
|
+
if (token) xhr.setRequestHeader(defaults.csrfHeader, token);
|
|
355
|
+
xhr.upload.addEventListener("progress", (event) => {
|
|
356
|
+
if (!event.lengthComputable) return;
|
|
357
|
+
options.onProgress?.(
|
|
358
|
+
Math.round(event.loaded / event.total * 100),
|
|
359
|
+
event.loaded,
|
|
360
|
+
event.total
|
|
361
|
+
);
|
|
362
|
+
});
|
|
363
|
+
xhr.addEventListener("load", () => {
|
|
364
|
+
const contentType = xhr.getResponseHeader("content-type") || "";
|
|
365
|
+
let data2 = xhr.responseText;
|
|
366
|
+
if (contentType.includes("json")) {
|
|
367
|
+
try {
|
|
368
|
+
data2 = JSON.parse(xhr.responseText);
|
|
369
|
+
} catch {
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
if (xhr.status >= 200 && xhr.status < 300) resolve(data2);
|
|
373
|
+
else reject(new HttpError(`Upload failed with status ${xhr.status}`));
|
|
374
|
+
});
|
|
375
|
+
xhr.addEventListener("error", () => reject(new HttpError("Network failure during upload")));
|
|
376
|
+
xhr.addEventListener("abort", () => reject(new HttpError("Upload canceled")));
|
|
377
|
+
options.signal?.addEventListener("abort", () => xhr.abort());
|
|
378
|
+
xhr.send(data);
|
|
379
|
+
});
|
|
380
|
+
},
|
|
381
|
+
/** Server-Sent Events with automatic reconnection by the browser. */
|
|
382
|
+
sse(url, handlers = {}) {
|
|
383
|
+
const source = new EventSource(buildURL({ url }));
|
|
384
|
+
source.addEventListener("message", (event) => {
|
|
385
|
+
let data = event.data;
|
|
386
|
+
try {
|
|
387
|
+
data = JSON.parse(event.data);
|
|
388
|
+
} catch {
|
|
389
|
+
}
|
|
390
|
+
handlers.message?.(data, event);
|
|
391
|
+
});
|
|
392
|
+
if (handlers.error) source.addEventListener("error", handlers.error);
|
|
393
|
+
return source;
|
|
394
|
+
},
|
|
395
|
+
/** Read a streaming response line by line (NDJSON). */
|
|
396
|
+
async stream(url, onLine, options = {}) {
|
|
397
|
+
const response = await fetch(buildURL({ url, params: options.params }), {
|
|
398
|
+
headers: { ...defaults.headers, ...options.headers },
|
|
399
|
+
signal: options.signal
|
|
400
|
+
});
|
|
401
|
+
if (!response.body) return;
|
|
402
|
+
const reader = response.body.getReader();
|
|
403
|
+
const decoder = new TextDecoder();
|
|
404
|
+
let buffer = "";
|
|
405
|
+
for (; ; ) {
|
|
406
|
+
const { done, value } = await reader.read();
|
|
407
|
+
if (done) break;
|
|
408
|
+
buffer += decoder.decode(value, { stream: true });
|
|
409
|
+
const lines = buffer.split("\n");
|
|
410
|
+
buffer = lines.pop() ?? "";
|
|
411
|
+
for (const line of lines) if (line.trim()) onLine(line);
|
|
412
|
+
}
|
|
413
|
+
if (buffer.trim()) onLine(buffer);
|
|
414
|
+
},
|
|
415
|
+
interceptors: {
|
|
416
|
+
request: {
|
|
417
|
+
use(fn) {
|
|
418
|
+
requestInterceptors.push(fn);
|
|
419
|
+
return () => {
|
|
420
|
+
const i = requestInterceptors.indexOf(fn);
|
|
421
|
+
if (i > -1) requestInterceptors.splice(i, 1);
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
},
|
|
425
|
+
response: {
|
|
426
|
+
use(fn) {
|
|
427
|
+
responseInterceptors.push(fn);
|
|
428
|
+
return () => {
|
|
429
|
+
const i = responseInterceptors.indexOf(fn);
|
|
430
|
+
if (i > -1) responseInterceptors.splice(i, 1);
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
},
|
|
434
|
+
error: {
|
|
435
|
+
use(fn) {
|
|
436
|
+
errorInterceptors.push(fn);
|
|
437
|
+
return () => {
|
|
438
|
+
const i = errorInterceptors.indexOf(fn);
|
|
439
|
+
if (i > -1) errorInterceptors.splice(i, 1);
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
},
|
|
444
|
+
/** Set headers sent on every request. */
|
|
445
|
+
setHeader(name, value) {
|
|
446
|
+
if (value === null) delete defaults.headers[name];
|
|
447
|
+
else defaults.headers[name] = value;
|
|
448
|
+
},
|
|
449
|
+
/** Shortcut for token-based authentication. */
|
|
450
|
+
setToken(token, scheme = "Bearer") {
|
|
451
|
+
this.setHeader("Authorization", token ? `${scheme} ${token}` : null);
|
|
452
|
+
},
|
|
453
|
+
setBaseURL(url) {
|
|
454
|
+
defaults.baseURL = url;
|
|
455
|
+
},
|
|
456
|
+
clearCache,
|
|
457
|
+
flushOfflineQueue,
|
|
458
|
+
parseDuration
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
exports.HttpError = HttpError;
|
|
462
|
+
exports.clearCache = clearCache;
|
|
463
|
+
exports.flushOfflineQueue = flushOfflineQueue;
|
|
464
|
+
exports.http = http;
|
|
465
|
+
exports.request = request;
|
|
466
|
+
//# sourceMappingURL=http.cjs.map
|
|
467
|
+
//# sourceMappingURL=http.cjs.map
|
package/dist/http.d.cts
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { parseDuration } from './utils.cjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @module http
|
|
5
|
+
*
|
|
6
|
+
* HTTP client built on `fetch`, with Axios ergonomics and no dependencies.
|
|
7
|
+
* Supports interceptors, timeout, retry with progressive backoff, response
|
|
8
|
+
* caching, cancellation, upload progress, and offline queue.
|
|
9
|
+
*
|
|
10
|
+
* Automatic retry only applies to `GET`, `HEAD`, and `OPTIONS`. For methods
|
|
11
|
+
* that change state, it requires explicit opt-in. See {@link podeRepetir}.
|
|
12
|
+
*
|
|
13
|
+
* ```ts
|
|
14
|
+
* const users = await V.http.get<User[]>('/api/users')
|
|
15
|
+
* await V.http.post('/api/users', { name: 'Ana' })
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';
|
|
20
|
+
interface RequestConfig {
|
|
21
|
+
url: string;
|
|
22
|
+
method?: HttpMethod;
|
|
23
|
+
/** Body. Objects become JSON, `FormData` is sent as-is. */
|
|
24
|
+
body?: unknown;
|
|
25
|
+
/** Query parameters added to the URL. */
|
|
26
|
+
params?: Record<string, string | number | boolean | null | undefined>;
|
|
27
|
+
headers?: Record<string, string>;
|
|
28
|
+
/** Milliseconds before aborting. `0` disables timeout. */
|
|
29
|
+
timeout?: number;
|
|
30
|
+
/**
|
|
31
|
+
* Extra attempts on network failure or 5xx errors.
|
|
32
|
+
*
|
|
33
|
+
* Works alone only for `GET`, `HEAD`, and `OPTIONS`. For other methods,
|
|
34
|
+
* retry must be enabled with `retryUnsafe` or an `Idempotency-Key` header.
|
|
35
|
+
* See {@link podeRepetir}.
|
|
36
|
+
*/
|
|
37
|
+
retry?: number;
|
|
38
|
+
/** Wait between attempts, doubled each round. */
|
|
39
|
+
retryDelay?: number;
|
|
40
|
+
/**
|
|
41
|
+
* Enables `retry` on state-changing methods (`POST`, `PATCH`, `PUT`,
|
|
42
|
+
* `DELETE`). Use only when the server handles repetition safely, either
|
|
43
|
+
* because the operation is naturally idempotent or it is deduplicated by a
|
|
44
|
+
* key. Sending `Idempotency-Key` has the same effect.
|
|
45
|
+
*/
|
|
46
|
+
retryUnsafe?: boolean;
|
|
47
|
+
/** Response cache duration in ms. GET only. */
|
|
48
|
+
cache?: number;
|
|
49
|
+
signal?: AbortSignal;
|
|
50
|
+
credentials?: RequestCredentials;
|
|
51
|
+
/** Expected type. `auto` decides by response header. */
|
|
52
|
+
responseType?: 'auto' | 'json' | 'text' | 'blob' | 'arrayBuffer' | 'formData';
|
|
53
|
+
/** Download progress callback when the server reports size. */
|
|
54
|
+
onProgress?: (loaded: number, total: number) => void;
|
|
55
|
+
/** Queue the request when browser is offline and resend later. */
|
|
56
|
+
offlineQueue?: boolean;
|
|
57
|
+
}
|
|
58
|
+
interface HttpResponse<T = unknown> {
|
|
59
|
+
data: T;
|
|
60
|
+
status: number;
|
|
61
|
+
statusText: string;
|
|
62
|
+
headers: Headers;
|
|
63
|
+
ok: boolean;
|
|
64
|
+
/** Original response for advanced cases. */
|
|
65
|
+
raw: Response;
|
|
66
|
+
config: RequestConfig;
|
|
67
|
+
}
|
|
68
|
+
declare class HttpError<T = unknown> extends Error {
|
|
69
|
+
readonly response?: HttpResponse<T> | undefined;
|
|
70
|
+
readonly config?: RequestConfig | undefined;
|
|
71
|
+
readonly cause?: unknown | undefined;
|
|
72
|
+
constructor(message: string, response?: HttpResponse<T> | undefined, config?: RequestConfig | undefined, cause?: unknown | undefined);
|
|
73
|
+
get status(): number;
|
|
74
|
+
/** `true` when error is network, timeout, or cancellation. */
|
|
75
|
+
get isNetworkError(): boolean;
|
|
76
|
+
}
|
|
77
|
+
type RequestInterceptor = (config: RequestConfig) => RequestConfig | Promise<RequestConfig>;
|
|
78
|
+
type ResponseInterceptor = (response: HttpResponse) => HttpResponse | Promise<HttpResponse>;
|
|
79
|
+
type ErrorInterceptor = (error: HttpError) => unknown;
|
|
80
|
+
interface HttpDefaults {
|
|
81
|
+
baseURL: string;
|
|
82
|
+
headers: Record<string, string>;
|
|
83
|
+
timeout: number;
|
|
84
|
+
retry: number;
|
|
85
|
+
retryDelay: number;
|
|
86
|
+
credentials: RequestCredentials;
|
|
87
|
+
/** Meta tag name read to send CSRF token automatically. */
|
|
88
|
+
csrfMeta: string;
|
|
89
|
+
/** Header used to send CSRF token. */
|
|
90
|
+
csrfHeader: string;
|
|
91
|
+
}
|
|
92
|
+
/** Clears entire cache or only entries matching the pattern. */
|
|
93
|
+
declare function clearCache(pattern?: string | RegExp): void;
|
|
94
|
+
/** Resends everything queued while the browser was offline. */
|
|
95
|
+
declare function flushOfflineQueue(): Promise<number>;
|
|
96
|
+
/**
|
|
97
|
+
* Executes a request through the entire pipeline: interceptors, timeout, retry,
|
|
98
|
+
* cache, and error handling.
|
|
99
|
+
*/
|
|
100
|
+
declare function request<T = unknown>(input: RequestConfig): Promise<HttpResponse<T>>;
|
|
101
|
+
type ShortcutOptions = Omit<RequestConfig, 'url' | 'method' | 'body'>;
|
|
102
|
+
declare const http: {
|
|
103
|
+
defaults: HttpDefaults;
|
|
104
|
+
get<T = unknown>(url: string, options?: ShortcutOptions): Promise<T>;
|
|
105
|
+
post<T = unknown>(url: string, body?: unknown, options?: ShortcutOptions): Promise<T>;
|
|
106
|
+
put<T = unknown>(url: string, body?: unknown, options?: ShortcutOptions): Promise<T>;
|
|
107
|
+
patch<T = unknown>(url: string, body?: unknown, options?: ShortcutOptions): Promise<T>;
|
|
108
|
+
delete<T = unknown>(url: string, options?: ShortcutOptions): Promise<T>;
|
|
109
|
+
head(url: string, options?: ShortcutOptions): Promise<unknown>;
|
|
110
|
+
/** Full request with status and headers. */
|
|
111
|
+
request: typeof request;
|
|
112
|
+
/** Upload files with real progress using XMLHttpRequest. */
|
|
113
|
+
upload<T = unknown>(url: string, data: FormData, options?: {
|
|
114
|
+
method?: "POST" | "PUT" | "PATCH";
|
|
115
|
+
headers?: Record<string, string>;
|
|
116
|
+
onProgress?: (percent: number, loaded: number, total: number) => void;
|
|
117
|
+
signal?: AbortSignal;
|
|
118
|
+
}): Promise<T>;
|
|
119
|
+
/** Server-Sent Events with automatic reconnection by the browser. */
|
|
120
|
+
sse(url: string, handlers?: {
|
|
121
|
+
message?: (data: unknown, event: MessageEvent) => void;
|
|
122
|
+
error?: (e: Event) => void;
|
|
123
|
+
}): EventSource;
|
|
124
|
+
/** Read a streaming response line by line (NDJSON). */
|
|
125
|
+
stream(url: string, onLine: (line: string) => void, options?: ShortcutOptions): Promise<void>;
|
|
126
|
+
interceptors: {
|
|
127
|
+
request: {
|
|
128
|
+
use(fn: RequestInterceptor): () => void;
|
|
129
|
+
};
|
|
130
|
+
response: {
|
|
131
|
+
use(fn: ResponseInterceptor): () => void;
|
|
132
|
+
};
|
|
133
|
+
error: {
|
|
134
|
+
use(fn: ErrorInterceptor): () => void;
|
|
135
|
+
};
|
|
136
|
+
};
|
|
137
|
+
/** Set headers sent on every request. */
|
|
138
|
+
setHeader(name: string, value: string | null): void;
|
|
139
|
+
/** Shortcut for token-based authentication. */
|
|
140
|
+
setToken(token: string | null, scheme?: string): void;
|
|
141
|
+
setBaseURL(url: string): void;
|
|
142
|
+
clearCache: typeof clearCache;
|
|
143
|
+
flushOfflineQueue: typeof flushOfflineQueue;
|
|
144
|
+
parseDuration: typeof parseDuration;
|
|
145
|
+
};
|
|
146
|
+
type Http = typeof http;
|
|
147
|
+
|
|
148
|
+
export { type ErrorInterceptor, type Http, type HttpDefaults, HttpError, type HttpMethod, type HttpResponse, type RequestConfig, type RequestInterceptor, type ResponseInterceptor, clearCache, flushOfflineQueue, http, request };
|