@igniter-js/caller 0.1.0 → 0.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/AGENTS.md +151 -9
- package/CHANGELOG.md +54 -0
- package/README.md +211 -37
- package/dist/index.d.mts +327 -41
- package/dist/index.d.ts +327 -41
- package/dist/index.js +428 -138
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +428 -138
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# @igniter-js/caller - AI Agent Instructions
|
|
2
2
|
|
|
3
3
|
> **Package Version:** 0.1.0
|
|
4
|
-
> **Last Updated:** 2025-12-
|
|
4
|
+
> **Last Updated:** 2025-12-14
|
|
5
5
|
> **Status:** Ready for Publication
|
|
6
6
|
|
|
7
7
|
---
|
|
@@ -14,13 +14,16 @@
|
|
|
14
14
|
|
|
15
15
|
### Core Features
|
|
16
16
|
|
|
17
|
-
- Fluent request builder API (`
|
|
17
|
+
- Fluent request builder API (`api.get('/users').execute()`)
|
|
18
|
+
- axios-style requests (`api.request({ method, url, body })`)
|
|
19
|
+
- Auto content-type detection (JSON, XML, Blob, Stream, etc.)
|
|
18
20
|
- Request and response interceptors
|
|
19
21
|
- Retry support (linear/exponential backoff + status-based retries)
|
|
20
22
|
- Caching (in-memory + optional persistent store adapter)
|
|
21
23
|
- Global response events (observe responses across the app)
|
|
22
24
|
- Schema validation using `StandardSchemaV1` from `@igniter-js/core`
|
|
23
25
|
- Optional Zod response validation via `responseType(zodSchema)`
|
|
26
|
+
- Auto-conversion of GET body to query params
|
|
24
27
|
|
|
25
28
|
---
|
|
26
29
|
|
|
@@ -38,32 +41,121 @@ This package is intentionally small and split into predictable layers:
|
|
|
38
41
|
|
|
39
42
|
1. **Fetch-first**
|
|
40
43
|
- Uses the global `fetch` API.
|
|
41
|
-
- Works in Node.js (18+), Bun, and modern
|
|
44
|
+
- Works in Node.js (18+), Bun, Deno, and modern browsers.
|
|
42
45
|
|
|
43
|
-
2. **
|
|
46
|
+
2. **Auto content-type detection**
|
|
47
|
+
- Response parsing is automatic based on `Content-Type` header.
|
|
48
|
+
- JSON, XML, CSV → parsed and validated if schema provided.
|
|
49
|
+
- Blob, Stream, ArrayBuffer → returned as-is, no validation.
|
|
50
|
+
|
|
51
|
+
3. **Typed error surface**
|
|
44
52
|
- Predictable errors are `IgniterCallerError` with stable error codes.
|
|
45
53
|
|
|
46
|
-
|
|
54
|
+
4. **Separation of concerns**
|
|
47
55
|
- `IgniterCallerRequestBuilder` focuses on request composition and execution.
|
|
48
56
|
- `IgniterCallerCacheUtils` focuses on caching.
|
|
49
57
|
- `IgniterCallerSchemaUtils` focuses on schema matching and validation.
|
|
50
58
|
|
|
51
|
-
|
|
59
|
+
5. **Stable public API**
|
|
52
60
|
- Keep exports in `src/index.ts` stable and backwards-compatible.
|
|
53
61
|
|
|
54
62
|
---
|
|
55
63
|
|
|
64
|
+
## API Design
|
|
65
|
+
|
|
66
|
+
### HTTP Methods
|
|
67
|
+
|
|
68
|
+
HTTP methods accept an optional URL directly and return a builder **without** the `.method()` function (since method is already set):
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
// These are equivalent:
|
|
72
|
+
api.get('/users').execute()
|
|
73
|
+
api.get().url('/users').execute()
|
|
74
|
+
|
|
75
|
+
// POST, PUT, PATCH, DELETE, HEAD work the same way:
|
|
76
|
+
api.post('/users').body({ name: 'John' }).execute()
|
|
77
|
+
api.delete('/users/1').execute()
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### axios-style Requests
|
|
81
|
+
|
|
82
|
+
The `.request()` method executes immediately with all options:
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
const result = await api.request({
|
|
86
|
+
method: 'POST',
|
|
87
|
+
url: '/users',
|
|
88
|
+
body: { name: 'John' },
|
|
89
|
+
headers: { 'X-Custom': 'value' },
|
|
90
|
+
timeout: 5000,
|
|
91
|
+
staleTime: 30000,
|
|
92
|
+
retry: { maxAttempts: 3 },
|
|
93
|
+
})
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Response Type Detection
|
|
97
|
+
|
|
98
|
+
Response is parsed automatically based on `Content-Type` header:
|
|
99
|
+
|
|
100
|
+
| Content-Type | Parsed As |
|
|
101
|
+
|-------------|-----------|
|
|
102
|
+
| `application/json` | JSON object |
|
|
103
|
+
| `text/xml`, `application/xml` | Text |
|
|
104
|
+
| `text/csv` | Text |
|
|
105
|
+
| `text/html`, `text/plain` | Text |
|
|
106
|
+
| `image/*`, `audio/*`, `video/*` | Blob |
|
|
107
|
+
| `application/octet-stream` | Blob |
|
|
108
|
+
|
|
109
|
+
### GET Body → Query Params
|
|
110
|
+
|
|
111
|
+
Body in GET/HEAD requests is automatically converted to query params:
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
api.get('/search').body({ q: 'test', page: 1 }).execute()
|
|
115
|
+
// → GET /search?q=test&page=1
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## CLI Integration
|
|
121
|
+
|
|
122
|
+
- The Igniter CLI provides `igniter generate caller` to ingest an OpenAPI 3 spec (URL or file) and emit `schema.ts` + `index.ts` under `src/callers/<hostname>` by default.
|
|
123
|
+
- Generated schemas must follow `IgniterCallerSchemaMap` (`{ [path]: { METHOD: { request?: ..., responses: { <status>: zod } } } }`) to work with `.withSchemas()`.
|
|
124
|
+
- The CLI prefixes schemas and the exported caller with `--name` (e.g., `facebookCaller`, `FacebookSchema`).
|
|
125
|
+
- If runtime schema expectations change, update both the CLI generator and this documentation so consumers stay aligned.
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
56
129
|
## File Structure
|
|
57
130
|
|
|
58
131
|
```
|
|
59
132
|
packages/caller/
|
|
60
133
|
├── src/
|
|
61
|
-
│ ├── index.ts
|
|
134
|
+
│ ├── index.ts # Public exports
|
|
135
|
+
│ ├── igniter-caller.spec.ts # Tests
|
|
62
136
|
│ ├── core/
|
|
137
|
+
│ │ ├── igniter-caller.ts # Main IgniterCaller class
|
|
138
|
+
│ │ └── igniter-caller-events.ts # Event emitter
|
|
63
139
|
│ ├── builder/
|
|
140
|
+
│ │ ├── igniter-caller.builder.ts # Builder for configuration
|
|
141
|
+
│ │ └── igniter-caller-request.builder.ts # Request builder
|
|
64
142
|
│ ├── errors/
|
|
143
|
+
│ │ └── igniter-caller.error.ts # Error class
|
|
65
144
|
│ ├── types/
|
|
145
|
+
│ │ ├── events.ts
|
|
146
|
+
│ │ ├── http.ts
|
|
147
|
+
│ │ ├── interceptors.ts
|
|
148
|
+
│ │ ├── request.ts
|
|
149
|
+
│ │ ├── response.ts
|
|
150
|
+
│ │ ├── retry.ts
|
|
151
|
+
│ │ ├── schemas.ts
|
|
152
|
+
│ │ └── store.ts
|
|
66
153
|
│ └── utils/
|
|
154
|
+
│ ├── body.ts
|
|
155
|
+
│ ├── cache.ts
|
|
156
|
+
│ ├── schema.ts
|
|
157
|
+
│ ├── testing.ts
|
|
158
|
+
│ └── url.ts
|
|
67
159
|
├── package.json
|
|
68
160
|
├── tsconfig.json
|
|
69
161
|
├── tsup.config.ts
|
|
@@ -89,10 +181,20 @@ packages/caller/
|
|
|
89
181
|
- Use `IgniterCallerError` for predictable failures.
|
|
90
182
|
- Prefer stable `code` values over new ad-hoc error messages.
|
|
91
183
|
|
|
184
|
+
### Error Codes
|
|
185
|
+
|
|
186
|
+
| Code | Description |
|
|
187
|
+
|------|-------------|
|
|
188
|
+
| `IGNITER_CALLER_HTTP_ERROR` | HTTP response with non-2xx status |
|
|
189
|
+
| `IGNITER_CALLER_TIMEOUT` | Request timeout |
|
|
190
|
+
| `IGNITER_CALLER_REQUEST_VALIDATION_FAILED` | Request body schema validation failed |
|
|
191
|
+
| `IGNITER_CALLER_RESPONSE_VALIDATION_FAILED` | Response schema validation failed |
|
|
192
|
+
| `IGNITER_CALLER_UNKNOWN_ERROR` | Unexpected error |
|
|
193
|
+
|
|
92
194
|
### Dependencies
|
|
93
195
|
|
|
94
|
-
- `@igniter-js/core` is a **peer dependency** (for shared types like `
|
|
95
|
-
- `zod` is a **peer dependency** (used when consumers call `responseType(zodSchema)`
|
|
196
|
+
- `@igniter-js/core` is a **peer dependency** (for shared types like `IgniterLogger` and `StandardSchemaV1`).
|
|
197
|
+
- `zod` is a **peer dependency** (used when consumers call `responseType(zodSchema)`).
|
|
96
198
|
|
|
97
199
|
---
|
|
98
200
|
|
|
@@ -104,6 +206,9 @@ packages/caller/
|
|
|
104
206
|
- Response schema validation returns `IgniterCallerError` on invalid payload.
|
|
105
207
|
- Caching returns cached data without calling `fetch` again.
|
|
106
208
|
- Event emission happens for responses.
|
|
209
|
+
- URL is passed correctly to HTTP methods.
|
|
210
|
+
- Body is converted to query params for GET requests.
|
|
211
|
+
- Content-type detection works correctly.
|
|
107
212
|
|
|
108
213
|
Run tests:
|
|
109
214
|
|
|
@@ -113,6 +218,43 @@ npm test --filter @igniter-js/caller
|
|
|
113
218
|
|
|
114
219
|
---
|
|
115
220
|
|
|
221
|
+
## Key Implementation Details
|
|
222
|
+
|
|
223
|
+
### IgniterCallerMethodRequestBuilder
|
|
224
|
+
|
|
225
|
+
When using specific HTTP methods (get, post, etc.), the builder returns a type that omits internal methods:
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
export type IgniterCallerMethodRequestBuilder<TResponse = unknown> = Omit<
|
|
229
|
+
IgniterCallerRequestBuilder<TResponse>,
|
|
230
|
+
'_setMethod' | '_setUrl'
|
|
231
|
+
>
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
This prevents calling `.method()` on a request that already has a method set.
|
|
235
|
+
|
|
236
|
+
### Content-Type Detection
|
|
237
|
+
|
|
238
|
+
The `detectContentType` function maps headers to response types:
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
function detectContentType(contentType: string | null): IgniterCallerResponseContentType
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Returns one of: `'json' | 'xml' | 'csv' | 'text' | 'html' | 'blob' | 'stream' | 'arraybuffer' | 'formdata'`
|
|
245
|
+
|
|
246
|
+
### Schema Validation
|
|
247
|
+
|
|
248
|
+
Schema validation only runs for validatable content types:
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
const VALIDATABLE_CONTENT_TYPES = ['json', 'xml', 'csv']
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Binary responses (Blob, Stream, ArrayBuffer) are never validated.
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
116
258
|
## Publishing Checklist
|
|
117
259
|
|
|
118
260
|
- [ ] `npm run build --filter @igniter-js/caller`
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,60 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this package will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## 0.1.1
|
|
6
|
+
|
|
7
|
+
### New Features
|
|
8
|
+
|
|
9
|
+
- **URL in HTTP methods** - All HTTP methods (`get`, `post`, `put`, `patch`, `delete`, `head`) now accept URL directly:
|
|
10
|
+
```ts
|
|
11
|
+
// Before
|
|
12
|
+
api.get().url('/users').execute()
|
|
13
|
+
|
|
14
|
+
// After
|
|
15
|
+
api.get('/users').execute()
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
- **axios-style requests** - New `.request()` method for object-based API:
|
|
19
|
+
```ts
|
|
20
|
+
const result = await api.request({
|
|
21
|
+
method: 'POST',
|
|
22
|
+
url: '/users',
|
|
23
|
+
body: { name: 'John' },
|
|
24
|
+
timeout: 5000,
|
|
25
|
+
})
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
- **Auto content-type detection** - Response is automatically parsed based on `Content-Type` header:
|
|
29
|
+
- `application/json` → JSON object
|
|
30
|
+
- `text/xml`, `text/csv`, `text/html`, `text/plain` → Text
|
|
31
|
+
- `image/*`, `audio/*`, `video/*`, `application/pdf` → Blob
|
|
32
|
+
- `application/octet-stream` → Blob
|
|
33
|
+
|
|
34
|
+
- **GET body → query params** - Body in GET/HEAD requests is automatically converted to query parameters:
|
|
35
|
+
```ts
|
|
36
|
+
api.get('/search').body({ q: 'test' }).execute()
|
|
37
|
+
// → GET /search?q=test
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
- **Response includes status and headers** - API response now includes HTTP status and headers:
|
|
41
|
+
```ts
|
|
42
|
+
const result = await api.get('/users').execute()
|
|
43
|
+
console.log(result.status) // 200
|
|
44
|
+
console.log(result.headers?.get('x-request-id'))
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
- **HEAD method** - Added `.head()` method for HEAD requests.
|
|
48
|
+
|
|
49
|
+
### Improvements
|
|
50
|
+
|
|
51
|
+
- `responseType()` now serves primarily for TypeScript typing. Schema validation only runs for validatable content types (JSON, XML, CSV).
|
|
52
|
+
- `.method()` is no longer available when using specific HTTP methods (`get`, `post`, etc.) to prevent confusion.
|
|
53
|
+
- Better type inference for schema maps with new utility types.
|
|
54
|
+
|
|
55
|
+
### Deprecated
|
|
56
|
+
|
|
57
|
+
- `getFile()` is deprecated. Use `.responseType<Blob>().execute()` or `.responseType<File>().execute()` instead. Content-type detection is automatic.
|
|
58
|
+
|
|
5
59
|
## 0.1.0
|
|
6
60
|
|
|
7
61
|
- Initial release of `@igniter-js/caller`.
|
package/README.md
CHANGED
|
@@ -7,13 +7,16 @@ Type-safe HTTP client for Igniter.js apps. Built on top of `fetch`, it gives you
|
|
|
7
7
|
|
|
8
8
|
## Features
|
|
9
9
|
|
|
10
|
-
- ✅ **Fluent API** - `
|
|
10
|
+
- ✅ **Fluent API** - `api.get('/users').execute()` or builder pattern
|
|
11
|
+
- ✅ **axios-style requests** - `api.request({ method, url, body, ... })`
|
|
12
|
+
- ✅ **Auto content-type detection** - JSON, XML, CSV, Blob, Stream, etc.
|
|
11
13
|
- ✅ **Interceptors** - modify requests and responses in one place
|
|
12
14
|
- ✅ **Retries** - linear or exponential backoff + status-based retry
|
|
13
15
|
- ✅ **Caching** - in-memory cache + optional persistent store adapter
|
|
14
16
|
- ✅ **Schema Validation** - validate request/response using `StandardSchemaV1`
|
|
15
17
|
- ✅ **Zod Support** - optional per-request `responseType(zodSchema)` validation
|
|
16
18
|
- ✅ **Global Events** - observe responses for logging/telemetry/cache invalidation
|
|
19
|
+
- ✅ **Auto query encoding** - body in GET requests converts to query params
|
|
17
20
|
|
|
18
21
|
## Installation
|
|
19
22
|
|
|
@@ -43,12 +46,14 @@ export const api = IgniterCaller.create()
|
|
|
43
46
|
.withHeaders({ Authorization: `Bearer ${process.env.API_TOKEN}` })
|
|
44
47
|
.build()
|
|
45
48
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
// Simple GET request with URL directly
|
|
50
|
+
const result = await api.get('/users').execute()
|
|
51
|
+
|
|
52
|
+
// With query params
|
|
53
|
+
const result = await api.get('/users').params({ page: 1 }).execute()
|
|
54
|
+
|
|
55
|
+
// With caching
|
|
56
|
+
const result = await api.get('/users').stale(10_000).execute()
|
|
52
57
|
|
|
53
58
|
if (result.error) {
|
|
54
59
|
throw result.error
|
|
@@ -57,13 +62,105 @@ if (result.error) {
|
|
|
57
62
|
console.log(result.data)
|
|
58
63
|
```
|
|
59
64
|
|
|
65
|
+
## HTTP Methods
|
|
66
|
+
|
|
67
|
+
All HTTP methods accept an optional URL directly:
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
// GET
|
|
71
|
+
const users = await api.get('/users').execute()
|
|
72
|
+
|
|
73
|
+
// POST with body
|
|
74
|
+
const created = await api.post('/users').body({ name: 'John' }).execute()
|
|
75
|
+
|
|
76
|
+
// PUT
|
|
77
|
+
const updated = await api.put('/users/1').body({ name: 'Jane' }).execute()
|
|
78
|
+
|
|
79
|
+
// PATCH
|
|
80
|
+
const patched = await api.patch('/users/1').body({ name: 'Jane' }).execute()
|
|
81
|
+
|
|
82
|
+
// DELETE
|
|
83
|
+
const deleted = await api.delete('/users/1').execute()
|
|
84
|
+
|
|
85
|
+
// HEAD
|
|
86
|
+
const head = await api.head('/users').execute()
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
You can also use the traditional builder pattern:
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
const result = await api.get().url('/users').params({ page: 1 }).execute()
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## axios-style Requests
|
|
96
|
+
|
|
97
|
+
For dynamic requests or when you prefer an object-based API:
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
const result = await api.request({
|
|
101
|
+
method: 'POST',
|
|
102
|
+
url: '/users',
|
|
103
|
+
body: { name: 'John' },
|
|
104
|
+
headers: { 'X-Custom': 'value' },
|
|
105
|
+
timeout: 5000,
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
// With caching
|
|
109
|
+
const result = await api.request({
|
|
110
|
+
method: 'GET',
|
|
111
|
+
url: '/users',
|
|
112
|
+
staleTime: 30000,
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
// With retry
|
|
116
|
+
const result = await api.request({
|
|
117
|
+
method: 'GET',
|
|
118
|
+
url: '/health',
|
|
119
|
+
retry: { maxAttempts: 3, backoff: 'exponential' },
|
|
120
|
+
})
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Auto Content-Type Detection
|
|
124
|
+
|
|
125
|
+
The response is automatically parsed based on the `Content-Type` header:
|
|
126
|
+
|
|
127
|
+
| Content-Type | Parsed As |
|
|
128
|
+
|-------------|-----------|
|
|
129
|
+
| `application/json` | JSON object |
|
|
130
|
+
| `text/xml`, `application/xml` | Text (parse with your XML library) |
|
|
131
|
+
| `text/csv` | Text |
|
|
132
|
+
| `text/html`, `text/plain` | Text |
|
|
133
|
+
| `image/*`, `audio/*`, `video/*` | Blob |
|
|
134
|
+
| `application/pdf`, `application/zip` | Blob |
|
|
135
|
+
| `application/octet-stream` | Blob |
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
// JSON response - automatically parsed
|
|
139
|
+
const { data } = await api.get('/users').execute()
|
|
140
|
+
|
|
141
|
+
// Blob response - automatically detected
|
|
142
|
+
const { data } = await api.get('/file.pdf').responseType<Blob>().execute()
|
|
143
|
+
|
|
144
|
+
// Stream response
|
|
145
|
+
const { data } = await api.get('/stream').responseType<ReadableStream>().execute()
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## GET with Body → Query Params
|
|
149
|
+
|
|
150
|
+
When you pass a body to a GET request, it's automatically converted to query parameters:
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
// This:
|
|
154
|
+
await api.get('/search').body({ q: 'test', page: 1 }).execute()
|
|
155
|
+
|
|
156
|
+
// Becomes: GET /search?q=test&page=1
|
|
157
|
+
```
|
|
158
|
+
|
|
60
159
|
## Interceptors
|
|
61
160
|
|
|
62
161
|
Interceptors are great for cross-cutting concerns like auth headers, request ids, logging, and response normalization.
|
|
63
162
|
|
|
64
163
|
```ts
|
|
65
|
-
import { IgniterCaller } from '@igniter-js/caller'
|
|
66
|
-
|
|
67
164
|
const api = IgniterCaller.create()
|
|
68
165
|
.withBaseUrl('https://api.example.com')
|
|
69
166
|
.withRequestInterceptor(async (request) => {
|
|
@@ -80,7 +177,6 @@ const api = IgniterCaller.create()
|
|
|
80
177
|
if (response.data === '') {
|
|
81
178
|
return { ...response, data: null as any }
|
|
82
179
|
}
|
|
83
|
-
|
|
84
180
|
return response
|
|
85
181
|
})
|
|
86
182
|
.build()
|
|
@@ -92,8 +188,7 @@ Configure retry behavior for transient errors:
|
|
|
92
188
|
|
|
93
189
|
```ts
|
|
94
190
|
const result = await api
|
|
95
|
-
.get()
|
|
96
|
-
.url('/health')
|
|
191
|
+
.get('/health')
|
|
97
192
|
.retry(3, {
|
|
98
193
|
baseDelay: 250,
|
|
99
194
|
backoff: 'exponential',
|
|
@@ -109,7 +204,7 @@ const result = await api
|
|
|
109
204
|
Use `.stale(ms)` to enable caching. The cache key defaults to the request URL, or you can set it via `.cache(cache, key)`.
|
|
110
205
|
|
|
111
206
|
```ts
|
|
112
|
-
const users = await api.get(
|
|
207
|
+
const users = await api.get('/users').stale(30_000).execute()
|
|
113
208
|
```
|
|
114
209
|
|
|
115
210
|
### Store-based caching
|
|
@@ -121,26 +216,18 @@ import { IgniterCaller } from '@igniter-js/caller'
|
|
|
121
216
|
|
|
122
217
|
const store = {
|
|
123
218
|
client: null,
|
|
124
|
-
async get(key) {
|
|
125
|
-
|
|
126
|
-
},
|
|
127
|
-
async
|
|
128
|
-
void key
|
|
129
|
-
void value
|
|
130
|
-
},
|
|
131
|
-
async delete(key) {
|
|
132
|
-
void key
|
|
133
|
-
},
|
|
134
|
-
async has(key) {
|
|
135
|
-
void key
|
|
136
|
-
return false
|
|
137
|
-
},
|
|
219
|
+
async get(key) { return null },
|
|
220
|
+
async set(key, value) { void key; void value },
|
|
221
|
+
async delete(key) { void key },
|
|
222
|
+
async has(key) { void key; return false },
|
|
138
223
|
}
|
|
139
224
|
|
|
140
|
-
const api = IgniterCaller.create()
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
225
|
+
const api = IgniterCaller.create()
|
|
226
|
+
.withStore(store, {
|
|
227
|
+
ttl: 3600,
|
|
228
|
+
keyPrefix: 'igniter:caller:',
|
|
229
|
+
})
|
|
230
|
+
.build()
|
|
144
231
|
```
|
|
145
232
|
|
|
146
233
|
## Schema Validation (StandardSchemaV1)
|
|
@@ -166,21 +253,45 @@ const api = IgniterCaller.create()
|
|
|
166
253
|
.withSchemas(schemas, { mode: 'strict' })
|
|
167
254
|
.build()
|
|
168
255
|
|
|
169
|
-
const result = await api.get(
|
|
256
|
+
const result = await api.get('/users/123').execute()
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
**Note:** Schema validation only runs for validatable content types (JSON, XML, CSV). Binary responses (Blob, Stream) are not validated.
|
|
260
|
+
|
|
261
|
+
## Generate schemas via CLI
|
|
262
|
+
|
|
263
|
+
You can bootstrap Zod schemas and a ready-to-use caller from an OpenAPI 3 spec using the Igniter CLI:
|
|
264
|
+
|
|
265
|
+
```bash
|
|
266
|
+
npx @igniter-js/cli generate caller --name facebook --url https://api.example.com/openapi.json
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
By default this outputs `src/callers/<hostname>/schema.ts` and `index.ts`:
|
|
270
|
+
|
|
271
|
+
```ts
|
|
272
|
+
import { facebookCaller } from './src/callers/api.example.com'
|
|
273
|
+
|
|
274
|
+
const result = await facebookCaller.get('/products').execute()
|
|
170
275
|
```
|
|
171
276
|
|
|
172
|
-
##
|
|
277
|
+
## `responseType()` for Typing and Validation
|
|
173
278
|
|
|
174
|
-
|
|
279
|
+
Use `responseType()` to:
|
|
280
|
+
|
|
281
|
+
1. **Type the response** - for TypeScript inference
|
|
282
|
+
2. **Validate the response** - if you pass a Zod/StandardSchema (only for JSON/XML/CSV)
|
|
175
283
|
|
|
176
284
|
```ts
|
|
177
285
|
import { z } from 'zod'
|
|
178
286
|
|
|
287
|
+
// With Zod schema - validates JSON response
|
|
179
288
|
const result = await api
|
|
180
|
-
.get()
|
|
181
|
-
.url('/users')
|
|
289
|
+
.get('/users')
|
|
182
290
|
.responseType(z.array(z.object({ id: z.string(), name: z.string() })))
|
|
183
291
|
.execute()
|
|
292
|
+
|
|
293
|
+
// With type marker - typing only, no validation
|
|
294
|
+
const result = await api.get('/file').responseType<Blob>().execute()
|
|
184
295
|
```
|
|
185
296
|
|
|
186
297
|
## Global Events
|
|
@@ -193,6 +304,7 @@ import { IgniterCaller } from '@igniter-js/caller'
|
|
|
193
304
|
const unsubscribe = IgniterCaller.on(/^\/users/, (result, ctx) => {
|
|
194
305
|
console.log(`[${ctx.method}] ${ctx.url}`, {
|
|
195
306
|
ok: !result.error,
|
|
307
|
+
status: result.status,
|
|
196
308
|
})
|
|
197
309
|
})
|
|
198
310
|
|
|
@@ -207,7 +319,7 @@ All predictable failures return an `IgniterCallerError` with stable error codes.
|
|
|
207
319
|
```ts
|
|
208
320
|
import { IgniterCallerError } from '@igniter-js/caller'
|
|
209
321
|
|
|
210
|
-
const result = await api.get(
|
|
322
|
+
const result = await api.get('/users').execute()
|
|
211
323
|
|
|
212
324
|
if (result.error) {
|
|
213
325
|
if (IgniterCallerError.is(result.error)) {
|
|
@@ -215,8 +327,70 @@ if (result.error) {
|
|
|
215
327
|
}
|
|
216
328
|
throw result.error
|
|
217
329
|
}
|
|
330
|
+
|
|
331
|
+
// Response includes status and headers
|
|
332
|
+
console.log(result.status) // 200
|
|
333
|
+
console.log(result.headers?.get('x-request-id'))
|
|
218
334
|
```
|
|
219
335
|
|
|
336
|
+
## API Reference
|
|
337
|
+
|
|
338
|
+
### `IgniterCaller.create()`
|
|
339
|
+
|
|
340
|
+
Creates a new caller builder.
|
|
341
|
+
|
|
342
|
+
### Builder Methods
|
|
343
|
+
|
|
344
|
+
| Method | Description |
|
|
345
|
+
|--------|-------------|
|
|
346
|
+
| `.withBaseUrl(url)` | Sets the base URL for all requests |
|
|
347
|
+
| `.withHeaders(headers)` | Sets default headers |
|
|
348
|
+
| `.withCookies(cookies)` | Sets default cookies |
|
|
349
|
+
| `.withLogger(logger)` | Attaches a logger |
|
|
350
|
+
| `.withRequestInterceptor(fn)` | Adds a request interceptor |
|
|
351
|
+
| `.withResponseInterceptor(fn)` | Adds a response interceptor |
|
|
352
|
+
| `.withStore(store, options)` | Configures a persistent store |
|
|
353
|
+
| `.withSchemas(schemas, options)` | Configures schema validation |
|
|
354
|
+
| `.build()` | Builds the caller instance |
|
|
355
|
+
|
|
356
|
+
### Request Methods
|
|
357
|
+
|
|
358
|
+
| Method | Description |
|
|
359
|
+
|--------|-------------|
|
|
360
|
+
| `.get(url?)` | Creates a GET request |
|
|
361
|
+
| `.post(url?)` | Creates a POST request |
|
|
362
|
+
| `.put(url?)` | Creates a PUT request |
|
|
363
|
+
| `.patch(url?)` | Creates a PATCH request |
|
|
364
|
+
| `.delete(url?)` | Creates a DELETE request |
|
|
365
|
+
| `.head(url?)` | Creates a HEAD request |
|
|
366
|
+
| `.request(options)` | Executes request directly (axios-style) |
|
|
367
|
+
|
|
368
|
+
### Request Builder Methods
|
|
369
|
+
|
|
370
|
+
| Method | Description |
|
|
371
|
+
|--------|-------------|
|
|
372
|
+
| `.url(url)` | Sets the URL |
|
|
373
|
+
| `.body(body)` | Sets the request body |
|
|
374
|
+
| `.params(params)` | Sets query parameters |
|
|
375
|
+
| `.headers(headers)` | Merges additional headers |
|
|
376
|
+
| `.timeout(ms)` | Sets request timeout |
|
|
377
|
+
| `.cache(cache, key?)` | Sets cache strategy |
|
|
378
|
+
| `.stale(ms)` | Sets cache stale time |
|
|
379
|
+
| `.retry(attempts, options)` | Configures retry behavior |
|
|
380
|
+
| `.fallback(fn)` | Provides fallback value |
|
|
381
|
+
| `.responseType(schema?)` | Sets expected response type |
|
|
382
|
+
| `.execute()` | Executes the request |
|
|
383
|
+
|
|
384
|
+
### Static Methods
|
|
385
|
+
|
|
386
|
+
| Method | Description |
|
|
387
|
+
|--------|-------------|
|
|
388
|
+
| `IgniterCaller.on(pattern, callback)` | Registers event listener |
|
|
389
|
+
| `IgniterCaller.off(pattern, callback?)` | Removes event listener |
|
|
390
|
+
| `IgniterCaller.invalidate(key)` | Invalidates cache entry |
|
|
391
|
+
| `IgniterCaller.invalidatePattern(pattern)` | Invalidates cache by pattern |
|
|
392
|
+
| `IgniterCaller.batch(requests)` | Executes requests in parallel |
|
|
393
|
+
|
|
220
394
|
## Contributing
|
|
221
395
|
|
|
222
396
|
Contributions are welcome! Please see the main [CONTRIBUTING.md](https://github.com/felipebarcelospro/igniter-js/blob/main/CONTRIBUTING.md) for details.
|