@igniter-js/caller 0.1.3 → 0.1.5
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 +2382 -172
- package/CHANGELOG.md +13 -0
- package/README.md +226 -14
- package/dist/adapters/index.d.mts +70 -0
- package/dist/adapters/index.d.ts +70 -0
- package/dist/adapters/index.js +91 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/index.mjs +89 -0
- package/dist/adapters/index.mjs.map +1 -0
- package/dist/client/index.d.mts +212 -0
- package/dist/client/index.d.ts +212 -0
- package/dist/client/index.js +688 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/index.mjs +679 -0
- package/dist/client/index.mjs.map +1 -0
- package/dist/index.d.mts +415 -870
- package/dist/index.d.ts +415 -870
- package/dist/index.js +1447 -297
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1439 -296
- package/dist/index.mjs.map +1 -1
- package/dist/manager-CVi9utzy.d.ts +1121 -0
- package/dist/manager-DWvX2zsz.d.mts +1121 -0
- package/dist/store-D2p2dqGN.d.mts +54 -0
- package/dist/store-D2p2dqGN.d.ts +54 -0
- package/dist/telemetry/index.d.mts +100 -0
- package/dist/telemetry/index.d.ts +100 -0
- package/dist/telemetry/index.js +54 -0
- package/dist/telemetry/index.js.map +1 -0
- package/dist/telemetry/index.mjs +52 -0
- package/dist/telemetry/index.mjs.map +1 -0
- package/package.json +35 -6
package/AGENTS.md
CHANGED
|
@@ -1,266 +1,2476 @@
|
|
|
1
|
-
# @igniter-js/caller
|
|
1
|
+
# AGENTS.md - @igniter-js/caller
|
|
2
2
|
|
|
3
|
-
> **
|
|
4
|
-
> **
|
|
5
|
-
> **
|
|
3
|
+
> **Last Updated:** 2025-12-23
|
|
4
|
+
> **Version:** 0.1.3
|
|
5
|
+
> **Goal:** Complete operational manual for Code Agents maintaining and developing the HTTP client package.
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
## Package
|
|
9
|
+
## 1. Package Vision & Context
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
**Purpose:** Type-safe HTTP client for Igniter.js with interceptors, retries, caching, and schema validation
|
|
13
|
-
**Type:** Standalone Library (can be used independently or alongside Igniter.js)
|
|
11
|
+
### 1.1 Purpose and Problem Statement
|
|
14
12
|
|
|
15
|
-
|
|
13
|
+
`@igniter-js/caller` is the type-safe HTTP client for the Igniter.js ecosystem. It solves the fundamental problem of making API calls in modern JavaScript applications with full end-to-end type safety, observability, and developer experience.
|
|
16
14
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
15
|
+
**Core Problems Solved:**
|
|
16
|
+
|
|
17
|
+
1. **Type Safety Gap:** Most HTTP libraries require manual type definitions for requests and responses, leading to drift between API contracts and implementation.
|
|
18
|
+
|
|
19
|
+
2. **Observability Silos:** HTTP requests are often "black boxes" in application monitoring, making debugging production issues difficult.
|
|
20
|
+
|
|
21
|
+
3. **Caching Complexity:** Implementing caching for HTTP responses requires significant boilerplate and careful synchronization.
|
|
22
|
+
|
|
23
|
+
4. **Retry Logic Reinvention:** Every application reimplements retry strategies for transient failures.
|
|
24
|
+
|
|
25
|
+
5. **Schema Validation Overhead:** Integrating runtime schema validation (Zod, Valibot, etc.) requires custom middleware.
|
|
26
|
+
|
|
27
|
+
### 1.2 Design Philosophy
|
|
28
|
+
|
|
29
|
+
The package follows these core principles:
|
|
30
|
+
|
|
31
|
+
- **Fetch-First:** Uses the global `fetch` API, working seamlessly in Node.js 18+, Bun, Deno, and modern browsers without polyfills.
|
|
32
|
+
|
|
33
|
+
- **Type-Safety by Default:** Leverages TypeScript's type system to infer request bodies, path parameters, and response types from schema definitions.
|
|
34
|
+
|
|
35
|
+
- **Observability Built-In:** Telemetry and logging are integrated from the ground up, not bolted on later.
|
|
36
|
+
|
|
37
|
+
- **Immutability First:** Builders use the immutable state pattern, preventing accidental mutation during request construction.
|
|
38
|
+
|
|
39
|
+
- **Schema Agnostic:** Supports any `StandardSchemaV1` implementation (Zod v4+, Valibot, ArkType, etc.).
|
|
40
|
+
|
|
41
|
+
- **Zero Runtime Dependency:** No external HTTP libraries—just `fetch` and `AbortController`.
|
|
42
|
+
|
|
43
|
+
### 1.3 Position in Igniter.js Ecosystem
|
|
44
|
+
|
|
45
|
+
`@igniter-js/caller` is a standalone library that can be used independently or alongside other Igniter.js packages. It integrates with:
|
|
46
|
+
|
|
47
|
+
- **`@igniter-js/common`:** Uses `IgniterError` base class, `IgniterLogger` interface, and `StandardSchemaV1` type.
|
|
48
|
+
|
|
49
|
+
- **`@igniter-js/telemetry`:** Emits structured telemetry events for request lifecycle monitoring.
|
|
50
|
+
|
|
51
|
+
- **`@igniter-js/store`:** (Optional) Can use Igniter Store adapters for persistent caching.
|
|
52
|
+
|
|
53
|
+
### 1.4 Client-Safe Design
|
|
54
|
+
|
|
55
|
+
Unlike most Igniter.js packages, `@igniter-js/caller` is **explicitly designed to work in both server and client environments**. It does NOT have a server-only shim because HTTP clients are a valid use case for browser applications.
|
|
56
|
+
|
|
57
|
+
Additional constraints for client usage:
|
|
58
|
+
|
|
59
|
+
- The React layer is exported from `@igniter-js/caller/client` to avoid bundling React into non-React consumers.
|
|
60
|
+
- If a caller instance is configured with server-only store adapters or telemetry managers, do not bundle that instance in browser code. Create a browser-safe instance instead.
|
|
27
61
|
|
|
28
62
|
---
|
|
29
63
|
|
|
30
|
-
## Architecture
|
|
64
|
+
## I. MAINTAINER GUIDE (Internal Architecture)
|
|
65
|
+
|
|
66
|
+
### 2. FileSystem Topology (Maintenance)
|
|
67
|
+
|
|
68
|
+
The source code is organized into clear functional domains. This section maps every file and folder to its responsibility.
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
packages/caller/src/
|
|
72
|
+
├── index.ts # Main entry point barrel
|
|
73
|
+
├── builders/ # Builder pattern implementations
|
|
74
|
+
│ ├── index.ts # Builder exports barrel
|
|
75
|
+
│ ├── main.builder.ts # IgniterCallerBuilder (package initializer)
|
|
76
|
+
│ ├── main.builder.spec.ts # Builder type inference tests
|
|
77
|
+
│ ├── request.builder.ts # IgniterCallerRequestBuilder (request lifecycle)
|
|
78
|
+
│ ├── schema.builder.ts # IgniterCallerSchema (schema registry builder)
|
|
79
|
+
│ ├── schema.builder.spec.ts # Schema builder tests
|
|
80
|
+
│ └── schema-path.builder.ts # IgniterCallerSchemaPathBuilder (path+methods)
|
|
81
|
+
├── client/ # React client layer (Provider + hooks)
|
|
82
|
+
│ ├── index.ts # React client entrypoint barrel
|
|
83
|
+
│ ├── builders/ # Provider + hook factories
|
|
84
|
+
│ ├── interfaces/ # Client-specific types
|
|
85
|
+
│ └── utils/ # Client-only helpers (cache, keys, cookies)
|
|
86
|
+
├── mock/ # Typed mock registry for schemas
|
|
87
|
+
│ ├── index.ts # Mock exports barrel
|
|
88
|
+
│ ├── main.builder.ts # IgniterCallerMockBuilder
|
|
89
|
+
│ └── manager.ts # IgniterCallerMockManager
|
|
90
|
+
├── core/ # Runtime execution layer
|
|
91
|
+
│ ├── index.ts # Core exports barrel
|
|
92
|
+
│ ├── manager.ts # IgniterCallerManager (HTTP client runtime)
|
|
93
|
+
│ ├── manager.spec.ts # Manager runtime tests
|
|
94
|
+
│ └── events.ts # IgniterCallerEvents (global event emitter)
|
|
95
|
+
├── errors/ # Error handling
|
|
96
|
+
│ ├── index.ts # Error exports barrel
|
|
97
|
+
│ └── caller.error.ts # IgniterCallerError class
|
|
98
|
+
├── telemetry/ # Telemetry definitions
|
|
99
|
+
│ └── index.ts # IgniterCallerTelemetryEvents registry
|
|
100
|
+
├── types/ # Type definitions (pure contracts)
|
|
101
|
+
│ ├── index.ts # Type exports barrel
|
|
102
|
+
│ ├── builder.ts # Builder state and params types
|
|
103
|
+
│ ├── events.ts # Event callback and pattern types
|
|
104
|
+
│ ├── http.ts # HTTP method constants
|
|
105
|
+
│ ├── infer.ts # Type inference helpers
|
|
106
|
+
│ ├── interceptors.ts # Request/response interceptor types
|
|
107
|
+
│ ├── manager.ts # Manager interface contract
|
|
108
|
+
│ ├── request.ts # Request configuration types
|
|
109
|
+
│ ├── response.ts # Response and content type types
|
|
110
|
+
│ ├── retry.ts # Retry configuration types
|
|
111
|
+
│ ├── schema-builder.ts # Schema builder helper types
|
|
112
|
+
│ ├── schemas.ts # Schema map and endpoint types
|
|
113
|
+
│ └── store.ts # Store adapter types
|
|
114
|
+
└── utils/ # Utility functions
|
|
115
|
+
├── index.ts # Utility exports barrel
|
|
116
|
+
├── body.ts # Body normalization utilities
|
|
117
|
+
├── body.spec.ts # Body utility tests
|
|
118
|
+
├── cache.ts # Cache utilities (in-memory + store)
|
|
119
|
+
├── cache.spec.ts # Cache utility tests
|
|
120
|
+
├── schema.ts # Schema matching and validation
|
|
121
|
+
├── schema.spec.ts # Schema utility tests
|
|
122
|
+
├── testing.ts # Testing helpers
|
|
123
|
+
├── testing.spec.ts # Testing utility tests
|
|
124
|
+
├── url.ts # URL construction utilities
|
|
125
|
+
└── url.spec.ts # URL utility tests
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
#### 2.1 Builders Directory (`src/builders/`)
|
|
31
129
|
|
|
32
|
-
|
|
130
|
+
**Purpose:** Implements the fluent builder pattern for configuration and request construction.
|
|
33
131
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
-
|
|
37
|
-
-
|
|
38
|
-
-
|
|
132
|
+
| File | Responsibility |
|
|
133
|
+
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
134
|
+
| `main.builder.ts` | `IgniterCallerBuilder` class - package initialization with immutable state accumulation. Handles baseURL, headers, cookies, interceptors, store, schemas, and telemetry configuration. |
|
|
135
|
+
| `request.builder.ts` | `IgniterCallerRequestBuilder` class - per-request builder. Manages request lifecycle including URL, body, params, headers, timeout, cache, retry, fallback, and response type. |
|
|
136
|
+
| `schema.builder.ts` | `IgniterCallerSchema` class - schema registry builder with `$Infer` type helpers and `get` runtime helpers. Prevents duplicate keys and paths. |
|
|
137
|
+
| `schema-path.builder.ts` | `IgniterCallerSchemaPathBuilder` class - path-first fluent API for defining HTTP methods (GET, POST, PUT, PATCH, DELETE, HEAD) on a path. Provides `ref()` helper for registry references. |
|
|
39
138
|
|
|
40
|
-
|
|
139
|
+
#### 2.2 Client Directory (`src/client/`)
|
|
41
140
|
|
|
42
|
-
|
|
43
|
-
- Uses the global `fetch` API.
|
|
44
|
-
- Works in Node.js (18+), Bun, Deno, and modern browsers.
|
|
141
|
+
**Purpose:** React-specific layer that provides Provider + hooks for multiple callers.
|
|
45
142
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
143
|
+
| Folder/File | Responsibility |
|
|
144
|
+
| -------------------- | -------------------------------------------------------------------------------------------------------- |
|
|
145
|
+
| `builders/` | `IgniterCallerProvider`, `useIgniterCaller`, and hook factories for `useQuery`/`useMutate`. |
|
|
146
|
+
| `interfaces/` | Client-only types for config, hooks, context, and the wrapper client API. |
|
|
147
|
+
| `utils/` | Client-only helpers (query keys, in-memory cache, cookie header merge). |
|
|
148
|
+
| `index.ts` | Client entrypoint barrel used by `@igniter-js/caller/client`. |
|
|
50
149
|
|
|
51
|
-
3
|
|
52
|
-
- Predictable errors are `IgniterCallerError` with stable error codes.
|
|
150
|
+
#### 2.3 Core Directory (`src/core/`)
|
|
53
151
|
|
|
54
|
-
|
|
55
|
-
- `IgniterCallerRequestBuilder` focuses on request composition and execution.
|
|
56
|
-
- `IgniterCallerCacheUtils` focuses on caching.
|
|
57
|
-
- `IgniterCallerSchemaUtils` focuses on schema matching and validation.
|
|
152
|
+
**Purpose:** Runtime execution and event handling.
|
|
58
153
|
|
|
59
|
-
|
|
60
|
-
|
|
154
|
+
| File | Responsibility |
|
|
155
|
+
| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
156
|
+
| `manager.ts` | `IgniterCallerManager` class - the main HTTP client runtime. Creates request builders, executes direct requests, manages global event emission, and provides static methods for cache invalidation and batch requests. |
|
|
157
|
+
| `events.ts` | `IgniterCallerEvents` class - global event emitter supporting exact URL matches and RegExp patterns. Handles listener registration, cleanup, and error-safe emission. |
|
|
158
|
+
|
|
159
|
+
#### 2.4 Types Directory (`src/types/`)
|
|
160
|
+
|
|
161
|
+
**Purpose:** Pure TypeScript contracts—no implementation code.
|
|
162
|
+
|
|
163
|
+
| File | Responsibility |
|
|
164
|
+
| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
165
|
+
| `builder.ts` | `IgniterCallerBuilderState` (builder state), `IgniterCallerRequestBuilderParams` (request builder constructor params), `IgniterCallerMethodRequestBuilder` (public builder type). |
|
|
166
|
+
| `events.ts` | `IgniterCallerEventCallback` (listener signature), `IgniterCallerUrlPattern` (string or RegExp). |
|
|
167
|
+
| `http.ts` | `IgniterCallerHttpMethod` union type. |
|
|
168
|
+
| `infer.ts` | Type inference helpers: `InferSuccessResponse`, `GetEndpoint`, `InferResponse`, `TypedRequestBuilder`. |
|
|
169
|
+
| `interceptors.ts` | `IgniterCallerRequestInterceptor`, `IgniterCallerResponseInterceptor`. |
|
|
170
|
+
| `manager.ts` | `IIgniterCallerManager` interface - public contract for the manager. |
|
|
171
|
+
| `request.ts` | `IgniterCallerBaseRequestOptions`, `IgniterCallerRequestOptions`, `IgniterCallerDirectRequestOptions`. |
|
|
172
|
+
| `response.ts` | `IgniterCallerApiResponse<T>`, `IgniterCallerFileResponse`, `IgniterCallerResponseContentType`, `IgniterCallerValidatableContentType`, `IgniterCallerResponseMarker`. |
|
|
173
|
+
| `retry.ts` | `IgniterCallerRetryOptions`. |
|
|
174
|
+
| `schema-builder.ts` | Complex types for schema builder: `IgniterCallerSchemaRegistry`, `IgniterCallerSchemaEndpointConfig`, schema wrapper types (`SchemaArray`, `SchemaNullable`, `SchemaOptional`, `SchemaRecord`), inference helpers (`IgniterCallerSchemaInfer`, `IgniterCallerSchemaGetters`), build result type. |
|
|
175
|
+
| `schemas.ts` | Core schema types: `IgniterCallerSchemaMethod`, `IgniterCallerEndpointSchema`, `IgniterCallerSchemaMap`, path extraction types (`ExtractPathParams`), inference types (`InferRequestType`, `InferResponseType`, `InferSuccessResponseType`, `InferAllResponseTypes`), path filtering types (`GetPaths`, `PostPaths`, etc.), endpoint info types, validation options. |
|
|
176
|
+
| `store.ts` | `IgniterCallerStoreAdapter<TClient>`, `IgniterCallerStoreOptions`. |
|
|
177
|
+
|
|
178
|
+
#### 2.5 Utils Directory (`src/utils/`)
|
|
179
|
+
|
|
180
|
+
**Purpose:** Pure functions for specific operations.
|
|
181
|
+
|
|
182
|
+
| File | Responsibility |
|
|
183
|
+
| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
184
|
+
| `body.ts` | `IgniterCallerBodyUtils` - detects raw body types (FormData, Blob, ArrayBuffer, etc.), normalizes headers for FormData (removes Content-Type). |
|
|
185
|
+
| `cache.ts" | `IgniterCallerCacheUtils` - in-memory Map-based cache with optional store adapter fallback. Handles get/set/clear with TTL and glob pattern invalidation. |
|
|
186
|
+
| `schema.ts` | `IgniterCallerSchemaUtils` - path matching with param extraction (`matchPath`), schema lookup (`findSchema`), StandardSchemaV1 validation (`validateWithStandardSchema`), request/response validation with strict/soft modes. |
|
|
187
|
+
| `testing.ts` | Testing helpers for mocking and assertions. |
|
|
188
|
+
| `url.ts` | `IgniterCallerUrlUtils` - builds full URLs with base URL concatenation and query parameter encoding using `URLSearchParams`. |
|
|
61
189
|
|
|
62
190
|
---
|
|
63
191
|
|
|
64
|
-
|
|
192
|
+
### 3. Architecture Deep-Dive
|
|
193
|
+
|
|
194
|
+
The package follows a **Builder → Manager → Request Builder → Execution** pattern with immutable state accumulation throughout.
|
|
65
195
|
|
|
66
|
-
|
|
196
|
+
#### 3.1 Architectural Layers
|
|
67
197
|
|
|
68
|
-
|
|
198
|
+
```
|
|
199
|
+
┌─────────────────────────────────────────────────────────────────────────┐
|
|
200
|
+
│ API Surface │
|
|
201
|
+
│ IgniterCaller.create().withX().build() │
|
|
202
|
+
└────────────────────────┬────────────────────────────────────────────────┘
|
|
203
|
+
│
|
|
204
|
+
▼
|
|
205
|
+
┌─────────────────────────────────────────────────────────────────────────┐
|
|
206
|
+
│ Builder Layer │
|
|
207
|
+
│ IgniterCallerBuilder (Immutable State Accumulation) │
|
|
208
|
+
│ ┌─────────────────────────────────────────────────────────────┐ │
|
|
209
|
+
│ │ State: │ │
|
|
210
|
+
│ │ - baseURL, headers, cookies │ │
|
|
211
|
+
│ │ - requestInterceptors, responseInterceptors │ │
|
|
212
|
+
│ │ - store, storeOptions │ │
|
|
213
|
+
│ │ - schemas, schemaValidation │ │
|
|
214
|
+
│ │ - logger, telemetry │ │
|
|
215
|
+
│ └─────────────────────────────────────────────────────────────┘ │
|
|
216
|
+
└────────────────────────┬────────────────────────────────────────────────┘
|
|
217
|
+
│ .build()
|
|
218
|
+
▼
|
|
219
|
+
┌─────────────────────────────────────────────────────────────────────────┐
|
|
220
|
+
│ Manager Layer │
|
|
221
|
+
│ IgniterCallerManager (Runtime) │
|
|
222
|
+
│ - get/post/put/patch/delete/head() methods │
|
|
223
|
+
│ - request() method (axios-style) │
|
|
224
|
+
│ - Static methods: batch(), on(), off(), invalidate() │
|
|
225
|
+
│ - Global event emission via IgniterCallerEvents │
|
|
226
|
+
└────────────────────────┬────────────────────────────────────────────────┘
|
|
227
|
+
│ Creates
|
|
228
|
+
▼
|
|
229
|
+
┌─────────────────────────────────────────────────────────────────────────┐
|
|
230
|
+
│ Request Builder Layer │
|
|
231
|
+
│ IgniterCallerRequestBuilder (Per-Request Configuration) │
|
|
232
|
+
│ ┌─────────────────────────────────────────────────────────────┐ │
|
|
233
|
+
│ │ Configuration: │ │
|
|
234
|
+
│ │ - url, method, body, params │ │
|
|
235
|
+
│ │ - headers (merged with defaults) │ │
|
|
236
|
+
│ │ - timeout, cache, staleTime │ │
|
|
237
|
+
│ │ - retry options │ │
|
|
238
|
+
│ │ - fallback function │ │
|
|
239
|
+
│ │ - responseType (schema or type marker) │ │
|
|
240
|
+
│ └─────────────────────────────────────────────────────────────┘ │
|
|
241
|
+
└────────────────────────┬────────────────────────────────────────────────┘
|
|
242
|
+
│ .execute()
|
|
243
|
+
▼
|
|
244
|
+
┌─────────────────────────────────────────────────────────────────────────┐
|
|
245
|
+
│ Execution Layer (Internal) │
|
|
246
|
+
│ 1. Cache Check (if staleTime set) │
|
|
247
|
+
│ 2. Request Interceptor Chain │
|
|
248
|
+
│ 3. Request Body Validation (if schema configured) │
|
|
249
|
+
│ 4. Fetch with Retry Logic │
|
|
250
|
+
│ 5. Response Parsing (Content-Type auto-detect) │
|
|
251
|
+
│ 6. Response Validation (if schema configured) │
|
|
252
|
+
│ 7. Response Interceptor Chain │
|
|
253
|
+
│ 8. Cache Store (if successful) │
|
|
254
|
+
│ 9. Fallback (if failed and fallback set) │
|
|
255
|
+
│ 10. Telemetry Emission │
|
|
256
|
+
│ 11. Global Event Emission │
|
|
257
|
+
└─────────────────────────────────────────────────────────────────────────┘
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
#### 3.2 Immutable State Pattern
|
|
261
|
+
|
|
262
|
+
The `IgniterCallerBuilder` implements strict immutability:
|
|
69
263
|
|
|
70
264
|
```typescript
|
|
71
|
-
//
|
|
72
|
-
|
|
73
|
-
|
|
265
|
+
// ✅ CORRECT: Each with* method returns new instance
|
|
266
|
+
.withBaseUrl(url) // returns new IgniterCallerBuilder<TSchemas>
|
|
267
|
+
.withHeaders(headers) // returns new IgniterCallerBuilder<TSchemas>
|
|
268
|
+
.withSchemas(schemas) // returns new IgniterCallerBuilder<TNewSchemas>
|
|
74
269
|
|
|
75
|
-
//
|
|
76
|
-
|
|
77
|
-
api.delete('/users/1').execute()
|
|
270
|
+
// ❌ INCORRECT: Mutating state is impossible
|
|
271
|
+
// The constructor is private, and all methods return new instances.
|
|
78
272
|
```
|
|
79
273
|
|
|
80
|
-
|
|
274
|
+
This ensures that builder instances can be reused safely and that configuration changes don't affect previously built managers.
|
|
275
|
+
|
|
276
|
+
#### 3.3 Schema Builder Architecture
|
|
277
|
+
|
|
278
|
+
The schema builder uses a **two-phase registry system**:
|
|
279
|
+
|
|
280
|
+
1. **Registry Phase:** Reusable schemas are registered via `.schema(key, schema)`. This allows referencing schemas multiple times across endpoints.
|
|
281
|
+
|
|
282
|
+
2. **Path Phase:** Endpoints are defined via `.path(path, builder)` with method-specific configurations.
|
|
283
|
+
|
|
284
|
+
The `.ref(key)` helper provides:
|
|
285
|
+
|
|
286
|
+
- `schema` - direct reference to registered schema
|
|
287
|
+
- `array()` - wraps in Zod array
|
|
288
|
+
- `nullable()` - wraps in Zod nullable
|
|
289
|
+
- `optional()` - wraps in Zod optional
|
|
290
|
+
- `record()` - wraps in Zod record
|
|
291
|
+
|
|
292
|
+
After `.build()`, the result includes:
|
|
293
|
+
|
|
294
|
+
- `$Infer` - Type-level inference helpers (Path, Endpoint, Request, Response, Responses, Schema)
|
|
295
|
+
- `get` - Runtime helpers (path, endpoint, request, response, schema)
|
|
296
|
+
|
|
297
|
+
#### 3.4 Event System Architecture
|
|
298
|
+
|
|
299
|
+
The `IgniterCallerEvents` class manages a dual-listener system:
|
|
300
|
+
|
|
301
|
+
- **Exact Match Listeners:** Stored in `Map<string, Set<Callback>>` for URL string patterns.
|
|
302
|
+
- **Pattern Listeners:** Stored in `Map<RegExp, Set<Callback>>` for RegExp patterns.
|
|
303
|
+
|
|
304
|
+
Emission flow:
|
|
305
|
+
|
|
306
|
+
1. Emit event with URL and result
|
|
307
|
+
2. Check all exact match listeners for the URL
|
|
308
|
+
3. Check all pattern listeners - execute if RegExp matches
|
|
309
|
+
4. All listeners are called with the result and a context object (url, method, timestamp)
|
|
310
|
+
|
|
311
|
+
The event system is static on `IgniterCallerManager`, enabling global observation across all manager instances.
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
### 4. Operational Flow Mapping (Pipelines)
|
|
316
|
+
|
|
317
|
+
This section provides step-by-step flow documentation for every public method.
|
|
318
|
+
|
|
319
|
+
#### 4.1 Method: `IgniterCallerBuilder.withBaseUrl(url)`
|
|
320
|
+
|
|
321
|
+
**Purpose:** Sets the base URL prefix for all requests.
|
|
322
|
+
|
|
323
|
+
**Flow:**
|
|
324
|
+
|
|
325
|
+
1. **Input Validation:** None (string is accepted directly).
|
|
326
|
+
2. **State Copy:** Creates new `IgniterCallerBuilder` instance with `...this.state, baseURL: url`.
|
|
327
|
+
3. **Return:** Returns the new builder instance.
|
|
328
|
+
|
|
329
|
+
**Telemetry Emitted:** None (builder methods don't emit telemetry).
|
|
330
|
+
|
|
331
|
+
**Logging:** None (builder methods don't log).
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
#### 4.2 Method: `IgniterCallerBuilder.withHeaders(headers)`
|
|
336
|
+
|
|
337
|
+
**Purpose:** Merges default headers into every request.
|
|
338
|
+
|
|
339
|
+
**Flow:**
|
|
340
|
+
|
|
341
|
+
1. **Input Validation:** None (Record<string, string> accepted).
|
|
342
|
+
2. **State Merge:** Creates new builder with merged headers (shallow merge).
|
|
343
|
+
3. **Return:** Returns new builder instance.
|
|
344
|
+
|
|
345
|
+
**Note:** Headers are merged shallow—nested objects are not deeply merged.
|
|
346
|
+
|
|
347
|
+
---
|
|
348
|
+
|
|
349
|
+
#### 4.3 Method: `IgniterCallerBuilder.withCookies(cookies)`
|
|
350
|
+
|
|
351
|
+
**Purpose:** Sets default cookies sent as the `Cookie` header.
|
|
352
|
+
|
|
353
|
+
**Flow:**
|
|
354
|
+
|
|
355
|
+
1. **Input Validation:** None.
|
|
356
|
+
2. **State Copy:** Creates new builder with `cookies` property.
|
|
357
|
+
3. **Return:** Returns new builder instance.
|
|
358
|
+
|
|
359
|
+
**Runtime Behavior:** During request construction, cookies are serialized to `key=value; key2=value2` format and set in the `Cookie` header.
|
|
360
|
+
|
|
361
|
+
---
|
|
362
|
+
|
|
363
|
+
#### 4.4 Method: `IgniterCallerBuilder.withLogger(logger)`
|
|
364
|
+
|
|
365
|
+
**Purpose:** Attaches an `IgniterLogger` instance for request lifecycle logging.
|
|
366
|
+
|
|
367
|
+
**Flow:**
|
|
368
|
+
|
|
369
|
+
1. **Input Validation:** None (must implement `IgniterLogger` interface).
|
|
370
|
+
2. **State Copy:** Creates new builder with `logger` property.
|
|
371
|
+
3. **Return:** Returns new builder instance.
|
|
372
|
+
|
|
373
|
+
**Logging Behavior:**
|
|
374
|
+
|
|
375
|
+
- Request started: `debug` with method, url, baseURL
|
|
376
|
+
- Request success: `info` with method, url, durationMs, status
|
|
377
|
+
- Request failed: `error` with method, url, durationMs, error
|
|
378
|
+
|
|
379
|
+
---
|
|
380
|
+
|
|
381
|
+
#### 4.5 Method: `IgniterCallerBuilder.withRequestInterceptor(interceptor)`
|
|
382
|
+
|
|
383
|
+
**Purpose:** Adds a function that modifies request options before execution.
|
|
384
|
+
|
|
385
|
+
**Flow:**
|
|
386
|
+
|
|
387
|
+
1. **Input Validation:** None (must be callable with correct signature).
|
|
388
|
+
2. **Array Accumulation:** Appends to existing `requestInterceptors` array.
|
|
389
|
+
3. **State Copy:** Creates new builder with updated array.
|
|
390
|
+
4. **Return:** Returns new builder instance.
|
|
391
|
+
|
|
392
|
+
**Execution Order:** Interceptors run in the order they were registered (FIFO).
|
|
393
|
+
|
|
394
|
+
---
|
|
395
|
+
|
|
396
|
+
#### 4.6 Method: `IgniterCallerBuilder.withResponseInterceptor(interceptor)`
|
|
397
|
+
|
|
398
|
+
**Purpose:** Adds a function that transforms responses after execution.
|
|
399
|
+
|
|
400
|
+
**Flow:**
|
|
401
|
+
|
|
402
|
+
1. **Input Validation:** None.
|
|
403
|
+
2. **Array Accumulation:** Appends to existing `responseInterceptors` array.
|
|
404
|
+
3. **State Copy:** Creates new builder with updated array.
|
|
405
|
+
4. **Return:** Returns new builder instance.
|
|
406
|
+
|
|
407
|
+
**Execution Order:** Interceptors run in the order they were registered (FIFO).
|
|
408
|
+
|
|
409
|
+
---
|
|
410
|
+
|
|
411
|
+
#### 4.7 Method: `IgniterCallerBuilder.withStore(store, options)`
|
|
412
|
+
|
|
413
|
+
**Purpose:** Configures a persistent store adapter for caching (e.g., Redis).
|
|
414
|
+
|
|
415
|
+
**Flow:**
|
|
416
|
+
|
|
417
|
+
1. **Input Validation:** None.
|
|
418
|
+
2. **State Copy:** Creates new builder with `store` and `storeOptions` properties.
|
|
419
|
+
3. **Return:** Returns new builder instance.
|
|
420
|
+
|
|
421
|
+
**Runtime Behavior:**
|
|
422
|
+
|
|
423
|
+
- On `build()`: Calls `IgniterCallerCacheUtils.setStore(store, options)`
|
|
424
|
+
- Store operations fallback to in-memory cache on failure
|
|
425
|
+
|
|
426
|
+
---
|
|
427
|
+
|
|
428
|
+
#### 4.8 Method: `IgniterCallerBuilder.withSchemas(schemas, validation)`
|
|
429
|
+
|
|
430
|
+
**Purpose:** Configures schema-based type safety and runtime validation.
|
|
81
431
|
|
|
82
|
-
|
|
432
|
+
**Flow:**
|
|
433
|
+
|
|
434
|
+
1. **Input Validation:** Type-level validation ensures schemas conform to `IgniterCallerSchemaInput`.
|
|
435
|
+
2. **State Copy:** Creates new builder with `schemas` and `schemaValidation` properties.
|
|
436
|
+
3. **Type Narrowing:** Returns builder narrowed to new schema map type `IgniterCallerSchemaMapFrom<TNewSchemas>`.
|
|
437
|
+
4. **Return:** Returns typed builder instance.
|
|
438
|
+
|
|
439
|
+
**Validation Options:**
|
|
440
|
+
|
|
441
|
+
- `mode`: `'strict'` (throw on failure), `'soft'` (log and continue), `'off'` (skip)
|
|
442
|
+
- `onValidationError`: Custom error handler callback
|
|
443
|
+
|
|
444
|
+
---
|
|
445
|
+
|
|
446
|
+
#### 4.9 Method: `IgniterCallerBuilder.withTelemetry(telemetry)`
|
|
447
|
+
|
|
448
|
+
**Purpose:** Attaches `IgniterTelemetryManager` for request observability.
|
|
449
|
+
|
|
450
|
+
**Flow:**
|
|
451
|
+
|
|
452
|
+
1. **Input Validation:** None.
|
|
453
|
+
2. **State Copy:** Creates new builder with `telemetry` property.
|
|
454
|
+
3. **Return:** Returns new builder instance.
|
|
455
|
+
|
|
456
|
+
**Telemetry Emitted:**
|
|
457
|
+
|
|
458
|
+
- `request.execute.started`
|
|
459
|
+
- `request.execute.success`
|
|
460
|
+
- `request.execute.error`
|
|
461
|
+
- `request.timeout.error`
|
|
462
|
+
- `cache.read.hit`
|
|
463
|
+
- `retry.attempt.started`
|
|
464
|
+
- `validation.request.error`
|
|
465
|
+
- `validation.response.error`
|
|
466
|
+
|
|
467
|
+
---
|
|
468
|
+
|
|
469
|
+
#### 4.10 Method: `IgniterCallerBuilder.build()`
|
|
470
|
+
|
|
471
|
+
**Purpose:** Creates the `IgniterCallerManager` instance.
|
|
472
|
+
|
|
473
|
+
**Flow:**
|
|
474
|
+
|
|
475
|
+
1. **Store Configuration:** If `this.state.store` is set, calls `IgniterCallerCacheUtils.setStore()`.
|
|
476
|
+
2. **Manager Instantiation:** Creates `new IgniterCallerManager(this.state.baseURL, { ...config })`.
|
|
477
|
+
3. **Initialization Logging:** Logs manager creation with metadata (baseURL, hasTelemetry, hasStore, hasSchemas).
|
|
478
|
+
4. **Return:** Returns manager instance.
|
|
479
|
+
|
|
480
|
+
---
|
|
481
|
+
|
|
482
|
+
#### 4.11 Method: `IgniterCallerManager.get(url)` and similar HTTP methods
|
|
483
|
+
|
|
484
|
+
**Purpose:** Creates a typed request builder for a specific HTTP method.
|
|
485
|
+
|
|
486
|
+
**Flow:**
|
|
487
|
+
|
|
488
|
+
1. **Builder Params Creation:** Calls `createBuilderParams()` to extract shared configuration (baseURL, headers, cookies, logger, telemetry, interceptors, eventEmitter, schemas, validation).
|
|
489
|
+
2. **Request Builder Instantiation:** Creates `new IgniterCallerRequestBuilder(params)`.
|
|
490
|
+
3. **Method Assignment:** Calls `_setMethod('GET')` (internal method).
|
|
491
|
+
4. **URL Assignment:** If URL is provided, calls `_setUrl(url)`.
|
|
492
|
+
5. **Type Narrowing:**
|
|
493
|
+
- If URL matches a schema path, returns `TypedRequestBuilder` with inferred types
|
|
494
|
+
- Otherwise, returns `IgniterCallerTypedRequestBuilder` with generic response type
|
|
495
|
+
6. **Return:** Returns typed request builder.
|
|
496
|
+
|
|
497
|
+
**Type Inference:**
|
|
498
|
+
|
|
499
|
+
- When URL matches a schema path, `TypedRequestBuilder` provides typed `body()` and `params()` methods.
|
|
500
|
+
- Response type is inferred from the schema's success status (200 or 201).
|
|
501
|
+
|
|
502
|
+
---
|
|
503
|
+
|
|
504
|
+
#### 4.12 Method: `IgniterCallerManager.request(options)`
|
|
505
|
+
|
|
506
|
+
**Purpose:** Executes a request directly with all options in one object (axios-style).
|
|
507
|
+
|
|
508
|
+
**Flow:**
|
|
509
|
+
|
|
510
|
+
1. **Builder Instantiation:** Creates `new IgniterCallerRequestBuilder(params)` with merged headers.
|
|
511
|
+
2. **Method Assignment:** Calls `_setMethod(options.method)`.
|
|
512
|
+
3. **URL Assignment:** Calls `_setUrl(options.url)`.
|
|
513
|
+
4. **Body Assignment:** If `options.body` is provided, calls `body(options.body)`.
|
|
514
|
+
5. **Params Assignment:** If `options.params` is provided, calls `params(options.params)`.
|
|
515
|
+
6. **Timeout Assignment:** If `options.timeout` is provided, calls `timeout(options.timeout)`.
|
|
516
|
+
7. **Cache Assignment:** If `options.cache` is provided, calls `cache(options.cache, options.cacheKey)`.
|
|
517
|
+
8. **Stale Time Assignment:** If `options.staleTime` is provided, calls `stale(options.staleTime)`.
|
|
518
|
+
9. **Retry Assignment:** If `options.retry` is provided, calls `retry(options.retry.maxAttempts, options.retry)`.
|
|
519
|
+
10. **Fallback Assignment:** If `options.fallback` is provided, calls `fallback(options.fallback)`.
|
|
520
|
+
11. **Response Schema Assignment:** If `options.responseSchema` is provided, calls `responseType(options.responseSchema)`.
|
|
521
|
+
12. **Execution:** Calls `execute()`.
|
|
522
|
+
13. **Return:** Returns `Promise<IgniterCallerApiResponse<T>>`.
|
|
523
|
+
|
|
524
|
+
---
|
|
525
|
+
|
|
526
|
+
#### 4.13 Method: `IgniterCallerRequestBuilder.execute()`
|
|
527
|
+
|
|
528
|
+
**Purpose:** Executes the HTTP request with full lifecycle management.
|
|
529
|
+
|
|
530
|
+
**Flow:**
|
|
531
|
+
|
|
532
|
+
1. **Telemetry (Started):** Emits `request.execute.started` with method, url, baseURL, timeoutMs.
|
|
533
|
+
2. **Logging (Started):** Logs request started at debug level.
|
|
534
|
+
3. **Cache Check:**
|
|
535
|
+
- Determines effective cache key (provided key or URL).
|
|
536
|
+
- If `staleTime` is set, calls `IgniterCallerCacheUtils.get()`.
|
|
537
|
+
- If cache hit: emits `cache.read.hit`, returns cached result immediately with success telemetry.
|
|
538
|
+
4. **Retry Loop:**
|
|
539
|
+
- For attempt = 0 to maxAttempts-1:
|
|
540
|
+
- If attempt > 0: calculate delay (linear or exponential), emit `retry.attempt.started`, wait for delay.
|
|
541
|
+
- Call `executeSingleRequest()`.
|
|
542
|
+
- If success: return result.
|
|
543
|
+
- If error is retryable (status code in `retryOnStatus`): continue loop.
|
|
544
|
+
- Otherwise: break and return error result.
|
|
545
|
+
5. **Fallback:**
|
|
546
|
+
- If request failed and `fallbackFn` is set:
|
|
547
|
+
- Return result with fallback value as data.
|
|
548
|
+
- Emit success telemetry with `ctx.request.fallback: true`.
|
|
549
|
+
6. **Cache Store:**
|
|
550
|
+
- If request succeeded and `effectiveCacheKey` is set:
|
|
551
|
+
- Call `IgniterCallerCacheUtils.set()` with data and staleTime.
|
|
552
|
+
7. **Telemetry (Result):**
|
|
553
|
+
- If error: emit `request.execute.error` with error details.
|
|
554
|
+
- If success: emit `request.execute.success` with duration, status, contentType, cache hit flag.
|
|
555
|
+
8. **Logging (Result):**
|
|
556
|
+
- If error: log error with duration and error details.
|
|
557
|
+
- If success: log success with duration and status.
|
|
558
|
+
9. **Global Event Emission:** Calls `eventEmitter(url, method, result)`.
|
|
559
|
+
10. **Return:** Returns `IgniterCallerApiResponse<TResponse>`.
|
|
560
|
+
|
|
561
|
+
---
|
|
562
|
+
|
|
563
|
+
#### 4.14 Method: `IgniterCallerRequestBuilder.executeSingleRequest()`
|
|
564
|
+
|
|
565
|
+
**Purpose:** Executes a single HTTP request attempt.
|
|
566
|
+
|
|
567
|
+
**Flow:**
|
|
568
|
+
|
|
569
|
+
1. **Request Building:** Calls `buildRequest()` to construct fetch options, AbortController, and timeout.
|
|
570
|
+
2. **URL Resolution:** Calls `resolveUrl()` to build final URL with query params and handle GET body → params conversion.
|
|
571
|
+
3. **Request Interceptors:**
|
|
572
|
+
- If interceptors are configured, chain them in order.
|
|
573
|
+
- Each interceptor receives and returns `IgniterCallerRequestOptions`.
|
|
574
|
+
- Rebuilds request after interceptor chain.
|
|
575
|
+
4. **Request Validation:**
|
|
576
|
+
- If schemas are configured and endpoint has request schema:
|
|
577
|
+
- Call `IgniterCallerSchemaUtils.validateRequest()`.
|
|
578
|
+
- If validation fails (strict mode): emit `validation.request.error`, return error result immediately.
|
|
579
|
+
5. **Fetch Execution:**
|
|
580
|
+
- Call `fetch(url, { ...requestInit, signal: controller.signal })`.
|
|
581
|
+
6. **Timeout Handling:**
|
|
582
|
+
- Clear timeout on response.
|
|
583
|
+
- If `AbortError`: emit `request.timeout.error`, return timeout error result.
|
|
584
|
+
7. **HTTP Error Handling:**
|
|
585
|
+
- If `!response.ok`: read error text, return HTTP error result with status.
|
|
586
|
+
8. **Content Type Detection:**
|
|
587
|
+
- Call `detectContentType(response.headers.get('content-type'))`.
|
|
588
|
+
9. **Response Parsing:**
|
|
589
|
+
- Call `parseResponseByContentType()` based on detected type (json, xml, csv, text, blob, stream, arraybuffer, formdata).
|
|
590
|
+
10. **Response Validation (Schema Map):**
|
|
591
|
+
- If schemas are configured and endpoint has response schema for status code:
|
|
592
|
+
- Call `IgniterCallerSchemaUtils.validateResponse()`.
|
|
593
|
+
- If validation fails (strict mode): emit `validation.response.error`, return error result.
|
|
594
|
+
11. **Response Validation (responseType):**
|
|
595
|
+
- If `responseTypeSchema` is set (Zod or StandardSchema):
|
|
596
|
+
- Validate parsed response data.
|
|
597
|
+
- If validation fails: emit `validation.response.error`, return error result.
|
|
598
|
+
12. **Response Interceptors:**
|
|
599
|
+
- If interceptors are configured, chain them in order.
|
|
600
|
+
- Each interceptor receives and returns `IgniterCallerApiResponse`.
|
|
601
|
+
13. **Return:** Returns `IgniterCallerApiResponse` with data, status, headers.
|
|
602
|
+
|
|
603
|
+
---
|
|
604
|
+
|
|
605
|
+
#### 4.15 Method: `IgniterCallerRequestBuilder.buildRequest()`
|
|
606
|
+
|
|
607
|
+
**Purpose:** Constructs fetch options, AbortController, and timeout.
|
|
608
|
+
|
|
609
|
+
**Flow:**
|
|
610
|
+
|
|
611
|
+
1. **URL Resolution:** Calls `resolveUrl()` to get final URL.
|
|
612
|
+
2. **Body Handling:**
|
|
613
|
+
- For GET/HEAD: body is converted to query params in `resolveUrl()`, so body is excluded from fetch.
|
|
614
|
+
- For other methods: body is included.
|
|
615
|
+
- Check if body is raw (FormData, Blob, ArrayBuffer, etc.).
|
|
616
|
+
- Normalize headers (remove Content-Type for FormData).
|
|
617
|
+
3. **Request Init Construction:**
|
|
618
|
+
```typescript
|
|
619
|
+
const requestInit: RequestInit = {
|
|
620
|
+
method,
|
|
621
|
+
headers: finalHeaders,
|
|
622
|
+
cache,
|
|
623
|
+
...(shouldIncludeBody
|
|
624
|
+
? { body: rawBody ? body : JSON.stringify(body) }
|
|
625
|
+
: {}),
|
|
626
|
+
};
|
|
627
|
+
```
|
|
628
|
+
4. **AbortController:** Create new `AbortController`.
|
|
629
|
+
5. **Timeout Setup:** Set `setTimeout(() => controller.abort(), timeout || 30000)`.
|
|
630
|
+
6. **Return:** Return `{ url, requestInit, controller, timeoutId }`.
|
|
631
|
+
|
|
632
|
+
---
|
|
633
|
+
|
|
634
|
+
#### 4.16 Method: `IgniterCallerRequestBuilder.resolveUrl()`
|
|
635
|
+
|
|
636
|
+
**Purpose:** Builds final URL with base URL and query parameters.
|
|
637
|
+
|
|
638
|
+
**Flow:**
|
|
639
|
+
|
|
640
|
+
1. **GET Body Conversion:**
|
|
641
|
+
- If method is GET or HEAD and body is an object:
|
|
642
|
+
- Convert body properties to query params.
|
|
643
|
+
- Merge with existing params.
|
|
644
|
+
2. **Base URL Concatenation:**
|
|
645
|
+
- If URL is not absolute (doesn't start with `http://` or `https://`) and baseURL is set:
|
|
646
|
+
- Full URL = baseURL + url
|
|
647
|
+
- Otherwise: Full URL = url
|
|
648
|
+
3. **Query Parameter Encoding:**
|
|
649
|
+
- Use `URLSearchParams` to encode params.
|
|
650
|
+
- Append to URL with `?` or `&` separator.
|
|
651
|
+
4. **Return:** Return `{ url: fullUrl, safeUrl: fullUrl.split('?')[0] }`.
|
|
652
|
+
|
|
653
|
+
---
|
|
654
|
+
|
|
655
|
+
#### 4.17 Method: `IgniterCallerManager.on(pattern, callback)`
|
|
656
|
+
|
|
657
|
+
**Purpose:** Registers a global event listener for API responses.
|
|
658
|
+
|
|
659
|
+
**Flow:**
|
|
660
|
+
|
|
661
|
+
1. **Listener Registration:**
|
|
662
|
+
- If pattern is string: add to `listeners` Map under exact URL key.
|
|
663
|
+
- If pattern is RegExp: add to `patternListeners` Map under RegExp key.
|
|
664
|
+
2. **Cleanup Function:**
|
|
665
|
+
- Return a function that removes the specific callback from the listener set.
|
|
666
|
+
- If no callbacks remain for the pattern, remove the pattern entry.
|
|
667
|
+
3. **Return:** Return cleanup function.
|
|
668
|
+
|
|
669
|
+
---
|
|
670
|
+
|
|
671
|
+
#### 4.18 Method: `IgniterCallerManager.invalidate(key)`
|
|
672
|
+
|
|
673
|
+
**Purpose:** Invalidates a specific cache entry.
|
|
674
|
+
|
|
675
|
+
**Flow:**
|
|
676
|
+
|
|
677
|
+
1. **Cache Clear:** Call `IgniterCallerCacheUtils.clear(key)`.
|
|
678
|
+
2. **Return:** Await cache clear completion.
|
|
679
|
+
|
|
680
|
+
---
|
|
681
|
+
|
|
682
|
+
#### 4.19 Method: `IgniterCallerManager.batch(requests)`
|
|
683
|
+
|
|
684
|
+
**Purpose:** Executes multiple requests in parallel.
|
|
685
|
+
|
|
686
|
+
**Flow:**
|
|
687
|
+
|
|
688
|
+
1. **Promise All:** Return `Promise.all(requests)`.
|
|
689
|
+
2. **Type Preservation:** Return typed array preserving individual result types.
|
|
690
|
+
3. **Return:** `Promise<{ [K in keyof T]: T[K] extends Promise<infer R> ? R : never }>`
|
|
691
|
+
|
|
692
|
+
---
|
|
693
|
+
|
|
694
|
+
### 5. Dependency & Type Graph
|
|
695
|
+
|
|
696
|
+
#### 5.1 External Dependencies
|
|
697
|
+
|
|
698
|
+
| Package | Purpose | Peer Dependency |
|
|
699
|
+
| ----------------------- | --------------------------------------------------- | ---------------------------------------- |
|
|
700
|
+
| `@igniter-js/common` | `IgniterError`, `IgniterLogger`, `StandardSchemaV1` | ✅ Required |
|
|
701
|
+
| `@igniter-js/telemetry` | `IgniterTelemetryManager`, `IgniterTelemetryEvents` | ⚙️ Optional |
|
|
702
|
+
| `zod` | Schema validation (v4+) | ⚙️ Optional (any StandardSchemaV1 works) |
|
|
703
|
+
|
|
704
|
+
#### 5.2 Internal Type Flow
|
|
705
|
+
|
|
706
|
+
```
|
|
707
|
+
IgniterCallerBuilderState<TSchemas>
|
|
708
|
+
│
|
|
709
|
+
├─→ IgniterCallerBuilder<TSchemas>
|
|
710
|
+
│ │
|
|
711
|
+
│ └─→ build()
|
|
712
|
+
│ │
|
|
713
|
+
│ ▼
|
|
714
|
+
│ IgniterCallerManager<TSchemas>
|
|
715
|
+
│ │
|
|
716
|
+
│ ├─→ createBuilderParams()
|
|
717
|
+
│ │ │
|
|
718
|
+
│ │ ▼
|
|
719
|
+
│ │ IgniterCallerRequestBuilderParams
|
|
720
|
+
│ │
|
|
721
|
+
│ ├─→ get/post/put/patch/delete/head()
|
|
722
|
+
│ │ │
|
|
723
|
+
│ │ ▼
|
|
724
|
+
│ │ TypedRequestBuilder<TSchemas, TPath, TMethod>
|
|
725
|
+
│ │ │
|
|
726
|
+
│ │ ├─→ body() / params() (typed from schema)
|
|
727
|
+
│ │ └─→ execute()
|
|
728
|
+
│ │ │
|
|
729
|
+
│ │ ▼
|
|
730
|
+
│ │ IgniterCallerApiResponse<TResponse>
|
|
731
|
+
|
|
732
|
+
IgniterCallerSchema<TSchemas, TRegistry>
|
|
733
|
+
│
|
|
734
|
+
├─→ schema() → Registry accumulation
|
|
735
|
+
│
|
|
736
|
+
├─→ path() → Path + methods accumulation
|
|
737
|
+
│
|
|
738
|
+
└─→ build() → IgniterCallerSchemaBuildResult
|
|
739
|
+
│
|
|
740
|
+
├─→ $Infer (type helpers)
|
|
741
|
+
│ ├─→ Path
|
|
742
|
+
│ ├─→ Endpoint
|
|
743
|
+
│ ├─→ Request
|
|
744
|
+
│ ├─→ Response
|
|
745
|
+
│ ├─→ Responses
|
|
746
|
+
│ └─→ Schema
|
|
747
|
+
│
|
|
748
|
+
└─→ get (runtime helpers)
|
|
749
|
+
├─→ path()
|
|
750
|
+
├─→ endpoint()
|
|
751
|
+
├─→ request()
|
|
752
|
+
├─→ response()
|
|
753
|
+
└─→ schema()
|
|
754
|
+
```
|
|
755
|
+
|
|
756
|
+
#### 5.3 Schema Type Resolution Flow
|
|
757
|
+
|
|
758
|
+
```
|
|
759
|
+
IgniterCallerSchemaMap
|
|
760
|
+
│
|
|
761
|
+
├─→ SchemaMapPaths<TSchemas> = keyof TSchemas
|
|
762
|
+
│
|
|
763
|
+
├─→ GetPaths<TSchemas> = PathsForMethod<TSchemas, 'GET'>
|
|
764
|
+
├─→ PostPaths<TSchemas> = PathsForMethod<TSchemas, 'POST'>
|
|
765
|
+
├─→ PutPaths<TSchemas> = PathsForMethod<TSchemas, 'PUT'>
|
|
766
|
+
├─→ PatchPaths<TSchemas> = PathsForMethod<TSchemas, 'PATCH'>
|
|
767
|
+
├─→ DeletePaths<TSchemas> = PathsForMethod<TSchemas, 'DELETE'>
|
|
768
|
+
├─→ HeadPaths<TSchemas> = PathsForMethod<TSchemas, 'HEAD'>
|
|
769
|
+
│
|
|
770
|
+
├─→ SchemaMapEndpoint<TSchemas, TPath, TMethod> = TSchemas[TPath][TMethod]
|
|
771
|
+
│
|
|
772
|
+
├─→ SchemaMapRequestType<TSchemas, TPath, TMethod>
|
|
773
|
+
│ (Infers from TSchemas[TPath][TMethod].request)
|
|
774
|
+
│
|
|
775
|
+
└─→ SchemaMapResponseType<TSchemas, TPath, TMethod, TStatus>
|
|
776
|
+
(Infers from TSchemas[TPath][TMethod].responses[TStatus])
|
|
777
|
+
```
|
|
778
|
+
|
|
779
|
+
---
|
|
780
|
+
|
|
781
|
+
### 6. Contribution Checklist
|
|
782
|
+
|
|
783
|
+
This section provides a step-by-step guide for making changes to the package.
|
|
784
|
+
|
|
785
|
+
#### 6.1 Adding a New Builder Configuration Option
|
|
786
|
+
|
|
787
|
+
1. **Define Type:** Add the new property to `IgniterCallerBuilderState` in `src/types/builder.ts`.
|
|
788
|
+
2. **Add Builder Method:** Implement `withNewOption(value)` in `src/builders/main.builder.ts` that returns a new builder instance.
|
|
789
|
+
3. **Update Constructor Params:** Add to `IgniterCallerRequestBuilderParams` in `src/types/builder.ts`.
|
|
790
|
+
4. **Update Manager:** Pass the new option to request builder params in `createBuilderParams()`.
|
|
791
|
+
5. **Write Tests:** Add type inference tests in `src/builders/main.builder.spec.ts`.
|
|
792
|
+
6. **Update Docs:** Add to this AGENTS.md and README.md.
|
|
793
|
+
|
|
794
|
+
#### 6.2 Adding a New HTTP Method
|
|
795
|
+
|
|
796
|
+
1. **Add to Type Union:** Add the method string to `IgniterCallerHttpMethod` in `src/types/http.ts`.
|
|
797
|
+
2. **Add to Schema Types:** Add to `IgniterCallerSchemaMethod` union and corresponding `XPaths` type in `src/types/schemas.ts`.
|
|
798
|
+
3. **Implement Manager Method:** Add method in `src/core/manager.ts` (e.g., `options(url)`).
|
|
799
|
+
4. **Update Schema Path Builder:** Add method in `src/builders/schema-path.builder.ts`.
|
|
800
|
+
5. **Update Interface:** Add method signature to `IIgniterCallerManager` in `src/types/manager.ts`.
|
|
801
|
+
6. **Write Tests:** Add tests in `src/core/manager.spec.ts`.
|
|
802
|
+
7. **Update Docs:** Add to API reference.
|
|
803
|
+
|
|
804
|
+
#### 6.3 Adding a New Telemetry Event
|
|
805
|
+
|
|
806
|
+
1. **Define Schema:** Add schema definition in `src/telemetry/index.ts` following naming conventions:
|
|
807
|
+
- Attributes use `ctx.<domain>.<attribute>` format
|
|
808
|
+
- Namespace is `igniter.caller.<group>.<event>`
|
|
809
|
+
2. **Add to Group:** Register event in the appropriate group (request, cache, retry, validation).
|
|
810
|
+
3. **Emit in Code:** Call `this.telemetry?.emit(IgniterCallerTelemetryEvents.get.key('group.event'), { ...attributes })`.
|
|
811
|
+
4. **Write Tests:** Add telemetry test in `src/core/manager.spec.ts` with attribute assertions.
|
|
812
|
+
5. **Update AGENTS.md:** Add to telemetry registry section.
|
|
813
|
+
|
|
814
|
+
#### 6.4 Adding a New Error Code
|
|
815
|
+
|
|
816
|
+
1. **Add to Union:** Add the error code string to `IgniterCallerErrorCode` in `src/errors/caller.error.ts`.
|
|
817
|
+
2. **Add to Operation Type:** If new operation, add to `IgniterCallerOperation` type.
|
|
818
|
+
3. **Throw Error:** Use `new IgniterCallerError({ code, operation, message, ... })`.
|
|
819
|
+
4. **Update AGENTS.md:** Add to error code library with context, cause, mitigation, solution.
|
|
820
|
+
|
|
821
|
+
#### 6.5 Adding a New Utility Function
|
|
822
|
+
|
|
823
|
+
1. **Implement Function:** Add to appropriate utility file in `src/utils/`.
|
|
824
|
+
2. **Export:** Add to `src/utils/index.ts` barrel.
|
|
825
|
+
3. **Write Tests:** Create `<utility>.spec.ts` in same directory with comprehensive coverage.
|
|
826
|
+
4. **Type Check:** Run `npm run typecheck`.
|
|
827
|
+
5. **Lint:** Run `npm run lint`.
|
|
828
|
+
|
|
829
|
+
---
|
|
830
|
+
|
|
831
|
+
### 7. Maintainer Troubleshooting
|
|
832
|
+
|
|
833
|
+
This section helps maintainers debug issues in the package code.
|
|
834
|
+
|
|
835
|
+
#### 7.1 Type Inference Not Working
|
|
836
|
+
|
|
837
|
+
**Symptoms:** Response type is `unknown` instead of inferred schema type.
|
|
838
|
+
|
|
839
|
+
**Causes:**
|
|
840
|
+
|
|
841
|
+
- Schema path uses full URL instead of relative path
|
|
842
|
+
- Schema map type is not properly const assertion
|
|
843
|
+
- Generic type parameters not propagating
|
|
844
|
+
|
|
845
|
+
**Debugging Steps:**
|
|
846
|
+
|
|
847
|
+
1. **Check Schema Path:** Ensure schema keys are relative paths (e.g., `'/users'`) not full URLs.
|
|
848
|
+
2. **Verify Const Assertion:** Ensure schema object has `as const` assertion.
|
|
849
|
+
3. **Check Generic Chain:** Verify `TSchemas` type parameter propagates through builder → manager → request builder.
|
|
850
|
+
|
|
851
|
+
**Example Fix:**
|
|
83
852
|
|
|
84
853
|
```typescript
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
854
|
+
// ❌ WRONG: Full URL in schema key
|
|
855
|
+
const schemas = {
|
|
856
|
+
'https://api.test/users': { GET: { ... } }
|
|
857
|
+
} as const
|
|
858
|
+
|
|
859
|
+
// ✅ CORRECT: Relative path
|
|
860
|
+
const schemas = {
|
|
861
|
+
'/users': { GET: { ... } }
|
|
862
|
+
} as const
|
|
863
|
+
```
|
|
864
|
+
|
|
865
|
+
---
|
|
866
|
+
|
|
867
|
+
#### 7.2 Cache Not Invalidating
|
|
868
|
+
|
|
869
|
+
**Symptoms:** Stale data returned after mutation.
|
|
870
|
+
|
|
871
|
+
**Causes:**
|
|
872
|
+
|
|
873
|
+
- Cache key doesn't match request URL
|
|
874
|
+
- Store adapter not configured correctly
|
|
875
|
+
- Pattern invalidation not supported by store
|
|
876
|
+
|
|
877
|
+
**Debugging Steps:**
|
|
878
|
+
|
|
879
|
+
1. **Check Cache Key:** Verify `effectiveCacheKey` in `execute()` method.
|
|
880
|
+
2. **Verify Store Configuration:** Ensure `withStore()` is called before `build()`.
|
|
881
|
+
3. **Check Pattern:** Pattern-based invalidation only works for in-memory cache.
|
|
882
|
+
|
|
883
|
+
---
|
|
884
|
+
|
|
885
|
+
#### 7.3 Telemetry Not Emitting
|
|
886
|
+
|
|
887
|
+
**Symptoms:** No telemetry events observed.
|
|
888
|
+
|
|
889
|
+
**Causes:**
|
|
890
|
+
|
|
891
|
+
- Telemetry manager not passed to builder
|
|
892
|
+
- Event key mismatch
|
|
893
|
+
- Telemetry not built into IgniterTelemetry
|
|
894
|
+
|
|
895
|
+
**Debugging Steps:**
|
|
896
|
+
|
|
897
|
+
1. **Verify Configuration:** Check `this.state.telemetry` in manager constructor.
|
|
898
|
+
2. **Check Event Key:** Verify `IgniterCallerTelemetryEvents.get.key('group.event')` matches defined event.
|
|
899
|
+
3. **Add Debug Log:** Add `console.log` before `this.telemetry?.emit()` to verify execution.
|
|
900
|
+
|
|
901
|
+
---
|
|
902
|
+
|
|
903
|
+
#### 7.4 Request Interceptors Not Running
|
|
904
|
+
|
|
905
|
+
**Symptoms:** Headers not being modified by interceptor.
|
|
906
|
+
|
|
907
|
+
**Causes:**
|
|
908
|
+
|
|
909
|
+
- Interceptor not added to builder
|
|
910
|
+
- Interceptor signature incorrect
|
|
911
|
+
- Interceptor chain not awaited
|
|
912
|
+
|
|
913
|
+
**Debugging Steps:**
|
|
914
|
+
|
|
915
|
+
1. **Check Builder State:** Verify `requestInterceptors` array in builder state.
|
|
916
|
+
2. **Verify Signature:** Ensure interceptor accepts and returns `IgniterCallerRequestOptions`.
|
|
917
|
+
3. **Check Async Handling:** Ensure interceptor chain uses `await`.
|
|
918
|
+
|
|
919
|
+
---
|
|
920
|
+
|
|
921
|
+
## II. CONSUMER GUIDE (Developer Manual)
|
|
922
|
+
|
|
923
|
+
### 8. Distribution Anatomy (Consumption)
|
|
924
|
+
|
|
925
|
+
The package is distributed as a standard npm package with multiple entry points.
|
|
926
|
+
|
|
927
|
+
#### 8.1 Package Structure
|
|
928
|
+
|
|
929
|
+
```
|
|
930
|
+
@igniter-js/caller/
|
|
931
|
+
├── dist/
|
|
932
|
+
│ ├── index.js # CommonJS main entry
|
|
933
|
+
│ ├── index.mjs # ESM main entry
|
|
934
|
+
│ ├── index.d.ts # TypeScript definitions
|
|
935
|
+
│ ├── client/
|
|
936
|
+
│ │ ├── index.js # React client entry (CJS)
|
|
937
|
+
│ │ ├── index.mjs # React client entry (ESM)
|
|
938
|
+
│ │ └── index.d.ts # React client types
|
|
939
|
+
│ ├── telemetry/
|
|
940
|
+
│ │ ├── index.js # Telemetry definitions (CJS)
|
|
941
|
+
│ │ ├── index.mjs # Telemetry definitions (ESM)
|
|
942
|
+
│ │ └── index.d.ts # Telemetry type definitions
|
|
943
|
+
│ └── adapters/
|
|
944
|
+
│ ├── index.js # Mock adapter (CJS)
|
|
945
|
+
│ ├── index.mjs # Mock adapter (ESM)
|
|
946
|
+
│ └── index.d.ts # Adapter type definitions
|
|
947
|
+
└── package.json
|
|
948
|
+
```
|
|
949
|
+
|
|
950
|
+
#### 8.2 Imports
|
|
951
|
+
|
|
952
|
+
```typescript
|
|
953
|
+
// Main entry - creates caller instances
|
|
954
|
+
import { IgniterCaller, IgniterCallerManager } from "@igniter-js/caller";
|
|
955
|
+
|
|
956
|
+
// Telemetry definitions - for observability setup
|
|
957
|
+
import { IgniterCallerTelemetryEvents } from "@igniter-js/caller/telemetry";
|
|
958
|
+
|
|
959
|
+
// Mock adapter - for testing
|
|
960
|
+
import { MockCallerStoreAdapter } from "@igniter-js/caller/adapters";
|
|
961
|
+
|
|
962
|
+
// React client - Provider + hooks
|
|
963
|
+
import { IgniterCallerProvider, useIgniterCaller } from "@igniter-js/caller/client";
|
|
964
|
+
|
|
965
|
+
// Typed mock registry
|
|
966
|
+
import { IgniterCallerMock } from "@igniter-js/caller";
|
|
967
|
+
|
|
968
|
+
// Types - for extending or advanced usage
|
|
969
|
+
import type {
|
|
970
|
+
IgniterCallerApiResponse,
|
|
971
|
+
IgniterCallerRequestInterceptor,
|
|
972
|
+
IgniterCallerRetryOptions,
|
|
973
|
+
} from "@igniter-js/caller";
|
|
974
|
+
```
|
|
975
|
+
|
|
976
|
+
#### 8.3 Runtime Support
|
|
977
|
+
|
|
978
|
+
| Runtime | Version Required | Notes |
|
|
979
|
+
| -------- | ---------------- | --------------------------------------------- |
|
|
980
|
+
| Node.js | 18+ | Uses native `fetch` (undici) |
|
|
981
|
+
| Bun | 1.0+ | Native `fetch` support |
|
|
982
|
+
| Deno | 1.30+ | Native `fetch` support |
|
|
983
|
+
| Browsers | Modern (ES2020+) | Native `fetch` and `AbortController` required |
|
|
984
|
+
|
|
985
|
+
---
|
|
986
|
+
|
|
987
|
+
### 9. Quick Start & Common Patterns
|
|
988
|
+
|
|
989
|
+
#### 9.1 Basic Usage
|
|
990
|
+
|
|
991
|
+
```typescript
|
|
992
|
+
import { IgniterCaller } from "@igniter-js/caller";
|
|
993
|
+
|
|
994
|
+
const api = IgniterCaller.create()
|
|
995
|
+
.withBaseUrl("https://api.example.com")
|
|
996
|
+
.withHeaders({ Authorization: `Bearer ${token}` })
|
|
997
|
+
.build();
|
|
998
|
+
|
|
999
|
+
const result = await api.get("/users").execute();
|
|
1000
|
+
|
|
1001
|
+
if (result.error) {
|
|
1002
|
+
console.error(result.error);
|
|
1003
|
+
throw result.error;
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
console.log(result.data);
|
|
1007
|
+
```
|
|
1008
|
+
|
|
1009
|
+
#### 9.2 With Query Parameters
|
|
1010
|
+
|
|
1011
|
+
```typescript
|
|
1012
|
+
const result = await api.get("/users").params({ page: 1, limit: 10 }).execute();
|
|
94
1013
|
```
|
|
95
1014
|
|
|
96
|
-
|
|
1015
|
+
#### 9.3 With POST Body
|
|
97
1016
|
|
|
98
|
-
|
|
1017
|
+
```typescript
|
|
1018
|
+
const result = await api
|
|
1019
|
+
.post("/users")
|
|
1020
|
+
.body({ name: "John Doe", email: "john@example.com" })
|
|
1021
|
+
.execute();
|
|
1022
|
+
```
|
|
99
1023
|
|
|
100
|
-
|
|
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 |
|
|
1024
|
+
#### 9.4 With Caching
|
|
108
1025
|
|
|
109
|
-
|
|
1026
|
+
```typescript
|
|
1027
|
+
const result = await api
|
|
1028
|
+
.get("/users")
|
|
1029
|
+
.stale(30_000) // Cache for 30 seconds
|
|
1030
|
+
.execute();
|
|
1031
|
+
```
|
|
110
1032
|
|
|
111
|
-
|
|
1033
|
+
#### 9.5 With Retry
|
|
112
1034
|
|
|
113
1035
|
```typescript
|
|
114
|
-
|
|
115
|
-
|
|
1036
|
+
const result = await api
|
|
1037
|
+
.get("/health")
|
|
1038
|
+
.retry(3, {
|
|
1039
|
+
baseDelay: 250,
|
|
1040
|
+
backoff: "exponential",
|
|
1041
|
+
retryOnStatus: [408, 429, 500, 502, 503, 504],
|
|
1042
|
+
})
|
|
1043
|
+
.execute();
|
|
116
1044
|
```
|
|
117
1045
|
|
|
1046
|
+
#### 9.6 React Client (Provider + Hooks)
|
|
1047
|
+
|
|
1048
|
+
```tsx
|
|
1049
|
+
import { IgniterCallerProvider, useIgniterCaller } from "@igniter-js/caller/client";
|
|
1050
|
+
|
|
1051
|
+
export function CallerProvider() {
|
|
1052
|
+
return (
|
|
1053
|
+
<IgniterCallerProvider callers={{ github: githubApi }}>
|
|
1054
|
+
<App />
|
|
1055
|
+
</IgniterCallerProvider>
|
|
1056
|
+
);
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
export const useCaller = useIgniterCaller<{
|
|
1060
|
+
github: typeof githubApi;
|
|
1061
|
+
}>();
|
|
1062
|
+
|
|
1063
|
+
function Profile() {
|
|
1064
|
+
const github = useCaller("github");
|
|
1065
|
+
const { data, isLoading, error, refetch, invalidate } = github
|
|
1066
|
+
.get("/me")
|
|
1067
|
+
.useQuery({ staleTime: 5000 });
|
|
1068
|
+
|
|
1069
|
+
if (isLoading) return <div>Loading...</div>;
|
|
1070
|
+
if (error) return <div>Error</div>;
|
|
1071
|
+
return <pre>{JSON.stringify(data, null, 2)}</pre>;
|
|
1072
|
+
}
|
|
1073
|
+
```
|
|
1074
|
+
|
|
1075
|
+
Notes:
|
|
1076
|
+
|
|
1077
|
+
- `@igniter-js/caller/client` is the only entrypoint that depends on React.
|
|
1078
|
+
- Global defaults can be set via `github.config.set(...)`.
|
|
1079
|
+
- Invalidation supports an optional data payload for optimistic updates.
|
|
1080
|
+
|
|
1081
|
+
#### 9.7 Typed Mocks (IgniterCallerMock)
|
|
1082
|
+
|
|
1083
|
+
```typescript
|
|
1084
|
+
import { IgniterCaller, IgniterCallerMock } from "@igniter-js/caller";
|
|
1085
|
+
|
|
1086
|
+
const schemas = {
|
|
1087
|
+
"/users/:id": {
|
|
1088
|
+
GET: {
|
|
1089
|
+
responses: {
|
|
1090
|
+
200: UserSchema,
|
|
1091
|
+
},
|
|
1092
|
+
},
|
|
1093
|
+
},
|
|
1094
|
+
};
|
|
1095
|
+
|
|
1096
|
+
const mock = IgniterCallerMock.create()
|
|
1097
|
+
.withSchemas(schemas)
|
|
1098
|
+
.mock("/users/:id", {
|
|
1099
|
+
GET: (request) => ({
|
|
1100
|
+
response: { id: request.params.id },
|
|
1101
|
+
status: 200,
|
|
1102
|
+
delayMs: 150,
|
|
1103
|
+
}),
|
|
1104
|
+
})
|
|
1105
|
+
.build();
|
|
1106
|
+
|
|
1107
|
+
const api = IgniterCaller.create()
|
|
1108
|
+
.withSchemas(schemas)
|
|
1109
|
+
.withMock({ enabled: true, mock })
|
|
1110
|
+
.build();
|
|
1111
|
+
```
|
|
1112
|
+
|
|
1113
|
+
Notes:
|
|
1114
|
+
|
|
1115
|
+
- Mock handlers receive the full request context (method, url, headers, query, params, body).
|
|
1116
|
+
- When mock is enabled and no handler is registered, the request falls back to the real transport.
|
|
1117
|
+
|
|
118
1118
|
---
|
|
119
1119
|
|
|
120
|
-
|
|
1120
|
+
### 10. Real-World Use Case Library
|
|
1121
|
+
|
|
1122
|
+
#### Case A: E-Commerce Product Catalog
|
|
1123
|
+
|
|
1124
|
+
**Scenario:** An e-commerce platform needs to fetch product listings with caching, type safety, and error handling.
|
|
1125
|
+
|
|
1126
|
+
```typescript
|
|
1127
|
+
import { IgniterCaller } from "@igniter-js/caller";
|
|
1128
|
+
import { z } from "zod";
|
|
1129
|
+
|
|
1130
|
+
// Define schemas
|
|
1131
|
+
const ProductSchema = z.object({
|
|
1132
|
+
id: z.string(),
|
|
1133
|
+
name: z.string(),
|
|
1134
|
+
price: z.number(),
|
|
1135
|
+
inStock: z.boolean(),
|
|
1136
|
+
});
|
|
1137
|
+
|
|
1138
|
+
const ProductsResponseSchema = z.object({
|
|
1139
|
+
products: z.array(ProductSchema),
|
|
1140
|
+
total: z.number(),
|
|
1141
|
+
});
|
|
1142
|
+
|
|
1143
|
+
const api = IgniterCaller.create()
|
|
1144
|
+
.withBaseUrl("https://shop-api.example.com")
|
|
1145
|
+
.build();
|
|
1146
|
+
|
|
1147
|
+
// Fetch products with caching (5 minutes)
|
|
1148
|
+
async function getProducts(category?: string) {
|
|
1149
|
+
const result = await api
|
|
1150
|
+
.get("/products")
|
|
1151
|
+
.params(category ? { category } : {})
|
|
1152
|
+
.responseType(ProductsResponseSchema)
|
|
1153
|
+
.stale(300_000) // 5 minutes
|
|
1154
|
+
.execute();
|
|
1155
|
+
|
|
1156
|
+
if (result.error) {
|
|
1157
|
+
throw new Error(`Failed to fetch products: ${result.error.message}`);
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
return result.data;
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
// Usage
|
|
1164
|
+
const products = await getProducts("electronics");
|
|
1165
|
+
console.log(`Found ${products.total} products`);
|
|
1166
|
+
```
|
|
1167
|
+
|
|
1168
|
+
**Best Practices Applied:**
|
|
121
1169
|
|
|
122
|
-
-
|
|
123
|
-
-
|
|
124
|
-
-
|
|
125
|
-
-
|
|
1170
|
+
- Caching for high-traffic endpoints
|
|
1171
|
+
- Schema validation for response integrity
|
|
1172
|
+
- Type-safe product objects
|
|
1173
|
+
- Centralized error handling
|
|
126
1174
|
|
|
127
1175
|
---
|
|
128
1176
|
|
|
129
|
-
|
|
1177
|
+
#### Case B: Fintech Payment Processing
|
|
130
1178
|
|
|
1179
|
+
**Scenario:** A fintech application needs to process payments with retry logic, timeout handling, and secure headers.
|
|
1180
|
+
|
|
1181
|
+
```typescript
|
|
1182
|
+
import { IgniterCaller } from "@igniter-js/caller";
|
|
1183
|
+
import { z } from "zod";
|
|
1184
|
+
|
|
1185
|
+
// Payment request schema
|
|
1186
|
+
const PaymentRequestSchema = z.object({
|
|
1187
|
+
amount: z.number().positive(),
|
|
1188
|
+
currency: z.enum(["USD", "EUR", "GBP"]),
|
|
1189
|
+
recipient: z.object({
|
|
1190
|
+
accountNumber: z.string().length(10),
|
|
1191
|
+
routingNumber: z.string().length(9),
|
|
1192
|
+
}),
|
|
1193
|
+
reference: z.string(),
|
|
1194
|
+
});
|
|
1195
|
+
|
|
1196
|
+
// Payment response schema
|
|
1197
|
+
const PaymentResponseSchema = z.object({
|
|
1198
|
+
id: z.string(),
|
|
1199
|
+
status: z.enum(["pending", "completed", "failed"]),
|
|
1200
|
+
transactionId: z.string(),
|
|
1201
|
+
});
|
|
1202
|
+
|
|
1203
|
+
const api = IgniterCaller.create()
|
|
1204
|
+
.withBaseUrl("https://payments-api.example.com")
|
|
1205
|
+
.withHeaders({
|
|
1206
|
+
"X-API-Key": process.env.PAYMENT_API_KEY!,
|
|
1207
|
+
"Content-Type": "application/json",
|
|
1208
|
+
})
|
|
1209
|
+
.build();
|
|
1210
|
+
|
|
1211
|
+
// Process payment with retry and timeout
|
|
1212
|
+
async function processPayment(payment: z.infer<typeof PaymentRequestSchema>) {
|
|
1213
|
+
const result = await api
|
|
1214
|
+
.post("/payments")
|
|
1215
|
+
.body(payment)
|
|
1216
|
+
.responseType(PaymentResponseSchema)
|
|
1217
|
+
.timeout(10_000) // 10 second timeout
|
|
1218
|
+
.retry(3, {
|
|
1219
|
+
baseDelay: 500,
|
|
1220
|
+
backoff: "exponential",
|
|
1221
|
+
retryOnStatus: [503, 504], // Only retry on server errors
|
|
1222
|
+
})
|
|
1223
|
+
.execute();
|
|
1224
|
+
|
|
1225
|
+
if (result.error) {
|
|
1226
|
+
// Log payment failure
|
|
1227
|
+
console.error(`Payment failed: ${result.error.message}`);
|
|
1228
|
+
|
|
1229
|
+
// Re-throw for upstream handling
|
|
1230
|
+
throw result.error;
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
return result.data;
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
// Usage
|
|
1237
|
+
try {
|
|
1238
|
+
const payment = await processPayment({
|
|
1239
|
+
amount: 100.0,
|
|
1240
|
+
currency: "USD",
|
|
1241
|
+
recipient: {
|
|
1242
|
+
accountNumber: "1234567890",
|
|
1243
|
+
routingNumber: "987654321",
|
|
1244
|
+
},
|
|
1245
|
+
reference: `ORDER-${Date.now()}`,
|
|
1246
|
+
});
|
|
1247
|
+
|
|
1248
|
+
console.log(`Payment initiated: ${payment.id}`);
|
|
1249
|
+
} catch (error) {
|
|
1250
|
+
console.error("Payment processing failed");
|
|
1251
|
+
}
|
|
131
1252
|
```
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
1253
|
+
|
|
1254
|
+
**Best Practices Applied:**
|
|
1255
|
+
|
|
1256
|
+
- Strict timeout for financial operations
|
|
1257
|
+
- Exponential backoff for transient failures
|
|
1258
|
+
- Schema validation for request/response
|
|
1259
|
+
- Secure API key management via headers
|
|
1260
|
+
- Only retry on server-side errors
|
|
1261
|
+
|
|
1262
|
+
---
|
|
1263
|
+
|
|
1264
|
+
#### Case C: Social Media Feed with Real-Time Updates
|
|
1265
|
+
|
|
1266
|
+
**Scenario:** A social media app needs to fetch feed posts, cache aggressively, and invalidate cache on new posts.
|
|
1267
|
+
|
|
1268
|
+
```typescript
|
|
1269
|
+
import { IgniterCaller, IgniterCallerManager } from "@igniter-js/caller";
|
|
1270
|
+
import { z } from "zod";
|
|
1271
|
+
|
|
1272
|
+
const PostSchema = z.object({
|
|
1273
|
+
id: z.string(),
|
|
1274
|
+
author: z.object({
|
|
1275
|
+
id: z.string(),
|
|
1276
|
+
name: z.string(),
|
|
1277
|
+
avatar: z.string().url(),
|
|
1278
|
+
}),
|
|
1279
|
+
content: z.string(),
|
|
1280
|
+
createdAt: z.string(),
|
|
1281
|
+
likes: z.number(),
|
|
1282
|
+
});
|
|
1283
|
+
|
|
1284
|
+
const FeedResponseSchema = z.object({
|
|
1285
|
+
posts: z.array(PostSchema),
|
|
1286
|
+
nextCursor: z.string().nullable(),
|
|
1287
|
+
});
|
|
1288
|
+
|
|
1289
|
+
// Set up global event listener for cache invalidation
|
|
1290
|
+
IgniterCallerManager.on(/^\/feed/, async (result, ctx) => {
|
|
1291
|
+
if (!result.error) {
|
|
1292
|
+
console.log(`Feed fetched: ${ctx.method} ${ctx.url}`);
|
|
1293
|
+
}
|
|
1294
|
+
});
|
|
1295
|
+
|
|
1296
|
+
const api = IgniterCaller.create()
|
|
1297
|
+
.withBaseUrl("https://social-api.example.com")
|
|
1298
|
+
.withHeaders({
|
|
1299
|
+
Authorization: `Bearer ${authToken}`,
|
|
1300
|
+
})
|
|
1301
|
+
.build();
|
|
1302
|
+
|
|
1303
|
+
// Fetch feed with aggressive caching (2 minutes)
|
|
1304
|
+
async function getFeed(cursor?: string) {
|
|
1305
|
+
const result = await api
|
|
1306
|
+
.get("/feed")
|
|
1307
|
+
.params(cursor ? { cursor } : {})
|
|
1308
|
+
.responseType(FeedResponseSchema)
|
|
1309
|
+
.stale(120_000) // 2 minutes
|
|
1310
|
+
.execute();
|
|
1311
|
+
|
|
1312
|
+
if (result.error) {
|
|
1313
|
+
throw new Error(`Failed to fetch feed: ${result.error.message}`);
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
return result.data;
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
// Create new post and invalidate feed cache
|
|
1320
|
+
async function createPost(content: string) {
|
|
1321
|
+
const result = await api
|
|
1322
|
+
.post("/posts")
|
|
1323
|
+
.body({ content })
|
|
1324
|
+
.responseType(PostSchema)
|
|
1325
|
+
.execute();
|
|
1326
|
+
|
|
1327
|
+
if (result.error) {
|
|
1328
|
+
throw new Error(`Failed to create post: ${result.error.message}`);
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
// Invalidate feed cache after creating post
|
|
1332
|
+
await IgniterCallerManager.invalidate("/feed");
|
|
1333
|
+
|
|
1334
|
+
return result.data;
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
// Usage
|
|
1338
|
+
const feed = await getFeed();
|
|
1339
|
+
console.log(`Feed has ${feed.posts.length} posts`);
|
|
1340
|
+
|
|
1341
|
+
await createPost("Hello, world!");
|
|
1342
|
+
const freshFeed = await getFeed(); // This will fetch fresh data
|
|
166
1343
|
```
|
|
167
1344
|
|
|
1345
|
+
**Best Practices Applied:**
|
|
1346
|
+
|
|
1347
|
+
- Aggressive caching for read-heavy feeds
|
|
1348
|
+
- Global event listener for observability
|
|
1349
|
+
- Manual cache invalidation after mutations
|
|
1350
|
+
- Cursor-based pagination support
|
|
1351
|
+
|
|
168
1352
|
---
|
|
169
1353
|
|
|
170
|
-
|
|
1354
|
+
#### Case D: IoT Device Management
|
|
171
1355
|
|
|
172
|
-
|
|
1356
|
+
**Scenario:** An IoT platform needs to manage devices with high concurrency, retry logic, and efficient JSON parsing.
|
|
173
1357
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
1358
|
+
```typescript
|
|
1359
|
+
import { IgniterCaller, IgniterCallerManager } from "@igniter-js/caller";
|
|
1360
|
+
import { z } from "zod";
|
|
1361
|
+
|
|
1362
|
+
const DeviceSchema = z.object({
|
|
1363
|
+
id: z.string(),
|
|
1364
|
+
name: z.string(),
|
|
1365
|
+
type: z.enum(["sensor", "actuator", "controller"]),
|
|
1366
|
+
status: z.enum(["online", "offline", "error"]),
|
|
1367
|
+
lastSeen: z.string(),
|
|
1368
|
+
metadata: z.record(z.unknown()),
|
|
1369
|
+
});
|
|
1370
|
+
|
|
1371
|
+
const api = IgniterCaller.create()
|
|
1372
|
+
.withBaseUrl("https://iot-api.example.com")
|
|
1373
|
+
.withHeaders({
|
|
1374
|
+
"X-Platform-Key": process.env.IOT_PLATFORM_KEY!,
|
|
1375
|
+
})
|
|
1376
|
+
.build();
|
|
1377
|
+
|
|
1378
|
+
// Fetch all devices with batch parallelization
|
|
1379
|
+
async function getAllDevices() {
|
|
1380
|
+
// Split into multiple requests for pagination
|
|
1381
|
+
const page1 = api.get("/devices").params({ page: 1, limit: 50 }).execute();
|
|
1382
|
+
const page2 = api.get("/devices").params({ page: 2, limit: 50 }).execute();
|
|
1383
|
+
const page3 = api.get("/devices").params({ page: 3, limit: 50 }).execute();
|
|
1384
|
+
|
|
1385
|
+
// Execute all requests in parallel
|
|
1386
|
+
const [result1, result2, result3] = await IgniterCallerManager.batch([
|
|
1387
|
+
page1,
|
|
1388
|
+
page2,
|
|
1389
|
+
page3,
|
|
1390
|
+
]);
|
|
1391
|
+
|
|
1392
|
+
if (result1.error || result2.error || result3.error) {
|
|
1393
|
+
throw new Error("Failed to fetch all devices");
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
return [...result1.data, ...result2.data, ...result3.data];
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
// Update device status with retry
|
|
1400
|
+
async function updateDeviceStatus(
|
|
1401
|
+
deviceId: string,
|
|
1402
|
+
status: "online" | "offline",
|
|
1403
|
+
) {
|
|
1404
|
+
const result = await api
|
|
1405
|
+
.put(`/devices/${deviceId}`)
|
|
1406
|
+
.body({ status })
|
|
1407
|
+
.responseType(DeviceSchema)
|
|
1408
|
+
.retry(5, {
|
|
1409
|
+
baseDelay: 1000,
|
|
1410
|
+
backoff: "linear",
|
|
1411
|
+
retryOnStatus: [408, 429, 500, 502, 503, 504],
|
|
1412
|
+
})
|
|
1413
|
+
.execute();
|
|
1414
|
+
|
|
1415
|
+
if (result.error) {
|
|
1416
|
+
throw new Error(`Failed to update device: ${result.error.message}`);
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
return result.data;
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
// Usage
|
|
1423
|
+
const devices = await getAllDevices();
|
|
1424
|
+
console.log(`Managing ${devices.length} devices`);
|
|
1425
|
+
|
|
1426
|
+
await updateDeviceStatus(devices[0].id, "online");
|
|
1427
|
+
```
|
|
178
1428
|
|
|
179
|
-
|
|
1429
|
+
**Best Practices Applied:**
|
|
180
1430
|
|
|
181
|
-
-
|
|
182
|
-
-
|
|
1431
|
+
- Parallel batch requests for efficiency
|
|
1432
|
+
- Retry with linear backoff for IoT network conditions
|
|
1433
|
+
- Type-safe device objects
|
|
1434
|
+
- Efficient JSON parsing (automatic)
|
|
1435
|
+
|
|
1436
|
+
---
|
|
183
1437
|
|
|
184
|
-
|
|
1438
|
+
#### Case E: Healthcare Patient Records
|
|
185
1439
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
1440
|
+
**Scenario:** A healthcare application needs to fetch patient records with strict validation, HIPAA-compliant logging, and fallback handling.
|
|
1441
|
+
|
|
1442
|
+
```typescript
|
|
1443
|
+
import { IgniterCaller } from "@igniter-js/caller";
|
|
1444
|
+
import { z } from "zod";
|
|
1445
|
+
|
|
1446
|
+
// Patient schema with validation
|
|
1447
|
+
const PatientSchema = z.object({
|
|
1448
|
+
id: z.string(),
|
|
1449
|
+
name: z.object({
|
|
1450
|
+
first: z.string().min(1),
|
|
1451
|
+
last: z.string().min(1),
|
|
1452
|
+
}),
|
|
1453
|
+
dateOfBirth: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
|
|
1454
|
+
medications: z.array(
|
|
1455
|
+
z.object({
|
|
1456
|
+
name: z.string(),
|
|
1457
|
+
dosage: z.string(),
|
|
1458
|
+
frequency: z.string(),
|
|
1459
|
+
}),
|
|
1460
|
+
),
|
|
1461
|
+
});
|
|
1462
|
+
|
|
1463
|
+
// Error schema
|
|
1464
|
+
const ApiErrorSchema = z.object({
|
|
1465
|
+
code: z.string(),
|
|
1466
|
+
message: z.string(),
|
|
1467
|
+
});
|
|
1468
|
+
|
|
1469
|
+
const api = IgniterCaller.create()
|
|
1470
|
+
.withBaseUrl("https://healthcare-api.example.com")
|
|
1471
|
+
.withHeaders({
|
|
1472
|
+
Authorization: `Bearer ${process.env.HEALTHCARE_API_KEY!}`,
|
|
1473
|
+
"X-Request-Id": crypto.randomUUID(),
|
|
1474
|
+
})
|
|
1475
|
+
.build();
|
|
1476
|
+
|
|
1477
|
+
// Fetch patient with strict validation and fallback
|
|
1478
|
+
async function getPatient(patientId: string) {
|
|
1479
|
+
const result = await api
|
|
1480
|
+
.get(`/patients/${patientId}`)
|
|
1481
|
+
.responseType(PatientSchema)
|
|
1482
|
+
.fallback(() => {
|
|
1483
|
+
// Return default patient structure if API is unavailable
|
|
1484
|
+
return {
|
|
1485
|
+
id: patientId,
|
|
1486
|
+
name: { first: "Unknown", last: "Patient" },
|
|
1487
|
+
dateOfBirth: "1900-01-01",
|
|
1488
|
+
medications: [],
|
|
1489
|
+
};
|
|
1490
|
+
})
|
|
1491
|
+
.execute();
|
|
1492
|
+
|
|
1493
|
+
return result.data;
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
// Create medication record with request validation
|
|
1497
|
+
async function addMedication(
|
|
1498
|
+
patientId: string,
|
|
1499
|
+
medication: { name: string; dosage: string; frequency: string },
|
|
1500
|
+
) {
|
|
1501
|
+
const MedicationSchema = z.object({
|
|
1502
|
+
patientId: z.string(),
|
|
1503
|
+
name: z.string().min(1),
|
|
1504
|
+
dosage: z.string().min(1),
|
|
1505
|
+
frequency: z.string().min(1),
|
|
1506
|
+
});
|
|
1507
|
+
|
|
1508
|
+
const result = await api
|
|
1509
|
+
.post(`/patients/${patientId}/medications`)
|
|
1510
|
+
.body(medication)
|
|
1511
|
+
.responseType(z.object({ id: z.string() }))
|
|
1512
|
+
.execute();
|
|
1513
|
+
|
|
1514
|
+
if (result.error) {
|
|
1515
|
+
// Log error for compliance audit
|
|
1516
|
+
console.error(`Medication add failed: ${result.error.message}`);
|
|
1517
|
+
throw result.error;
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
return result.data;
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
// Usage
|
|
1524
|
+
try {
|
|
1525
|
+
const patient = await getPatient("PAT-12345");
|
|
1526
|
+
console.log(`Patient: ${patient.name.first} ${patient.name.last}`);
|
|
1527
|
+
|
|
1528
|
+
await addMedication("PAT-12345", {
|
|
1529
|
+
name: "Aspirin",
|
|
1530
|
+
dosage: "81mg",
|
|
1531
|
+
frequency: "Daily",
|
|
1532
|
+
});
|
|
1533
|
+
} catch (error) {
|
|
1534
|
+
console.error("Healthcare operation failed");
|
|
1535
|
+
}
|
|
1536
|
+
```
|
|
193
1537
|
|
|
194
|
-
|
|
1538
|
+
**Best Practices Applied:**
|
|
195
1539
|
|
|
196
|
-
-
|
|
197
|
-
-
|
|
1540
|
+
- Strict schema validation for sensitive data
|
|
1541
|
+
- Fallback values for API unavailability
|
|
1542
|
+
- Request ID tracking for audit trails
|
|
1543
|
+
- Centralized error logging for compliance
|
|
198
1544
|
|
|
199
1545
|
---
|
|
200
1546
|
|
|
201
|
-
|
|
1547
|
+
#### Case F: Content Management System (CMS)
|
|
202
1548
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
1549
|
+
**Scenario:** A CMS needs to fetch content, handle file uploads, and cache aggressively.
|
|
1550
|
+
|
|
1551
|
+
```typescript
|
|
1552
|
+
import { IgniterCaller } from "@igniter-js/caller";
|
|
1553
|
+
import { z } from "zod";
|
|
1554
|
+
|
|
1555
|
+
const ContentSchema = z.object({
|
|
1556
|
+
id: z.string(),
|
|
1557
|
+
title: z.string(),
|
|
1558
|
+
body: z.string(),
|
|
1559
|
+
publishedAt: z.string().nullable(),
|
|
1560
|
+
author: z.object({
|
|
1561
|
+
id: z.string(),
|
|
1562
|
+
name: z.string(),
|
|
1563
|
+
}),
|
|
1564
|
+
});
|
|
1565
|
+
|
|
1566
|
+
const ContentListSchema = z.object({
|
|
1567
|
+
items: z.array(ContentSchema),
|
|
1568
|
+
total: z.number(),
|
|
1569
|
+
});
|
|
1570
|
+
|
|
1571
|
+
const api = IgniterCaller.create()
|
|
1572
|
+
.withBaseUrl("https://cms-api.example.com")
|
|
1573
|
+
.withHeaders({
|
|
1574
|
+
"X-CMS-API-Key": process.env.CMS_API_KEY!,
|
|
1575
|
+
})
|
|
1576
|
+
.build();
|
|
1577
|
+
|
|
1578
|
+
// Fetch published articles with caching (10 minutes)
|
|
1579
|
+
async function getArticles(params: { page?: number; category?: string }) {
|
|
1580
|
+
const result = await api
|
|
1581
|
+
.get("/articles")
|
|
1582
|
+
.params(params)
|
|
1583
|
+
.responseType(ContentListSchema)
|
|
1584
|
+
.stale(600_000) // 10 minutes
|
|
1585
|
+
.execute();
|
|
1586
|
+
|
|
1587
|
+
if (result.error) {
|
|
1588
|
+
throw new Error(`Failed to fetch articles: ${result.error.message}`);
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
return result.data;
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
// Upload image file
|
|
1595
|
+
async function uploadImage(file: File) {
|
|
1596
|
+
const formData = new FormData();
|
|
1597
|
+
formData.append("file", file);
|
|
1598
|
+
|
|
1599
|
+
const result = await api
|
|
1600
|
+
.post("/images")
|
|
1601
|
+
.body(formData)
|
|
1602
|
+
.headers({ "Content-Type": "multipart/form-data" }) // Let fetch set boundary
|
|
1603
|
+
.execute();
|
|
1604
|
+
|
|
1605
|
+
if (result.error) {
|
|
1606
|
+
throw new Error(`Failed to upload image: ${result.error.message}`);
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
return result.data;
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
// Create article and invalidate cache
|
|
1613
|
+
async function createArticle(article: { title: string; body: string }) {
|
|
1614
|
+
const result = await api
|
|
1615
|
+
.post("/articles")
|
|
1616
|
+
.body(article)
|
|
1617
|
+
.responseType(ContentSchema)
|
|
1618
|
+
.execute();
|
|
1619
|
+
|
|
1620
|
+
if (result.error) {
|
|
1621
|
+
throw new Error(`Failed to create article: ${result.error.message}`);
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
// Invalidate article list cache
|
|
1625
|
+
await IgniterCallerManager.invalidate("/articles");
|
|
1626
|
+
|
|
1627
|
+
return result.data;
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
// Usage
|
|
1631
|
+
const articles = await getArticles({ page: 1, category: "news" });
|
|
1632
|
+
console.log(`Found ${articles.total} articles`);
|
|
1633
|
+
|
|
1634
|
+
const imageUpload = await uploadImage(fileInput.files[0]);
|
|
1635
|
+
console.log(`Image uploaded: ${imageUpload.id}`);
|
|
1636
|
+
```
|
|
1637
|
+
|
|
1638
|
+
**Best Practices Applied:**
|
|
1639
|
+
|
|
1640
|
+
- FormData handling for file uploads
|
|
1641
|
+
- Aggressive caching for content
|
|
1642
|
+
- Cache invalidation after mutations
|
|
1643
|
+
- Type-safe content objects
|
|
1644
|
+
|
|
1645
|
+
---
|
|
212
1646
|
|
|
213
|
-
|
|
1647
|
+
#### Case G: Analytics Dashboard
|
|
214
1648
|
|
|
215
|
-
|
|
216
|
-
|
|
1649
|
+
**Scenario:** An analytics dashboard needs to fetch metrics with long polling, caching, and error recovery.
|
|
1650
|
+
|
|
1651
|
+
```typescript
|
|
1652
|
+
import { IgniterCaller } from "@igniter-js/caller";
|
|
1653
|
+
import { z } from "zod";
|
|
1654
|
+
|
|
1655
|
+
const MetricSchema = z.object({
|
|
1656
|
+
name: z.string(),
|
|
1657
|
+
value: z.number(),
|
|
1658
|
+
unit: z.string(),
|
|
1659
|
+
timestamp: z.string(),
|
|
1660
|
+
});
|
|
1661
|
+
|
|
1662
|
+
const MetricsResponseSchema = z.object({
|
|
1663
|
+
metrics: z.array(MetricSchema),
|
|
1664
|
+
period: z.object({
|
|
1665
|
+
start: z.string(),
|
|
1666
|
+
end: z.string(),
|
|
1667
|
+
}),
|
|
1668
|
+
});
|
|
1669
|
+
|
|
1670
|
+
const api = IgniterCaller.create()
|
|
1671
|
+
.withBaseUrl("https://analytics-api.example.com")
|
|
1672
|
+
.withHeaders({
|
|
1673
|
+
"X-Analytics-Key": process.env.ANALYTICS_API_KEY!,
|
|
1674
|
+
})
|
|
1675
|
+
.build();
|
|
1676
|
+
|
|
1677
|
+
// Fetch metrics with short-term caching (30 seconds)
|
|
1678
|
+
async function getMetrics(period: { start: string; end: string }) {
|
|
1679
|
+
const result = await api
|
|
1680
|
+
.get("/metrics")
|
|
1681
|
+
.params(period)
|
|
1682
|
+
.responseType(MetricsResponseSchema)
|
|
1683
|
+
.stale(30_000) // 30 seconds
|
|
1684
|
+
.execute();
|
|
1685
|
+
|
|
1686
|
+
if (result.error) {
|
|
1687
|
+
// Return empty metrics on error (graceful degradation)
|
|
1688
|
+
return {
|
|
1689
|
+
metrics: [],
|
|
1690
|
+
period: {
|
|
1691
|
+
start: period.start,
|
|
1692
|
+
end: period.end,
|
|
1693
|
+
},
|
|
1694
|
+
};
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
return result.data;
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
// Start polling for real-time updates
|
|
1701
|
+
function startPolling(intervalMs: number) {
|
|
1702
|
+
const poll = async () => {
|
|
1703
|
+
const end = new Date().toISOString();
|
|
1704
|
+
const start = new Date(Date.now() - 300_000).toISOString(); // Last 5 minutes
|
|
1705
|
+
|
|
1706
|
+
const metrics = await getMetrics({ start, end });
|
|
1707
|
+
|
|
1708
|
+
// Update UI with new metrics
|
|
1709
|
+
updateDashboard(metrics);
|
|
1710
|
+
};
|
|
1711
|
+
|
|
1712
|
+
poll(); // Initial fetch
|
|
1713
|
+
return setInterval(poll, intervalMs);
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
// Usage
|
|
1717
|
+
const pollInterval = startPolling(30_000); // Poll every 30 seconds
|
|
1718
|
+
|
|
1719
|
+
// Later: stop polling
|
|
1720
|
+
clearInterval(pollInterval);
|
|
217
1721
|
```
|
|
218
1722
|
|
|
1723
|
+
**Best Practices Applied:**
|
|
1724
|
+
|
|
1725
|
+
- Short-term caching for polling
|
|
1726
|
+
- Graceful degradation on errors
|
|
1727
|
+
- Interval-based polling for real-time updates
|
|
1728
|
+
- Type-safe metric objects
|
|
1729
|
+
|
|
1730
|
+
---
|
|
1731
|
+
|
|
1732
|
+
#### Case H: Travel Booking System
|
|
1733
|
+
|
|
1734
|
+
**Scenario:** A travel booking system needs to search flights, handle rate limiting, and cache search results.
|
|
1735
|
+
|
|
1736
|
+
```typescript
|
|
1737
|
+
import { IgniterCaller } from "@igniter-js/caller";
|
|
1738
|
+
import { z } from "zod";
|
|
1739
|
+
|
|
1740
|
+
const FlightSchema = z.object({
|
|
1741
|
+
id: z.string(),
|
|
1742
|
+
airline: z.string(),
|
|
1743
|
+
flightNumber: z.string(),
|
|
1744
|
+
departure: z.object({
|
|
1745
|
+
airport: z.string(),
|
|
1746
|
+
time: z.string(),
|
|
1747
|
+
}),
|
|
1748
|
+
arrival: z.object({
|
|
1749
|
+
airport: z.string(),
|
|
1750
|
+
time: z.string(),
|
|
1751
|
+
}),
|
|
1752
|
+
price: z.object({
|
|
1753
|
+
amount: z.number(),
|
|
1754
|
+
currency: z.string(),
|
|
1755
|
+
}),
|
|
1756
|
+
seatsAvailable: z.number(),
|
|
1757
|
+
});
|
|
1758
|
+
|
|
1759
|
+
const FlightSearchResponseSchema = z.object({
|
|
1760
|
+
flights: z.array(FlightSchema),
|
|
1761
|
+
searchId: z.string(),
|
|
1762
|
+
expiresAt: z.string(),
|
|
1763
|
+
});
|
|
1764
|
+
|
|
1765
|
+
const api = IgniterCaller.create()
|
|
1766
|
+
.withBaseUrl("https://flights-api.example.com")
|
|
1767
|
+
.withHeaders({
|
|
1768
|
+
"X-Travel-API-Key": process.env.TRAVEL_API_KEY!,
|
|
1769
|
+
})
|
|
1770
|
+
.build();
|
|
1771
|
+
|
|
1772
|
+
// Search flights with rate-limit-aware retry
|
|
1773
|
+
async function searchFlights(search: {
|
|
1774
|
+
origin: string;
|
|
1775
|
+
destination: string;
|
|
1776
|
+
date: string;
|
|
1777
|
+
}) {
|
|
1778
|
+
const result = await api
|
|
1779
|
+
.get("/flights/search")
|
|
1780
|
+
.params(search)
|
|
1781
|
+
.responseType(FlightSearchResponseSchema)
|
|
1782
|
+
.retry(5, {
|
|
1783
|
+
baseDelay: 2000,
|
|
1784
|
+
backoff: "exponential",
|
|
1785
|
+
retryOnStatus: [429, 500, 502, 503, 504], // Include rate limit (429)
|
|
1786
|
+
})
|
|
1787
|
+
.stale(60_000) // Cache for 1 minute
|
|
1788
|
+
.execute();
|
|
1789
|
+
|
|
1790
|
+
if (result.error) {
|
|
1791
|
+
// Check if rate limited
|
|
1792
|
+
if ((result.error as any).statusCode === 429) {
|
|
1793
|
+
throw new Error("Rate limited. Please try again later.");
|
|
1794
|
+
}
|
|
1795
|
+
throw new Error(`Search failed: ${result.error.message}`);
|
|
1796
|
+
}
|
|
1797
|
+
|
|
1798
|
+
return result.data;
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
// Book flight with validation
|
|
1802
|
+
async function bookFlight(
|
|
1803
|
+
flightId: string,
|
|
1804
|
+
passenger: { name: string; email: string },
|
|
1805
|
+
) {
|
|
1806
|
+
const BookingRequestSchema = z.object({
|
|
1807
|
+
flightId: z.string(),
|
|
1808
|
+
passenger: z.object({
|
|
1809
|
+
name: z.string().min(2),
|
|
1810
|
+
email: z.string().email(),
|
|
1811
|
+
}),
|
|
1812
|
+
});
|
|
1813
|
+
|
|
1814
|
+
const result = await api
|
|
1815
|
+
.post("/flights/book")
|
|
1816
|
+
.body({ flightId, passenger })
|
|
1817
|
+
.responseType(
|
|
1818
|
+
z.object({
|
|
1819
|
+
bookingId: z.string(),
|
|
1820
|
+
confirmationCode: z.string(),
|
|
1821
|
+
}),
|
|
1822
|
+
)
|
|
1823
|
+
.timeout(15_000) // 15 second timeout for booking
|
|
1824
|
+
.execute();
|
|
1825
|
+
|
|
1826
|
+
if (result.error) {
|
|
1827
|
+
throw new Error(`Booking failed: ${result.error.message}`);
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1830
|
+
return result.data;
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
// Usage
|
|
1834
|
+
const search = await searchFlights({
|
|
1835
|
+
origin: "JFK",
|
|
1836
|
+
destination: "LAX",
|
|
1837
|
+
date: "2024-03-15",
|
|
1838
|
+
});
|
|
1839
|
+
|
|
1840
|
+
console.log(`Found ${search.flights.length} flights`);
|
|
1841
|
+
|
|
1842
|
+
const booking = await bookFlight(search.flights[0].id, {
|
|
1843
|
+
name: "John Doe",
|
|
1844
|
+
email: "john@example.com",
|
|
1845
|
+
});
|
|
1846
|
+
|
|
1847
|
+
console.log(`Booked! Confirmation: ${booking.confirmationCode}`);
|
|
1848
|
+
```
|
|
1849
|
+
|
|
1850
|
+
**Best Practices Applied:**
|
|
1851
|
+
|
|
1852
|
+
- Rate limit aware retry with exponential backoff
|
|
1853
|
+
- Aggressive caching for expensive search operations
|
|
1854
|
+
- Extended timeout for booking operations
|
|
1855
|
+
- Request validation
|
|
1856
|
+
|
|
219
1857
|
---
|
|
220
1858
|
|
|
221
|
-
|
|
1859
|
+
### 11. Domain-Specific Guidance
|
|
222
1860
|
|
|
223
|
-
|
|
1861
|
+
#### 11.1 High-Performance Caching
|
|
224
1862
|
|
|
225
|
-
|
|
1863
|
+
For high-traffic APIs, use aggressive caching and store-based persistence:
|
|
226
1864
|
|
|
227
1865
|
```typescript
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
1866
|
+
import { IgniterCaller } from "@igniter-js/caller";
|
|
1867
|
+
import { MockCallerStoreAdapter } from "@igniter-js/caller/adapters";
|
|
1868
|
+
|
|
1869
|
+
// Use mock store for development, switch to Redis in production
|
|
1870
|
+
const store =
|
|
1871
|
+
process.env.NODE_ENV === "production"
|
|
1872
|
+
? createRedisStore()
|
|
1873
|
+
: MockCallerStoreAdapter.create();
|
|
1874
|
+
|
|
1875
|
+
const api = IgniterCaller.create()
|
|
1876
|
+
.withBaseUrl("https://high-traffic-api.example.com")
|
|
1877
|
+
.withStore(store, {
|
|
1878
|
+
ttl: 3600, // 1 hour
|
|
1879
|
+
keyPrefix: "api-cache:",
|
|
1880
|
+
})
|
|
1881
|
+
.build();
|
|
1882
|
+
|
|
1883
|
+
// Cache for 10 minutes (shorter than TTL for fresh data)
|
|
1884
|
+
const result = await api.get("/popular-items").stale(600_000).execute();
|
|
232
1885
|
```
|
|
233
1886
|
|
|
234
|
-
|
|
1887
|
+
**Recommendations:**
|
|
235
1888
|
|
|
236
|
-
|
|
1889
|
+
- Set stale time < store TTL for cache revalidation
|
|
1890
|
+
- Use Redis for production (shared cache across instances)
|
|
1891
|
+
- Use pattern-based invalidation for batch updates
|
|
237
1892
|
|
|
238
|
-
|
|
1893
|
+
---
|
|
1894
|
+
|
|
1895
|
+
#### 11.2 Schema-First Development
|
|
1896
|
+
|
|
1897
|
+
For type-safe API clients, define schemas first, then generate callers:
|
|
1898
|
+
|
|
1899
|
+
```typescript
|
|
1900
|
+
import { IgniterCaller, IgniterCallerSchema } from '@igniter-js/caller'
|
|
1901
|
+
import { z } from 'zod'
|
|
1902
|
+
|
|
1903
|
+
// Define reusable schemas
|
|
1904
|
+
const UserSchema = z.object({
|
|
1905
|
+
id: z.string(),
|
|
1906
|
+
name: z.string(),
|
|
1907
|
+
email: z.string().email(),
|
|
1908
|
+
})
|
|
1909
|
+
|
|
1910
|
+
const ErrorSchema = z.object({
|
|
1911
|
+
message: z.string(),
|
|
1912
|
+
code: z.string(),
|
|
1913
|
+
})
|
|
1914
|
+
|
|
1915
|
+
// Build schema registry with path-first API
|
|
1916
|
+
const apiSchemas = IgniterCallerSchema.create()
|
|
1917
|
+
.schema('User', UserSchema)
|
|
1918
|
+
.schema('Error', ErrorSchema)
|
|
1919
|
+
.path('/users', (path) =>
|
|
1920
|
+
path.get({
|
|
1921
|
+
responses: {
|
|
1922
|
+
200: path.ref('User').array(),
|
|
1923
|
+
401: path.ref('Error').schema,
|
|
1924
|
+
},
|
|
1925
|
+
})
|
|
1926
|
+
.post({
|
|
1927
|
+
request: z.object({
|
|
1928
|
+
name: z.string(),
|
|
1929
|
+
email: z.string().email(),
|
|
1930
|
+
}),
|
|
1931
|
+
responses: {
|
|
1932
|
+
201: path.ref('User').schema,
|
|
1933
|
+
400: path.ref('Error').schema,
|
|
1934
|
+
},
|
|
1935
|
+
})
|
|
1936
|
+
)
|
|
1937
|
+
.path('/users/:id', (path) =>
|
|
1938
|
+
path.get({
|
|
1939
|
+
responses: {
|
|
1940
|
+
200: path.ref('User').schema,
|
|
1941
|
+
404: path.ref('Error').schema,
|
|
1942
|
+
},
|
|
1943
|
+
})
|
|
1944
|
+
.delete({
|
|
1945
|
+
responses: {
|
|
1946
|
+
204: z.void(),
|
|
1947
|
+
404: path.ref('Error').schema,
|
|
1948
|
+
},
|
|
1949
|
+
})
|
|
1950
|
+
)
|
|
1951
|
+
.build()
|
|
1952
|
+
|
|
1953
|
+
// Create typed API client
|
|
1954
|
+
const api = IgniterCaller.create()
|
|
1955
|
+
.withBaseUrl('https://api.example.com')
|
|
1956
|
+
.withSchemas(apiSchemas, { mode: 'strict' })
|
|
1957
|
+
.build()
|
|
1958
|
+
|
|
1959
|
+
// Type inference works automatically
|
|
1960
|
+
type UsersResponse = Awaited<ReturnType<typeof api.get('/users').execute>>
|
|
1961
|
+
|
|
1962
|
+
async function main() {
|
|
1963
|
+
// All calls are fully typed
|
|
1964
|
+
const usersResult = await api.get('/users').execute()
|
|
1965
|
+
const users = usersResult.data // User[] | undefined
|
|
1966
|
+
|
|
1967
|
+
const userResult = await api.get('/users/123')
|
|
1968
|
+
.params({ id: '123' }) // params are typed from path pattern
|
|
1969
|
+
.execute()
|
|
1970
|
+
const user = userResult.data // User | undefined
|
|
1971
|
+
|
|
1972
|
+
const createResult = await api.post('/users')
|
|
1973
|
+
.body({ name: 'John', email: 'john@example.com' }) // body is typed
|
|
1974
|
+
.execute()
|
|
1975
|
+
const created = createResult.data // User | undefined
|
|
1976
|
+
}
|
|
1977
|
+
```
|
|
1978
|
+
|
|
1979
|
+
---
|
|
1980
|
+
|
|
1981
|
+
#### 11.3 Error Boundary Integration
|
|
1982
|
+
|
|
1983
|
+
Integrate with React error boundaries:
|
|
1984
|
+
|
|
1985
|
+
```typescript
|
|
1986
|
+
import { useQuery } from "@tanstack/react-query";
|
|
1987
|
+
import { IgniterCaller } from "@igniter-js/caller";
|
|
1988
|
+
|
|
1989
|
+
const api = IgniterCaller.create()
|
|
1990
|
+
.withBaseUrl("https://api.example.com")
|
|
1991
|
+
.build();
|
|
1992
|
+
|
|
1993
|
+
function useUsers() {
|
|
1994
|
+
return useQuery({
|
|
1995
|
+
queryKey: ["users"],
|
|
1996
|
+
queryFn: async () => {
|
|
1997
|
+
const result = await api.get("/users").execute();
|
|
1998
|
+
if (result.error) throw result.error;
|
|
1999
|
+
return result.data;
|
|
2000
|
+
},
|
|
2001
|
+
retry: (failureCount, error) => {
|
|
2002
|
+
// Only retry on transient errors
|
|
2003
|
+
const isTransient =
|
|
2004
|
+
error instanceof IgniterCallerError &&
|
|
2005
|
+
[408, 429, 500, 502, 503, 504].includes(error.statusCode || 0);
|
|
2006
|
+
|
|
2007
|
+
return isTransient && failureCount < 3;
|
|
2008
|
+
},
|
|
2009
|
+
});
|
|
2010
|
+
}
|
|
2011
|
+
```
|
|
2012
|
+
|
|
2013
|
+
---
|
|
2014
|
+
|
|
2015
|
+
### 12. Best Practices & Anti-Patterns
|
|
2016
|
+
|
|
2017
|
+
#### 12.1 Builder Configuration
|
|
2018
|
+
|
|
2019
|
+
| Practice | Why | Example |
|
|
2020
|
+
| -------------------------------------- | --------------------------------------------------- | ---------------------------------------------------- |
|
|
2021
|
+
| ✅ Chain builder methods | Fluent API improves readability | `IgniterCaller.create().withBaseUrl('...').build()` |
|
|
2022
|
+
| ✅ Use immutable builder pattern | Prevents accidental mutation | Each `with*` returns new instance |
|
|
2023
|
+
| ✅ Configure logging early | Captures all initialization errors | `.withLogger(logger)` before `.build()` |
|
|
2024
|
+
| ❌ Don't reuse builder instances | Builder state persists, causing unexpected behavior | Create new builder or manager for each configuration |
|
|
2025
|
+
| ❌ Don't call `build()` multiple times | Each call creates a new manager instance | Store manager in a variable |
|
|
2026
|
+
|
|
2027
|
+
#### 12.2 Request Execution
|
|
2028
|
+
|
|
2029
|
+
| Practice | Why | Example |
|
|
2030
|
+
| -------------------------------------------------- | -------------------------------- | ------------------------------------------- |
|
|
2031
|
+
| ✅ Check `result.error` | Result is always returned | `if (result.error) throw result.error` |
|
|
2032
|
+
| ✅ Use `stale()` for caching | Simple cache strategy | `.stale(60_000)` for 1-minute cache |
|
|
2033
|
+
| ✅ Set reasonable timeouts | Prevents hanging requests | `.timeout(30_000)` for 30-second limit |
|
|
2034
|
+
| ❌ Don't ignore errors | Errors indicate problems | Always handle or re-throw |
|
|
2035
|
+
| ❌ Don't use short stale times with network errors | Causes repeated failed requests | Increase stale time or fix underlying error |
|
|
2036
|
+
| ❌ Don't retry on client errors (4xx) | Only server errors are transient | Exclude 4xx from `retryOnStatus` |
|
|
2037
|
+
|
|
2038
|
+
#### 12.3 Schema Validation
|
|
2039
|
+
|
|
2040
|
+
| Practice | Why | Example |
|
|
2041
|
+
| -------------------------------- | ---------------------------------- | ----------------------------------------- |
|
|
2042
|
+
| ✅ Define schemas first | Enables full type inference | Use `IgniterCallerSchema.create()` |
|
|
2043
|
+
| ✅ Use relative paths in schemas | Allows base URL configuration | `'/users'` not `'https://api.test/users'` |
|
|
2044
|
+
| ✅ Use `as const` assertion | Enables literal type inference | `schemas as const` |
|
|
2045
|
+
| ❌ Don't mix Zod versions | Incompatible APIs can cause issues | Use Zod v4+ only |
|
|
2046
|
+
| ❌ Don't use loose validation | Type safety is lost | Use `mode: 'strict'` for production |
|
|
2047
|
+
|
|
2048
|
+
#### 12.4 Cache Management
|
|
2049
|
+
|
|
2050
|
+
| Practice | Why | Example |
|
|
2051
|
+
| --------------------------------------- | ---------------------------------- | ------------------------------------------- |
|
|
2052
|
+
| ✅ Invalidate after mutations | Ensures fresh data | `IgniterCallerManager.invalidate('/users')` |
|
|
2053
|
+
| ✅ Use appropriate stale times | Balances freshness and performance | `.stale(300_000)` for rarely-changing data |
|
|
2054
|
+
| ❌ Don't cache sensitive data | Security risk | Avoid caching personal information |
|
|
2055
|
+
| ❌ Don't rely solely on in-memory cache | Lost on restart | Use store adapter for persistence |
|
|
2056
|
+
|
|
2057
|
+
---
|
|
2058
|
+
|
|
2059
|
+
### 13. Exhaustive Error & Troubleshooting Library
|
|
2060
|
+
|
|
2061
|
+
This section provides detailed information for every error code emitted by `@igniter-js/caller`.
|
|
2062
|
+
|
|
2063
|
+
#### IGNITER_CALLER_HTTP_ERROR
|
|
2064
|
+
|
|
2065
|
+
- **Context:** Returned when the HTTP response status indicates an error (4xx or 5xx).
|
|
2066
|
+
- **Cause:** The server returned an error status code. This can be due to invalid request data, authentication issues, or server errors.
|
|
2067
|
+
- **Mitigation:**
|
|
2068
|
+
- Check the request body and parameters for correctness.
|
|
2069
|
+
- Verify authentication credentials.
|
|
2070
|
+
- Review the server's API documentation.
|
|
2071
|
+
- **Solution:**
|
|
2072
|
+
|
|
2073
|
+
```typescript
|
|
2074
|
+
const result = await api.get("/users").execute();
|
|
2075
|
+
|
|
2076
|
+
if (result.error) {
|
|
2077
|
+
if (
|
|
2078
|
+
IgniterCallerError.is(result.error) &&
|
|
2079
|
+
result.error.code === "IGNITER_CALLER_HTTP_ERROR"
|
|
2080
|
+
) {
|
|
2081
|
+
console.error(
|
|
2082
|
+
`HTTP ${result.error.statusCode}: ${result.error.statusText}`,
|
|
2083
|
+
);
|
|
2084
|
+
// Handle specific status codes
|
|
2085
|
+
if (result.error.statusCode === 401) {
|
|
2086
|
+
// Redirect to login
|
|
2087
|
+
} else if (result.error.statusCode === 404) {
|
|
2088
|
+
// Show not found message
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
2091
|
+
throw result.error;
|
|
2092
|
+
}
|
|
2093
|
+
```
|
|
2094
|
+
|
|
2095
|
+
---
|
|
2096
|
+
|
|
2097
|
+
#### IGNITER_CALLER_TIMEOUT
|
|
2098
|
+
|
|
2099
|
+
- **Context:** Returned when the request exceeds the configured timeout duration.
|
|
2100
|
+
- **Cause:** The server did not respond within the timeout period. This can be due to network issues, slow server response, or a hung request.
|
|
2101
|
+
- **Mitigation:**
|
|
2102
|
+
- Increase the timeout for long-running operations.
|
|
2103
|
+
- Check network connectivity.
|
|
2104
|
+
- Monitor server performance.
|
|
2105
|
+
- **Solution:**
|
|
2106
|
+
|
|
2107
|
+
```typescript
|
|
2108
|
+
const result = await api
|
|
2109
|
+
.get("/slow-endpoint")
|
|
2110
|
+
.timeout(60_000) // Increase timeout to 60 seconds
|
|
2111
|
+
.execute();
|
|
2112
|
+
|
|
2113
|
+
if (result.error?.code === "IGNITER_CALLER_TIMEOUT") {
|
|
2114
|
+
console.error("Request timed out. Please try again.");
|
|
2115
|
+
// Implement retry logic or user notification
|
|
2116
|
+
}
|
|
2117
|
+
```
|
|
2118
|
+
|
|
2119
|
+
---
|
|
2120
|
+
|
|
2121
|
+
#### IGNITER_CALLER_REQUEST_VALIDATION_FAILED
|
|
2122
|
+
|
|
2123
|
+
- **Context:** Returned when the request body fails schema validation in strict mode.
|
|
2124
|
+
- **Cause:** The request body does not match the defined schema. This can be due to missing fields, incorrect types, or validation constraint violations.
|
|
2125
|
+
- **Mitigation:**
|
|
2126
|
+
- Review the request body against the schema.
|
|
2127
|
+
- Ensure all required fields are present.
|
|
2128
|
+
- Check field types and formats.
|
|
2129
|
+
- **Solution:**
|
|
2130
|
+
|
|
2131
|
+
```typescript
|
|
2132
|
+
const result = await api
|
|
2133
|
+
.post("/users")
|
|
2134
|
+
.body({ name: "John" }) // Missing email field
|
|
2135
|
+
.execute();
|
|
2136
|
+
|
|
2137
|
+
if (result.error?.code === "IGNITER_CALLER_REQUEST_VALIDATION_FAILED") {
|
|
2138
|
+
console.error("Request validation failed:", result.error.details);
|
|
2139
|
+
// Show validation errors to user
|
|
2140
|
+
if (result.error.details) {
|
|
2141
|
+
for (const issue of result.error.details as any[]) {
|
|
2142
|
+
console.error("- " + issue.message);
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
```
|
|
2147
|
+
|
|
2148
|
+
---
|
|
2149
|
+
|
|
2150
|
+
#### IGNITER_CALLER_RESPONSE_VALIDATION_FAILED
|
|
2151
|
+
|
|
2152
|
+
- **Context:** Returned when the response data fails schema validation in strict mode.
|
|
2153
|
+
- **Cause:** The server response does not match the defined schema. This can be due to API contract changes, missing fields, or incorrect types.
|
|
2154
|
+
- **Mitigation:**
|
|
2155
|
+
- Review the API documentation.
|
|
2156
|
+
- Update the schema to match the current API response.
|
|
2157
|
+
- Contact API provider if the contract has changed.
|
|
2158
|
+
- **Solution:**
|
|
239
2159
|
|
|
240
2160
|
```typescript
|
|
241
|
-
|
|
2161
|
+
const result = await api.get("/users").execute();
|
|
2162
|
+
|
|
2163
|
+
if (result.error?.code === "IGNITER_CALLER_RESPONSE_VALIDATION_FAILED") {
|
|
2164
|
+
console.error("Response validation failed:", result.error.details);
|
|
2165
|
+
// Log unexpected response for debugging
|
|
2166
|
+
console.error("Unexpected response:", JSON.stringify(result.error.cause));
|
|
2167
|
+
// Consider updating schema or using mode: 'soft' for migration
|
|
2168
|
+
}
|
|
242
2169
|
```
|
|
243
2170
|
|
|
244
|
-
|
|
2171
|
+
---
|
|
2172
|
+
|
|
2173
|
+
#### IGNITER_CALLER_SCHEMA_DUPLICATE
|
|
2174
|
+
|
|
2175
|
+
- **Context:** Thrown when trying to register a duplicate schema key or duplicate method on a path.
|
|
2176
|
+
- **Cause:** Attempted to register the same registry key twice or define the same method on a path twice.
|
|
2177
|
+
- **Mitigation:**
|
|
2178
|
+
- Use unique keys for schema registry.
|
|
2179
|
+
- Ensure each method is only defined once per path.
|
|
2180
|
+
- **Solution:**
|
|
2181
|
+
|
|
2182
|
+
```typescript
|
|
2183
|
+
try {
|
|
2184
|
+
const schemas = IgniterCallerSchema.create()
|
|
2185
|
+
.schema("User", UserSchema)
|
|
2186
|
+
.schema("User", UserSchema) // Duplicate key!
|
|
2187
|
+
.build();
|
|
2188
|
+
} catch (error) {
|
|
2189
|
+
if (
|
|
2190
|
+
IgniterCallerError.is(error) &&
|
|
2191
|
+
error.code === "IGNITER_CALLER_SCHEMA_DUPLICATE"
|
|
2192
|
+
) {
|
|
2193
|
+
console.error("Schema key already exists:", error.metadata?.key);
|
|
2194
|
+
// Use a different key or remove duplicate registration
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2197
|
+
```
|
|
2198
|
+
|
|
2199
|
+
---
|
|
2200
|
+
|
|
2201
|
+
#### IGNITER_CALLER_SCHEMA_INVALID
|
|
2202
|
+
|
|
2203
|
+
- **Context:** Thrown when schema path or key is invalid.
|
|
2204
|
+
- **Cause:**
|
|
2205
|
+
- Schema path is empty.
|
|
2206
|
+
- Schema path does not start with `/`.
|
|
2207
|
+
- Schema registry key is empty.
|
|
2208
|
+
- **Mitigation:**
|
|
2209
|
+
- Ensure paths start with `/`.
|
|
2210
|
+
- Ensure registry keys are non-empty strings.
|
|
2211
|
+
- **Solution:**
|
|
2212
|
+
|
|
2213
|
+
```typescript
|
|
2214
|
+
try {
|
|
2215
|
+
const schemas = IgniterCallerSchema.create()
|
|
2216
|
+
.path(
|
|
2217
|
+
"users",
|
|
2218
|
+
(
|
|
2219
|
+
path, // Missing leading slash!
|
|
2220
|
+
) => path.get({ responses: { 200: UserSchema } }),
|
|
2221
|
+
)
|
|
2222
|
+
.build();
|
|
2223
|
+
} catch (error) {
|
|
2224
|
+
if (
|
|
2225
|
+
IgniterCallerError.is(error) &&
|
|
2226
|
+
error.code === "IGNITER_CALLER_SCHEMA_INVALID"
|
|
2227
|
+
) {
|
|
2228
|
+
console.error("Invalid schema path:", error.message);
|
|
2229
|
+
// Fix path to '/users'
|
|
2230
|
+
}
|
|
2231
|
+
}
|
|
2232
|
+
```
|
|
2233
|
+
|
|
2234
|
+
---
|
|
245
2235
|
|
|
246
|
-
|
|
2236
|
+
#### IGNITER_CALLER_UNKNOWN_ERROR
|
|
247
2237
|
|
|
248
|
-
|
|
2238
|
+
- **Context:** Returned when an unexpected error occurs during request execution.
|
|
2239
|
+
- **Cause:**
|
|
2240
|
+
- Network failure (DNS resolution, connection refused).
|
|
2241
|
+
- Runtime error in request builder.
|
|
2242
|
+
- AbortController cancellation without timeout.
|
|
2243
|
+
- **Mitigation:**
|
|
2244
|
+
- Check network connectivity.
|
|
2245
|
+
- Verify the URL is correct.
|
|
2246
|
+
- Review error stack trace for debugging.
|
|
2247
|
+
- **Solution:**
|
|
249
2248
|
|
|
250
2249
|
```typescript
|
|
251
|
-
const
|
|
2250
|
+
const result = await api.get("/users").execute();
|
|
2251
|
+
|
|
2252
|
+
if (result.error?.code === "IGNITER_CALLER_UNKNOWN_ERROR") {
|
|
2253
|
+
console.error("Unexpected error:", result.error.message);
|
|
2254
|
+
console.error("Cause:", result.error.cause);
|
|
2255
|
+
|
|
2256
|
+
// Handle network errors
|
|
2257
|
+
if (result.error.cause instanceof TypeError) {
|
|
2258
|
+
console.error("Network error. Check your connection.");
|
|
2259
|
+
}
|
|
2260
|
+
|
|
2261
|
+
// Throw for upstream error handling
|
|
2262
|
+
throw result.error;
|
|
2263
|
+
}
|
|
252
2264
|
```
|
|
253
2265
|
|
|
254
|
-
|
|
2266
|
+
---
|
|
2267
|
+
|
|
2268
|
+
## III. TECHNICAL REFERENCE & RESILIENCE
|
|
2269
|
+
|
|
2270
|
+
### 14. Exhaustive API Reference
|
|
2271
|
+
|
|
2272
|
+
#### 14.1 Classes
|
|
2273
|
+
|
|
2274
|
+
| Class | Purpose | Location |
|
|
2275
|
+
| ----------------------------------------------------- | ----------------------------------- | ------------------------------------- |
|
|
2276
|
+
| `IgniterCallerBuilder<TSchemas>` | Package initializer with fluent API | `src/builders/main.builder.ts` |
|
|
2277
|
+
| `IgniterCallerManager<TSchemas>` | HTTP client runtime | `src/core/manager.ts` |
|
|
2278
|
+
| `IgniterCallerRequestBuilder<TResponse>` | Per-request configuration | `src/builders/request.builder.ts` |
|
|
2279
|
+
| `IgniterCallerSchema<TSchemas, TRegistry>` | Schema registry builder | `src/builders/schema.builder.ts` |
|
|
2280
|
+
| `IgniterCallerSchemaPathBuilder<TMethods, TRegistry>` | Path + methods builder | `src/builders/schema-path.builder.ts` |
|
|
2281
|
+
| `IgniterCallerEvents` | Global event emitter | `src/core/events.ts` |
|
|
2282
|
+
| `IgniterCallerError` | Typed error class | `src/errors/caller.error.ts` |
|
|
2283
|
+
| `IgniterCallerCacheUtils` | Cache utilities | `src/utils/cache.ts` |
|
|
2284
|
+
| `IgniterCallerBodyUtils` | Body normalization | `src/utils/body.ts` |
|
|
2285
|
+
| `IgniterCallerSchemaUtils` | Schema validation | `src/utils/schema.ts` |
|
|
2286
|
+
| `IgniterCallerUrlUtils` | URL construction | `src/utils/url.ts` |
|
|
2287
|
+
| `MockCallerStoreAdapter` | Mock store for testing | `src/adapters/mock.adapter.ts` |
|
|
255
2288
|
|
|
256
2289
|
---
|
|
257
2290
|
|
|
258
|
-
|
|
2291
|
+
#### 14.2 IgniterCallerBuilder Methods
|
|
2292
|
+
|
|
2293
|
+
| Method | Signature | Description |
|
|
2294
|
+
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------ |
|
|
2295
|
+
| `create` | `static create(): IgniterCallerBuilder<{}>` | Creates a new builder instance |
|
|
2296
|
+
| `withBaseUrl` | `withBaseUrl(baseURL: string): IgniterCallerBuilder<TSchemas>` | Sets base URL for all requests |
|
|
2297
|
+
| `withHeaders` | `withHeaders(headers: Record<string, string>): IgniterCallerBuilder<TSchemas>` | Sets default headers |
|
|
2298
|
+
| `withCookies" | `withCookies(cookies: Record<string, string>): IgniterCallerBuilder<TSchemas>` | Sets default cookies |
|
|
2299
|
+
| `withLogger` | `withLogger(logger: IgniterLogger): IgniterCallerBuilder<TSchemas>` | Attaches logger instance |
|
|
2300
|
+
| `withRequestInterceptor` | `withRequestInterceptor(interceptor: IgniterCallerRequestInterceptor): IgniterCallerBuilder<TSchemas>` | Adds request interceptor |
|
|
2301
|
+
| `withResponseInterceptor` | `withResponseInterceptor(interceptor: IgniterCallerResponseInterceptor): IgniterCallerBuilder<TSchemas>` | Adds response interceptor |
|
|
2302
|
+
| `withStore` | `withStore(store: IgniterCallerStoreAdapter, options?: IgniterCallerStoreOptions): IgniterCallerBuilder<TSchemas>` | Configures persistent store |
|
|
2303
|
+
| `withSchemas` | `withSchemas<TNewSchemas>(schemas: TNewSchemas, validation?: IgniterCallerSchemaValidationOptions): IgniterCallerBuilder<IgniterCallerSchemaMapFrom<TNewSchemas>>` | Configures schema validation |
|
|
2304
|
+
| `withTelemetry` | `withTelemetry(telemetry: IgniterTelemetryManager): IgniterCallerBuilder<TSchemas>` | Attaches telemetry manager |
|
|
2305
|
+
| `build` | `build(): IgniterCallerManager<TSchemas>` | Builds the manager instance |
|
|
2306
|
+
|
|
2307
|
+
---
|
|
2308
|
+
|
|
2309
|
+
#### 14.3 IgniterCallerManager Methods
|
|
2310
|
+
|
|
2311
|
+
| Method | Signature | Description |
|
|
2312
|
+
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- |
|
|
2313
|
+
| `get` | `get<TPath extends GetPaths<TSchemas>>(url: TPath): TypedRequestBuilder<TSchemas, TPath, 'GET'>` | Creates a GET request |
|
|
2314
|
+
| `post` | `post<TPath extends PostPaths<TSchemas>>(url: TPath): TypedRequestBuilder<TSchemas, TPath, 'POST'>` | Creates a POST request |
|
|
2315
|
+
| `put` | `put<TPath extends PutPaths<TSchemas>>(url: TPath): TypedRequestBuilder<TSchemas, TPath, 'PUT'>` | Creates a PUT request |
|
|
2316
|
+
| `patch` | `patch<TPath extends PatchPaths<TSchemas>>(url: TPath): TypedRequestBuilder<TSchemas, TPath, 'PATCH'>` | Creates a PATCH request |
|
|
2317
|
+
| `delete` | `delete<TPath extends DeletePaths<TSchemas>>(url: TPath): TypedRequestBuilder<TSchemas, TPath, 'DELETE'>` | Creates a DELETE request |
|
|
2318
|
+
| `head` | `head<TPath extends HeadPaths<TSchemas>>(url: TPath): TypedRequestBuilder<TSchemas, TPath, 'HEAD'>` | Creates a HEAD request |
|
|
2319
|
+
| `request` | `async request<T = unknown>(options: IgniterCallerDirectRequestOptions): Promise<IgniterCallerApiResponse<T>>` | Executes request directly (axios-style) |
|
|
2320
|
+
| `batch` | `static async batch<T extends readonly Promise<IgniterCallerApiResponse<any>>[]>(requests: [...T]): Promise<{ [K in keyof T]: T[K] extends Promise<infer R> ? R : never }>` | Executes requests in parallel |
|
|
2321
|
+
| `on` | `static on(pattern: IgniterCallerUrlPattern, callback: IgniterCallerEventCallback): () => void` | Registers global event listener |
|
|
2322
|
+
| `off` | `static off(pattern: IgniterCallerUrlPattern, callback?: IgniterCallerEventCallback): void` | Removes event listener |
|
|
2323
|
+
| `invalidate` | `static async invalidate(key: string): Promise<void>` | Invalidates specific cache entry |
|
|
2324
|
+
| `invalidatePattern` | `static async invalidatePattern(pattern: string): Promise<void>` | Invalidates cache by pattern |
|
|
2325
|
+
|
|
2326
|
+
---
|
|
2327
|
+
|
|
2328
|
+
#### 14.4 IgniterCallerRequestBuilder Methods
|
|
2329
|
+
|
|
2330
|
+
| Method | Signature | Description |
|
|
2331
|
+
| -------------- | ---------------------------------------------------------------------------------------------- | ----------------------------------------------------- |
|
|
2332
|
+
| `url` | `url(url: string): this` | Sets the request URL |
|
|
2333
|
+
| `body` | `body<TBody>(body: TBody): this` | Sets the request body |
|
|
2334
|
+
| `params` | `params(params: Record<string, string \| number \| boolean>): this` | Sets query parameters |
|
|
2335
|
+
| `headers` | `headers(headers: Record<string, string>): this` | Merges additional headers |
|
|
2336
|
+
| `timeout` | `timeout(timeout: number): this` | Sets request timeout in milliseconds |
|
|
2337
|
+
| `cache` | `cache(cache: RequestCache, key?: string): this` | Sets cache strategy and optional key |
|
|
2338
|
+
| `retry` | `retry(maxAttempts: number, options?: Omit<IgniterCallerRetryOptions, 'maxAttempts'>): this` | Configures retry behavior |
|
|
2339
|
+
| `fallback` | `fallback<T>(fn: () => T): this` | Provides fallback value on failure |
|
|
2340
|
+
| `stale` | `stale(milliseconds: number): this` | Sets cache stale time in milliseconds |
|
|
2341
|
+
| `responseType` | `responseType<T>(schema?: z.ZodSchema<T> \| StandardSchemaV1): IgniterCallerRequestBuilder<T>` | Sets expected response type for typing and validation |
|
|
2342
|
+
| `execute` | `async execute(): Promise<IgniterCallerApiResponse<TResponse>>` | Executes the HTTP request |
|
|
2343
|
+
| `getFile` | `getFile(url: string): { execute: () => Promise<IgniterCallerFileResponse> }` | Downloads a file (deprecated) |
|
|
2344
|
+
|
|
2345
|
+
---
|
|
2346
|
+
|
|
2347
|
+
#### 14.5 IgniterCallerSchema Methods
|
|
2348
|
+
|
|
2349
|
+
| Method | Signature | Description |
|
|
2350
|
+
| -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- |
|
|
2351
|
+
| `create` | `static create(): IgniterCallerSchema<{}, {}>` | Creates a new empty schema builder |
|
|
2352
|
+
| `schema` | `schema<TKey extends string, TSchema extends StandardSchemaV1>(key: TKey, schema: TSchema): IgniterCallerSchema<TSchemas, TRegistry & { [K in TKey]: TSchema }>` | Registers a reusable schema |
|
|
2353
|
+
| `path` | `path<TPath extends string, TMethods extends Partial<Record<IgniterCallerSchemaMethod, IgniterCallerEndpointSchema<any, any>>>>(path: TPath, builder: (pathBuilder: IgniterCallerSchemaPathBuilder<{}, TRegistry>) => IgniterCallerSchemaPathBuilder<TMethods, TRegistry>): IgniterCallerSchema<TSchemas & { [K in TPath]: TMethods }, TRegistry>` | Defines a path with methods |
|
|
2354
|
+
| `build` | `build(): IgniterCallerSchemaBuildResult<TSchemas, TRegistry>` | Builds schema map and attaches helpers |
|
|
2355
|
+
|
|
2356
|
+
---
|
|
2357
|
+
|
|
2358
|
+
#### 14.6 IgniterCallerSchemaPathBuilder Methods
|
|
2359
|
+
|
|
2360
|
+
| Method | Signature | Description |
|
|
2361
|
+
| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------- |
|
|
2362
|
+
| `create` | `static create<TRegistry extends IgniterCallerSchemaRegistry>(registry: TRegistry): IgniterCallerSchemaPathBuilder<{}, TRegistry>` | Creates a new path builder |
|
|
2363
|
+
| `ref` | `ref<TKey extends keyof TRegistry>(key: TKey): { schema: TRegistry[TKey]; array(): SchemaArray<TRegistry[TKey]>; nullable(): SchemaNullable<TRegistry[TKey]>; optional(): SchemaOptional<TRegistry[TKey]>; record(keyType?: StandardSchemaV1): SchemaRecord<TRegistry[TKey]> }` | Returns registry reference helper |
|
|
2364
|
+
| `get` | `get<TRequest, TResponses>(config: IgniterCallerSchemaEndpointConfig<TRequest, TResponses>): IgniterCallerSchemaPathBuilder<TMethods & { GET: IgniterCallerEndpointSchema<TRequest, TResponses> }, TRegistry>` | Defines a GET endpoint |
|
|
2365
|
+
| `post` | `post<TRequest, TResponses>(config: IgniterCallerSchemaEndpointConfig<TRequest, TResponses>): IgniterCallerSchemaPathBuilder<TMethods & { POST: IgniterCallerEndpointSchema<TRequest, TResponses> }, TRegistry>` | Defines a POST endpoint |
|
|
2366
|
+
| `put` | `put<TRequest, TResponses>(config: IgniterCallerSchemaEndpointConfig<TRequest, TResponses>): IgniterCallerSchemaPathBuilder<TMethods & { PUT: IgniterCallerEndpointSchema<TRequest, TResponses> }, TRegistry>` | Defines a PUT endpoint |
|
|
2367
|
+
| `patch` | `patch<TRequest, TResponses>(config: IgniterCallerSchemaEndpointConfig<TRequest, TResponses>): IgniterCallerSchemaPathBuilder<TMethods & { PATCH: IgniterCallerEndpointSchema<TRequest, TResponses> }, TRegistry>` | Defines a PATCH endpoint |
|
|
2368
|
+
| `delete` | `delete<TRequest, TResponses>(config: IgniterCallerSchemaEndpointConfig<TRequest, TResponses>): IgniterCallerSchemaPathBuilder<TMethods & { DELETE: IgniterCallerEndpointSchema<TRequest, TResponses> }, TRegistry>` | Defines a DELETE endpoint |
|
|
2369
|
+
| `head` | `head<TRequest, TResponses>(config: IgniterCallerSchemaEndpointConfig<TRequest, TResponses>): IgniterCallerSchemaPathBuilder<TMethods & { HEAD: IgniterCallerEndpointSchema<TRequest, TResponses> }, TRegistry>` | Defines a HEAD endpoint |
|
|
2370
|
+
| `build` | `build(): TMethods` | Builds the method map for the path |
|
|
2371
|
+
|
|
2372
|
+
---
|
|
2373
|
+
|
|
2374
|
+
#### 14.7 IgniterCallerCacheUtils Static Methods
|
|
2375
|
+
|
|
2376
|
+
| Method | Signature | Description |
|
|
2377
|
+
| -------------- | ---------------------------------------------------------------------------------------------- | ----------------------------------- |
|
|
2378
|
+
| `setStore` | `static setStore(store: IgniterCallerStoreAdapter, options?: IgniterCallerStoreOptions): void` | Configures persistent store adapter |
|
|
2379
|
+
| `getStore` | `static getStore(): IgniterCallerStoreAdapter \| null` | Gets configured store adapter |
|
|
2380
|
+
| `get` | `static async get<T>(key: string, staleTime?: number): Promise<T \| undefined>` | Gets cached data if not stale |
|
|
2381
|
+
| `set` | `static async set(key: string, data: unknown, ttl?: number): Promise<void>` | Stores data in cache |
|
|
2382
|
+
| `clear` | `static async clear(key: string): Promise<void>` | Clears specific cache entry |
|
|
2383
|
+
| `clearPattern` | `static async clearPattern(pattern: string): Promise<void>` | Clears entries matching pattern |
|
|
2384
|
+
| `clearAll` | `static async clearAll(): Promise<void>` | Clears all in-memory cache |
|
|
2385
|
+
|
|
2386
|
+
---
|
|
2387
|
+
|
|
2388
|
+
### 15. Telemetry & Observability Registry
|
|
2389
|
+
|
|
2390
|
+
#### 15.1 Telemetry Events
|
|
2391
|
+
|
|
2392
|
+
All telemetry events use the namespace `igniter.caller` and follow the `igniter.caller.<group>.<event>` naming convention.
|
|
2393
|
+
|
|
2394
|
+
| Event Key | Group | Description | Attributes |
|
|
2395
|
+
| --------------------------- | ---------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
2396
|
+
| `request.execute.started` | request | Emitted when a request starts | `ctx.request.method`, `ctx.request.url`, `ctx.request.baseUrl`, `ctx.request.timeoutMs` |
|
|
2397
|
+
| `request.execute.success` | request | Emitted when a request succeeds | `ctx.request.method`, `ctx.request.url`, `ctx.request.durationMs`, `ctx.response.status`, `ctx.response.contentType`, `ctx.cache.hit`, `ctx.request.fallback` |
|
|
2398
|
+
| `request.execute.error` | request | Emitted when a request fails | `ctx.request.method`, `ctx.request.url`, `ctx.request.durationMs`, `ctx.response.status`, `ctx.error.code`, `ctx.error.message` |
|
|
2399
|
+
| `request.timeout.error` | request | Emitted when a request times out | `ctx.request.method`, `ctx.request.url`, `ctx.request.timeoutMs` |
|
|
2400
|
+
| `cache.read.hit` | cache | Emitted when cache hit occurs | `ctx.request.method`, `ctx.request.url`, `ctx.cache.key`, `ctx.cache.staleTime` |
|
|
2401
|
+
| `retry.attempt.started` | retry | Emitted before each retry attempt | `ctx.request.method`, `ctx.request.url`, `ctx.retry.attempt`, `ctx.retry.maxAttempts`, `ctx.retry.delayMs` |
|
|
2402
|
+
| `validation.request.error` | validation | Emitted when request validation fails | `ctx.request.method`, `ctx.request.url`, `ctx.validation.type`, `ctx.validation.error` |
|
|
2403
|
+
| `validation.response.error` | validation | Emitted when response validation fails | `ctx.request.method`, `ctx.request.url`, `ctx.validation.type`, `ctx.validation.error`, `ctx.response.status` |
|
|
2404
|
+
|
|
2405
|
+
#### 15.2 Attribute Naming Convention
|
|
2406
|
+
|
|
2407
|
+
All telemetry attributes follow the pattern `ctx.<domain>.<attribute>`:
|
|
2408
|
+
|
|
2409
|
+
- `ctx.request.*` - Request-related attributes (method, url, timeout)
|
|
2410
|
+
- `ctx.response.*` - Response-related attributes (status, contentType)
|
|
2411
|
+
- `ctx.cache.*` - Cache-related attributes (key, hit, staleTime)
|
|
2412
|
+
- `ctx.retry.*` - Retry-related attributes (attempt, maxAttempts, delayMs)
|
|
2413
|
+
- `ctx.validation.*` - Validation-related attributes (type, error)
|
|
2414
|
+
- `ctx.error.*` - Error-related attributes (code, message)
|
|
2415
|
+
|
|
2416
|
+
---
|
|
2417
|
+
|
|
2418
|
+
### 16. Type Reference Summary
|
|
2419
|
+
|
|
2420
|
+
#### 16.1 Core Types
|
|
2421
|
+
|
|
2422
|
+
| Type | Description |
|
|
2423
|
+
| ----------------------------- | ------------------------------------------------------------------------------------------------------- |
|
|
2424
|
+
| `IgniterCallerApiResponse<T>` | Response envelope with `data?`, `error?`, `status?`, `headers?` |
|
|
2425
|
+
| `IgniterCallerFileResponse` | File download response with `file: File \| null`, `error: Error \| null` |
|
|
2426
|
+
| `IgniterCallerHttpMethod` | Union: `'GET' \| 'POST' \| 'PUT' \| 'PATCH' \| 'DELETE' \| 'HEAD'` |
|
|
2427
|
+
| `IgniterCallerUrlPattern` | Union: `string \| RegExp` |
|
|
2428
|
+
| `IgniterCallerEventCallback` | Signature: `(result: IgniterApiResponse, context: { url, method, timestamp }) => void \| Promise<void>` |
|
|
2429
|
+
|
|
2430
|
+
#### 16.2 Builder Types
|
|
2431
|
+
|
|
2432
|
+
| Type | Description |
|
|
2433
|
+
| ---------------------------------------------- | ------------------------------------------------------------------------------------- |
|
|
2434
|
+
| `IgniterCallerBuilderState<TSchemas>` | Builder state with baseURL, headers, cookies, interceptors, store, schemas, telemetry |
|
|
2435
|
+
| `IgniterCallerRequestBuilderParams` | Constructor params for request builder |
|
|
2436
|
+
| `IgniterCallerMethodRequestBuilder<TResponse>` | Request builder without internal `_setMethod` and `_setUrl` |
|
|
2437
|
+
| `IgniterCallerTypedRequestBuilder<TResponse>` | Typed request builder for HTTP methods |
|
|
2438
|
+
|
|
2439
|
+
#### 16.3 Schema Types
|
|
2440
|
+
|
|
2441
|
+
| Type | Description |
|
|
2442
|
+
| ----------------------------------------------------- | ------------------------------------------------------------------ |
|
|
2443
|
+
| `IgniterCallerSchemaMap` | Record of path → method → endpoint schema |
|
|
2444
|
+
| `IgniterCallerSchemaMethod` | Union: `'GET' \| 'POST' \| 'PUT' \| 'PATCH' \| 'DELETE' \| 'HEAD'` |
|
|
2445
|
+
| `IgniterCallerEndpointSchema<TRequest, TResponses>` | Endpoint with optional request schema and response schemas |
|
|
2446
|
+
| `IgniterCallerSchemaValidationOptions` | `{ mode?: 'strict' \| 'soft' \| 'off', onValidationError?: ... }` |
|
|
2447
|
+
| `IgniterCallerSchemaRegistry` | Record of reusable schema names to schemas |
|
|
2448
|
+
| `IgniterCallerSchemaBuildResult<TSchemas, TRegistry>` | Built schema map with `$Infer` and `get` helpers |
|
|
2449
|
+
| `IgniterCallerSchemaInfer<TSchemas, TRegistry>` | Type helpers: Path, Endpoint, Request, Response, Responses, Schema |
|
|
2450
|
+
| `IgniterCallerSchemaGetters<TSchemas, TRegistry>` | Runtime helpers: path, endpoint, request, response, schema |
|
|
2451
|
+
|
|
2452
|
+
#### 16.4 Configuration Types
|
|
2453
|
+
|
|
2454
|
+
| Type | Description |
|
|
2455
|
+
| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ |
|
|
2456
|
+
| `IgniterCallerRequestOptions<TBody>` | Request configuration with method, url, body, params, headers, timeout, cache |
|
|
2457
|
+
| `IgniterCallerDirectRequestOptions<TBody>` | Axios-style options with all configurations in one object |
|
|
2458
|
+
| `IgniterCallerRetryOptions` | `{ maxAttempts: number, backoff?: 'linear' \| 'exponential', baseDelay?: number, retryOnStatus?: number[] }` |
|
|
2459
|
+
| `IgniterCallerStoreAdapter<TClient>` | Store adapter interface with `client`, `get`, `set`, `delete`, `has` methods |
|
|
2460
|
+
| `IgniterCallerStoreOptions` | `{ ttl?: number, keyPrefix?: string, fallbackToFetch?: boolean }` |
|
|
2461
|
+
| `IgniterCallerRequestInterceptor` | Signature: `(config: IgniterCallerRequestOptions) => Promise<IgniterCallerRequestOptions> \| IgniterCallerRequestOptions` |
|
|
2462
|
+
| `IgniterCallerResponseInterceptor` | Signature: `<T>(response: IgniterCallerApiResponse<T>) => Promise<IgniterCallerApiResponse<T>> \| IgniterCallerApiResponse<T>` |
|
|
2463
|
+
|
|
2464
|
+
#### 16.5 Content Type Types
|
|
2465
|
+
|
|
2466
|
+
| Type | Description |
|
|
2467
|
+
| ------------------------------------- | --------------------------------------------------------------------------------------------------- |
|
|
2468
|
+
| `IgniterCallerResponseContentType` | `'json' \| 'xml' \| 'csv' \| 'text' \| 'html' \| 'blob' \| 'stream' \| 'arraybuffer' \| 'formdata'` |
|
|
2469
|
+
| `IgniterCallerValidatableContentType` | `'json' \| 'xml' \| 'csv'` |
|
|
2470
|
+
| `IgniterCallerResponseMarker` | `File \| Blob \| ReadableStream \| ArrayBuffer \| FormData` |
|
|
2471
|
+
|
|
2472
|
+
---
|
|
259
2473
|
|
|
260
|
-
|
|
261
|
-
- [ ] `npm test --filter @igniter-js/caller`
|
|
262
|
-
- [ ] `npm run typecheck --filter @igniter-js/caller`
|
|
263
|
-
- [ ] `npm run lint --filter @igniter-js/caller`
|
|
264
|
-
- [ ] README and CHANGELOG are up-to-date
|
|
2474
|
+
## End of Document
|
|
265
2475
|
|
|
266
|
-
|
|
2476
|
+
This AGENTS.md document provides a complete reference for maintaining and using the `@igniter-js/caller` package. For updates or questions, refer to the package repository and contribution guidelines.
|