@anmetric/neta 1.1.0 → 1.1.2
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 +122 -87
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -16,18 +16,20 @@ npm install @anmetric/neta
|
|
|
16
16
|
## Quick Start
|
|
17
17
|
|
|
18
18
|
```js
|
|
19
|
-
import neta from
|
|
19
|
+
import neta from "@anmetric/neta";
|
|
20
20
|
|
|
21
21
|
// GET with JSON parsing
|
|
22
|
-
const data = await neta.get(
|
|
22
|
+
const data = await neta.get("https://api.example.com/users").json();
|
|
23
23
|
|
|
24
24
|
// POST with JSON body
|
|
25
|
-
const user = await neta
|
|
26
|
-
|
|
27
|
-
}
|
|
25
|
+
const user = await neta
|
|
26
|
+
.post("https://api.example.com/users", {
|
|
27
|
+
json: { name: "John", email: "john@example.com" },
|
|
28
|
+
})
|
|
29
|
+
.json();
|
|
28
30
|
|
|
29
31
|
// Direct callable
|
|
30
|
-
const res = await neta(
|
|
32
|
+
const res = await neta("https://api.example.com/users", { method: "get" });
|
|
31
33
|
```
|
|
32
34
|
|
|
33
35
|
## API
|
|
@@ -53,9 +55,11 @@ Type: `unknown`
|
|
|
53
55
|
JSON body. Automatically stringified and sets `Content-Type: application/json`.
|
|
54
56
|
|
|
55
57
|
```js
|
|
56
|
-
const data = await neta
|
|
57
|
-
|
|
58
|
-
}
|
|
58
|
+
const data = await neta
|
|
59
|
+
.post("https://api.example.com/items", {
|
|
60
|
+
json: { title: "New Item" },
|
|
61
|
+
})
|
|
62
|
+
.json();
|
|
59
63
|
```
|
|
60
64
|
|
|
61
65
|
##### searchParams
|
|
@@ -65,9 +69,11 @@ Type: `string | object | URLSearchParams | Array<[string, string]>`
|
|
|
65
69
|
Query parameters appended to the URL.
|
|
66
70
|
|
|
67
71
|
```js
|
|
68
|
-
const data = await neta
|
|
69
|
-
|
|
70
|
-
}
|
|
72
|
+
const data = await neta
|
|
73
|
+
.get("https://api.example.com/search", {
|
|
74
|
+
searchParams: { q: "hello", page: 2 },
|
|
75
|
+
})
|
|
76
|
+
.json();
|
|
71
77
|
// => GET https://api.example.com/search?q=hello&page=2
|
|
72
78
|
```
|
|
73
79
|
|
|
@@ -80,9 +86,9 @@ Type: `string | URL`
|
|
|
80
86
|
Prefix prepended to the input URL. Useful for API base paths.
|
|
81
87
|
|
|
82
88
|
```js
|
|
83
|
-
const api = neta.create({ prefix:
|
|
89
|
+
const api = neta.create({ prefix: "https://api.example.com/v2" });
|
|
84
90
|
|
|
85
|
-
await api.get(
|
|
91
|
+
await api.get("users").json();
|
|
86
92
|
// => GET https://api.example.com/v2/users
|
|
87
93
|
```
|
|
88
94
|
|
|
@@ -93,12 +99,12 @@ Type: `string | URL`
|
|
|
93
99
|
Base URL for resolving relative inputs using standard [URL resolution](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL).
|
|
94
100
|
|
|
95
101
|
```js
|
|
96
|
-
const api = neta.create({ baseUrl:
|
|
102
|
+
const api = neta.create({ baseUrl: "https://api.example.com/v2/" });
|
|
97
103
|
|
|
98
|
-
await api.get(
|
|
104
|
+
await api.get("users").json();
|
|
99
105
|
// => GET https://api.example.com/v2/users
|
|
100
106
|
|
|
101
|
-
await api.get(
|
|
107
|
+
await api.get("../v1/legacy").json();
|
|
102
108
|
// => GET https://api.example.com/v1/legacy
|
|
103
109
|
```
|
|
104
110
|
|
|
@@ -117,7 +123,7 @@ Default: `false`
|
|
|
117
123
|
Total timeout across all retries in milliseconds.
|
|
118
124
|
|
|
119
125
|
```js
|
|
120
|
-
await neta.get(
|
|
126
|
+
await neta.get("https://api.example.com/slow", {
|
|
121
127
|
timeout: 5000,
|
|
122
128
|
totalTimeout: 30000,
|
|
123
129
|
retry: 5,
|
|
@@ -139,7 +145,7 @@ await neta.get(url, { retry: 3 });
|
|
|
139
145
|
await neta.get(url, {
|
|
140
146
|
retry: {
|
|
141
147
|
limit: 3,
|
|
142
|
-
methods: [
|
|
148
|
+
methods: ["get", "put", "head", "delete", "options"],
|
|
143
149
|
statusCodes: [408, 413, 429, 500, 502, 503, 504],
|
|
144
150
|
afterStatusCodes: [413, 429, 503],
|
|
145
151
|
maxRetryAfter: Infinity,
|
|
@@ -274,11 +280,13 @@ Type: `(text: string, context: { request, response }) => unknown`
|
|
|
274
280
|
Custom JSON parser. Useful for reviving dates, BigInts, etc.
|
|
275
281
|
|
|
276
282
|
```js
|
|
277
|
-
import LosslessJSON from
|
|
283
|
+
import LosslessJSON from "lossless-json";
|
|
278
284
|
|
|
279
|
-
const data = await neta
|
|
280
|
-
|
|
281
|
-
|
|
285
|
+
const data = await neta
|
|
286
|
+
.get(url, {
|
|
287
|
+
parseJson: (text) => LosslessJSON.parse(text),
|
|
288
|
+
})
|
|
289
|
+
.json();
|
|
282
290
|
```
|
|
283
291
|
|
|
284
292
|
##### stringifyJson
|
|
@@ -288,7 +296,7 @@ Type: `(value: unknown) => string`
|
|
|
288
296
|
Custom JSON serializer for the `json` option.
|
|
289
297
|
|
|
290
298
|
```js
|
|
291
|
-
import LosslessJSON from
|
|
299
|
+
import LosslessJSON from "lossless-json";
|
|
292
300
|
|
|
293
301
|
await neta.post(url, {
|
|
294
302
|
json: data,
|
|
@@ -303,15 +311,17 @@ Type: `string`
|
|
|
303
311
|
Convenience option to set the `Authorization: Bearer <token>` header. If an `Authorization` header is already set explicitly, `bearerToken` will not override it.
|
|
304
312
|
|
|
305
313
|
```js
|
|
306
|
-
const data = await neta
|
|
307
|
-
|
|
308
|
-
|
|
314
|
+
const data = await neta
|
|
315
|
+
.get("https://api.example.com/me", {
|
|
316
|
+
bearerToken: "abc123",
|
|
317
|
+
})
|
|
318
|
+
.json();
|
|
309
319
|
// Sets header: Authorization: Bearer abc123
|
|
310
320
|
|
|
311
321
|
// Works with create/extend
|
|
312
322
|
const api = neta.create({
|
|
313
|
-
prefix:
|
|
314
|
-
bearerToken:
|
|
323
|
+
prefix: "https://api.example.com",
|
|
324
|
+
bearerToken: "abc123",
|
|
315
325
|
});
|
|
316
326
|
```
|
|
317
327
|
|
|
@@ -324,11 +334,16 @@ Arbitrary data passed through to hooks. Not sent with the request.
|
|
|
324
334
|
|
|
325
335
|
```js
|
|
326
336
|
await neta.get(url, {
|
|
327
|
-
context: { token:
|
|
337
|
+
context: { token: "abc123" },
|
|
328
338
|
hooks: {
|
|
329
|
-
init: [
|
|
330
|
-
options
|
|
331
|
-
|
|
339
|
+
init: [
|
|
340
|
+
(options) => {
|
|
341
|
+
options.headers = {
|
|
342
|
+
...options.headers,
|
|
343
|
+
Authorization: `Bearer ${options.context.token}`,
|
|
344
|
+
};
|
|
345
|
+
},
|
|
346
|
+
],
|
|
332
347
|
},
|
|
333
348
|
});
|
|
334
349
|
```
|
|
@@ -340,7 +355,7 @@ Type: `typeof globalThis.fetch`
|
|
|
340
355
|
Custom fetch implementation.
|
|
341
356
|
|
|
342
357
|
```js
|
|
343
|
-
import { fetch } from
|
|
358
|
+
import { fetch } from "undici";
|
|
344
359
|
|
|
345
360
|
const api = neta.create({ fetch });
|
|
346
361
|
```
|
|
@@ -352,9 +367,11 @@ Type: `(progress: { percent, transferredBytes, totalBytes }) => void`
|
|
|
352
367
|
Download progress callback. Requires `ReadableStream` support.
|
|
353
368
|
|
|
354
369
|
```js
|
|
355
|
-
await neta.get(
|
|
370
|
+
await neta.get("https://example.com/large-file", {
|
|
356
371
|
onDownloadProgress: ({ percent, transferredBytes, totalBytes }) => {
|
|
357
|
-
console.log(
|
|
372
|
+
console.log(
|
|
373
|
+
`${Math.round(percent * 100)}% (${transferredBytes}/${totalBytes})`,
|
|
374
|
+
);
|
|
358
375
|
},
|
|
359
376
|
});
|
|
360
377
|
```
|
|
@@ -394,12 +411,14 @@ const form = await neta.get(url).formData();
|
|
|
394
411
|
Parse response as JSON. Optionally validate against a [Standard Schema](https://github.com/standard-schema/standard-schema):
|
|
395
412
|
|
|
396
413
|
```js
|
|
397
|
-
import { z } from
|
|
398
|
-
|
|
399
|
-
const user = await neta.get(
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
414
|
+
import { z } from "zod";
|
|
415
|
+
|
|
416
|
+
const user = await neta.get("/user/1").json(
|
|
417
|
+
z.object({
|
|
418
|
+
id: z.number(),
|
|
419
|
+
name: z.string(),
|
|
420
|
+
}),
|
|
421
|
+
);
|
|
403
422
|
// Throws SchemaValidationError if validation fails
|
|
404
423
|
```
|
|
405
424
|
|
|
@@ -411,13 +430,13 @@ Create a new instance with default options:
|
|
|
411
430
|
|
|
412
431
|
```js
|
|
413
432
|
const api = neta.create({
|
|
414
|
-
prefix:
|
|
415
|
-
bearerToken:
|
|
433
|
+
prefix: "https://api.example.com",
|
|
434
|
+
bearerToken: "my-token",
|
|
416
435
|
timeout: 30000,
|
|
417
436
|
retry: 3,
|
|
418
437
|
});
|
|
419
438
|
|
|
420
|
-
const data = await api.get(
|
|
439
|
+
const data = await api.get("users").json();
|
|
421
440
|
```
|
|
422
441
|
|
|
423
442
|
#### neta.extend(defaults?)
|
|
@@ -425,8 +444,8 @@ const data = await api.get('users').json();
|
|
|
425
444
|
Alias for `neta.create()`. Creates a new instance by extending existing defaults:
|
|
426
445
|
|
|
427
446
|
```js
|
|
428
|
-
const api = neta.create({ prefix:
|
|
429
|
-
const authApi = api.extend({ bearerToken:
|
|
447
|
+
const api = neta.create({ prefix: "https://api.example.com" });
|
|
448
|
+
const authApi = api.extend({ bearerToken: "my-token" });
|
|
430
449
|
```
|
|
431
450
|
|
|
432
451
|
## Hooks
|
|
@@ -442,9 +461,14 @@ Called synchronously before anything else. Can mutate options directly.
|
|
|
442
461
|
```js
|
|
443
462
|
neta.create({
|
|
444
463
|
hooks: {
|
|
445
|
-
init: [
|
|
446
|
-
options
|
|
447
|
-
|
|
464
|
+
init: [
|
|
465
|
+
(options) => {
|
|
466
|
+
options.headers = {
|
|
467
|
+
...options.headers,
|
|
468
|
+
"X-Request-Id": crypto.randomUUID(),
|
|
469
|
+
};
|
|
470
|
+
},
|
|
471
|
+
],
|
|
448
472
|
},
|
|
449
473
|
});
|
|
450
474
|
```
|
|
@@ -458,9 +482,11 @@ Called before each request. Return a `Request` to replace it, a `Response` to sh
|
|
|
458
482
|
```js
|
|
459
483
|
neta.create({
|
|
460
484
|
hooks: {
|
|
461
|
-
beforeRequest: [
|
|
462
|
-
|
|
463
|
-
|
|
485
|
+
beforeRequest: [
|
|
486
|
+
({ request }) => {
|
|
487
|
+
console.log(`${request.method} ${request.url}`);
|
|
488
|
+
},
|
|
489
|
+
],
|
|
464
490
|
},
|
|
465
491
|
});
|
|
466
492
|
```
|
|
@@ -474,16 +500,21 @@ Called after a successful response. Return a `Response` to replace it, or `neta.
|
|
|
474
500
|
```js
|
|
475
501
|
const api = neta.create({
|
|
476
502
|
hooks: {
|
|
477
|
-
afterResponse: [
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
503
|
+
afterResponse: [
|
|
504
|
+
async ({ request, response }) => {
|
|
505
|
+
if (response.status === 401) {
|
|
506
|
+
const token = await refreshToken();
|
|
507
|
+
return neta.retry({
|
|
508
|
+
request: new Request(request, {
|
|
509
|
+
headers: {
|
|
510
|
+
...Object.fromEntries(request.headers),
|
|
511
|
+
Authorization: `Bearer ${token}`,
|
|
512
|
+
},
|
|
513
|
+
}),
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
},
|
|
517
|
+
],
|
|
487
518
|
},
|
|
488
519
|
});
|
|
489
520
|
```
|
|
@@ -497,12 +528,14 @@ Called before an error is thrown. Return an `Error` to replace it.
|
|
|
497
528
|
```js
|
|
498
529
|
neta.create({
|
|
499
530
|
hooks: {
|
|
500
|
-
beforeError: [
|
|
501
|
-
|
|
502
|
-
error
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
531
|
+
beforeError: [
|
|
532
|
+
({ error }) => {
|
|
533
|
+
if (error instanceof HTTPError) {
|
|
534
|
+
error.message = `API Error: ${error.response.status}`;
|
|
535
|
+
}
|
|
536
|
+
return error;
|
|
537
|
+
},
|
|
538
|
+
],
|
|
506
539
|
},
|
|
507
540
|
});
|
|
508
541
|
```
|
|
@@ -519,14 +552,16 @@ Called before each retry attempt. Return:
|
|
|
519
552
|
- nothing — proceed normally
|
|
520
553
|
|
|
521
554
|
```js
|
|
522
|
-
import { stop } from
|
|
555
|
+
import { stop } from "neta";
|
|
523
556
|
|
|
524
557
|
neta.create({
|
|
525
558
|
hooks: {
|
|
526
|
-
beforeRetry: [
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
559
|
+
beforeRetry: [
|
|
560
|
+
({ error, retryCount }) => {
|
|
561
|
+
console.log(`Retry #${retryCount}: ${error.message}`);
|
|
562
|
+
if (retryCount > 3) return stop;
|
|
563
|
+
},
|
|
564
|
+
],
|
|
530
565
|
},
|
|
531
566
|
});
|
|
532
567
|
```
|
|
@@ -538,15 +573,15 @@ neta.create({
|
|
|
538
573
|
Thrown for non-2xx responses (when `throwHttpErrors` is true).
|
|
539
574
|
|
|
540
575
|
```js
|
|
541
|
-
import { HTTPError } from
|
|
576
|
+
import { HTTPError } from "neta";
|
|
542
577
|
|
|
543
578
|
try {
|
|
544
|
-
await neta.get(
|
|
579
|
+
await neta.get("https://api.example.com/missing");
|
|
545
580
|
} catch (error) {
|
|
546
581
|
if (error instanceof HTTPError) {
|
|
547
582
|
console.log(error.response.status); // 404
|
|
548
|
-
console.log(error.data);
|
|
549
|
-
console.log(error.request);
|
|
583
|
+
console.log(error.data); // Auto-parsed response body
|
|
584
|
+
console.log(error.request); // The Request object
|
|
550
585
|
}
|
|
551
586
|
}
|
|
552
587
|
```
|
|
@@ -556,13 +591,13 @@ try {
|
|
|
556
591
|
Thrown when a request exceeds the `timeout` or `totalTimeout`.
|
|
557
592
|
|
|
558
593
|
```js
|
|
559
|
-
import { TimeoutError } from
|
|
594
|
+
import { TimeoutError } from "neta";
|
|
560
595
|
|
|
561
596
|
try {
|
|
562
597
|
await neta.get(url, { timeout: 1000 });
|
|
563
598
|
} catch (error) {
|
|
564
599
|
if (error instanceof TimeoutError) {
|
|
565
|
-
console.log(
|
|
600
|
+
console.log("Request timed out:", error.request.url);
|
|
566
601
|
}
|
|
567
602
|
}
|
|
568
603
|
```
|
|
@@ -572,14 +607,14 @@ try {
|
|
|
572
607
|
Thrown on network failures (DNS, connection refused, etc.).
|
|
573
608
|
|
|
574
609
|
```js
|
|
575
|
-
import { NetworkError } from
|
|
610
|
+
import { NetworkError } from "neta";
|
|
576
611
|
|
|
577
612
|
try {
|
|
578
|
-
await neta.get(
|
|
613
|
+
await neta.get("https://nonexistent.invalid");
|
|
579
614
|
} catch (error) {
|
|
580
615
|
if (error instanceof NetworkError) {
|
|
581
|
-
console.log(
|
|
582
|
-
console.log(
|
|
616
|
+
console.log("Network error:", error.message);
|
|
617
|
+
console.log("Cause:", error.cause);
|
|
583
618
|
}
|
|
584
619
|
}
|
|
585
620
|
```
|
|
@@ -589,13 +624,13 @@ try {
|
|
|
589
624
|
Thrown when JSON response fails schema validation.
|
|
590
625
|
|
|
591
626
|
```js
|
|
592
|
-
import { SchemaValidationError } from
|
|
627
|
+
import { SchemaValidationError } from "neta";
|
|
593
628
|
|
|
594
629
|
try {
|
|
595
630
|
await neta.get(url).json(mySchema);
|
|
596
631
|
} catch (error) {
|
|
597
632
|
if (error instanceof SchemaValidationError) {
|
|
598
|
-
console.log(
|
|
633
|
+
console.log("Validation issues:", error.issues);
|
|
599
634
|
}
|
|
600
635
|
}
|
|
601
636
|
```
|
|
@@ -610,7 +645,7 @@ interface User {
|
|
|
610
645
|
name: string;
|
|
611
646
|
}
|
|
612
647
|
|
|
613
|
-
const user = await neta.get(
|
|
648
|
+
const user = await neta.get("https://api.example.com/user/1").json<User>();
|
|
614
649
|
// user is typed as User
|
|
615
650
|
```
|
|
616
651
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@anmetric/neta",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"description": "Tiny, elegant HTTP client built on fetch for browser and Node.js",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/anmetrics/neta.git"
|
|
8
|
+
},
|
|
5
9
|
"type": "module",
|
|
6
10
|
"exports": {
|
|
7
11
|
".": {
|