@igniter-js/caller 0.1.3 → 0.1.4

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