@igniter-js/caller 0.1.2 → 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/dist/index.js CHANGED
@@ -1,112 +1,15 @@
1
1
  'use strict';
2
2
 
3
3
  var core = require('@igniter-js/core');
4
+ var zod = require('zod');
4
5
 
5
- // src/builder/igniter-caller.builder.ts
6
- var IgniterCallerBuilder = class _IgniterCallerBuilder {
7
- constructor(state, factory) {
8
- this.state = state;
9
- this.factory = factory;
10
- }
11
- /**
12
- * Creates a new builder instance.
13
- */
14
- static create(factory) {
15
- return new _IgniterCallerBuilder({}, factory);
16
- }
17
- /** Sets the base URL for all requests. */
18
- withBaseUrl(baseURL) {
19
- return new _IgniterCallerBuilder({ ...this.state, baseURL }, this.factory);
20
- }
21
- /** Merges default headers for all requests. */
22
- withHeaders(headers) {
23
- return new _IgniterCallerBuilder({ ...this.state, headers }, this.factory);
24
- }
25
- /** Sets default cookies (sent as the `Cookie` header). */
26
- withCookies(cookies) {
27
- return new _IgniterCallerBuilder({ ...this.state, cookies }, this.factory);
28
- }
29
- /** Attaches a logger instance. */
30
- withLogger(logger) {
31
- return new _IgniterCallerBuilder({ ...this.state, logger }, this.factory);
32
- }
33
- /** Adds a request interceptor that runs before each request. */
34
- withRequestInterceptor(interceptor) {
35
- const requestInterceptors = [
36
- ...this.state.requestInterceptors || [],
37
- interceptor
38
- ];
39
- return new _IgniterCallerBuilder(
40
- { ...this.state, requestInterceptors },
41
- this.factory
42
- );
43
- }
44
- /** Adds a response interceptor that runs after each request. */
45
- withResponseInterceptor(interceptor) {
46
- const responseInterceptors = [
47
- ...this.state.responseInterceptors || [],
48
- interceptor
49
- ];
50
- return new _IgniterCallerBuilder(
51
- { ...this.state, responseInterceptors },
52
- this.factory
53
- );
54
- }
55
- /**
56
- * Configures a persistent store adapter for caching.
57
- *
58
- * When configured, cache operations will use the store (e.g., Redis)
59
- * instead of in-memory cache, enabling persistent cache across deployments.
60
- */
61
- withStore(store, options) {
62
- return new _IgniterCallerBuilder(
63
- { ...this.state, store, storeOptions: options },
64
- this.factory
65
- );
66
- }
6
+ // src/errors/caller.error.ts
7
+ var IgniterCallerError = class _IgniterCallerError extends core.IgniterError {
67
8
  /**
68
- * Configures schema-based type safety and validation.
9
+ * Creates a new typed caller error.
69
10
  *
70
- * Enables automatic type inference for requests/responses based on
71
- * route and method, with optional runtime validation via Zod.
72
- *
73
- * @example
74
- * ```ts
75
- * const api = IgniterCaller.create()
76
- * .withSchemas({
77
- * '/users': {
78
- * GET: {
79
- * responses: {
80
- * 200: z.array(UserSchema),
81
- * 401: ErrorSchema,
82
- * },
83
- * },
84
- * POST: {
85
- * request: CreateUserSchema,
86
- * responses: {
87
- * 201: UserSchema,
88
- * 400: ValidationErrorSchema,
89
- * },
90
- * },
91
- * },
92
- * })
93
- * .build()
94
- * ```
11
+ * @param payload - Error payload with code, message, and metadata.
95
12
  */
96
- withSchemas(schemas, validation) {
97
- return new _IgniterCallerBuilder(
98
- { ...this.state, schemas, schemaValidation: validation },
99
- this.factory
100
- );
101
- }
102
- /**
103
- * Builds the `IgniterCaller` instance.
104
- */
105
- build() {
106
- return this.factory(this.state);
107
- }
108
- };
109
- var IgniterCallerError = class _IgniterCallerError extends core.IgniterError {
110
13
  constructor(payload) {
111
14
  const metadata = {
112
15
  ...payload.metadata,
@@ -121,13 +24,19 @@ var IgniterCallerError = class _IgniterCallerError extends core.IgniterError {
121
24
  causer: "@igniter-js/caller",
122
25
  details,
123
26
  metadata,
124
- logger: payload.logger
27
+ logger: payload.logger,
28
+ cause: payload.cause
125
29
  });
126
30
  this.name = "IgniterCallerError";
127
31
  this.operation = payload.operation;
128
32
  this.statusText = payload.statusText;
129
33
  this.cause = payload.cause;
130
34
  }
35
+ /**
36
+ * Type guard for `IgniterCallerError`.
37
+ *
38
+ * @param error - Value to check.
39
+ */
131
40
  static is(error) {
132
41
  return error instanceof _IgniterCallerError;
133
42
  }
@@ -137,6 +46,9 @@ var IgniterCallerError = class _IgniterCallerError extends core.IgniterError {
137
46
  var IgniterCallerBodyUtils = class {
138
47
  /**
139
48
  * Returns true when the request body should be passed to `fetch` as-is.
49
+ *
50
+ * @param body - Request body to inspect.
51
+ * @returns True if the body should be sent as raw data.
140
52
  */
141
53
  static isRawBody(body) {
142
54
  if (!body) return false;
@@ -155,6 +67,10 @@ var IgniterCallerBodyUtils = class {
155
67
  }
156
68
  /**
157
69
  * Removes Content-Type for FormData so fetch can set boundaries automatically.
70
+ *
71
+ * @param headers - Request headers map.
72
+ * @param body - Request body.
73
+ * @returns Updated headers without Content-Type when needed.
158
74
  */
159
75
  static normalizeHeadersForBody(headers, body) {
160
76
  if (!headers) return headers;
@@ -173,6 +89,9 @@ var _IgniterCallerCacheUtils = class _IgniterCallerCacheUtils {
173
89
  *
174
90
  * When configured, cache operations will use the store (e.g., Redis)
175
91
  * instead of in-memory cache, enabling persistent cache across deployments.
92
+ *
93
+ * @param store - Store adapter implementation.
94
+ * @param options - Store options such as ttl and key prefix.
176
95
  */
177
96
  static setStore(store, options) {
178
97
  _IgniterCallerCacheUtils.store = store;
@@ -185,12 +104,18 @@ var _IgniterCallerCacheUtils = class _IgniterCallerCacheUtils {
185
104
  }
186
105
  /**
187
106
  * Gets the configured store adapter.
107
+ *
108
+ * @returns Store adapter or null when unset.
188
109
  */
189
110
  static getStore() {
190
111
  return _IgniterCallerCacheUtils.store;
191
112
  }
192
113
  /**
193
114
  * Gets cached data if it exists and is not stale.
115
+ *
116
+ * @param key - Cache key (without prefix).
117
+ * @param staleTime - Optional stale time in milliseconds.
118
+ * @returns Cached value or undefined when missing/stale.
194
119
  */
195
120
  static async get(key, staleTime) {
196
121
  const prefixedKey = _IgniterCallerCacheUtils.getPrefixedKey(key);
@@ -214,6 +139,10 @@ var _IgniterCallerCacheUtils = class _IgniterCallerCacheUtils {
214
139
  }
215
140
  /**
216
141
  * Stores data in cache with current timestamp.
142
+ *
143
+ * @param key - Cache key (without prefix).
144
+ * @param data - Data to cache.
145
+ * @param ttl - Optional TTL override in seconds.
217
146
  */
218
147
  static async set(key, data, ttl) {
219
148
  const prefixedKey = _IgniterCallerCacheUtils.getPrefixedKey(key);
@@ -235,6 +164,8 @@ var _IgniterCallerCacheUtils = class _IgniterCallerCacheUtils {
235
164
  }
236
165
  /**
237
166
  * Clears a specific cache entry.
167
+ *
168
+ * @param key - Cache key (without prefix).
238
169
  */
239
170
  static async clear(key) {
240
171
  const prefixedKey = _IgniterCallerCacheUtils.getPrefixedKey(key);
@@ -268,6 +199,8 @@ var _IgniterCallerCacheUtils = class _IgniterCallerCacheUtils {
268
199
  }
269
200
  /**
270
201
  * Clears all cache entries.
202
+ *
203
+ * @returns Promise that resolves when in-memory cache is cleared.
271
204
  */
272
205
  static async clearAll() {
273
206
  _IgniterCallerCacheUtils.cache.clear();
@@ -306,6 +239,10 @@ var IgniterCallerSchemaUtils = class _IgniterCallerSchemaUtils {
306
239
  /**
307
240
  * Matches a URL path against schema map paths (supports path parameters).
308
241
  *
242
+ * @param actualPath - Incoming request path.
243
+ * @param schemaPath - Schema path pattern.
244
+ * @returns Match result with params when matched.
245
+ *
309
246
  * @example
310
247
  * ```ts
311
248
  * matchPath('/users/123', '/users/:id') // { matched: true, params: { id: '123' } }
@@ -334,6 +271,11 @@ var IgniterCallerSchemaUtils = class _IgniterCallerSchemaUtils {
334
271
  }
335
272
  /**
336
273
  * Finds the schema for a given path and method from the schema map.
274
+ *
275
+ * @param schemaMap - Schema map from the builder.
276
+ * @param path - Request path to match.
277
+ * @param method - HTTP method to match.
278
+ * @returns Matching schema and extracted params.
337
279
  */
338
280
  static findSchema(schemaMap, path, method) {
339
281
  if (!schemaMap) {
@@ -359,6 +301,10 @@ var IgniterCallerSchemaUtils = class _IgniterCallerSchemaUtils {
359
301
  *
360
302
  * If the schema provides `~standard.validate`, it will be used.
361
303
  * Otherwise, returns the input as-is.
304
+ *
305
+ * @param schema - StandardSchema instance.
306
+ * @param input - Input value to validate.
307
+ * @returns Validated input value.
362
308
  */
363
309
  static async validateWithStandardSchema(schema, input) {
364
310
  const standard = schema?.["~standard"];
@@ -377,7 +323,12 @@ var IgniterCallerSchemaUtils = class _IgniterCallerSchemaUtils {
377
323
  /**
378
324
  * Validates request body against schema.
379
325
  *
380
- * @returns Validated data or throws/logs error based on validation mode
326
+ * @param data - Request body data.
327
+ * @param schema - Request schema (if any).
328
+ * @param options - Validation options.
329
+ * @param context - Request context for error reporting.
330
+ * @param logger - Optional logger instance.
331
+ * @returns Validated data or throws/logs error based on validation mode.
381
332
  */
382
333
  static async validateRequest(data, schema, options, context, logger) {
383
334
  if (!schema || options?.mode === "off") {
@@ -410,7 +361,13 @@ var IgniterCallerSchemaUtils = class _IgniterCallerSchemaUtils {
410
361
  /**
411
362
  * Validates response data against schema.
412
363
  *
413
- * @returns Validated data or throws/logs error based on validation mode
364
+ * @param data - Response payload to validate.
365
+ * @param schema - Response schema (if any).
366
+ * @param statusCode - HTTP status code.
367
+ * @param options - Validation options.
368
+ * @param context - Request context for error reporting.
369
+ * @param logger - Optional logger instance.
370
+ * @returns Validated data or throws/logs error based on validation mode.
414
371
  */
415
372
  static async validateResponse(data, schema, statusCode, options, context, logger) {
416
373
  if (!schema || options?.mode === "off") {
@@ -445,6 +402,12 @@ var IgniterCallerSchemaUtils = class _IgniterCallerSchemaUtils {
445
402
 
446
403
  // src/utils/url.ts
447
404
  var IgniterCallerUrlUtils = class {
405
+ /**
406
+ * Builds a full URL with optional base URL and query parameters.
407
+ *
408
+ * @param params - URL construction parameters.
409
+ * @returns Full URL string.
410
+ */
448
411
  static buildUrl(params) {
449
412
  const { url, baseURL, query } = params;
450
413
  let fullUrl = url;
@@ -461,7 +424,7 @@ var IgniterCallerUrlUtils = class {
461
424
  }
462
425
  };
463
426
 
464
- // src/builder/igniter-caller-request.builder.ts
427
+ // src/builders/request.builder.ts
465
428
  var VALIDATABLE_CONTENT_TYPES = [
466
429
  "json",
467
430
  "xml",
@@ -509,6 +472,11 @@ async function parseResponseByContentType(response, contentType) {
509
472
  }
510
473
  }
511
474
  var IgniterCallerRequestBuilder = class {
475
+ /**
476
+ * Creates a new request builder instance.
477
+ *
478
+ * @param params - Builder configuration from the manager.
479
+ */
512
480
  constructor(params) {
513
481
  this.options = {
514
482
  method: "GET",
@@ -531,6 +499,7 @@ var IgniterCallerRequestBuilder = class {
531
499
  this.options.headers = { ...this.options.headers, Cookie: cookieStr };
532
500
  }
533
501
  this.logger = params.logger;
502
+ this.telemetry = params.telemetry;
534
503
  this.requestInterceptors = params.requestInterceptors;
535
504
  this.responseInterceptors = params.responseInterceptors;
536
505
  this.eventEmitter = params.eventEmitter;
@@ -540,6 +509,8 @@ var IgniterCallerRequestBuilder = class {
540
509
  /**
541
510
  * Sets the HTTP method for this request.
542
511
  * @internal Used by IgniterCaller.request() for generic requests.
512
+ *
513
+ * @param method - HTTP method for the request.
543
514
  */
544
515
  _setMethod(method) {
545
516
  this.options.method = method;
@@ -548,6 +519,8 @@ var IgniterCallerRequestBuilder = class {
548
519
  /**
549
520
  * Sets the URL for this request.
550
521
  * @internal Used when URL is passed to HTTP method directly.
522
+ *
523
+ * @param url - Request URL or path.
551
524
  */
552
525
  _setUrl(url) {
553
526
  this.options.url = url;
@@ -555,6 +528,8 @@ var IgniterCallerRequestBuilder = class {
555
528
  }
556
529
  /**
557
530
  * Overrides the logger for this request chain.
531
+ *
532
+ * @param logger - Logger implementation from `@igniter-js/core`.
558
533
  */
559
534
  withLogger(logger) {
560
535
  this.logger = logger;
@@ -562,6 +537,8 @@ var IgniterCallerRequestBuilder = class {
562
537
  }
563
538
  /**
564
539
  * Sets the request URL.
540
+ *
541
+ * @param url - Request URL or path.
565
542
  */
566
543
  url(url) {
567
544
  this.options.url = url;
@@ -570,6 +547,8 @@ var IgniterCallerRequestBuilder = class {
570
547
  /**
571
548
  * Sets the request body.
572
549
  * For GET/HEAD requests, body will be automatically converted to query params.
550
+ *
551
+ * @param body - Body payload for the request.
573
552
  */
574
553
  body(body) {
575
554
  this.options.body = body;
@@ -577,6 +556,8 @@ var IgniterCallerRequestBuilder = class {
577
556
  }
578
557
  /**
579
558
  * Sets URL query parameters.
559
+ *
560
+ * @param params - Query string parameters.
580
561
  */
581
562
  params(params) {
582
563
  this.options.params = params;
@@ -584,6 +565,8 @@ var IgniterCallerRequestBuilder = class {
584
565
  }
585
566
  /**
586
567
  * Merges additional headers into the request.
568
+ *
569
+ * @param headers - Header map merged into existing headers.
587
570
  */
588
571
  headers(headers) {
589
572
  this.options.headers = { ...this.options.headers, ...headers };
@@ -591,6 +574,8 @@ var IgniterCallerRequestBuilder = class {
591
574
  }
592
575
  /**
593
576
  * Sets request timeout in milliseconds.
577
+ *
578
+ * @param timeout - Timeout in milliseconds.
594
579
  */
595
580
  timeout(timeout) {
596
581
  this.options.timeout = timeout;
@@ -598,6 +583,9 @@ var IgniterCallerRequestBuilder = class {
598
583
  }
599
584
  /**
600
585
  * Sets cache strategy and optional cache key.
586
+ *
587
+ * @param cache - Cache strategy for the request.
588
+ * @param key - Optional cache key override.
601
589
  */
602
590
  cache(cache, key) {
603
591
  this.options.cache = cache;
@@ -606,6 +594,9 @@ var IgniterCallerRequestBuilder = class {
606
594
  }
607
595
  /**
608
596
  * Configures retry behavior for failed requests.
597
+ *
598
+ * @param maxAttempts - Maximum number of attempts.
599
+ * @param options - Retry options excluding `maxAttempts`.
609
600
  */
610
601
  retry(maxAttempts, options) {
611
602
  this.retryOptions = { maxAttempts, ...options };
@@ -613,6 +604,8 @@ var IgniterCallerRequestBuilder = class {
613
604
  }
614
605
  /**
615
606
  * Provides a fallback value if the request fails.
607
+ *
608
+ * @param fn - Fallback factory called when the request fails.
616
609
  */
617
610
  fallback(fn) {
618
611
  this.fallbackFn = fn;
@@ -620,6 +613,8 @@ var IgniterCallerRequestBuilder = class {
620
613
  }
621
614
  /**
622
615
  * Sets cache stale time in milliseconds.
616
+ *
617
+ * @param milliseconds - Stale time in milliseconds.
623
618
  */
624
619
  stale(milliseconds) {
625
620
  this.staleTime = milliseconds;
@@ -633,6 +628,8 @@ var IgniterCallerRequestBuilder = class {
633
628
  *
634
629
  * The actual parsing is based on Content-Type headers, not this setting.
635
630
  *
631
+ * @param schema - Zod/StandardSchema instance for validation (optional).
632
+ *
636
633
  * @example
637
634
  * ```ts
638
635
  * // With Zod schema (validates JSON response)
@@ -651,6 +648,8 @@ var IgniterCallerRequestBuilder = class {
651
648
  /**
652
649
  * Downloads a file via GET request.
653
650
  * @deprecated Use `.responseType<File>().execute()` instead. The response type is auto-detected.
651
+ *
652
+ * @param url - URL or path to download.
654
653
  */
655
654
  getFile(url) {
656
655
  this.options.method = "GET";
@@ -725,7 +724,7 @@ var IgniterCallerRequestBuilder = class {
725
724
  statusCode: 408,
726
725
  logger: this.logger,
727
726
  metadata: { url: finalUrl },
728
- cause: error
727
+ cause: error instanceof Error ? error : void 0
729
728
  })
730
729
  };
731
730
  }
@@ -737,7 +736,7 @@ var IgniterCallerRequestBuilder = class {
737
736
  message: error?.message || "Failed to download file",
738
737
  logger: this.logger,
739
738
  metadata: { url: finalUrl },
740
- cause: error
739
+ cause: error instanceof Error ? error : void 0
741
740
  })
742
741
  };
743
742
  }
@@ -756,8 +755,32 @@ var IgniterCallerRequestBuilder = class {
756
755
  * - `application/octet-stream` → returned as Blob
757
756
  *
758
757
  * Schema validation (if configured) only runs for validatable content types (JSON, XML, CSV).
758
+ *
759
+ * @returns Response envelope with data or error.
759
760
  */
760
761
  async execute() {
762
+ const startTime = Date.now();
763
+ const { safeUrl } = this.resolveUrl();
764
+ const method = this.options.method;
765
+ const baseURL = this.options.baseURL;
766
+ const timeoutMs = this.options.timeout;
767
+ this.telemetry?.emit(
768
+ "igniter.caller.request.execute.started",
769
+ {
770
+ level: "debug",
771
+ attributes: {
772
+ "ctx.request.method": method,
773
+ "ctx.request.url": safeUrl,
774
+ "ctx.request.baseUrl": baseURL,
775
+ "ctx.request.timeoutMs": timeoutMs
776
+ }
777
+ }
778
+ );
779
+ this.logger?.debug("IgniterCaller.request.execute started", {
780
+ method,
781
+ url: safeUrl,
782
+ baseURL
783
+ });
761
784
  const effectiveCacheKey = this.cacheKey || this.options.url;
762
785
  if (effectiveCacheKey && this.staleTime) {
763
786
  const cached = await IgniterCallerCacheUtils.get(
@@ -765,8 +788,36 @@ var IgniterCallerRequestBuilder = class {
765
788
  this.staleTime
766
789
  );
767
790
  if (cached !== void 0) {
768
- this.logger?.debug("IgniterCaller.execute cache hit", {
769
- key: effectiveCacheKey
791
+ this.telemetry?.emit(
792
+ "igniter.caller.cache.read.hit",
793
+ {
794
+ level: "debug",
795
+ attributes: {
796
+ "ctx.request.method": method,
797
+ "ctx.request.url": safeUrl,
798
+ "ctx.cache.key": effectiveCacheKey,
799
+ "ctx.cache.staleTime": this.staleTime
800
+ }
801
+ }
802
+ );
803
+ const durationMs2 = Date.now() - startTime;
804
+ this.telemetry?.emit(
805
+ "igniter.caller.request.execute.success",
806
+ {
807
+ level: "info",
808
+ attributes: {
809
+ "ctx.request.method": method,
810
+ "ctx.request.url": safeUrl,
811
+ "ctx.request.durationMs": durationMs2,
812
+ "ctx.cache.hit": true
813
+ }
814
+ }
815
+ );
816
+ this.logger?.info("IgniterCaller.request.execute success (cache)", {
817
+ key: effectiveCacheKey,
818
+ method,
819
+ url: safeUrl,
820
+ durationMs: durationMs2
770
821
  });
771
822
  const cachedResult = {
772
823
  data: cached,
@@ -778,13 +829,33 @@ var IgniterCallerRequestBuilder = class {
778
829
  }
779
830
  const result = await this.executeWithRetry();
780
831
  if (result.error && this.fallbackFn) {
781
- this.logger?.debug("IgniterCaller.execute applying fallback", {
832
+ this.logger?.debug("IgniterCaller.request.execute applying fallback", {
833
+ method,
834
+ url: safeUrl,
782
835
  error: result.error
783
836
  });
784
837
  const fallbackResult = {
785
838
  data: this.fallbackFn(),
786
839
  error: void 0
787
840
  };
841
+ const durationMs2 = Date.now() - startTime;
842
+ this.telemetry?.emit(
843
+ "igniter.caller.request.execute.success",
844
+ {
845
+ level: "info",
846
+ attributes: {
847
+ "ctx.request.method": method,
848
+ "ctx.request.url": safeUrl,
849
+ "ctx.request.durationMs": durationMs2,
850
+ "ctx.request.fallback": true
851
+ }
852
+ }
853
+ );
854
+ this.logger?.info("IgniterCaller.request.execute success (fallback)", {
855
+ method,
856
+ url: safeUrl,
857
+ durationMs: durationMs2
858
+ });
788
859
  await this.emitEvent(fallbackResult);
789
860
  return fallbackResult;
790
861
  }
@@ -795,12 +866,57 @@ var IgniterCallerRequestBuilder = class {
795
866
  this.staleTime
796
867
  );
797
868
  }
869
+ const durationMs = Date.now() - startTime;
870
+ if (result.error) {
871
+ this.telemetry?.emit(
872
+ "igniter.caller.request.execute.error",
873
+ {
874
+ level: "error",
875
+ attributes: {
876
+ "ctx.request.method": method,
877
+ "ctx.request.url": safeUrl,
878
+ "ctx.request.durationMs": durationMs,
879
+ "ctx.error.code": result.error instanceof IgniterCallerError ? result.error.code : "IGNITER_CALLER_UNKNOWN_ERROR",
880
+ "ctx.error.message": result.error.message,
881
+ "ctx.response.status": result.status
882
+ }
883
+ }
884
+ );
885
+ this.logger?.error("IgniterCaller.request.execute failed", {
886
+ method,
887
+ url: safeUrl,
888
+ durationMs,
889
+ error: result.error
890
+ });
891
+ } else {
892
+ const contentType = result.headers?.get("content-type") || void 0;
893
+ this.telemetry?.emit(
894
+ "igniter.caller.request.execute.success",
895
+ {
896
+ level: "info",
897
+ attributes: {
898
+ "ctx.request.method": method,
899
+ "ctx.request.url": safeUrl,
900
+ "ctx.request.durationMs": durationMs,
901
+ "ctx.response.status": result.status,
902
+ "ctx.response.contentType": contentType,
903
+ "ctx.cache.hit": false
904
+ }
905
+ }
906
+ );
907
+ this.logger?.info("IgniterCaller.request.execute success", {
908
+ method,
909
+ url: safeUrl,
910
+ durationMs,
911
+ status: result.status
912
+ });
913
+ }
798
914
  await this.emitEvent(result);
799
915
  return result;
800
916
  }
801
917
  async executeWithRetry() {
802
918
  const maxAttempts = this.retryOptions?.maxAttempts || 1;
803
- const baseDelay = this.retryOptions?.baseDelay || 1e3;
919
+ const baseDelay = this.retryOptions?.baseDelay ?? 1e3;
804
920
  const backoff = this.retryOptions?.backoff || "linear";
805
921
  const retryOnStatus = this.retryOptions?.retryOnStatus || [
806
922
  408,
@@ -810,13 +926,30 @@ var IgniterCallerRequestBuilder = class {
810
926
  503,
811
927
  504
812
928
  ];
929
+ const { safeUrl } = this.resolveUrl();
930
+ const method = this.options.method;
813
931
  let lastError;
814
932
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
815
933
  if (attempt > 0) {
816
934
  const delay = backoff === "exponential" ? baseDelay * 2 ** (attempt - 1) : baseDelay * attempt;
817
- this.logger?.debug("IgniterCaller.execute retrying", {
818
- attempt,
819
- delay
935
+ this.telemetry?.emit(
936
+ "igniter.caller.retry.attempt.started",
937
+ {
938
+ level: "debug",
939
+ attributes: {
940
+ "ctx.request.method": method,
941
+ "ctx.request.url": safeUrl,
942
+ "ctx.retry.attempt": attempt + 1,
943
+ "ctx.retry.maxAttempts": maxAttempts,
944
+ "ctx.retry.delayMs": delay
945
+ }
946
+ }
947
+ );
948
+ this.logger?.debug("IgniterCaller.request.execute retrying", {
949
+ method,
950
+ url: safeUrl,
951
+ attempt: attempt + 1,
952
+ delayMs: delay
820
953
  });
821
954
  await new Promise((resolve) => setTimeout(resolve, delay));
822
955
  }
@@ -837,10 +970,8 @@ var IgniterCallerRequestBuilder = class {
837
970
  }
838
971
  async executeSingleRequest() {
839
972
  let { url, requestInit, controller, timeoutId } = this.buildRequest();
840
- this.logger?.debug("IgniterCaller.execute started", {
841
- method: this.options.method,
842
- url
843
- });
973
+ const { safeUrl } = this.resolveUrl();
974
+ const method = this.options.method;
844
975
  if (this.requestInterceptors && this.requestInterceptors.length > 0) {
845
976
  let modifiedOptions = { ...this.options, url };
846
977
  for (const interceptor of this.requestInterceptors) {
@@ -870,9 +1001,27 @@ var IgniterCallerRequestBuilder = class {
870
1001
  );
871
1002
  } catch (error) {
872
1003
  clearTimeout(timeoutId);
1004
+ const err = error;
1005
+ this.telemetry?.emit(
1006
+ "igniter.caller.validation.request.error",
1007
+ {
1008
+ level: "error",
1009
+ attributes: {
1010
+ "ctx.request.method": method,
1011
+ "ctx.request.url": safeUrl,
1012
+ "ctx.validation.type": "request",
1013
+ "ctx.validation.error": err.message
1014
+ }
1015
+ }
1016
+ );
1017
+ this.logger?.error("IgniterCaller.request.validation failed", {
1018
+ method,
1019
+ url: safeUrl,
1020
+ error: err
1021
+ });
873
1022
  return {
874
1023
  data: void 0,
875
- error
1024
+ error: err
876
1025
  };
877
1026
  }
878
1027
  }
@@ -926,9 +1075,29 @@ var IgniterCallerRequestBuilder = class {
926
1075
  this.logger
927
1076
  );
928
1077
  } catch (error) {
1078
+ const err = error;
1079
+ this.telemetry?.emit(
1080
+ "igniter.caller.validation.response.error",
1081
+ {
1082
+ level: "error",
1083
+ attributes: {
1084
+ "ctx.request.method": method,
1085
+ "ctx.request.url": safeUrl,
1086
+ "ctx.validation.type": "response",
1087
+ "ctx.validation.error": err.message,
1088
+ "ctx.response.status": httpResponse.status
1089
+ }
1090
+ }
1091
+ );
1092
+ this.logger?.error("IgniterCaller.response.validation failed", {
1093
+ method,
1094
+ url: safeUrl,
1095
+ status: httpResponse.status,
1096
+ error: err
1097
+ });
929
1098
  return {
930
1099
  data: void 0,
931
- error,
1100
+ error: err,
932
1101
  status: httpResponse.status,
933
1102
  headers: httpResponse.headers
934
1103
  };
@@ -940,20 +1109,40 @@ var IgniterCallerRequestBuilder = class {
940
1109
  const zodSchema = this.responseTypeSchema;
941
1110
  const result = zodSchema.safeParse(data);
942
1111
  if (!result.success) {
1112
+ const err = new IgniterCallerError({
1113
+ code: "IGNITER_CALLER_RESPONSE_VALIDATION_FAILED",
1114
+ operation: "parseResponse",
1115
+ message: `Response validation failed: ${result.error.message}`,
1116
+ logger: this.logger,
1117
+ statusCode: httpResponse.status,
1118
+ metadata: {
1119
+ method: this.options.method,
1120
+ url
1121
+ },
1122
+ cause: result.error
1123
+ });
1124
+ this.telemetry?.emit(
1125
+ "igniter.caller.validation.response.error",
1126
+ {
1127
+ level: "error",
1128
+ attributes: {
1129
+ "ctx.request.method": method,
1130
+ "ctx.request.url": safeUrl,
1131
+ "ctx.validation.type": "response",
1132
+ "ctx.validation.error": err.message,
1133
+ "ctx.response.status": httpResponse.status
1134
+ }
1135
+ }
1136
+ );
1137
+ this.logger?.error("IgniterCaller.response.validation failed", {
1138
+ method,
1139
+ url: safeUrl,
1140
+ status: httpResponse.status,
1141
+ error: err
1142
+ });
943
1143
  return {
944
1144
  data: void 0,
945
- error: new IgniterCallerError({
946
- code: "IGNITER_CALLER_RESPONSE_VALIDATION_FAILED",
947
- operation: "parseResponse",
948
- message: `Response validation failed: ${result.error.message}`,
949
- logger: this.logger,
950
- statusCode: httpResponse.status,
951
- metadata: {
952
- method: this.options.method,
953
- url
954
- },
955
- cause: result.error
956
- }),
1145
+ error: err,
957
1146
  status: httpResponse.status,
958
1147
  headers: httpResponse.headers
959
1148
  };
@@ -964,40 +1153,80 @@ var IgniterCallerRequestBuilder = class {
964
1153
  const standardSchema = this.responseTypeSchema;
965
1154
  const result = await standardSchema["~standard"].validate(data);
966
1155
  if (result.issues) {
1156
+ const err = new IgniterCallerError({
1157
+ code: "IGNITER_CALLER_RESPONSE_VALIDATION_FAILED",
1158
+ operation: "parseResponse",
1159
+ message: `Response validation failed`,
1160
+ logger: this.logger,
1161
+ statusCode: httpResponse.status,
1162
+ metadata: {
1163
+ method: this.options.method,
1164
+ url,
1165
+ issues: result.issues
1166
+ }
1167
+ });
1168
+ this.telemetry?.emit(
1169
+ "igniter.caller.validation.response.error",
1170
+ {
1171
+ level: "error",
1172
+ attributes: {
1173
+ "ctx.request.method": method,
1174
+ "ctx.request.url": safeUrl,
1175
+ "ctx.validation.type": "response",
1176
+ "ctx.validation.error": err.message,
1177
+ "ctx.response.status": httpResponse.status
1178
+ }
1179
+ }
1180
+ );
1181
+ this.logger?.error("IgniterCaller.response.validation failed", {
1182
+ method,
1183
+ url: safeUrl,
1184
+ status: httpResponse.status,
1185
+ error: err
1186
+ });
967
1187
  return {
968
1188
  data: void 0,
969
- error: new IgniterCallerError({
970
- code: "IGNITER_CALLER_RESPONSE_VALIDATION_FAILED",
971
- operation: "parseResponse",
972
- message: `Response validation failed`,
973
- logger: this.logger,
974
- statusCode: httpResponse.status,
975
- metadata: {
976
- method: this.options.method,
977
- url,
978
- issues: result.issues
979
- }
980
- }),
1189
+ error: err,
981
1190
  status: httpResponse.status,
982
1191
  headers: httpResponse.headers
983
1192
  };
984
1193
  }
985
1194
  data = result.value;
986
1195
  } catch (error) {
1196
+ const err = new IgniterCallerError({
1197
+ code: "IGNITER_CALLER_RESPONSE_VALIDATION_FAILED",
1198
+ operation: "parseResponse",
1199
+ message: error?.message || "Response validation failed",
1200
+ logger: this.logger,
1201
+ statusCode: httpResponse.status,
1202
+ metadata: {
1203
+ method: this.options.method,
1204
+ url
1205
+ },
1206
+ cause: error instanceof Error ? error : void 0
1207
+ });
1208
+ this.telemetry?.emit(
1209
+ "igniter.caller.validation.response.error",
1210
+ {
1211
+ level: "error",
1212
+ attributes: {
1213
+ "ctx.request.method": method,
1214
+ "ctx.request.url": safeUrl,
1215
+ "ctx.validation.type": "response",
1216
+ "ctx.validation.error": err.message,
1217
+ "ctx.response.status": httpResponse.status
1218
+ }
1219
+ }
1220
+ );
1221
+ this.logger?.error("IgniterCaller.response.validation failed", {
1222
+ method,
1223
+ url: safeUrl,
1224
+ status: httpResponse.status,
1225
+ error: err
1226
+ });
987
1227
  return {
988
1228
  data: void 0,
989
- error: new IgniterCallerError({
990
- code: "IGNITER_CALLER_RESPONSE_VALIDATION_FAILED",
991
- operation: "parseResponse",
992
- message: error?.message || "Response validation failed",
993
- logger: this.logger,
994
- statusCode: httpResponse.status,
995
- metadata: {
996
- method: this.options.method,
997
- url
998
- },
999
- cause: error
1000
- }),
1229
+ error: err,
1001
1230
  status: httpResponse.status,
1002
1231
  headers: httpResponse.headers
1003
1232
  };
@@ -1020,20 +1249,38 @@ var IgniterCallerRequestBuilder = class {
1020
1249
  } catch (error) {
1021
1250
  clearTimeout(timeoutId);
1022
1251
  if (error instanceof Error && error.name === "AbortError") {
1252
+ const err = new IgniterCallerError({
1253
+ code: "IGNITER_CALLER_TIMEOUT",
1254
+ operation: "execute",
1255
+ message: `Request timeout after ${this.options.timeout || 3e4}ms`,
1256
+ statusCode: 408,
1257
+ logger: this.logger,
1258
+ metadata: {
1259
+ method: this.options.method,
1260
+ url
1261
+ },
1262
+ cause: error instanceof Error ? error : void 0
1263
+ });
1264
+ this.telemetry?.emit(
1265
+ "igniter.caller.request.timeout.error",
1266
+ {
1267
+ level: "error",
1268
+ attributes: {
1269
+ "ctx.request.method": method,
1270
+ "ctx.request.url": safeUrl,
1271
+ "ctx.request.timeoutMs": this.options.timeout || 3e4
1272
+ }
1273
+ }
1274
+ );
1275
+ this.logger?.error("IgniterCaller.request.execute timeout", {
1276
+ method,
1277
+ url: safeUrl,
1278
+ timeoutMs: this.options.timeout || 3e4,
1279
+ error: err
1280
+ });
1023
1281
  return {
1024
1282
  data: void 0,
1025
- error: new IgniterCallerError({
1026
- code: "IGNITER_CALLER_TIMEOUT",
1027
- operation: "execute",
1028
- message: `Request timeout after ${this.options.timeout || 3e4}ms`,
1029
- statusCode: 408,
1030
- logger: this.logger,
1031
- metadata: {
1032
- method: this.options.method,
1033
- url
1034
- },
1035
- cause: error
1036
- })
1283
+ error: err
1037
1284
  };
1038
1285
  }
1039
1286
  return {
@@ -1048,13 +1295,13 @@ var IgniterCallerRequestBuilder = class {
1048
1295
  method: this.options.method,
1049
1296
  url
1050
1297
  },
1051
- cause: error
1298
+ cause: error instanceof Error ? error : void 0
1052
1299
  })
1053
1300
  };
1054
1301
  }
1055
1302
  }
1056
- buildRequest() {
1057
- const { method, url, body, params, headers, timeout, baseURL, cache } = this.options;
1303
+ resolveUrl() {
1304
+ const { method, url, body, params, baseURL } = this.options;
1058
1305
  let finalParams = params;
1059
1306
  if ((method === "GET" || method === "HEAD") && body && typeof body === "object") {
1060
1307
  const bodyParams = {};
@@ -1070,6 +1317,14 @@ var IgniterCallerRequestBuilder = class {
1070
1317
  baseURL,
1071
1318
  query: finalParams
1072
1319
  });
1320
+ return {
1321
+ url: fullUrl,
1322
+ safeUrl: fullUrl.split("?")[0]
1323
+ };
1324
+ }
1325
+ buildRequest() {
1326
+ const { method, body, headers, timeout, cache } = this.options;
1327
+ const { url } = this.resolveUrl();
1073
1328
  const shouldIncludeBody = body && method !== "GET" && method !== "HEAD";
1074
1329
  const rawBody = shouldIncludeBody && IgniterCallerBodyUtils.isRawBody(body);
1075
1330
  const finalHeaders = IgniterCallerBodyUtils.normalizeHeadersForBody(
@@ -1085,7 +1340,7 @@ var IgniterCallerRequestBuilder = class {
1085
1340
  const controller = new AbortController();
1086
1341
  const timeoutId = setTimeout(() => controller.abort(), timeout || 3e4);
1087
1342
  return {
1088
- url: fullUrl,
1343
+ url,
1089
1344
  requestInit,
1090
1345
  controller,
1091
1346
  timeoutId
@@ -1105,7 +1360,7 @@ var IgniterCallerRequestBuilder = class {
1105
1360
  }
1106
1361
  };
1107
1362
 
1108
- // src/core/igniter-caller-events.ts
1363
+ // src/core/events.ts
1109
1364
  var IgniterCallerEvents = class {
1110
1365
  constructor() {
1111
1366
  this.listeners = /* @__PURE__ */ new Map();
@@ -1172,6 +1427,9 @@ var IgniterCallerEvents = class {
1172
1427
  }
1173
1428
  /**
1174
1429
  * Removes a specific listener or all listeners for a pattern.
1430
+ *
1431
+ * @param pattern - URL string or RegExp pattern.
1432
+ * @param callback - Optional specific callback to remove.
1175
1433
  */
1176
1434
  off(pattern, callback) {
1177
1435
  if (typeof pattern === "string") {
@@ -1192,6 +1450,10 @@ var IgniterCallerEvents = class {
1192
1450
  * Emits an event to all matching listeners.
1193
1451
  *
1194
1452
  * @internal
1453
+ *
1454
+ * @param url - Request URL to match listeners against.
1455
+ * @param method - HTTP method.
1456
+ * @param result - Response envelope.
1195
1457
  */
1196
1458
  async emit(url, method, result) {
1197
1459
  const context = {
@@ -1223,6 +1485,8 @@ var IgniterCallerEvents = class {
1223
1485
  }
1224
1486
  /**
1225
1487
  * Removes all listeners.
1488
+ *
1489
+ * @returns Nothing.
1226
1490
  */
1227
1491
  clear() {
1228
1492
  this.listeners.clear();
@@ -1230,65 +1494,25 @@ var IgniterCallerEvents = class {
1230
1494
  }
1231
1495
  };
1232
1496
 
1233
- // src/core/igniter-caller.ts
1234
- var _IgniterCaller = class _IgniterCaller {
1497
+ // src/core/manager.ts
1498
+ var _IgniterCallerManager = class _IgniterCallerManager {
1499
+ /**
1500
+ * Creates a new manager instance.
1501
+ *
1502
+ * @param baseURL - Base URL prefix for requests.
1503
+ * @param opts - Optional configuration (headers, cookies, telemetry, schemas).
1504
+ */
1235
1505
  constructor(baseURL, opts) {
1236
1506
  this.baseURL = baseURL;
1237
1507
  this.headers = opts?.headers;
1238
1508
  this.cookies = opts?.cookies;
1239
1509
  this.logger = opts?.logger;
1510
+ this.telemetry = opts?.telemetry;
1240
1511
  this.requestInterceptors = opts?.requestInterceptors;
1241
1512
  this.responseInterceptors = opts?.responseInterceptors;
1242
1513
  this.schemas = opts?.schemas;
1243
1514
  this.schemaValidation = opts?.schemaValidation;
1244
1515
  }
1245
- /**
1246
- * Canonical initialization entrypoint.
1247
- *
1248
- * This is designed to remain stable when extracted to `@igniter-js/caller`.
1249
- */
1250
- static create() {
1251
- return IgniterCallerBuilder.create((state) => {
1252
- if (state.store) {
1253
- IgniterCallerCacheUtils.setStore(state.store, state.storeOptions);
1254
- }
1255
- return new _IgniterCaller(state.baseURL, {
1256
- headers: state.headers,
1257
- cookies: state.cookies,
1258
- logger: state.logger,
1259
- requestInterceptors: state.requestInterceptors,
1260
- responseInterceptors: state.responseInterceptors,
1261
- schemas: state.schemas,
1262
- schemaValidation: state.schemaValidation
1263
- });
1264
- });
1265
- }
1266
- /**
1267
- * Returns a new client with the same config and a new logger.
1268
- */
1269
- withLogger(logger) {
1270
- return new _IgniterCaller(this.baseURL, {
1271
- headers: this.headers,
1272
- cookies: this.cookies,
1273
- logger,
1274
- requestInterceptors: this.requestInterceptors,
1275
- responseInterceptors: this.responseInterceptors,
1276
- schemas: this.schemas,
1277
- schemaValidation: this.schemaValidation
1278
- });
1279
- }
1280
- setBaseURL(baseURL) {
1281
- this.baseURL = baseURL;
1282
- return this;
1283
- }
1284
- setHeaders(headers) {
1285
- this.headers = headers;
1286
- return this;
1287
- }
1288
- setCookies(cookies) {
1289
- this.cookies = cookies;
1290
- return this;
1291
- }
1292
1516
  /**
1293
1517
  * Creates common request builder params.
1294
1518
  */
@@ -1298,126 +1522,46 @@ var _IgniterCaller = class _IgniterCaller {
1298
1522
  defaultHeaders: this.headers,
1299
1523
  defaultCookies: this.cookies,
1300
1524
  logger: this.logger,
1525
+ telemetry: this.telemetry,
1301
1526
  requestInterceptors: this.requestInterceptors,
1302
1527
  responseInterceptors: this.responseInterceptors,
1303
1528
  eventEmitter: async (url, method, result) => {
1304
- await _IgniterCaller.emitEvent(url, method, result);
1529
+ await _IgniterCallerManager.emitEvent(url, method, result);
1305
1530
  },
1306
1531
  schemas: this.schemas,
1307
1532
  schemaValidation: this.schemaValidation
1308
1533
  };
1309
1534
  }
1310
- /**
1311
- * Resolves the full URL path by prepending baseURL if needed.
1312
- */
1313
- resolveSchemaPath(url) {
1314
- if (this.baseURL && !url.startsWith("http")) {
1315
- return `${this.baseURL}${url}`;
1316
- }
1317
- return url;
1318
- }
1319
- /**
1320
- * Creates a GET request.
1321
- *
1322
- * When a URL is provided and matches a schema, the response type is automatically inferred.
1323
- *
1324
- * @param url Optional URL for the request. Can also be set via `.url()`.
1325
- *
1326
- * @example
1327
- * ```ts
1328
- * // With typed schema - response type is inferred
1329
- * const result = await api.get('/users').execute()
1330
- * // result.data is typed based on schema
1331
- *
1332
- * // Without schema or URL set later
1333
- * const result = await api.get().url('/users').execute()
1334
- * ```
1335
- */
1336
1535
  get(url) {
1337
1536
  const builder = new IgniterCallerRequestBuilder(this.createBuilderParams());
1338
1537
  builder._setMethod("GET");
1339
1538
  if (url) builder._setUrl(url);
1340
1539
  return builder;
1341
1540
  }
1342
- /**
1343
- * Creates a POST request.
1344
- *
1345
- * When a URL is provided and matches a schema, the response type is automatically inferred.
1346
- *
1347
- * @param url Optional URL for the request. Can also be set via `.url()`.
1348
- *
1349
- * @example
1350
- * ```ts
1351
- * const result = await api.post('/users').body({ name: 'John' }).execute()
1352
- * ```
1353
- */
1354
1541
  post(url) {
1355
1542
  const builder = new IgniterCallerRequestBuilder(this.createBuilderParams());
1356
1543
  builder._setMethod("POST");
1357
1544
  if (url) builder._setUrl(url);
1358
1545
  return builder;
1359
1546
  }
1360
- /**
1361
- * Creates a PUT request.
1362
- *
1363
- * When a URL is provided and matches a schema, the response type is automatically inferred.
1364
- *
1365
- * @param url Optional URL for the request. Can also be set via `.url()`.
1366
- *
1367
- * @example
1368
- * ```ts
1369
- * const result = await api.put('/users/1').body({ name: 'Jane' }).execute()
1370
- * ```
1371
- */
1372
1547
  put(url) {
1373
1548
  const builder = new IgniterCallerRequestBuilder(this.createBuilderParams());
1374
1549
  builder._setMethod("PUT");
1375
1550
  if (url) builder._setUrl(url);
1376
1551
  return builder;
1377
1552
  }
1378
- /**
1379
- * Creates a PATCH request.
1380
- *
1381
- * When a URL is provided and matches a schema, the response type is automatically inferred.
1382
- *
1383
- * @param url Optional URL for the request. Can also be set via `.url()`.
1384
- *
1385
- * @example
1386
- * ```ts
1387
- * const result = await api.patch('/users/1').body({ name: 'Jane' }).execute()
1388
- * ```
1389
- */
1390
1553
  patch(url) {
1391
1554
  const builder = new IgniterCallerRequestBuilder(this.createBuilderParams());
1392
1555
  builder._setMethod("PATCH");
1393
1556
  if (url) builder._setUrl(url);
1394
1557
  return builder;
1395
1558
  }
1396
- /**
1397
- * Creates a DELETE request.
1398
- *
1399
- * When a URL is provided and matches a schema, the response type is automatically inferred.
1400
- *
1401
- * @param url Optional URL for the request. Can also be set via `.url()`.
1402
- *
1403
- * @example
1404
- * ```ts
1405
- * const result = await api.delete('/users/1').execute()
1406
- * ```
1407
- */
1408
1559
  delete(url) {
1409
1560
  const builder = new IgniterCallerRequestBuilder(this.createBuilderParams());
1410
1561
  builder._setMethod("DELETE");
1411
1562
  if (url) builder._setUrl(url);
1412
1563
  return builder;
1413
1564
  }
1414
- /**
1415
- * Creates a HEAD request.
1416
- *
1417
- * When a URL is provided and matches a schema, the response type is automatically inferred.
1418
- *
1419
- * @param url Optional URL for the request. Can also be set via `.url()`.
1420
- */
1421
1565
  head(url) {
1422
1566
  const builder = new IgniterCallerRequestBuilder(this.createBuilderParams());
1423
1567
  builder._setMethod("HEAD");
@@ -1430,6 +1574,9 @@ var _IgniterCaller = class _IgniterCaller {
1430
1574
  * This is a convenience method for making requests without using the builder pattern.
1431
1575
  * Useful for dynamic requests where options are constructed programmatically.
1432
1576
  *
1577
+ * @param options - Request configuration for method, url, and behavior.
1578
+ * @returns Response envelope with data or error.
1579
+ *
1433
1580
  * @example
1434
1581
  * ```ts
1435
1582
  * const result = await api.request({
@@ -1496,6 +1643,9 @@ var _IgniterCaller = class _IgniterCaller {
1496
1643
  * Executes multiple requests in parallel and returns results as an array.
1497
1644
  *
1498
1645
  * This is useful for batching independent API calls.
1646
+ *
1647
+ * @param requests - Array of request promises.
1648
+ * @returns Array of resolved results in the same order.
1499
1649
  */
1500
1650
  static async batch(requests) {
1501
1651
  return Promise.all(requests);
@@ -1516,7 +1666,7 @@ var _IgniterCaller = class _IgniterCaller {
1516
1666
  * @example
1517
1667
  * ```ts
1518
1668
  * // Listen to all user endpoints
1519
- * const cleanup = IgniterCaller.on(/^\/users/, (result, context) => {
1669
+ * const cleanup = IgniterCallerManager.on(/^\/users/, (result, context) => {
1520
1670
  * console.log(`${context.method} ${context.url}`, result)
1521
1671
  * })
1522
1672
  *
@@ -1525,24 +1675,29 @@ var _IgniterCaller = class _IgniterCaller {
1525
1675
  * ```
1526
1676
  */
1527
1677
  static on(pattern, callback) {
1528
- return _IgniterCaller.events.on(pattern, callback);
1678
+ return _IgniterCallerManager.events.on(pattern, callback);
1529
1679
  }
1530
1680
  /**
1531
1681
  * Removes event listeners for a pattern.
1682
+ *
1683
+ * @param pattern - URL string or RegExp pattern.
1684
+ * @param callback - Callback to remove (optional).
1532
1685
  */
1533
1686
  static off(pattern, callback) {
1534
- _IgniterCaller.events.off(pattern, callback);
1687
+ _IgniterCallerManager.events.off(pattern, callback);
1535
1688
  }
1536
1689
  /**
1537
1690
  * Invalidates a specific cache entry.
1538
1691
  *
1539
1692
  * This is useful after mutations to ensure fresh data on next fetch.
1540
1693
  *
1694
+ * @param key - Cache key to invalidate.
1695
+ *
1541
1696
  * @example
1542
1697
  * ```ts
1543
1698
  * // After creating a user
1544
1699
  * await api.post('/users').body(newUser).execute()
1545
- * await IgniterCaller.invalidate('/users') // Clear users list cache
1700
+ * await IgniterCallerManager.invalidate('/users') // Clear users list cache
1546
1701
  * ```
1547
1702
  */
1548
1703
  static async invalidate(key) {
@@ -1552,11 +1707,12 @@ var _IgniterCaller = class _IgniterCaller {
1552
1707
  * Invalidates all cache entries matching a pattern.
1553
1708
  *
1554
1709
  * @param pattern Glob pattern (e.g., '/users/*') or exact key
1710
+ * @returns Promise that resolves when invalidation completes.
1555
1711
  *
1556
1712
  * @example
1557
1713
  * ```ts
1558
1714
  * // Invalidate all user-related caches
1559
- * await IgniterCaller.invalidatePattern('/users/*')
1715
+ * await IgniterCallerManager.invalidatePattern('/users/*')
1560
1716
  * ```
1561
1717
  */
1562
1718
  static async invalidatePattern(pattern) {
@@ -1566,25 +1722,539 @@ var _IgniterCaller = class _IgniterCaller {
1566
1722
  * Emits an event to all registered listeners.
1567
1723
  *
1568
1724
  * @internal
1725
+ *
1726
+ * @param url - Request URL (resolved).
1727
+ * @param method - HTTP method.
1728
+ * @param result - Response envelope.
1569
1729
  */
1570
1730
  static async emitEvent(url, method, result) {
1571
- await _IgniterCaller.events.emit(url, method, result);
1731
+ await _IgniterCallerManager.events.emit(url, method, result);
1572
1732
  }
1573
1733
  };
1574
1734
  /** Global event emitter for observing HTTP responses */
1575
- _IgniterCaller.events = new IgniterCallerEvents();
1576
- var IgniterCaller = _IgniterCaller;
1735
+ _IgniterCallerManager.events = new IgniterCallerEvents();
1736
+ var IgniterCallerManager = _IgniterCallerManager;
1737
+
1738
+ // src/builders/main.builder.ts
1739
+ var IgniterCallerBuilder = class _IgniterCallerBuilder {
1740
+ constructor(state) {
1741
+ this.state = state;
1742
+ }
1743
+ /**
1744
+ * Creates a new builder instance.
1745
+ *
1746
+ * @returns New builder instance with empty state.
1747
+ */
1748
+ static create() {
1749
+ return new _IgniterCallerBuilder({});
1750
+ }
1751
+ /**
1752
+ * Sets the base URL for all requests.
1753
+ *
1754
+ * @param baseURL - Base URL prefix for outgoing requests.
1755
+ */
1756
+ withBaseUrl(baseURL) {
1757
+ return new _IgniterCallerBuilder({ ...this.state, baseURL });
1758
+ }
1759
+ /**
1760
+ * Merges default headers for all requests.
1761
+ *
1762
+ * @param headers - Header map merged into every request.
1763
+ */
1764
+ withHeaders(headers) {
1765
+ return new _IgniterCallerBuilder({ ...this.state, headers });
1766
+ }
1767
+ /**
1768
+ * Sets default cookies (sent as the `Cookie` header).
1769
+ *
1770
+ * @param cookies - Cookie key/value pairs.
1771
+ */
1772
+ withCookies(cookies) {
1773
+ return new _IgniterCallerBuilder({ ...this.state, cookies });
1774
+ }
1775
+ /**
1776
+ * Attaches a logger instance.
1777
+ *
1778
+ * @param logger - Logger implementation from `@igniter-js/core`.
1779
+ */
1780
+ withLogger(logger) {
1781
+ return new _IgniterCallerBuilder({ ...this.state, logger });
1782
+ }
1783
+ /**
1784
+ * Adds a request interceptor that runs before each request.
1785
+ *
1786
+ * @param interceptor - Interceptor called with request options.
1787
+ */
1788
+ withRequestInterceptor(interceptor) {
1789
+ const requestInterceptors = [
1790
+ ...this.state.requestInterceptors || [],
1791
+ interceptor
1792
+ ];
1793
+ return new _IgniterCallerBuilder({ ...this.state, requestInterceptors });
1794
+ }
1795
+ /**
1796
+ * Adds a response interceptor that runs after each request.
1797
+ *
1798
+ * @param interceptor - Interceptor called with the response result.
1799
+ */
1800
+ withResponseInterceptor(interceptor) {
1801
+ const responseInterceptors = [
1802
+ ...this.state.responseInterceptors || [],
1803
+ interceptor
1804
+ ];
1805
+ return new _IgniterCallerBuilder({ ...this.state, responseInterceptors });
1806
+ }
1807
+ /**
1808
+ * Configures a persistent store adapter for caching.
1809
+ *
1810
+ * When configured, cache operations will use the store (e.g., Redis)
1811
+ * instead of in-memory cache, enabling persistent cache across deployments.
1812
+ *
1813
+ * @param store - Store adapter implementation.
1814
+ * @param options - Store options (ttl, keyPrefix, fallback).
1815
+ */
1816
+ withStore(store, options) {
1817
+ return new _IgniterCallerBuilder({
1818
+ ...this.state,
1819
+ store,
1820
+ storeOptions: options
1821
+ });
1822
+ }
1823
+ /**
1824
+ * Configures schema-based type safety and validation.
1825
+ *
1826
+ * Enables automatic type inference for requests/responses based on
1827
+ * route and method, with optional runtime validation via StandardSchemaV1
1828
+ * (Zod is supported).
1829
+ *
1830
+ * @param schemas - Schema map keyed by URL path and method.
1831
+ * @param validation - Validation options for request/response checks.
1832
+ *
1833
+ * @example
1834
+ * ```ts
1835
+ * const api = IgniterCaller.create()
1836
+ * .withSchemas({
1837
+ * '/users': {
1838
+ * GET: {
1839
+ * responses: {
1840
+ * 200: z.array(UserSchema),
1841
+ * 401: ErrorSchema,
1842
+ * },
1843
+ * },
1844
+ * POST: {
1845
+ * request: CreateUserSchema,
1846
+ * responses: {
1847
+ * 201: UserSchema,
1848
+ * 400: ValidationErrorSchema,
1849
+ * },
1850
+ * },
1851
+ * },
1852
+ * })
1853
+ * .build()
1854
+ * ```
1855
+ */
1856
+ withSchemas(schemas, validation) {
1857
+ const nextState = {
1858
+ ...this.state,
1859
+ schemas,
1860
+ schemaValidation: validation
1861
+ };
1862
+ return new _IgniterCallerBuilder(nextState);
1863
+ }
1864
+ /**
1865
+ * Attaches telemetry for request monitoring and observability.
1866
+ *
1867
+ * Telemetry is optional and only emits events when a manager is provided.
1868
+ *
1869
+ * Telemetry events emitted by the caller package include:
1870
+ * - `request.execute.started`
1871
+ * - `request.execute.success`
1872
+ * - `request.execute.error`
1873
+ * - `request.timeout.error`
1874
+ * - `cache.read.hit`
1875
+ * - `retry.attempt.started`
1876
+ * - `validation.request.error`
1877
+ * - `validation.response.error`
1878
+ *
1879
+ * @param telemetry - Telemetry manager instance.
1880
+ *
1881
+ * @example
1882
+ * ```ts
1883
+ * import { IgniterTelemetry } from '@igniter-js/telemetry'
1884
+ * import { IgniterCallerTelemetryEvents } from '@igniter-js/caller/telemetry'
1885
+ *
1886
+ * const telemetry = IgniterTelemetry.create()
1887
+ * .withService('my-api')
1888
+ * .addEvents(IgniterCallerTelemetryEvents)
1889
+ * .build()
1890
+ *
1891
+ * const api = IgniterCaller.create()
1892
+ * .withBaseUrl('https://api.example.com')
1893
+ * .withTelemetry(telemetry)
1894
+ * .build()
1895
+ * ```
1896
+ */
1897
+ withTelemetry(telemetry) {
1898
+ return new _IgniterCallerBuilder({ ...this.state, telemetry });
1899
+ }
1900
+ /**
1901
+ * Builds the `IgniterCaller` instance.
1902
+ *
1903
+ * @returns Configured manager instance.
1904
+ */
1905
+ build() {
1906
+ if (this.state.store) {
1907
+ IgniterCallerCacheUtils.setStore(this.state.store, this.state.storeOptions);
1908
+ }
1909
+ const manager = new IgniterCallerManager(this.state.baseURL, {
1910
+ headers: this.state.headers,
1911
+ cookies: this.state.cookies,
1912
+ logger: this.state.logger,
1913
+ telemetry: this.state.telemetry,
1914
+ requestInterceptors: this.state.requestInterceptors,
1915
+ responseInterceptors: this.state.responseInterceptors,
1916
+ schemas: this.state.schemas,
1917
+ schemaValidation: this.state.schemaValidation
1918
+ });
1919
+ this.state.logger?.info("IgniterCaller initialized", {
1920
+ baseURL: this.state.baseURL,
1921
+ hasTelemetry: Boolean(this.state.telemetry),
1922
+ hasStore: Boolean(this.state.store),
1923
+ hasSchemas: Boolean(this.state.schemas)
1924
+ });
1925
+ return manager;
1926
+ }
1927
+ };
1928
+ var IgniterCaller = {
1929
+ create: IgniterCallerBuilder.create
1930
+ };
1931
+ var IgniterCallerSchemaPathBuilder = class _IgniterCallerSchemaPathBuilder {
1932
+ constructor(methods, registry) {
1933
+ this.methods = methods;
1934
+ this.registry = registry;
1935
+ }
1936
+ /**
1937
+ * Creates a new path builder for the provided registry.
1938
+ */
1939
+ static create(registry) {
1940
+ return new _IgniterCallerSchemaPathBuilder({}, registry);
1941
+ }
1942
+ /**
1943
+ * Returns a registry reference helper for a given key.
1944
+ * The helper exposes optional Zod-based wrappers (array/optional/nullable/record).
1945
+ */
1946
+ ref(key) {
1947
+ const schema = this.registry[key];
1948
+ const zodSchema = schema;
1949
+ return {
1950
+ schema,
1951
+ array: () => zod.z.array(zodSchema),
1952
+ nullable: () => zodSchema.nullable(),
1953
+ optional: () => zodSchema.optional(),
1954
+ record: (keyType) => zod.z.record(
1955
+ zod.z.any(),
1956
+ zodSchema
1957
+ )
1958
+ };
1959
+ }
1960
+ /**
1961
+ * Defines a GET endpoint.
1962
+ */
1963
+ get(config) {
1964
+ return this.addMethod("GET", config);
1965
+ }
1966
+ /**
1967
+ * Defines a POST endpoint.
1968
+ */
1969
+ post(config) {
1970
+ return this.addMethod("POST", config);
1971
+ }
1972
+ /**
1973
+ * Defines a PUT endpoint.
1974
+ */
1975
+ put(config) {
1976
+ return this.addMethod("PUT", config);
1977
+ }
1978
+ /**
1979
+ * Defines a PATCH endpoint.
1980
+ */
1981
+ patch(config) {
1982
+ return this.addMethod("PATCH", config);
1983
+ }
1984
+ /**
1985
+ * Defines a DELETE endpoint.
1986
+ */
1987
+ delete(config) {
1988
+ return this.addMethod("DELETE", config);
1989
+ }
1990
+ /**
1991
+ * Defines a HEAD endpoint.
1992
+ */
1993
+ head(config) {
1994
+ return this.addMethod("HEAD", config);
1995
+ }
1996
+ /**
1997
+ * Builds the accumulated method map for the path.
1998
+ */
1999
+ build() {
2000
+ return this.methods;
2001
+ }
2002
+ addMethod(method, config) {
2003
+ if (method in this.methods) {
2004
+ throw new IgniterCallerError({
2005
+ code: "IGNITER_CALLER_SCHEMA_DUPLICATE",
2006
+ operation: "buildSchema",
2007
+ message: `Schema for method "${method}" is already defined on this path.`,
2008
+ statusCode: 400,
2009
+ metadata: { method }
2010
+ });
2011
+ }
2012
+ return new _IgniterCallerSchemaPathBuilder(
2013
+ {
2014
+ ...this.methods,
2015
+ [method]: {
2016
+ ...config
2017
+ }
2018
+ },
2019
+ this.registry
2020
+ );
2021
+ }
2022
+ };
2023
+
2024
+ // src/builders/schema.builder.ts
2025
+ var IgniterCallerSchema = class _IgniterCallerSchema {
2026
+ constructor(schemas, registry) {
2027
+ this.schemas = schemas;
2028
+ this.registry = registry;
2029
+ }
2030
+ /**
2031
+ * Creates a new empty schema builder.
2032
+ */
2033
+ static create() {
2034
+ return new _IgniterCallerSchema({}, {});
2035
+ }
2036
+ /**
2037
+ * Registers a reusable schema in the registry.
2038
+ */
2039
+ schema(key, schema, options) {
2040
+ ensureValidSchemaKey(key);
2041
+ if (key in this.registry) {
2042
+ throw new IgniterCallerError({
2043
+ code: "IGNITER_CALLER_SCHEMA_DUPLICATE",
2044
+ operation: "buildSchema",
2045
+ message: `Schema registry key "${key}" is already defined.`,
2046
+ statusCode: 400,
2047
+ metadata: { key }
2048
+ });
2049
+ }
2050
+ const nextRegistry = {
2051
+ ...this.registry,
2052
+ [key]: schema
2053
+ };
2054
+ void options?.internal;
2055
+ return new _IgniterCallerSchema(this.schemas, nextRegistry);
2056
+ }
2057
+ /**
2058
+ * Defines a path with its methods using a fluent builder.
2059
+ */
2060
+ path(path, builder) {
2061
+ ensureValidPath(path);
2062
+ const pathBuilder = IgniterCallerSchemaPathBuilder.create(this.registry);
2063
+ const builtMethods = builder(pathBuilder).build();
2064
+ const existing = this.schemas[path] ?? {};
2065
+ for (const method of Object.keys(
2066
+ builtMethods
2067
+ )) {
2068
+ if (method in existing) {
2069
+ throw new IgniterCallerError({
2070
+ code: "IGNITER_CALLER_SCHEMA_DUPLICATE",
2071
+ operation: "buildSchema",
2072
+ message: `Schema for "${path}" with method "${method}" is already defined.`,
2073
+ statusCode: 400,
2074
+ metadata: { path, method }
2075
+ });
2076
+ }
2077
+ }
2078
+ const merged = {
2079
+ ...existing,
2080
+ ...builtMethods
2081
+ };
2082
+ const nextSchemas = {
2083
+ ...this.schemas,
2084
+ [path]: merged
2085
+ };
2086
+ return new _IgniterCallerSchema(nextSchemas, this.registry);
2087
+ }
2088
+ /**
2089
+ * Builds the schema map and attaches inference + runtime helpers.
2090
+ */
2091
+ build() {
2092
+ const result = {
2093
+ ...this.schemas
2094
+ };
2095
+ const inferHelpers = createInferHelpers();
2096
+ const getHelpers = createGetHelpers(this.schemas, this.registry);
2097
+ Object.defineProperty(result, "$Infer", {
2098
+ value: inferHelpers,
2099
+ enumerable: false
2100
+ });
2101
+ Object.defineProperty(result, "get", {
2102
+ value: getHelpers,
2103
+ enumerable: false
2104
+ });
2105
+ return result;
2106
+ }
2107
+ };
2108
+ function createInferHelpers() {
2109
+ return {
2110
+ Path: void 0,
2111
+ Endpoint: (() => void 0),
2112
+ Request: (() => void 0),
2113
+ Response: (() => void 0),
2114
+ Responses: (() => void 0),
2115
+ Schema: (() => void 0)
2116
+ };
2117
+ }
2118
+ function createGetHelpers(schemas, registry) {
2119
+ return {
2120
+ path: (path) => schemas[path],
2121
+ endpoint: (path, method) => schemas[path][method],
2122
+ request: (path, method) => schemas[path][method]?.request,
2123
+ response: (path, method, status) => schemas[path][method]?.responses?.[status],
2124
+ schema: (key) => registry[key]
2125
+ };
2126
+ }
2127
+ function ensureValidPath(path) {
2128
+ if (!path || path.trim().length === 0) {
2129
+ throw new IgniterCallerError({
2130
+ code: "IGNITER_CALLER_SCHEMA_INVALID",
2131
+ operation: "buildSchema",
2132
+ message: "Path cannot be empty.",
2133
+ statusCode: 400
2134
+ });
2135
+ }
2136
+ if (!path.startsWith("/")) {
2137
+ throw new IgniterCallerError({
2138
+ code: "IGNITER_CALLER_SCHEMA_INVALID",
2139
+ operation: "buildSchema",
2140
+ message: `Path "${path}" must start with "/".`,
2141
+ statusCode: 400,
2142
+ metadata: { path }
2143
+ });
2144
+ }
2145
+ }
2146
+ function ensureValidSchemaKey(key) {
2147
+ if (!key || key.trim().length === 0) {
2148
+ throw new IgniterCallerError({
2149
+ code: "IGNITER_CALLER_SCHEMA_INVALID",
2150
+ operation: "buildSchema",
2151
+ message: "Schema registry key cannot be empty.",
2152
+ statusCode: 400
2153
+ });
2154
+ }
2155
+ }
2156
+
2157
+ // src/adapters/mock.adapter.ts
2158
+ var MockCallerStoreAdapter = class _MockCallerStoreAdapter {
2159
+ constructor() {
2160
+ /** Underlying in-memory store. */
2161
+ this.client = /* @__PURE__ */ new Map();
2162
+ /** Tracks all calls for assertions. */
2163
+ this.calls = {
2164
+ get: 0,
2165
+ set: 0,
2166
+ delete: 0,
2167
+ has: 0
2168
+ };
2169
+ /** Captures recent operations. */
2170
+ this.history = {
2171
+ get: [],
2172
+ set: [],
2173
+ delete: [],
2174
+ has: []
2175
+ };
2176
+ }
2177
+ /** Creates a new mock adapter instance. */
2178
+ static create() {
2179
+ return new _MockCallerStoreAdapter();
2180
+ }
2181
+ /**
2182
+ * Retrieves a cached value by key.
2183
+ *
2184
+ * @param key - Cache key (without prefix).
2185
+ * @returns Cached value or null.
2186
+ */
2187
+ async get(key) {
2188
+ this.calls.get += 1;
2189
+ this.history.get.push(key);
2190
+ return this.client.has(key) ? this.client.get(key) : null;
2191
+ }
2192
+ /**
2193
+ * Stores a cached value.
2194
+ *
2195
+ * @param key - Cache key (without prefix).
2196
+ * @param value - Value to store.
2197
+ * @param options - Cache options (ttl, etc).
2198
+ */
2199
+ async set(key, value, options) {
2200
+ this.calls.set += 1;
2201
+ this.history.set.push({ key, value, options });
2202
+ this.client.set(key, value);
2203
+ }
2204
+ /**
2205
+ * Removes a cached value.
2206
+ *
2207
+ * @param key - Cache key (without prefix).
2208
+ */
2209
+ async delete(key) {
2210
+ this.calls.delete += 1;
2211
+ this.history.delete.push(key);
2212
+ this.client.delete(key);
2213
+ }
2214
+ /**
2215
+ * Checks if a cached value exists.
2216
+ *
2217
+ * @param key - Cache key (without prefix).
2218
+ * @returns True when the key exists.
2219
+ */
2220
+ async has(key) {
2221
+ this.calls.has += 1;
2222
+ this.history.has.push(key);
2223
+ return this.client.has(key);
2224
+ }
2225
+ /**
2226
+ * Clears all tracked state.
2227
+ *
2228
+ * @returns Nothing.
2229
+ */
2230
+ clear() {
2231
+ this.client.clear();
2232
+ this.calls.get = 0;
2233
+ this.calls.set = 0;
2234
+ this.calls.delete = 0;
2235
+ this.calls.has = 0;
2236
+ this.history.get = [];
2237
+ this.history.set = [];
2238
+ this.history.delete = [];
2239
+ this.history.has = [];
2240
+ }
2241
+ };
1577
2242
 
1578
2243
  // src/utils/testing.ts
1579
2244
  var IgniterCallerMock = class {
1580
2245
  /**
1581
2246
  * Creates a successful mock response.
2247
+ *
2248
+ * @param data - Mock response data.
1582
2249
  */
1583
2250
  static mockResponse(data) {
1584
2251
  return { data, error: void 0 };
1585
2252
  }
1586
2253
  /**
1587
2254
  * Creates an error mock response.
2255
+ *
2256
+ * @param code - Error code to use.
2257
+ * @param message - Optional error message.
1588
2258
  */
1589
2259
  static mockError(code, message = "Mock error") {
1590
2260
  return {
@@ -1598,6 +2268,9 @@ var IgniterCallerMock = class {
1598
2268
  }
1599
2269
  /**
1600
2270
  * Creates a successful file download mock.
2271
+ *
2272
+ * @param filename - File name for the mock.
2273
+ * @param content - File contents as string or Blob.
1601
2274
  */
1602
2275
  static mockFile(filename, content) {
1603
2276
  const blob = typeof content === "string" ? new Blob([content]) : content;
@@ -1606,6 +2279,8 @@ var IgniterCallerMock = class {
1606
2279
  }
1607
2280
  /**
1608
2281
  * Creates a failed file download mock.
2282
+ *
2283
+ * @param message - Optional error message.
1609
2284
  */
1610
2285
  static mockFileError(message = "Mock file error") {
1611
2286
  return {
@@ -1625,9 +2300,13 @@ exports.IgniterCallerBuilder = IgniterCallerBuilder;
1625
2300
  exports.IgniterCallerCacheUtils = IgniterCallerCacheUtils;
1626
2301
  exports.IgniterCallerError = IgniterCallerError;
1627
2302
  exports.IgniterCallerEvents = IgniterCallerEvents;
2303
+ exports.IgniterCallerManager = IgniterCallerManager;
1628
2304
  exports.IgniterCallerMock = IgniterCallerMock;
1629
2305
  exports.IgniterCallerRequestBuilder = IgniterCallerRequestBuilder;
2306
+ exports.IgniterCallerSchema = IgniterCallerSchema;
2307
+ exports.IgniterCallerSchemaPathBuilder = IgniterCallerSchemaPathBuilder;
1630
2308
  exports.IgniterCallerSchemaUtils = IgniterCallerSchemaUtils;
1631
2309
  exports.IgniterCallerUrlUtils = IgniterCallerUrlUtils;
2310
+ exports.MockCallerStoreAdapter = MockCallerStoreAdapter;
1632
2311
  //# sourceMappingURL=index.js.map
1633
2312
  //# sourceMappingURL=index.js.map