@http-client-toolkit/core 0.0.1
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/LICENSE +15 -0
- package/README.md +160 -0
- package/lib/index.cjs +615 -0
- package/lib/index.cjs.map +1 -0
- package/lib/index.d.cts +382 -0
- package/lib/index.d.ts +382 -0
- package/lib/index.js +604 -0
- package/lib/index.js.map +1 -0
- package/package.json +76 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025, Ally Murray
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
10
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
11
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
12
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
13
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
14
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
15
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# @http-client-toolkit/core
|
|
2
|
+
|
|
3
|
+
Core HTTP client with pluggable caching, deduplication, and rate limiting. Part of the [http-client-toolkit](https://github.com/AllyMurray/http-client-toolkit) monorepo.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @http-client-toolkit/core
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Requires Node.js >= 20.
|
|
12
|
+
|
|
13
|
+
You'll also need at least one store backend:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @http-client-toolkit/store-memory
|
|
17
|
+
# or
|
|
18
|
+
npm install @http-client-toolkit/store-sqlite
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Quick Start
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import { HttpClient } from '@http-client-toolkit/core';
|
|
25
|
+
import {
|
|
26
|
+
InMemoryCacheStore,
|
|
27
|
+
InMemoryDedupeStore,
|
|
28
|
+
InMemoryRateLimitStore,
|
|
29
|
+
} from '@http-client-toolkit/store-memory';
|
|
30
|
+
|
|
31
|
+
const client = new HttpClient(
|
|
32
|
+
{
|
|
33
|
+
cache: new InMemoryCacheStore(),
|
|
34
|
+
dedupe: new InMemoryDedupeStore(),
|
|
35
|
+
rateLimit: new InMemoryRateLimitStore(),
|
|
36
|
+
},
|
|
37
|
+
{ defaultCacheTTL: 300 },
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const data = await client.get<{ name: string }>(
|
|
41
|
+
'https://api.example.com/user/1',
|
|
42
|
+
);
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Every store is optional. Use only what you need:
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
// Cache-only client
|
|
49
|
+
const client = new HttpClient({ cache: new InMemoryCacheStore() });
|
|
50
|
+
|
|
51
|
+
// Rate-limited client with no caching
|
|
52
|
+
const client = new HttpClient({
|
|
53
|
+
rateLimit: new InMemoryRateLimitStore({
|
|
54
|
+
defaultConfig: { limit: 100, windowMs: 60_000 },
|
|
55
|
+
}),
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## API
|
|
60
|
+
|
|
61
|
+
### `new HttpClient(stores?, options?)`
|
|
62
|
+
|
|
63
|
+
`HttpClient` exposes a single request method: `get(url, options?)`. The `url` must be an absolute URL.
|
|
64
|
+
|
|
65
|
+
**Request options (`client.get`)**
|
|
66
|
+
|
|
67
|
+
| Property | Type | Default | Description |
|
|
68
|
+
| ---------- | ------------------------ | -------------- | ----------------------------------- |
|
|
69
|
+
| `signal` | `AbortSignal` | - | Cancels wait + request when aborted |
|
|
70
|
+
| `priority` | `'user' \| 'background'` | `'background'` | Used by adaptive rate-limit stores |
|
|
71
|
+
|
|
72
|
+
**Stores:**
|
|
73
|
+
|
|
74
|
+
| Property | Type | Description |
|
|
75
|
+
| ----------- | ------------------------------------------ | --------------------- |
|
|
76
|
+
| `cache` | `CacheStore` | Response caching |
|
|
77
|
+
| `dedupe` | `DedupeStore` | Request deduplication |
|
|
78
|
+
| `rateLimit` | `RateLimitStore \| AdaptiveRateLimitStore` | Rate limiting |
|
|
79
|
+
|
|
80
|
+
**Options:**
|
|
81
|
+
|
|
82
|
+
| Property | Type | Default | Description |
|
|
83
|
+
| --------------------- | ---------------------------- | -------- | --------------------------------------- |
|
|
84
|
+
| `defaultCacheTTL` | `number` | `3600` | Cache TTL in seconds |
|
|
85
|
+
| `throwOnRateLimit` | `boolean` | `true` | Throw when rate limited vs. wait |
|
|
86
|
+
| `maxWaitTime` | `number` | `60000` | Max wait time (ms) before throwing |
|
|
87
|
+
| `responseTransformer` | `(data: unknown) => unknown` | - | Transform raw response data |
|
|
88
|
+
| `responseHandler` | `(data: unknown) => unknown` | - | Validate/process transformed data |
|
|
89
|
+
| `errorHandler` | `(error: unknown) => Error` | - | Convert errors to domain-specific types |
|
|
90
|
+
| `rateLimitHeaders` | `RateLimitHeaderConfig` | defaults | Configure standard/custom header names |
|
|
91
|
+
|
|
92
|
+
### Request Flow
|
|
93
|
+
|
|
94
|
+
1. **Cache** - Return cached response if available
|
|
95
|
+
2. **Dedupe** - If an identical request is already in-flight, wait for its result
|
|
96
|
+
3. **Rate Limit** - Wait or throw if the rate limit is exceeded
|
|
97
|
+
4. **Fetch** - Execute the HTTP request
|
|
98
|
+
5. **Transform & Validate** - Apply `responseTransformer` then `responseHandler`
|
|
99
|
+
6. **Store** - Cache the result, record the rate limit hit, and resolve any deduplicated waiters
|
|
100
|
+
|
|
101
|
+
### Error Handling
|
|
102
|
+
|
|
103
|
+
All HTTP errors are wrapped in `HttpClientError`:
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
import { HttpClientError } from '@http-client-toolkit/core';
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
await client.get(url);
|
|
110
|
+
} catch (error) {
|
|
111
|
+
if (error instanceof HttpClientError) {
|
|
112
|
+
console.log(error.message);
|
|
113
|
+
console.log(error.statusCode);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Cancellation
|
|
119
|
+
|
|
120
|
+
Pass an `AbortSignal` to cancel a request, including while waiting for a rate limit window:
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
const controller = new AbortController();
|
|
124
|
+
const data = await client.get(url, { signal: controller.signal });
|
|
125
|
+
controller.abort();
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Header-Based Rate Limiting
|
|
129
|
+
|
|
130
|
+
`HttpClient` respects server-provided rate-limit headers out of the box:
|
|
131
|
+
|
|
132
|
+
- `Retry-After`
|
|
133
|
+
- `RateLimit-Remaining` / `RateLimit-Reset`
|
|
134
|
+
- `X-RateLimit-Remaining` / `X-RateLimit-Reset`
|
|
135
|
+
|
|
136
|
+
Map non-standard header names per API:
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
const client = new HttpClient(
|
|
140
|
+
{},
|
|
141
|
+
{
|
|
142
|
+
rateLimitHeaders: {
|
|
143
|
+
retryAfter: ['RetryAfterSeconds'],
|
|
144
|
+
remaining: ['Remaining-Requests'],
|
|
145
|
+
reset: ['Window-Reset-Seconds'],
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
);
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Exports
|
|
152
|
+
|
|
153
|
+
- `HttpClient` - Main client class
|
|
154
|
+
- `HttpClientError` - Error class with `statusCode`
|
|
155
|
+
- `hashRequest` - Deterministic SHA-256 request hashing
|
|
156
|
+
- Store interfaces: `CacheStore`, `DedupeStore`, `RateLimitStore`, `AdaptiveRateLimitStore`
|
|
157
|
+
|
|
158
|
+
## License
|
|
159
|
+
|
|
160
|
+
ISC
|