@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 CHANGED
@@ -1,19 +1,22 @@
1
1
  <h1 align="center">CallApi</h1>
2
2
 
3
3
  <p align="center">
4
- <img src="https://raw.githubusercontent.com/zayne-labs/callapi/refs/heads/main/apps/docs/public/logo.png" alt="CallApi Logo" width="30%">
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
- <a href="https://deepwiki.com/zayne-labs/callapi"><img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki"></a>
13
- <a href="https://code2tutorial.com/tutorial/02b6c57c-4847-4e76-b91e-d64dde370609/index.md"><img src="https://img.shields.io/badge/Code2Tutorial-blue?color=blue&logo=victoriametrics" alt="Code2Tutorial"></a>
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">A fetch wrapper that actually solves real problems.</p>
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
- Stop writing the same HTTP boilerplate in every project. CallApi is a drop-in replacement for fetch with all the features you'd build yourself anyway - just done properly.
29
+ ## Why?
27
30
 
28
- **Under 6KB. Zero dependencies. Runs everywhere.**
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
- ## The Problem
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
- You end up writing the same code over and over. Or pulling in a 100KB library.
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"); // Reuses req1's response
47
+ const req2 = callApi("/api/user"); // Shares req1's response
60
48
  ```
61
49
 
62
- Three strategies:
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
- ### Error Handling That Makes Sense
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", "TimeoutError"
85
- console.log(error.errorData); // The actual error response from your API
61
+ console.log(error.name); // "HTTPError", "ValidationError"
62
+ console.log(error.errorData); // Actual API response
86
63
  }
87
64
  ```
88
65
 
89
- No more `try/catch` hell. No more checking `response.ok`. Just clean, predictable errors.
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", // 1s, 2s, 4s, 8s...
71
+ retryStrategy: "exponential",
99
72
  retryStatusCodes: [429, 500, 502, 503],
100
73
  });
101
74
  ```
102
75
 
103
- ### Schema Validation (Type-Safe + Runtime)
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
- ### Hooks for Everything
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
- // Download progress
175
- updateProgressBar(event.progress);
107
+ console.log(`Downloaded ${event.progress}%`);
176
108
  },
177
109
  });
178
110
  ```
179
111
 
180
- ### Plugin System
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: ({ options }) => {
119
+ middlewares: (ctx) => {
189
120
  const cache = new Map();
190
121
 
191
122
  return {
192
123
  fetchMiddleware: (fetchImpl) => async (input, init) => {
193
- // Check cache first
194
- const cached = cache.get(input.toString());
195
- if (cached && !isExpired(cached)) {
196
- return cached.response.clone();
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(input.toString(), { response: response.clone(), timestamp: Date.now() });
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
- Plugins can wrap fetch (middleware), add hooks, define custom options. They compose automatically.
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 usage
221
- const { data, error } = await callApi("/api/users");
163
+ // Simple
164
+ const { data } = await callApi("/api/users");
222
165
 
223
- // Configured client
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 (No Build Step)
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
- ## Why It's Nice to Use
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
- **It works everywhere.** Browsers, Node 18+, Deno, Bun, Cloudflare Workers. If it runs JavaScript, it runs CallApi.
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 = (customFetchImpl, fetchMiddleware) => {
533
- const initFetchApi = getInitFetchImpl(customFetchImpl);
534
- return fetchMiddleware ? fetchMiddleware(initFetchApi) : initFetchApi;
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-B2rPuIEQ.js.map
580
+ //# sourceMappingURL=common-CNM4LjFu.js.map