@zayne-labs/callapi 1.11.1 → 1.11.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 +58 -124
- package/dist/esm/{common-B2rPuIEQ.js → common-CNM4LjFu.js} +8 -4
- package/dist/esm/{common-B2rPuIEQ.js.map → common-CNM4LjFu.js.map} +1 -1
- package/dist/esm/{common-BMWVqV15.d.ts → common-DBLyu1c5.d.ts} +11 -9
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js +24 -9
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/utils/index.d.ts +1 -1
- package/dist/esm/utils/index.js +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
<h1 align="center">CallApi</h1>
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
|
-
|
|
4
|
+
<img src="https://raw.githubusercontent.com/zayne-labs/callapi/refs/heads/main/apps/docs/public/logo.png" alt="CallApi Logo" width="30%">
|
|
5
5
|
</p>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
|
+
<!-- <a href="https://deno.bundlejs.com/badge?q=@zayne-labs/callapi,@zayne-labs/callapi&treeshake=%5B*%5D,%5B%7B+createFetchClient+%7D%5D&config=%7B%22compression%22:%7B%22type%22:%22brotli%22,%22quality%22:11%7D%7D"><img src="https://deno.bundlejs.com/badge?q=@zayne-labs/callapi,@zayne-labs/callapi&treeshake=%5B*%5D,%5B%7B+createFetchClient+%7D%5D&config=%7B%22compression%22:%7B%22type%22:%22brotli%22,%22quality%22:11%7D%7D" alt="bundle size"></a> -->
|
|
8
9
|
<a href="https://www.npmjs.com/package/@zayne-labs/callapi"><img src="https://img.shields.io/npm/v/@zayne-labs/callapi?style=flat&color=EFBA5F" alt="npm version"></a>
|
|
9
10
|
<a href="https://github.com/zayne-labs/callapi/blob/master/LICENSE"><img src="https://img.shields.io/npm/l/@zayne-labs/callapi?style=flat&color=EFBA5F" alt="license"></a>
|
|
10
11
|
<a href="https://www.npmjs.com/package/@zayne-labs/callapi"><img src="https://img.shields.io/npm/dm/@zayne-labs/callapi?style=flat&color=EFBA5F" alt="downloads per month"></a>
|
|
11
12
|
<a href="https://github.com/zayne-labs/callapi/graphs/commit-activity"><img src="https://img.shields.io/github/commit-activity/m/zayne-labs/callapi?style=flat&color=EFBA5F" alt="commit activity"></a>
|
|
12
|
-
|
|
13
|
-
<a href="https://code2tutorial.com/tutorial/
|
|
14
|
-
</p>
|
|
13
|
+
<a href="https://deepwiki.com/zayne-labs/callapi"><img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki"></a>
|
|
14
|
+
<a href="https://code2tutorial.com/tutorial/f77cfbd0-3c37-4c37-9608-b3c977e46f00/index.md"><img src="https://img.shields.io/badge/Code2Tutorial-blue?color=blue&logo=victoriametrics" alt="Code2Tutorial"></a>
|
|
15
|
+
</p>
|
|
15
16
|
|
|
16
|
-
<p align="center">
|
|
17
|
+
<p align="center">
|
|
18
|
+
<strong>An advanced fetch library that actually solves real problems.</strong>
|
|
19
|
+
</p>
|
|
17
20
|
|
|
18
21
|
<p align="center">
|
|
19
22
|
<a href="https://zayne-labs-callapi.netlify.app"><strong>Documentation</strong></a> ·
|
|
@@ -23,9 +26,11 @@
|
|
|
23
26
|
|
|
24
27
|
---
|
|
25
28
|
|
|
26
|
-
|
|
29
|
+
## Why?
|
|
27
30
|
|
|
28
|
-
|
|
31
|
+
Fetch is too basic for real apps. You end up writing the same boilerplate: error handling, retries, deduplication, response parsing etc. CallApi handles all of that and practically more.
|
|
32
|
+
|
|
33
|
+
**Drop-in replacement for fetch. Under 6KB. Zero dependencies.**
|
|
29
34
|
|
|
30
35
|
```js
|
|
31
36
|
import { callApi } from "@zayne-labs/callapi";
|
|
@@ -33,180 +38,118 @@ import { callApi } from "@zayne-labs/callapi";
|
|
|
33
38
|
const { data, error } = await callApi("/api/users");
|
|
34
39
|
```
|
|
35
40
|
|
|
36
|
-
##
|
|
37
|
-
|
|
38
|
-
Fetch is great for simple requests, but real apps need more:
|
|
39
|
-
|
|
40
|
-
- Handling duplicate requests (user spam-clicks a button)
|
|
41
|
-
- Retrying failed requests (network flaky, API rate-limiting)
|
|
42
|
-
- Parsing responses (stop writing `await response.json()` everywhere)
|
|
43
|
-
- Error handling that makes sense (not just `response.ok`)
|
|
44
|
-
- TypeScript types that actually work
|
|
41
|
+
## Features
|
|
45
42
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
## What CallApi Does
|
|
49
|
-
|
|
50
|
-
It's fetch, but with the stuff you actually need:
|
|
51
|
-
|
|
52
|
-
### Request Deduplication
|
|
53
|
-
|
|
54
|
-
User spam-clicks? No problem. Duplicate requests are handled automatically.
|
|
43
|
+
**Request Deduplication** - User spam-clicks a button? Handled. No race conditions.
|
|
55
44
|
|
|
56
45
|
```js
|
|
57
|
-
// These share the same request - no race conditions
|
|
58
46
|
const req1 = callApi("/api/user");
|
|
59
|
-
const req2 = callApi("/api/user"); //
|
|
47
|
+
const req2 = callApi("/api/user"); // Shares req1's response
|
|
60
48
|
```
|
|
61
49
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
- **Cancel**: Latest request wins (perfect for search-as-you-type)
|
|
65
|
-
- **Defer**: Share response between duplicates (when multiple components need the same data)
|
|
66
|
-
- **None**: Let them all through (for polling)
|
|
67
|
-
|
|
68
|
-
### Smart Response Parsing
|
|
69
|
-
|
|
70
|
-
Looks at Content-Type and does the right thing. No more manual parsing.
|
|
50
|
+
**Smart Response Parsing** - Looks at Content-Type, does the right thing.
|
|
71
51
|
|
|
72
52
|
```js
|
|
73
53
|
const { data } = await callApi("/api/data"); // JSON? Parsed.
|
|
74
|
-
const { data } = await callApi("/page.html"); // HTML? String.
|
|
75
|
-
const { data } = await callApi("/image.png"); // Image? Blob.
|
|
76
54
|
```
|
|
77
55
|
|
|
78
|
-
|
|
56
|
+
**Error Handling** - Structured errors you can actually use.
|
|
79
57
|
|
|
80
58
|
```js
|
|
81
59
|
const { data, error } = await callApi("/api/users");
|
|
82
|
-
|
|
83
60
|
if (error) {
|
|
84
|
-
console.log(error.name); // "HTTPError", "ValidationError"
|
|
85
|
-
console.log(error.errorData); //
|
|
61
|
+
console.log(error.name); // "HTTPError", "ValidationError"
|
|
62
|
+
console.log(error.errorData); // Actual API response
|
|
86
63
|
}
|
|
87
64
|
```
|
|
88
65
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
### Retries That Actually Work
|
|
92
|
-
|
|
93
|
-
Network flaky? API rate-limiting you? Handled.
|
|
66
|
+
**Retries** - Exponential backoff, custom conditions.
|
|
94
67
|
|
|
95
68
|
```js
|
|
96
69
|
await callApi("/api/data", {
|
|
97
70
|
retryAttempts: 3,
|
|
98
|
-
retryStrategy: "exponential",
|
|
71
|
+
retryStrategy: "exponential",
|
|
99
72
|
retryStatusCodes: [429, 500, 502, 503],
|
|
100
73
|
});
|
|
101
74
|
```
|
|
102
75
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
Define your API schema once, get TypeScript types AND runtime validation:
|
|
76
|
+
**Schema Validation** - TypeScript types + runtime validation.
|
|
106
77
|
|
|
107
78
|
```js
|
|
108
79
|
import { z } from "zod";
|
|
109
80
|
import { defineSchema, createFetchClient } from "@zayne-labs/callapi";
|
|
110
81
|
|
|
111
82
|
const api = createFetchClient({
|
|
112
|
-
baseURL: "https://api.example.com",
|
|
113
83
|
schema: defineSchema({
|
|
114
84
|
"/users/:id": {
|
|
115
85
|
data: z.object({
|
|
116
86
|
id: z.number(),
|
|
117
87
|
name: z.string(),
|
|
118
|
-
email: z.string(),
|
|
119
88
|
}),
|
|
120
89
|
},
|
|
121
90
|
}),
|
|
122
91
|
});
|
|
123
92
|
|
|
124
|
-
const user = await api("/users/123");
|
|
125
|
-
// TypeScript knows the exact shape
|
|
126
|
-
// Runtime validates it matches
|
|
127
|
-
// If it doesn't? ValidationError with details
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
Works with Zod, Valibot, ArkType, or any Standard Schema validator.
|
|
131
|
-
|
|
132
|
-
### URL Helpers
|
|
133
|
-
|
|
134
|
-
```js
|
|
135
|
-
// Dynamic params
|
|
136
|
-
await callApi("/users/:id/posts/:postId", {
|
|
137
|
-
params: { id: 123, postId: 456 },
|
|
138
|
-
}); // → /users/123/posts/456
|
|
139
|
-
|
|
140
|
-
// Query strings
|
|
141
|
-
await callApi("/search", {
|
|
142
|
-
query: { q: "javascript", limit: 10 },
|
|
143
|
-
}); // → /search?q=javascript&limit=10
|
|
144
|
-
|
|
145
|
-
// Method prefixes
|
|
146
|
-
await callApi("@delete/users/123"); // Sets method to DELETE
|
|
93
|
+
const user = await api("/users/123"); // Fully typed + validated
|
|
147
94
|
```
|
|
148
95
|
|
|
149
|
-
|
|
96
|
+
**Hooks** - Intercept at any point.
|
|
150
97
|
|
|
151
98
|
```js
|
|
152
99
|
const api = createFetchClient({
|
|
153
100
|
onRequest: ({ request }) => {
|
|
154
|
-
// Add auth, modify headers
|
|
155
101
|
request.headers.set("Authorization", `Bearer ${token}`);
|
|
156
102
|
},
|
|
157
|
-
|
|
158
|
-
onResponse: ({ data, response }) => {
|
|
159
|
-
// Log, cache, transform
|
|
160
|
-
console.log(`${response.status} - ${response.url}`);
|
|
161
|
-
},
|
|
162
|
-
|
|
163
103
|
onError: ({ error }) => {
|
|
164
|
-
// Send to error tracking
|
|
165
104
|
Sentry.captureException(error);
|
|
166
105
|
},
|
|
167
|
-
|
|
168
|
-
onRequestStream: ({ event }) => {
|
|
169
|
-
// Upload progress
|
|
170
|
-
console.log(`Uploaded ${event.progress}%`);
|
|
171
|
-
},
|
|
172
|
-
|
|
173
106
|
onResponseStream: ({ event }) => {
|
|
174
|
-
|
|
175
|
-
updateProgressBar(event.progress);
|
|
107
|
+
console.log(`Downloaded ${event.progress}%`);
|
|
176
108
|
},
|
|
177
109
|
});
|
|
178
110
|
```
|
|
179
111
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
Build plugins for caching, upload progress with XHR, offline detection, whatever you need:
|
|
112
|
+
**Plugins** - Extend with middleware.
|
|
183
113
|
|
|
184
114
|
```js
|
|
185
115
|
const cachingPlugin = definePlugin({
|
|
186
|
-
id: "caching",
|
|
116
|
+
id: "caching-plugin",
|
|
117
|
+
name: "Caching plugin",
|
|
187
118
|
|
|
188
|
-
middlewares: (
|
|
119
|
+
middlewares: (ctx) => {
|
|
189
120
|
const cache = new Map();
|
|
190
121
|
|
|
191
122
|
return {
|
|
192
123
|
fetchMiddleware: (fetchImpl) => async (input, init) => {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
if (
|
|
196
|
-
return
|
|
124
|
+
const key = input.toString();
|
|
125
|
+
|
|
126
|
+
if (cache.has(key)) {
|
|
127
|
+
return cache.get(key).clone();
|
|
197
128
|
}
|
|
198
129
|
|
|
199
|
-
// Fetch and cache
|
|
200
130
|
const response = await fetchImpl(input, init);
|
|
201
|
-
cache.set(
|
|
131
|
+
cache.set(key, response.clone());
|
|
132
|
+
|
|
202
133
|
return response;
|
|
203
134
|
},
|
|
204
135
|
};
|
|
205
136
|
},
|
|
206
137
|
});
|
|
138
|
+
|
|
139
|
+
const callBackendApi = createFetchClient({
|
|
140
|
+
plugins: [cachingPlugin],
|
|
141
|
+
});
|
|
207
142
|
```
|
|
208
143
|
|
|
209
|
-
|
|
144
|
+
**URL Helpers** - Dynamic params, query strings, method prefixes.
|
|
145
|
+
|
|
146
|
+
```js
|
|
147
|
+
await callApi("/users/:id", { params: { id: 123 } });
|
|
148
|
+
await callApi("/search", { query: { q: "test" } });
|
|
149
|
+
await callApi("@delete/users/123");
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
And so much more.
|
|
210
153
|
|
|
211
154
|
## Installation
|
|
212
155
|
|
|
@@ -217,42 +160,33 @@ npm install @zayne-labs/callapi
|
|
|
217
160
|
```js
|
|
218
161
|
import { callApi, createFetchClient } from "@zayne-labs/callapi";
|
|
219
162
|
|
|
220
|
-
// Simple
|
|
221
|
-
const { data
|
|
163
|
+
// Simple
|
|
164
|
+
const { data } = await callApi("/api/users");
|
|
222
165
|
|
|
223
|
-
// Configured
|
|
166
|
+
// Configured
|
|
224
167
|
const api = createFetchClient({
|
|
225
168
|
baseURL: "https://api.example.com",
|
|
226
169
|
retryAttempts: 2,
|
|
227
170
|
timeout: 10000,
|
|
228
|
-
dedupeStrategy: "cancel",
|
|
229
171
|
onError: ({ error }) => trackError(error),
|
|
230
172
|
});
|
|
231
|
-
|
|
232
|
-
const users = await api("/users");
|
|
233
173
|
```
|
|
234
174
|
|
|
235
|
-
### CDN
|
|
175
|
+
### CDN
|
|
236
176
|
|
|
237
177
|
```html
|
|
238
178
|
<script type="module">
|
|
239
179
|
import { callApi } from "https://esm.run/@zayne-labs/callapi";
|
|
240
|
-
|
|
241
|
-
const { data } = await callApi("/api/users");
|
|
242
180
|
</script>
|
|
243
181
|
```
|
|
244
182
|
|
|
245
|
-
##
|
|
246
|
-
|
|
247
|
-
**TypeScript just works.** Full inference everywhere. Your editor knows what `data` is without you telling it.
|
|
248
|
-
|
|
249
|
-
**The API feels familiar.** If you know fetch, you know 90% of CallApi. The rest is just additions that make sense.
|
|
250
|
-
|
|
251
|
-
**It's actually small.** Under 6KB. Zero dependencies. Tree-shakeable. Not one of those "lightweight" libraries that's actually 50KB.
|
|
252
|
-
|
|
253
|
-
**It's fast.** Built on native Web APIs (Fetch, AbortController, Streams). No abstractions that slow things down.
|
|
183
|
+
## What makes it worth considering?
|
|
254
184
|
|
|
255
|
-
**
|
|
185
|
+
- **TypeScript-first** - Full inference everywhere
|
|
186
|
+
- **Familiar API** - If you know fetch, you know CallApi
|
|
187
|
+
- **Actually small** - Zero dependencies and Under 6KB, unlike other 50kb libs in the wild
|
|
188
|
+
- **Fast** - Built on native Web APIs
|
|
189
|
+
- **Works everywhere** - Browsers, Node 18+, Deno, Bun, Cloudflare Workers
|
|
256
190
|
|
|
257
191
|
## License
|
|
258
192
|
|
|
@@ -529,9 +529,13 @@ const getInitFetchImpl = (customFetchImpl) => {
|
|
|
529
529
|
if (typeof globalThis !== "undefined" && isFunction(globalThis.fetch)) return globalThis.fetch;
|
|
530
530
|
throw new Error("No fetch implementation found");
|
|
531
531
|
};
|
|
532
|
-
const getFetchImpl = (
|
|
533
|
-
const
|
|
534
|
-
|
|
532
|
+
const getFetchImpl = (context) => {
|
|
533
|
+
const { customFetchImpl, fetchMiddleware, requestContext } = context;
|
|
534
|
+
const initFetchImpl = getInitFetchImpl(customFetchImpl);
|
|
535
|
+
return fetchMiddleware ? fetchMiddleware({
|
|
536
|
+
...requestContext,
|
|
537
|
+
fetchImpl: initFetchImpl
|
|
538
|
+
}) : initFetchImpl;
|
|
535
539
|
};
|
|
536
540
|
const PromiseWithResolvers = () => {
|
|
537
541
|
let reject;
|
|
@@ -573,4 +577,4 @@ const toArray = (value) => isArray(value) ? value : [value];
|
|
|
573
577
|
|
|
574
578
|
//#endregion
|
|
575
579
|
export { HTTPError, ValidationError, createCombinedSignal, createTimeoutSignal, deterministicHashFn, extraOptionDefaults, fallBackRouteSchemaKey, getBody, getCurrentRouteSchemaKeyAndMainInitURL, getFetchImpl, getFullAndNormalizedURL, getHeaders, getMethod, handleConfigValidation, handleSchemaValidation, isArray, isBoolean, isFunction, isHTTPError, isHTTPErrorInstance, isJavascriptError, isObject, isPlainObject, isReadableStream, isString, isValidationError, isValidationErrorInstance, splitBaseConfig, splitConfig, toQueryString, waitFor };
|
|
576
|
-
//# sourceMappingURL=common-
|
|
580
|
+
//# sourceMappingURL=common-CNM4LjFu.js.map
|