@igniter-js/caller 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,112 +1,15 @@
1
1
  'use strict';
2
2
 
3
- var core = require('@igniter-js/core');
3
+ var common = require('@igniter-js/common');
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 common.IgniterError {
67
8
  /**
68
- * Configures schema-based type safety and validation.
69
- *
70
- * Enables automatic type inference for requests/responses based on
71
- * route and method, with optional runtime validation via Zod.
9
+ * Creates a new typed caller error.
72
10
  *
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
- * ```
95
- */
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.
11
+ * @param payload - Error payload with code, message, and metadata.
104
12
  */
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,15 +499,19 @@ 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;
537
506
  this.schemas = params.schemas;
538
507
  this.schemaValidation = params.schemaValidation;
508
+ this.mock = params.mock;
539
509
  }
540
510
  /**
541
511
  * Sets the HTTP method for this request.
542
512
  * @internal Used by IgniterCaller.request() for generic requests.
513
+ *
514
+ * @param method - HTTP method for the request.
543
515
  */
544
516
  _setMethod(method) {
545
517
  this.options.method = method;
@@ -548,6 +520,8 @@ var IgniterCallerRequestBuilder = class {
548
520
  /**
549
521
  * Sets the URL for this request.
550
522
  * @internal Used when URL is passed to HTTP method directly.
523
+ *
524
+ * @param url - Request URL or path.
551
525
  */
552
526
  _setUrl(url) {
553
527
  this.options.url = url;
@@ -555,6 +529,8 @@ var IgniterCallerRequestBuilder = class {
555
529
  }
556
530
  /**
557
531
  * Overrides the logger for this request chain.
532
+ *
533
+ * @param logger - Logger implementation from `@igniter-js/common`.
558
534
  */
559
535
  withLogger(logger) {
560
536
  this.logger = logger;
@@ -562,6 +538,8 @@ var IgniterCallerRequestBuilder = class {
562
538
  }
563
539
  /**
564
540
  * Sets the request URL.
541
+ *
542
+ * @param url - Request URL or path.
565
543
  */
566
544
  url(url) {
567
545
  this.options.url = url;
@@ -570,6 +548,8 @@ var IgniterCallerRequestBuilder = class {
570
548
  /**
571
549
  * Sets the request body.
572
550
  * For GET/HEAD requests, body will be automatically converted to query params.
551
+ *
552
+ * @param body - Body payload for the request.
573
553
  */
574
554
  body(body) {
575
555
  this.options.body = body;
@@ -577,6 +557,8 @@ var IgniterCallerRequestBuilder = class {
577
557
  }
578
558
  /**
579
559
  * Sets URL query parameters.
560
+ *
561
+ * @param params - Query string parameters.
580
562
  */
581
563
  params(params) {
582
564
  this.options.params = params;
@@ -584,6 +566,8 @@ var IgniterCallerRequestBuilder = class {
584
566
  }
585
567
  /**
586
568
  * Merges additional headers into the request.
569
+ *
570
+ * @param headers - Header map merged into existing headers.
587
571
  */
588
572
  headers(headers) {
589
573
  this.options.headers = { ...this.options.headers, ...headers };
@@ -591,6 +575,8 @@ var IgniterCallerRequestBuilder = class {
591
575
  }
592
576
  /**
593
577
  * Sets request timeout in milliseconds.
578
+ *
579
+ * @param timeout - Timeout in milliseconds.
594
580
  */
595
581
  timeout(timeout) {
596
582
  this.options.timeout = timeout;
@@ -598,6 +584,9 @@ var IgniterCallerRequestBuilder = class {
598
584
  }
599
585
  /**
600
586
  * Sets cache strategy and optional cache key.
587
+ *
588
+ * @param cache - Cache strategy for the request.
589
+ * @param key - Optional cache key override.
601
590
  */
602
591
  cache(cache, key) {
603
592
  this.options.cache = cache;
@@ -606,6 +595,9 @@ var IgniterCallerRequestBuilder = class {
606
595
  }
607
596
  /**
608
597
  * Configures retry behavior for failed requests.
598
+ *
599
+ * @param maxAttempts - Maximum number of attempts.
600
+ * @param options - Retry options excluding `maxAttempts`.
609
601
  */
610
602
  retry(maxAttempts, options) {
611
603
  this.retryOptions = { maxAttempts, ...options };
@@ -613,6 +605,8 @@ var IgniterCallerRequestBuilder = class {
613
605
  }
614
606
  /**
615
607
  * Provides a fallback value if the request fails.
608
+ *
609
+ * @param fn - Fallback factory called when the request fails.
616
610
  */
617
611
  fallback(fn) {
618
612
  this.fallbackFn = fn;
@@ -620,6 +614,8 @@ var IgniterCallerRequestBuilder = class {
620
614
  }
621
615
  /**
622
616
  * Sets cache stale time in milliseconds.
617
+ *
618
+ * @param milliseconds - Stale time in milliseconds.
623
619
  */
624
620
  stale(milliseconds) {
625
621
  this.staleTime = milliseconds;
@@ -633,6 +629,8 @@ var IgniterCallerRequestBuilder = class {
633
629
  *
634
630
  * The actual parsing is based on Content-Type headers, not this setting.
635
631
  *
632
+ * @param schema - Zod/StandardSchema instance for validation (optional).
633
+ *
636
634
  * @example
637
635
  * ```ts
638
636
  * // With Zod schema (validates JSON response)
@@ -651,6 +649,8 @@ var IgniterCallerRequestBuilder = class {
651
649
  /**
652
650
  * Downloads a file via GET request.
653
651
  * @deprecated Use `.responseType<File>().execute()` instead. The response type is auto-detected.
652
+ *
653
+ * @param url - URL or path to download.
654
654
  */
655
655
  getFile(url) {
656
656
  this.options.method = "GET";
@@ -725,7 +725,7 @@ var IgniterCallerRequestBuilder = class {
725
725
  statusCode: 408,
726
726
  logger: this.logger,
727
727
  metadata: { url: finalUrl },
728
- cause: error
728
+ cause: error instanceof Error ? error : void 0
729
729
  })
730
730
  };
731
731
  }
@@ -737,7 +737,7 @@ var IgniterCallerRequestBuilder = class {
737
737
  message: error?.message || "Failed to download file",
738
738
  logger: this.logger,
739
739
  metadata: { url: finalUrl },
740
- cause: error
740
+ cause: error instanceof Error ? error : void 0
741
741
  })
742
742
  };
743
743
  }
@@ -756,8 +756,32 @@ var IgniterCallerRequestBuilder = class {
756
756
  * - `application/octet-stream` → returned as Blob
757
757
  *
758
758
  * Schema validation (if configured) only runs for validatable content types (JSON, XML, CSV).
759
+ *
760
+ * @returns Response envelope with data or error.
759
761
  */
760
762
  async execute() {
763
+ const startTime = Date.now();
764
+ const { safeUrl } = this.resolveUrl();
765
+ const method = this.options.method;
766
+ const baseURL = this.options.baseURL;
767
+ const timeoutMs = this.options.timeout;
768
+ this.telemetry?.emit(
769
+ "igniter.caller.request.execute.started",
770
+ {
771
+ level: "debug",
772
+ attributes: {
773
+ "ctx.request.method": method,
774
+ "ctx.request.url": safeUrl,
775
+ "ctx.request.baseUrl": baseURL,
776
+ "ctx.request.timeoutMs": timeoutMs
777
+ }
778
+ }
779
+ );
780
+ this.logger?.debug("IgniterCaller.request.execute started", {
781
+ method,
782
+ url: safeUrl,
783
+ baseURL
784
+ });
761
785
  const effectiveCacheKey = this.cacheKey || this.options.url;
762
786
  if (effectiveCacheKey && this.staleTime) {
763
787
  const cached = await IgniterCallerCacheUtils.get(
@@ -765,8 +789,36 @@ var IgniterCallerRequestBuilder = class {
765
789
  this.staleTime
766
790
  );
767
791
  if (cached !== void 0) {
768
- this.logger?.debug("IgniterCaller.execute cache hit", {
769
- key: effectiveCacheKey
792
+ this.telemetry?.emit(
793
+ "igniter.caller.cache.read.hit",
794
+ {
795
+ level: "debug",
796
+ attributes: {
797
+ "ctx.request.method": method,
798
+ "ctx.request.url": safeUrl,
799
+ "ctx.cache.key": effectiveCacheKey,
800
+ "ctx.cache.staleTime": this.staleTime
801
+ }
802
+ }
803
+ );
804
+ const durationMs2 = Date.now() - startTime;
805
+ this.telemetry?.emit(
806
+ "igniter.caller.request.execute.success",
807
+ {
808
+ level: "info",
809
+ attributes: {
810
+ "ctx.request.method": method,
811
+ "ctx.request.url": safeUrl,
812
+ "ctx.request.durationMs": durationMs2,
813
+ "ctx.cache.hit": true
814
+ }
815
+ }
816
+ );
817
+ this.logger?.info("IgniterCaller.request.execute success (cache)", {
818
+ key: effectiveCacheKey,
819
+ method,
820
+ url: safeUrl,
821
+ durationMs: durationMs2
770
822
  });
771
823
  const cachedResult = {
772
824
  data: cached,
@@ -778,13 +830,33 @@ var IgniterCallerRequestBuilder = class {
778
830
  }
779
831
  const result = await this.executeWithRetry();
780
832
  if (result.error && this.fallbackFn) {
781
- this.logger?.debug("IgniterCaller.execute applying fallback", {
833
+ this.logger?.debug("IgniterCaller.request.execute applying fallback", {
834
+ method,
835
+ url: safeUrl,
782
836
  error: result.error
783
837
  });
784
838
  const fallbackResult = {
785
839
  data: this.fallbackFn(),
786
840
  error: void 0
787
841
  };
842
+ const durationMs2 = Date.now() - startTime;
843
+ this.telemetry?.emit(
844
+ "igniter.caller.request.execute.success",
845
+ {
846
+ level: "info",
847
+ attributes: {
848
+ "ctx.request.method": method,
849
+ "ctx.request.url": safeUrl,
850
+ "ctx.request.durationMs": durationMs2,
851
+ "ctx.request.fallback": true
852
+ }
853
+ }
854
+ );
855
+ this.logger?.info("IgniterCaller.request.execute success (fallback)", {
856
+ method,
857
+ url: safeUrl,
858
+ durationMs: durationMs2
859
+ });
788
860
  await this.emitEvent(fallbackResult);
789
861
  return fallbackResult;
790
862
  }
@@ -795,12 +867,57 @@ var IgniterCallerRequestBuilder = class {
795
867
  this.staleTime
796
868
  );
797
869
  }
870
+ const durationMs = Date.now() - startTime;
871
+ if (result.error) {
872
+ this.telemetry?.emit(
873
+ "igniter.caller.request.execute.error",
874
+ {
875
+ level: "error",
876
+ attributes: {
877
+ "ctx.request.method": method,
878
+ "ctx.request.url": safeUrl,
879
+ "ctx.request.durationMs": durationMs,
880
+ "ctx.error.code": result.error instanceof IgniterCallerError ? result.error.code : "IGNITER_CALLER_UNKNOWN_ERROR",
881
+ "ctx.error.message": result.error.message,
882
+ "ctx.response.status": result.status
883
+ }
884
+ }
885
+ );
886
+ this.logger?.error("IgniterCaller.request.execute failed", {
887
+ method,
888
+ url: safeUrl,
889
+ durationMs,
890
+ error: result.error
891
+ });
892
+ } else {
893
+ const contentType = result.headers?.get("content-type") || void 0;
894
+ this.telemetry?.emit(
895
+ "igniter.caller.request.execute.success",
896
+ {
897
+ level: "info",
898
+ attributes: {
899
+ "ctx.request.method": method,
900
+ "ctx.request.url": safeUrl,
901
+ "ctx.request.durationMs": durationMs,
902
+ "ctx.response.status": result.status,
903
+ "ctx.response.contentType": contentType,
904
+ "ctx.cache.hit": false
905
+ }
906
+ }
907
+ );
908
+ this.logger?.info("IgniterCaller.request.execute success", {
909
+ method,
910
+ url: safeUrl,
911
+ durationMs,
912
+ status: result.status
913
+ });
914
+ }
798
915
  await this.emitEvent(result);
799
916
  return result;
800
917
  }
801
918
  async executeWithRetry() {
802
919
  const maxAttempts = this.retryOptions?.maxAttempts || 1;
803
- const baseDelay = this.retryOptions?.baseDelay || 1e3;
920
+ const baseDelay = this.retryOptions?.baseDelay ?? 1e3;
804
921
  const backoff = this.retryOptions?.backoff || "linear";
805
922
  const retryOnStatus = this.retryOptions?.retryOnStatus || [
806
923
  408,
@@ -810,13 +927,30 @@ var IgniterCallerRequestBuilder = class {
810
927
  503,
811
928
  504
812
929
  ];
930
+ const { safeUrl } = this.resolveUrl();
931
+ const method = this.options.method;
813
932
  let lastError;
814
933
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
815
934
  if (attempt > 0) {
816
935
  const delay = backoff === "exponential" ? baseDelay * 2 ** (attempt - 1) : baseDelay * attempt;
817
- this.logger?.debug("IgniterCaller.execute retrying", {
818
- attempt,
819
- delay
936
+ this.telemetry?.emit(
937
+ "igniter.caller.retry.attempt.started",
938
+ {
939
+ level: "debug",
940
+ attributes: {
941
+ "ctx.request.method": method,
942
+ "ctx.request.url": safeUrl,
943
+ "ctx.retry.attempt": attempt + 1,
944
+ "ctx.retry.maxAttempts": maxAttempts,
945
+ "ctx.retry.delayMs": delay
946
+ }
947
+ }
948
+ );
949
+ this.logger?.debug("IgniterCaller.request.execute retrying", {
950
+ method,
951
+ url: safeUrl,
952
+ attempt: attempt + 1,
953
+ delayMs: delay
820
954
  });
821
955
  await new Promise((resolve) => setTimeout(resolve, delay));
822
956
  }
@@ -837,10 +971,8 @@ var IgniterCallerRequestBuilder = class {
837
971
  }
838
972
  async executeSingleRequest() {
839
973
  let { url, requestInit, controller, timeoutId } = this.buildRequest();
840
- this.logger?.debug("IgniterCaller.execute started", {
841
- method: this.options.method,
842
- url
843
- });
974
+ const { safeUrl } = this.resolveUrl();
975
+ const method = this.options.method;
844
976
  if (this.requestInterceptors && this.requestInterceptors.length > 0) {
845
977
  let modifiedOptions = { ...this.options, url };
846
978
  for (const interceptor of this.requestInterceptors) {
@@ -870,13 +1002,36 @@ var IgniterCallerRequestBuilder = class {
870
1002
  );
871
1003
  } catch (error) {
872
1004
  clearTimeout(timeoutId);
1005
+ const err = error;
1006
+ this.telemetry?.emit(
1007
+ "igniter.caller.validation.request.error",
1008
+ {
1009
+ level: "error",
1010
+ attributes: {
1011
+ "ctx.request.method": method,
1012
+ "ctx.request.url": safeUrl,
1013
+ "ctx.validation.type": "request",
1014
+ "ctx.validation.error": err.message
1015
+ }
1016
+ }
1017
+ );
1018
+ this.logger?.error("IgniterCaller.request.validation failed", {
1019
+ method,
1020
+ url: safeUrl,
1021
+ error: err
1022
+ });
873
1023
  return {
874
1024
  data: void 0,
875
- error
1025
+ error: err
876
1026
  };
877
1027
  }
878
1028
  }
879
1029
  }
1030
+ const mockResult = await this.executeMockRequest(url, safeUrl);
1031
+ if (mockResult) {
1032
+ clearTimeout(timeoutId);
1033
+ return mockResult;
1034
+ }
880
1035
  try {
881
1036
  const httpResponse = await fetch(url, {
882
1037
  ...requestInit,
@@ -926,9 +1081,29 @@ var IgniterCallerRequestBuilder = class {
926
1081
  this.logger
927
1082
  );
928
1083
  } catch (error) {
1084
+ const err = error;
1085
+ this.telemetry?.emit(
1086
+ "igniter.caller.validation.response.error",
1087
+ {
1088
+ level: "error",
1089
+ attributes: {
1090
+ "ctx.request.method": method,
1091
+ "ctx.request.url": safeUrl,
1092
+ "ctx.validation.type": "response",
1093
+ "ctx.validation.error": err.message,
1094
+ "ctx.response.status": httpResponse.status
1095
+ }
1096
+ }
1097
+ );
1098
+ this.logger?.error("IgniterCaller.response.validation failed", {
1099
+ method,
1100
+ url: safeUrl,
1101
+ status: httpResponse.status,
1102
+ error: err
1103
+ });
929
1104
  return {
930
1105
  data: void 0,
931
- error,
1106
+ error: err,
932
1107
  status: httpResponse.status,
933
1108
  headers: httpResponse.headers
934
1109
  };
@@ -940,20 +1115,40 @@ var IgniterCallerRequestBuilder = class {
940
1115
  const zodSchema = this.responseTypeSchema;
941
1116
  const result = zodSchema.safeParse(data);
942
1117
  if (!result.success) {
1118
+ const err = new IgniterCallerError({
1119
+ code: "IGNITER_CALLER_RESPONSE_VALIDATION_FAILED",
1120
+ operation: "parseResponse",
1121
+ message: `Response validation failed: ${result.error.message}`,
1122
+ logger: this.logger,
1123
+ statusCode: httpResponse.status,
1124
+ metadata: {
1125
+ method: this.options.method,
1126
+ url
1127
+ },
1128
+ cause: result.error
1129
+ });
1130
+ this.telemetry?.emit(
1131
+ "igniter.caller.validation.response.error",
1132
+ {
1133
+ level: "error",
1134
+ attributes: {
1135
+ "ctx.request.method": method,
1136
+ "ctx.request.url": safeUrl,
1137
+ "ctx.validation.type": "response",
1138
+ "ctx.validation.error": err.message,
1139
+ "ctx.response.status": httpResponse.status
1140
+ }
1141
+ }
1142
+ );
1143
+ this.logger?.error("IgniterCaller.response.validation failed", {
1144
+ method,
1145
+ url: safeUrl,
1146
+ status: httpResponse.status,
1147
+ error: err
1148
+ });
943
1149
  return {
944
1150
  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
- }),
1151
+ error: err,
957
1152
  status: httpResponse.status,
958
1153
  headers: httpResponse.headers
959
1154
  };
@@ -964,40 +1159,80 @@ var IgniterCallerRequestBuilder = class {
964
1159
  const standardSchema = this.responseTypeSchema;
965
1160
  const result = await standardSchema["~standard"].validate(data);
966
1161
  if (result.issues) {
1162
+ const err = new IgniterCallerError({
1163
+ code: "IGNITER_CALLER_RESPONSE_VALIDATION_FAILED",
1164
+ operation: "parseResponse",
1165
+ message: `Response validation failed`,
1166
+ logger: this.logger,
1167
+ statusCode: httpResponse.status,
1168
+ metadata: {
1169
+ method: this.options.method,
1170
+ url,
1171
+ issues: result.issues
1172
+ }
1173
+ });
1174
+ this.telemetry?.emit(
1175
+ "igniter.caller.validation.response.error",
1176
+ {
1177
+ level: "error",
1178
+ attributes: {
1179
+ "ctx.request.method": method,
1180
+ "ctx.request.url": safeUrl,
1181
+ "ctx.validation.type": "response",
1182
+ "ctx.validation.error": err.message,
1183
+ "ctx.response.status": httpResponse.status
1184
+ }
1185
+ }
1186
+ );
1187
+ this.logger?.error("IgniterCaller.response.validation failed", {
1188
+ method,
1189
+ url: safeUrl,
1190
+ status: httpResponse.status,
1191
+ error: err
1192
+ });
967
1193
  return {
968
1194
  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
- }),
1195
+ error: err,
981
1196
  status: httpResponse.status,
982
1197
  headers: httpResponse.headers
983
1198
  };
984
1199
  }
985
1200
  data = result.value;
986
1201
  } catch (error) {
1202
+ const err = new IgniterCallerError({
1203
+ code: "IGNITER_CALLER_RESPONSE_VALIDATION_FAILED",
1204
+ operation: "parseResponse",
1205
+ message: error?.message || "Response validation failed",
1206
+ logger: this.logger,
1207
+ statusCode: httpResponse.status,
1208
+ metadata: {
1209
+ method: this.options.method,
1210
+ url
1211
+ },
1212
+ cause: error instanceof Error ? error : void 0
1213
+ });
1214
+ this.telemetry?.emit(
1215
+ "igniter.caller.validation.response.error",
1216
+ {
1217
+ level: "error",
1218
+ attributes: {
1219
+ "ctx.request.method": method,
1220
+ "ctx.request.url": safeUrl,
1221
+ "ctx.validation.type": "response",
1222
+ "ctx.validation.error": err.message,
1223
+ "ctx.response.status": httpResponse.status
1224
+ }
1225
+ }
1226
+ );
1227
+ this.logger?.error("IgniterCaller.response.validation failed", {
1228
+ method,
1229
+ url: safeUrl,
1230
+ status: httpResponse.status,
1231
+ error: err
1232
+ });
987
1233
  return {
988
1234
  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
- }),
1235
+ error: err,
1001
1236
  status: httpResponse.status,
1002
1237
  headers: httpResponse.headers
1003
1238
  };
@@ -1020,20 +1255,38 @@ var IgniterCallerRequestBuilder = class {
1020
1255
  } catch (error) {
1021
1256
  clearTimeout(timeoutId);
1022
1257
  if (error instanceof Error && error.name === "AbortError") {
1258
+ const err = new IgniterCallerError({
1259
+ code: "IGNITER_CALLER_TIMEOUT",
1260
+ operation: "execute",
1261
+ message: `Request timeout after ${this.options.timeout || 3e4}ms`,
1262
+ statusCode: 408,
1263
+ logger: this.logger,
1264
+ metadata: {
1265
+ method: this.options.method,
1266
+ url
1267
+ },
1268
+ cause: error instanceof Error ? error : void 0
1269
+ });
1270
+ this.telemetry?.emit(
1271
+ "igniter.caller.request.timeout.error",
1272
+ {
1273
+ level: "error",
1274
+ attributes: {
1275
+ "ctx.request.method": method,
1276
+ "ctx.request.url": safeUrl,
1277
+ "ctx.request.timeoutMs": this.options.timeout || 3e4
1278
+ }
1279
+ }
1280
+ );
1281
+ this.logger?.error("IgniterCaller.request.execute timeout", {
1282
+ method,
1283
+ url: safeUrl,
1284
+ timeoutMs: this.options.timeout || 3e4,
1285
+ error: err
1286
+ });
1023
1287
  return {
1024
1288
  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
- })
1289
+ error: err
1037
1290
  };
1038
1291
  }
1039
1292
  return {
@@ -1048,13 +1301,13 @@ var IgniterCallerRequestBuilder = class {
1048
1301
  method: this.options.method,
1049
1302
  url
1050
1303
  },
1051
- cause: error
1304
+ cause: error instanceof Error ? error : void 0
1052
1305
  })
1053
1306
  };
1054
1307
  }
1055
1308
  }
1056
- buildRequest() {
1057
- const { method, url, body, params, headers, timeout, baseURL, cache } = this.options;
1309
+ resolveUrl() {
1310
+ const { method, url, body, params, baseURL } = this.options;
1058
1311
  let finalParams = params;
1059
1312
  if ((method === "GET" || method === "HEAD") && body && typeof body === "object") {
1060
1313
  const bodyParams = {};
@@ -1070,6 +1323,14 @@ var IgniterCallerRequestBuilder = class {
1070
1323
  baseURL,
1071
1324
  query: finalParams
1072
1325
  });
1326
+ return {
1327
+ url: fullUrl,
1328
+ safeUrl: fullUrl.split("?")[0]
1329
+ };
1330
+ }
1331
+ buildRequest() {
1332
+ const { method, body, headers, timeout, cache } = this.options;
1333
+ const { url } = this.resolveUrl();
1073
1334
  const shouldIncludeBody = body && method !== "GET" && method !== "HEAD";
1074
1335
  const rawBody = shouldIncludeBody && IgniterCallerBodyUtils.isRawBody(body);
1075
1336
  const finalHeaders = IgniterCallerBodyUtils.normalizeHeadersForBody(
@@ -1085,76 +1346,366 @@ var IgniterCallerRequestBuilder = class {
1085
1346
  const controller = new AbortController();
1086
1347
  const timeoutId = setTimeout(() => controller.abort(), timeout || 3e4);
1087
1348
  return {
1088
- url: fullUrl,
1349
+ url,
1089
1350
  requestInit,
1090
1351
  controller,
1091
1352
  timeoutId
1092
1353
  };
1093
1354
  }
1094
1355
  /**
1095
- * Emits event for this response using injected emitter.
1356
+ * Normalizes a URL into a path for mock matching.
1096
1357
  */
1097
- async emitEvent(result) {
1098
- if (this.eventEmitter) {
1358
+ normalizeMockPath(url, baseURL) {
1359
+ if (/^https?:\/\//i.test(url)) {
1099
1360
  try {
1100
- await this.eventEmitter(this.options.url, this.options.method, result);
1101
- } catch (error) {
1102
- this.logger?.debug("Failed to emit event", { error });
1361
+ return new URL(url).pathname;
1362
+ } catch {
1363
+ return url;
1103
1364
  }
1104
1365
  }
1105
- }
1106
- };
1107
-
1108
- // src/core/igniter-caller-events.ts
1109
- var IgniterCallerEvents = class {
1110
- constructor() {
1111
- this.listeners = /* @__PURE__ */ new Map();
1112
- this.patternListeners = /* @__PURE__ */ new Map();
1366
+ if (baseURL && /^https?:\/\//i.test(baseURL)) {
1367
+ try {
1368
+ const resolved = IgniterCallerUrlUtils.buildUrl({ url, baseURL });
1369
+ return new URL(resolved).pathname;
1370
+ } catch {
1371
+ return url;
1372
+ }
1373
+ }
1374
+ return url.startsWith("/") ? url : `/${url}`;
1113
1375
  }
1114
1376
  /**
1115
- * Registers a listener for a specific URL or pattern.
1116
- *
1117
- * @param pattern URL string (exact match) or RegExp pattern
1118
- * @param callback Function to execute when a response matches
1119
- * @returns Cleanup function to remove the listener
1120
- *
1121
- * @example
1122
- * ```ts
1123
- * // Listen to specific endpoint
1124
- * const cleanup = api.on('/users', (result) => {
1125
- * console.log('Users fetched:', result.data)
1126
- * })
1127
- *
1128
- * // Listen to pattern
1129
- * api.on(/^\/users\/\d+$/, (result) => {
1130
- * console.log('User detail fetched')
1131
- * })
1132
- *
1133
- * // Cleanup when done
1134
- * cleanup()
1135
- * ```
1377
+ * Resolves the final query object (merges GET/HEAD body into params).
1136
1378
  */
1137
- on(pattern, callback) {
1138
- if (typeof pattern === "string") {
1139
- if (!this.listeners.has(pattern)) {
1140
- this.listeners.set(pattern, /* @__PURE__ */ new Set());
1141
- }
1142
- const callbacks2 = this.listeners.get(pattern);
1143
- if (callbacks2) {
1144
- callbacks2.add(callback);
1145
- }
1146
- return () => {
1147
- const callbacks3 = this.listeners.get(pattern);
1148
- if (callbacks3) {
1149
- callbacks3.delete(callback);
1150
- if (callbacks3.size === 0) {
1151
- this.listeners.delete(pattern);
1152
- }
1379
+ getFinalQuery() {
1380
+ const { method, body, params } = this.options;
1381
+ if ((method === "GET" || method === "HEAD") && body && typeof body === "object") {
1382
+ const bodyParams = {};
1383
+ for (const [key, value] of Object.entries(body)) {
1384
+ if (value !== void 0 && value !== null) {
1385
+ bodyParams[key] = String(value);
1153
1386
  }
1154
- };
1387
+ }
1388
+ return { ...bodyParams, ...params || {} };
1155
1389
  }
1156
- if (!this.patternListeners.has(pattern)) {
1157
- this.patternListeners.set(pattern, /* @__PURE__ */ new Set());
1390
+ return params || {};
1391
+ }
1392
+ /**
1393
+ * Executes a mock handler when enabled and matched.
1394
+ */
1395
+ async executeMockRequest(url, safeUrl) {
1396
+ if (!this.mock?.enabled) return null;
1397
+ const path = this.normalizeMockPath(this.options.url, this.options.baseURL);
1398
+ const method = this.options.method;
1399
+ const resolved = this.mock.mock.resolve(path, method);
1400
+ if (!resolved) return null;
1401
+ const query = this.getFinalQuery();
1402
+ const mockRequest = {
1403
+ method,
1404
+ path: resolved.path,
1405
+ url,
1406
+ safeUrl,
1407
+ baseURL: this.options.baseURL,
1408
+ headers: this.options.headers || {},
1409
+ query,
1410
+ params: resolved.params || {},
1411
+ body: this.options.body,
1412
+ timeoutMs: this.options.timeout,
1413
+ cache: this.options.cache,
1414
+ cacheKey: this.cacheKey,
1415
+ staleTime: this.staleTime,
1416
+ responseTypeSchema: this.responseTypeSchema
1417
+ };
1418
+ const response = await this.resolveMockResponse(
1419
+ resolved.handler,
1420
+ mockRequest
1421
+ );
1422
+ if (response.delayMs && response.delayMs > 0) {
1423
+ await new Promise((resolve) => setTimeout(resolve, response.delayMs));
1424
+ }
1425
+ const status = response.status;
1426
+ const headers = new Headers(response.headers);
1427
+ if (status >= 400) {
1428
+ return {
1429
+ data: void 0,
1430
+ error: new IgniterCallerError({
1431
+ code: "IGNITER_CALLER_MOCK_HTTP_ERROR",
1432
+ operation: "execute",
1433
+ message: response.errorMessage || `Mocked request failed with status ${status}`,
1434
+ statusCode: status,
1435
+ logger: this.logger,
1436
+ metadata: {
1437
+ method,
1438
+ url
1439
+ }
1440
+ }),
1441
+ status,
1442
+ headers
1443
+ };
1444
+ }
1445
+ let data = response.response;
1446
+ if (this.schemas) {
1447
+ const { schema: endpointSchema } = IgniterCallerSchemaUtils.findSchema(
1448
+ this.schemas,
1449
+ path,
1450
+ method
1451
+ );
1452
+ const responseSchema = endpointSchema?.responses?.[status];
1453
+ if (responseSchema) {
1454
+ try {
1455
+ data = await IgniterCallerSchemaUtils.validateResponse(
1456
+ data,
1457
+ responseSchema,
1458
+ status,
1459
+ this.schemaValidation,
1460
+ { url: safeUrl, method },
1461
+ this.logger
1462
+ );
1463
+ } catch (error) {
1464
+ const err = error;
1465
+ this.telemetry?.emit(
1466
+ "igniter.caller.validation.response.error",
1467
+ {
1468
+ level: "error",
1469
+ attributes: {
1470
+ "ctx.request.method": method,
1471
+ "ctx.request.url": safeUrl,
1472
+ "ctx.validation.type": "response",
1473
+ "ctx.validation.error": err.message,
1474
+ "ctx.response.status": status
1475
+ }
1476
+ }
1477
+ );
1478
+ this.logger?.error("IgniterCaller.response.validation failed", {
1479
+ method,
1480
+ url: safeUrl,
1481
+ status,
1482
+ error: err
1483
+ });
1484
+ return {
1485
+ data: void 0,
1486
+ error: err,
1487
+ status,
1488
+ headers
1489
+ };
1490
+ }
1491
+ }
1492
+ }
1493
+ if (this.responseTypeSchema) {
1494
+ if ("safeParse" in this.responseTypeSchema) {
1495
+ const zodSchema = this.responseTypeSchema;
1496
+ const result = zodSchema.safeParse(data);
1497
+ if (!result.success) {
1498
+ const err = new IgniterCallerError({
1499
+ code: "IGNITER_CALLER_RESPONSE_VALIDATION_FAILED",
1500
+ operation: "parseResponse",
1501
+ message: `Response validation failed: ${result.error.message}`,
1502
+ logger: this.logger,
1503
+ statusCode: status,
1504
+ metadata: {
1505
+ method,
1506
+ url
1507
+ },
1508
+ cause: result.error
1509
+ });
1510
+ this.telemetry?.emit(
1511
+ "igniter.caller.validation.response.error",
1512
+ {
1513
+ level: "error",
1514
+ attributes: {
1515
+ "ctx.request.method": method,
1516
+ "ctx.request.url": safeUrl,
1517
+ "ctx.validation.type": "response",
1518
+ "ctx.validation.error": err.message,
1519
+ "ctx.response.status": status
1520
+ }
1521
+ }
1522
+ );
1523
+ this.logger?.error("IgniterCaller.response.validation failed", {
1524
+ method,
1525
+ url: safeUrl,
1526
+ status,
1527
+ error: err
1528
+ });
1529
+ return {
1530
+ data: void 0,
1531
+ error: err,
1532
+ status,
1533
+ headers
1534
+ };
1535
+ }
1536
+ data = result.data;
1537
+ } else if ("~standard" in this.responseTypeSchema) {
1538
+ try {
1539
+ const standardSchema = this.responseTypeSchema;
1540
+ const result = await standardSchema["~standard"].validate(data);
1541
+ if (result.issues) {
1542
+ const err = new IgniterCallerError({
1543
+ code: "IGNITER_CALLER_RESPONSE_VALIDATION_FAILED",
1544
+ operation: "parseResponse",
1545
+ message: `Response validation failed`,
1546
+ logger: this.logger,
1547
+ statusCode: status,
1548
+ metadata: {
1549
+ method,
1550
+ url,
1551
+ issues: result.issues
1552
+ }
1553
+ });
1554
+ this.telemetry?.emit(
1555
+ "igniter.caller.validation.response.error",
1556
+ {
1557
+ level: "error",
1558
+ attributes: {
1559
+ "ctx.request.method": method,
1560
+ "ctx.request.url": safeUrl,
1561
+ "ctx.validation.type": "response",
1562
+ "ctx.validation.error": err.message,
1563
+ "ctx.response.status": status
1564
+ }
1565
+ }
1566
+ );
1567
+ this.logger?.error("IgniterCaller.response.validation failed", {
1568
+ method,
1569
+ url: safeUrl,
1570
+ status,
1571
+ error: err
1572
+ });
1573
+ return {
1574
+ data: void 0,
1575
+ error: err,
1576
+ status,
1577
+ headers
1578
+ };
1579
+ }
1580
+ data = result.value;
1581
+ } catch (error) {
1582
+ const err = error;
1583
+ this.telemetry?.emit(
1584
+ "igniter.caller.validation.response.error",
1585
+ {
1586
+ level: "error",
1587
+ attributes: {
1588
+ "ctx.request.method": method,
1589
+ "ctx.request.url": safeUrl,
1590
+ "ctx.validation.type": "response",
1591
+ "ctx.validation.error": err.message,
1592
+ "ctx.response.status": status
1593
+ }
1594
+ }
1595
+ );
1596
+ this.logger?.error("IgniterCaller.response.validation failed", {
1597
+ method,
1598
+ url: safeUrl,
1599
+ status,
1600
+ error: err
1601
+ });
1602
+ return {
1603
+ data: void 0,
1604
+ error: err,
1605
+ status,
1606
+ headers
1607
+ };
1608
+ }
1609
+ }
1610
+ }
1611
+ let responseResult = {
1612
+ data,
1613
+ error: void 0,
1614
+ status,
1615
+ headers
1616
+ };
1617
+ if (this.responseInterceptors && this.responseInterceptors.length > 0) {
1618
+ for (const interceptor of this.responseInterceptors) {
1619
+ responseResult = await interceptor(responseResult);
1620
+ }
1621
+ }
1622
+ return responseResult;
1623
+ }
1624
+ /**
1625
+ * Normalizes a mock handler result into a response payload with status.
1626
+ */
1627
+ async resolveMockResponse(handler, request) {
1628
+ const result = typeof handler === "function" ? await handler(request) : handler;
1629
+ const hasStatus = typeof result.status === "number";
1630
+ if (hasStatus) {
1631
+ return result;
1632
+ }
1633
+ const schemas = this.schemas;
1634
+ const schemaMatch = schemas ? IgniterCallerSchemaUtils.findSchema(
1635
+ schemas,
1636
+ request.path,
1637
+ request.method
1638
+ ).schema : void 0;
1639
+ const fallbackStatus = schemaMatch?.responses?.[200] ? 200 : schemaMatch?.responses?.[201] ? 201 : 200;
1640
+ return {
1641
+ ...result,
1642
+ status: fallbackStatus
1643
+ };
1644
+ }
1645
+ /**
1646
+ * Emits event for this response using injected emitter.
1647
+ */
1648
+ async emitEvent(result) {
1649
+ if (this.eventEmitter) {
1650
+ try {
1651
+ await this.eventEmitter(this.options.url, this.options.method, result);
1652
+ } catch (error) {
1653
+ this.logger?.debug("Failed to emit event", { error });
1654
+ }
1655
+ }
1656
+ }
1657
+ };
1658
+
1659
+ // src/core/events.ts
1660
+ var IgniterCallerEvents = class {
1661
+ constructor() {
1662
+ this.listeners = /* @__PURE__ */ new Map();
1663
+ this.patternListeners = /* @__PURE__ */ new Map();
1664
+ }
1665
+ /**
1666
+ * Registers a listener for a specific URL or pattern.
1667
+ *
1668
+ * @param pattern URL string (exact match) or RegExp pattern
1669
+ * @param callback Function to execute when a response matches
1670
+ * @returns Cleanup function to remove the listener
1671
+ *
1672
+ * @example
1673
+ * ```ts
1674
+ * // Listen to specific endpoint
1675
+ * const cleanup = api.on('/users', (result) => {
1676
+ * console.log('Users fetched:', result.data)
1677
+ * })
1678
+ *
1679
+ * // Listen to pattern
1680
+ * api.on(/^\/users\/\d+$/, (result) => {
1681
+ * console.log('User detail fetched')
1682
+ * })
1683
+ *
1684
+ * // Cleanup when done
1685
+ * cleanup()
1686
+ * ```
1687
+ */
1688
+ on(pattern, callback) {
1689
+ if (typeof pattern === "string") {
1690
+ if (!this.listeners.has(pattern)) {
1691
+ this.listeners.set(pattern, /* @__PURE__ */ new Set());
1692
+ }
1693
+ const callbacks2 = this.listeners.get(pattern);
1694
+ if (callbacks2) {
1695
+ callbacks2.add(callback);
1696
+ }
1697
+ return () => {
1698
+ const callbacks3 = this.listeners.get(pattern);
1699
+ if (callbacks3) {
1700
+ callbacks3.delete(callback);
1701
+ if (callbacks3.size === 0) {
1702
+ this.listeners.delete(pattern);
1703
+ }
1704
+ }
1705
+ };
1706
+ }
1707
+ if (!this.patternListeners.has(pattern)) {
1708
+ this.patternListeners.set(pattern, /* @__PURE__ */ new Set());
1158
1709
  }
1159
1710
  const callbacks = this.patternListeners.get(pattern);
1160
1711
  if (callbacks) {
@@ -1172,6 +1723,9 @@ var IgniterCallerEvents = class {
1172
1723
  }
1173
1724
  /**
1174
1725
  * Removes a specific listener or all listeners for a pattern.
1726
+ *
1727
+ * @param pattern - URL string or RegExp pattern.
1728
+ * @param callback - Optional specific callback to remove.
1175
1729
  */
1176
1730
  off(pattern, callback) {
1177
1731
  if (typeof pattern === "string") {
@@ -1192,6 +1746,10 @@ var IgniterCallerEvents = class {
1192
1746
  * Emits an event to all matching listeners.
1193
1747
  *
1194
1748
  * @internal
1749
+ *
1750
+ * @param url - Request URL to match listeners against.
1751
+ * @param method - HTTP method.
1752
+ * @param result - Response envelope.
1195
1753
  */
1196
1754
  async emit(url, method, result) {
1197
1755
  const context = {
@@ -1223,6 +1781,8 @@ var IgniterCallerEvents = class {
1223
1781
  }
1224
1782
  /**
1225
1783
  * Removes all listeners.
1784
+ *
1785
+ * @returns Nothing.
1226
1786
  */
1227
1787
  clear() {
1228
1788
  this.listeners.clear();
@@ -1230,64 +1790,25 @@ var IgniterCallerEvents = class {
1230
1790
  }
1231
1791
  };
1232
1792
 
1233
- // src/core/igniter-caller.ts
1234
- var _IgniterCaller = class _IgniterCaller {
1793
+ // src/core/manager.ts
1794
+ var _IgniterCallerManager = class _IgniterCallerManager {
1795
+ /**
1796
+ * Creates a new manager instance.
1797
+ *
1798
+ * @param baseURL - Base URL prefix for requests.
1799
+ * @param opts - Optional configuration (headers, cookies, telemetry, schemas).
1800
+ */
1235
1801
  constructor(baseURL, opts) {
1236
1802
  this.baseURL = baseURL;
1237
1803
  this.headers = opts?.headers;
1238
1804
  this.cookies = opts?.cookies;
1239
1805
  this.logger = opts?.logger;
1806
+ this.telemetry = opts?.telemetry;
1240
1807
  this.requestInterceptors = opts?.requestInterceptors;
1241
1808
  this.responseInterceptors = opts?.responseInterceptors;
1242
1809
  this.schemas = opts?.schemas;
1243
1810
  this.schemaValidation = opts?.schemaValidation;
1244
- }
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;
1811
+ this.mock = opts?.mock;
1291
1812
  }
1292
1813
  /**
1293
1814
  * Creates common request builder params.
@@ -1298,24 +1819,17 @@ var _IgniterCaller = class _IgniterCaller {
1298
1819
  defaultHeaders: this.headers,
1299
1820
  defaultCookies: this.cookies,
1300
1821
  logger: this.logger,
1822
+ telemetry: this.telemetry,
1301
1823
  requestInterceptors: this.requestInterceptors,
1302
1824
  responseInterceptors: this.responseInterceptors,
1303
1825
  eventEmitter: async (url, method, result) => {
1304
- await _IgniterCaller.emitEvent(url, method, result);
1826
+ await _IgniterCallerManager.emitEvent(url, method, result);
1305
1827
  },
1306
1828
  schemas: this.schemas,
1307
- schemaValidation: this.schemaValidation
1829
+ schemaValidation: this.schemaValidation,
1830
+ mock: this.mock
1308
1831
  };
1309
1832
  }
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
1833
  get(url) {
1320
1834
  const builder = new IgniterCallerRequestBuilder(this.createBuilderParams());
1321
1835
  builder._setMethod("GET");
@@ -1358,6 +1872,9 @@ var _IgniterCaller = class _IgniterCaller {
1358
1872
  * This is a convenience method for making requests without using the builder pattern.
1359
1873
  * Useful for dynamic requests where options are constructed programmatically.
1360
1874
  *
1875
+ * @param options - Request configuration for method, url, and behavior.
1876
+ * @returns Response envelope with data or error.
1877
+ *
1361
1878
  * @example
1362
1879
  * ```ts
1363
1880
  * const result = await api.request({
@@ -1424,6 +1941,9 @@ var _IgniterCaller = class _IgniterCaller {
1424
1941
  * Executes multiple requests in parallel and returns results as an array.
1425
1942
  *
1426
1943
  * This is useful for batching independent API calls.
1944
+ *
1945
+ * @param requests - Array of request promises.
1946
+ * @returns Array of resolved results in the same order.
1427
1947
  */
1428
1948
  static async batch(requests) {
1429
1949
  return Promise.all(requests);
@@ -1444,7 +1964,7 @@ var _IgniterCaller = class _IgniterCaller {
1444
1964
  * @example
1445
1965
  * ```ts
1446
1966
  * // Listen to all user endpoints
1447
- * const cleanup = IgniterCaller.on(/^\/users/, (result, context) => {
1967
+ * const cleanup = IgniterCallerManager.on(/^\/users/, (result, context) => {
1448
1968
  * console.log(`${context.method} ${context.url}`, result)
1449
1969
  * })
1450
1970
  *
@@ -1453,24 +1973,29 @@ var _IgniterCaller = class _IgniterCaller {
1453
1973
  * ```
1454
1974
  */
1455
1975
  static on(pattern, callback) {
1456
- return _IgniterCaller.events.on(pattern, callback);
1976
+ return _IgniterCallerManager.events.on(pattern, callback);
1457
1977
  }
1458
1978
  /**
1459
1979
  * Removes event listeners for a pattern.
1980
+ *
1981
+ * @param pattern - URL string or RegExp pattern.
1982
+ * @param callback - Callback to remove (optional).
1460
1983
  */
1461
1984
  static off(pattern, callback) {
1462
- _IgniterCaller.events.off(pattern, callback);
1985
+ _IgniterCallerManager.events.off(pattern, callback);
1463
1986
  }
1464
1987
  /**
1465
1988
  * Invalidates a specific cache entry.
1466
1989
  *
1467
1990
  * This is useful after mutations to ensure fresh data on next fetch.
1468
1991
  *
1992
+ * @param key - Cache key to invalidate.
1993
+ *
1469
1994
  * @example
1470
1995
  * ```ts
1471
1996
  * // After creating a user
1472
1997
  * await api.post('/users').body(newUser).execute()
1473
- * await IgniterCaller.invalidate('/users') // Clear users list cache
1998
+ * await IgniterCallerManager.invalidate('/users') // Clear users list cache
1474
1999
  * ```
1475
2000
  */
1476
2001
  static async invalidate(key) {
@@ -1480,11 +2005,12 @@ var _IgniterCaller = class _IgniterCaller {
1480
2005
  * Invalidates all cache entries matching a pattern.
1481
2006
  *
1482
2007
  * @param pattern Glob pattern (e.g., '/users/*') or exact key
2008
+ * @returns Promise that resolves when invalidation completes.
1483
2009
  *
1484
2010
  * @example
1485
2011
  * ```ts
1486
2012
  * // Invalidate all user-related caches
1487
- * await IgniterCaller.invalidatePattern('/users/*')
2013
+ * await IgniterCallerManager.invalidatePattern('/users/*')
1488
2014
  * ```
1489
2015
  */
1490
2016
  static async invalidatePattern(pattern) {
@@ -1494,25 +2020,637 @@ var _IgniterCaller = class _IgniterCaller {
1494
2020
  * Emits an event to all registered listeners.
1495
2021
  *
1496
2022
  * @internal
2023
+ *
2024
+ * @param url - Request URL (resolved).
2025
+ * @param method - HTTP method.
2026
+ * @param result - Response envelope.
1497
2027
  */
1498
2028
  static async emitEvent(url, method, result) {
1499
- await _IgniterCaller.events.emit(url, method, result);
2029
+ await _IgniterCallerManager.events.emit(url, method, result);
1500
2030
  }
1501
2031
  };
1502
2032
  /** Global event emitter for observing HTTP responses */
1503
- _IgniterCaller.events = new IgniterCallerEvents();
1504
- var IgniterCaller = _IgniterCaller;
2033
+ _IgniterCallerManager.events = new IgniterCallerEvents();
2034
+ var IgniterCallerManager = _IgniterCallerManager;
2035
+
2036
+ // src/core/mock.ts
2037
+ var IgniterCallerMockManager = class {
2038
+ constructor(registry) {
2039
+ this.registry = registry;
2040
+ }
2041
+ /**
2042
+ * Resolves a mock handler for a path+method pair.
2043
+ *
2044
+ * @param path - Request path (normalized).
2045
+ * @param method - HTTP method.
2046
+ * @returns Resolved handler info or null when no match is found.
2047
+ */
2048
+ resolve(path, method) {
2049
+ const direct = this.registry[path]?.[method];
2050
+ if (direct) {
2051
+ return {
2052
+ handler: direct,
2053
+ params: {},
2054
+ path,
2055
+ method
2056
+ };
2057
+ }
2058
+ for (const [registeredPath, methods] of Object.entries(this.registry)) {
2059
+ if (!methods) continue;
2060
+ const match = IgniterCallerSchemaUtils.matchPath(path, registeredPath);
2061
+ if (!match.matched) continue;
2062
+ const handler = methods[method];
2063
+ if (!handler) continue;
2064
+ return {
2065
+ handler,
2066
+ params: match.params || {},
2067
+ path: registeredPath,
2068
+ method
2069
+ };
2070
+ }
2071
+ return null;
2072
+ }
2073
+ };
2074
+
2075
+ // src/builders/main.builder.ts
2076
+ var IgniterCallerBuilder = class _IgniterCallerBuilder {
2077
+ constructor(state) {
2078
+ this.state = state;
2079
+ }
2080
+ /**
2081
+ * Creates a new builder instance.
2082
+ *
2083
+ * @returns New builder instance with empty state.
2084
+ */
2085
+ static create() {
2086
+ return new _IgniterCallerBuilder({});
2087
+ }
2088
+ /**
2089
+ * Sets the base URL for all requests.
2090
+ *
2091
+ * @param baseURL - Base URL prefix for outgoing requests.
2092
+ */
2093
+ withBaseUrl(baseURL) {
2094
+ return new _IgniterCallerBuilder({ ...this.state, baseURL });
2095
+ }
2096
+ /**
2097
+ * Merges default headers for all requests.
2098
+ *
2099
+ * @param headers - Header map merged into every request.
2100
+ */
2101
+ withHeaders(headers) {
2102
+ return new _IgniterCallerBuilder({ ...this.state, headers });
2103
+ }
2104
+ /**
2105
+ * Sets default cookies (sent as the `Cookie` header).
2106
+ *
2107
+ * @param cookies - Cookie key/value pairs.
2108
+ */
2109
+ withCookies(cookies) {
2110
+ return new _IgniterCallerBuilder({ ...this.state, cookies });
2111
+ }
2112
+ /**
2113
+ * Attaches a logger instance.
2114
+ *
2115
+ * @param logger - Logger implementation from `@igniter-js/common`.
2116
+ */
2117
+ withLogger(logger) {
2118
+ return new _IgniterCallerBuilder({ ...this.state, logger });
2119
+ }
2120
+ /**
2121
+ * Adds a request interceptor that runs before each request.
2122
+ *
2123
+ * @param interceptor - Interceptor called with request options.
2124
+ */
2125
+ withRequestInterceptor(interceptor) {
2126
+ const requestInterceptors = [
2127
+ ...this.state.requestInterceptors || [],
2128
+ interceptor
2129
+ ];
2130
+ return new _IgniterCallerBuilder({ ...this.state, requestInterceptors });
2131
+ }
2132
+ /**
2133
+ * Adds a response interceptor that runs after each request.
2134
+ *
2135
+ * @param interceptor - Interceptor called with the response result.
2136
+ */
2137
+ withResponseInterceptor(interceptor) {
2138
+ const responseInterceptors = [
2139
+ ...this.state.responseInterceptors || [],
2140
+ interceptor
2141
+ ];
2142
+ return new _IgniterCallerBuilder({ ...this.state, responseInterceptors });
2143
+ }
2144
+ /**
2145
+ * Configures a persistent store adapter for caching.
2146
+ *
2147
+ * When configured, cache operations will use the store (e.g., Redis)
2148
+ * instead of in-memory cache, enabling persistent cache across deployments.
2149
+ *
2150
+ * @param store - Store adapter implementation.
2151
+ * @param options - Store options (ttl, keyPrefix, fallback).
2152
+ */
2153
+ withStore(store, options) {
2154
+ return new _IgniterCallerBuilder({
2155
+ ...this.state,
2156
+ store,
2157
+ storeOptions: options
2158
+ });
2159
+ }
2160
+ /**
2161
+ * Configures schema-based type safety and validation.
2162
+ *
2163
+ * Enables automatic type inference for requests/responses based on
2164
+ * route and method, with optional runtime validation via StandardSchemaV1
2165
+ * (Zod is supported).
2166
+ *
2167
+ * @param schemas - Schema map keyed by URL path and method.
2168
+ * @param validation - Validation options for request/response checks.
2169
+ *
2170
+ * @example
2171
+ * ```ts
2172
+ * const api = IgniterCaller.create()
2173
+ * .withSchemas({
2174
+ * '/users': {
2175
+ * GET: {
2176
+ * responses: {
2177
+ * 200: z.array(UserSchema),
2178
+ * 401: ErrorSchema,
2179
+ * },
2180
+ * },
2181
+ * POST: {
2182
+ * request: CreateUserSchema,
2183
+ * responses: {
2184
+ * 201: UserSchema,
2185
+ * 400: ValidationErrorSchema,
2186
+ * },
2187
+ * },
2188
+ * },
2189
+ * })
2190
+ * .build()
2191
+ * ```
2192
+ */
2193
+ withSchemas(schemas, validation) {
2194
+ const nextState = {
2195
+ ...this.state,
2196
+ schemas,
2197
+ schemaValidation: validation
2198
+ };
2199
+ return new _IgniterCallerBuilder(nextState);
2200
+ }
2201
+ /**
2202
+ * Attaches telemetry for request monitoring and observability.
2203
+ *
2204
+ * Telemetry is optional and only emits events when a manager is provided.
2205
+ *
2206
+ * Telemetry events emitted by the caller package include:
2207
+ * - `request.execute.started`
2208
+ * - `request.execute.success`
2209
+ * - `request.execute.error`
2210
+ * - `request.timeout.error`
2211
+ * - `cache.read.hit`
2212
+ * - `retry.attempt.started`
2213
+ * - `validation.request.error`
2214
+ * - `validation.response.error`
2215
+ *
2216
+ * @param telemetry - Telemetry manager instance.
2217
+ *
2218
+ * @example
2219
+ * ```ts
2220
+ * import { IgniterTelemetry } from '@igniter-js/telemetry'
2221
+ * import { IgniterCallerTelemetryEvents } from '@igniter-js/caller/telemetry'
2222
+ *
2223
+ * const telemetry = IgniterTelemetry.create()
2224
+ * .withService('my-api')
2225
+ * .addEvents(IgniterCallerTelemetryEvents)
2226
+ * .build()
2227
+ *
2228
+ * const api = IgniterCaller.create()
2229
+ * .withBaseUrl('https://api.example.com')
2230
+ * .withTelemetry(telemetry)
2231
+ * .build()
2232
+ * ```
2233
+ */
2234
+ withTelemetry(telemetry) {
2235
+ return new _IgniterCallerBuilder({ ...this.state, telemetry });
2236
+ }
2237
+ /**
2238
+ * Enables request mocking using a mock registry.
2239
+ *
2240
+ * When enabled, matching requests are routed to the mock handlers instead of fetch.
2241
+ *
2242
+ * @param config - Mock configuration with registry and enable flag.
2243
+ */
2244
+ withMock(config) {
2245
+ return new _IgniterCallerBuilder({ ...this.state, mock: config });
2246
+ }
2247
+ /**
2248
+ * Builds the `IgniterCaller` instance.
2249
+ *
2250
+ * @returns Configured manager instance.
2251
+ */
2252
+ build() {
2253
+ if (this.state.store) {
2254
+ IgniterCallerCacheUtils.setStore(this.state.store, this.state.storeOptions);
2255
+ }
2256
+ const manager = new IgniterCallerManager(this.state.baseURL, {
2257
+ headers: this.state.headers,
2258
+ cookies: this.state.cookies,
2259
+ logger: this.state.logger,
2260
+ telemetry: this.state.telemetry,
2261
+ requestInterceptors: this.state.requestInterceptors,
2262
+ responseInterceptors: this.state.responseInterceptors,
2263
+ schemas: this.state.schemas,
2264
+ schemaValidation: this.state.schemaValidation,
2265
+ mock: this.state.mock
2266
+ });
2267
+ this.state.logger?.info("IgniterCaller initialized", {
2268
+ baseURL: this.state.baseURL,
2269
+ hasTelemetry: Boolean(this.state.telemetry),
2270
+ hasStore: Boolean(this.state.store),
2271
+ hasSchemas: Boolean(this.state.schemas)
2272
+ });
2273
+ return manager;
2274
+ }
2275
+ };
2276
+ var IgniterCaller = {
2277
+ create: IgniterCallerBuilder.create
2278
+ };
2279
+ var IgniterCallerSchemaPathBuilder = class _IgniterCallerSchemaPathBuilder {
2280
+ constructor(methods, registry) {
2281
+ this.methods = methods;
2282
+ this.registry = registry;
2283
+ }
2284
+ /**
2285
+ * Creates a new path builder for the provided registry.
2286
+ */
2287
+ static create(registry) {
2288
+ return new _IgniterCallerSchemaPathBuilder({}, registry);
2289
+ }
2290
+ /**
2291
+ * Returns a registry reference helper for a given key.
2292
+ * The helper exposes optional Zod-based wrappers (array/optional/nullable/record).
2293
+ */
2294
+ ref(key) {
2295
+ const schema = this.registry[key];
2296
+ const zodSchema = schema;
2297
+ return {
2298
+ schema,
2299
+ array: () => zod.z.array(zodSchema),
2300
+ nullable: () => zodSchema.nullable(),
2301
+ optional: () => zodSchema.optional(),
2302
+ record: (keyType) => zod.z.record(
2303
+ zod.z.any(),
2304
+ zodSchema
2305
+ )
2306
+ };
2307
+ }
2308
+ /**
2309
+ * Defines a GET endpoint.
2310
+ */
2311
+ get(config) {
2312
+ return this.addMethod("GET", config);
2313
+ }
2314
+ /**
2315
+ * Defines a POST endpoint.
2316
+ */
2317
+ post(config) {
2318
+ return this.addMethod("POST", config);
2319
+ }
2320
+ /**
2321
+ * Defines a PUT endpoint.
2322
+ */
2323
+ put(config) {
2324
+ return this.addMethod("PUT", config);
2325
+ }
2326
+ /**
2327
+ * Defines a PATCH endpoint.
2328
+ */
2329
+ patch(config) {
2330
+ return this.addMethod("PATCH", config);
2331
+ }
2332
+ /**
2333
+ * Defines a DELETE endpoint.
2334
+ */
2335
+ delete(config) {
2336
+ return this.addMethod("DELETE", config);
2337
+ }
2338
+ /**
2339
+ * Defines a HEAD endpoint.
2340
+ */
2341
+ head(config) {
2342
+ return this.addMethod("HEAD", config);
2343
+ }
2344
+ /**
2345
+ * Builds the accumulated method map for the path.
2346
+ */
2347
+ build() {
2348
+ return this.methods;
2349
+ }
2350
+ addMethod(method, config) {
2351
+ if (method in this.methods) {
2352
+ throw new IgniterCallerError({
2353
+ code: "IGNITER_CALLER_SCHEMA_DUPLICATE",
2354
+ operation: "buildSchema",
2355
+ message: `Schema for method "${method}" is already defined on this path.`,
2356
+ statusCode: 400,
2357
+ metadata: { method }
2358
+ });
2359
+ }
2360
+ return new _IgniterCallerSchemaPathBuilder(
2361
+ {
2362
+ ...this.methods,
2363
+ [method]: {
2364
+ ...config
2365
+ }
2366
+ },
2367
+ this.registry
2368
+ );
2369
+ }
2370
+ };
2371
+
2372
+ // src/builders/schema.builder.ts
2373
+ var IgniterCallerSchema = class _IgniterCallerSchema {
2374
+ constructor(schemas, registry) {
2375
+ this.schemas = schemas;
2376
+ this.registry = registry;
2377
+ }
2378
+ /**
2379
+ * Creates a new empty schema builder.
2380
+ */
2381
+ static create() {
2382
+ return new _IgniterCallerSchema({}, {});
2383
+ }
2384
+ /**
2385
+ * Registers a reusable schema in the registry.
2386
+ */
2387
+ schema(key, schema, options) {
2388
+ ensureValidSchemaKey(key);
2389
+ if (key in this.registry) {
2390
+ throw new IgniterCallerError({
2391
+ code: "IGNITER_CALLER_SCHEMA_DUPLICATE",
2392
+ operation: "buildSchema",
2393
+ message: `Schema registry key "${key}" is already defined.`,
2394
+ statusCode: 400,
2395
+ metadata: { key }
2396
+ });
2397
+ }
2398
+ const nextRegistry = {
2399
+ ...this.registry,
2400
+ [key]: schema
2401
+ };
2402
+ void options?.internal;
2403
+ return new _IgniterCallerSchema(this.schemas, nextRegistry);
2404
+ }
2405
+ /**
2406
+ * Defines a path with its methods using a fluent builder.
2407
+ */
2408
+ path(path, builder) {
2409
+ ensureValidPath(path);
2410
+ const pathBuilder = IgniterCallerSchemaPathBuilder.create(this.registry);
2411
+ const builtMethods = builder(pathBuilder).build();
2412
+ const existing = this.schemas[path] ?? {};
2413
+ for (const method of Object.keys(
2414
+ builtMethods
2415
+ )) {
2416
+ if (method in existing) {
2417
+ throw new IgniterCallerError({
2418
+ code: "IGNITER_CALLER_SCHEMA_DUPLICATE",
2419
+ operation: "buildSchema",
2420
+ message: `Schema for "${path}" with method "${method}" is already defined.`,
2421
+ statusCode: 400,
2422
+ metadata: { path, method }
2423
+ });
2424
+ }
2425
+ }
2426
+ const merged = {
2427
+ ...existing,
2428
+ ...builtMethods
2429
+ };
2430
+ const nextSchemas = {
2431
+ ...this.schemas,
2432
+ [path]: merged
2433
+ };
2434
+ return new _IgniterCallerSchema(nextSchemas, this.registry);
2435
+ }
2436
+ /**
2437
+ * Builds the schema map and attaches inference + runtime helpers.
2438
+ */
2439
+ build() {
2440
+ const result = {
2441
+ ...this.schemas
2442
+ };
2443
+ const inferHelpers = createInferHelpers();
2444
+ const getHelpers = createGetHelpers(this.schemas, this.registry);
2445
+ Object.defineProperty(result, "$Infer", {
2446
+ value: inferHelpers,
2447
+ enumerable: false
2448
+ });
2449
+ Object.defineProperty(result, "get", {
2450
+ value: getHelpers,
2451
+ enumerable: false
2452
+ });
2453
+ return result;
2454
+ }
2455
+ };
2456
+ function createInferHelpers() {
2457
+ return {
2458
+ Path: void 0,
2459
+ Endpoint: (() => void 0),
2460
+ Request: (() => void 0),
2461
+ Response: (() => void 0),
2462
+ Responses: (() => void 0),
2463
+ Schema: (() => void 0)
2464
+ };
2465
+ }
2466
+ function createGetHelpers(schemas, registry) {
2467
+ return {
2468
+ path: (path) => schemas[path],
2469
+ endpoint: (path, method) => schemas[path][method],
2470
+ request: (path, method) => schemas[path][method]?.request,
2471
+ response: (path, method, status) => schemas[path][method]?.responses?.[status],
2472
+ schema: (key) => registry[key]
2473
+ };
2474
+ }
2475
+ function ensureValidPath(path) {
2476
+ if (!path || path.trim().length === 0) {
2477
+ throw new IgniterCallerError({
2478
+ code: "IGNITER_CALLER_SCHEMA_INVALID",
2479
+ operation: "buildSchema",
2480
+ message: "Path cannot be empty.",
2481
+ statusCode: 400
2482
+ });
2483
+ }
2484
+ if (!path.startsWith("/")) {
2485
+ throw new IgniterCallerError({
2486
+ code: "IGNITER_CALLER_SCHEMA_INVALID",
2487
+ operation: "buildSchema",
2488
+ message: `Path "${path}" must start with "/".`,
2489
+ statusCode: 400,
2490
+ metadata: { path }
2491
+ });
2492
+ }
2493
+ }
2494
+ function ensureValidSchemaKey(key) {
2495
+ if (!key || key.trim().length === 0) {
2496
+ throw new IgniterCallerError({
2497
+ code: "IGNITER_CALLER_SCHEMA_INVALID",
2498
+ operation: "buildSchema",
2499
+ message: "Schema registry key cannot be empty.",
2500
+ statusCode: 400
2501
+ });
2502
+ }
2503
+ }
2504
+
2505
+ // src/builders/mock.builder.ts
2506
+ var IgniterCallerMockBuilder = class _IgniterCallerMockBuilder {
2507
+ constructor(state) {
2508
+ this.state = state;
2509
+ }
2510
+ /**
2511
+ * Creates a new mock builder.
2512
+ */
2513
+ static create() {
2514
+ return new _IgniterCallerMockBuilder({ registry: {} });
2515
+ }
2516
+ /**
2517
+ * Sets schemas to enable typed mock definitions.
2518
+ *
2519
+ * @param _schemas - Schema map or build result.
2520
+ */
2521
+ withSchemas(_schemas) {
2522
+ return new _IgniterCallerMockBuilder({
2523
+ registry: this.state.registry
2524
+ });
2525
+ }
2526
+ /**
2527
+ * Registers mock handlers for a path.
2528
+ *
2529
+ * @param path - Schema path.
2530
+ * @param handlers - Method handlers or static responses.
2531
+ */
2532
+ mock(path, handlers) {
2533
+ const registry = {
2534
+ ...this.state.registry,
2535
+ [path]: {
2536
+ ...this.state.registry[path] || {},
2537
+ ...handlers
2538
+ }
2539
+ };
2540
+ return new _IgniterCallerMockBuilder({ registry });
2541
+ }
2542
+ /**
2543
+ * Builds a mock manager instance.
2544
+ */
2545
+ build() {
2546
+ return new IgniterCallerMockManager(this.state.registry);
2547
+ }
2548
+ };
2549
+ var IgniterCallerMock = {
2550
+ create: IgniterCallerMockBuilder.create
2551
+ };
2552
+
2553
+ // src/adapters/mock.adapter.ts
2554
+ var MockCallerStoreAdapter = class _MockCallerStoreAdapter {
2555
+ constructor() {
2556
+ /** Underlying in-memory store. */
2557
+ this.client = /* @__PURE__ */ new Map();
2558
+ /** Tracks all calls for assertions. */
2559
+ this.calls = {
2560
+ get: 0,
2561
+ set: 0,
2562
+ delete: 0,
2563
+ has: 0
2564
+ };
2565
+ /** Captures recent operations. */
2566
+ this.history = {
2567
+ get: [],
2568
+ set: [],
2569
+ delete: [],
2570
+ has: []
2571
+ };
2572
+ }
2573
+ /** Creates a new mock adapter instance. */
2574
+ static create() {
2575
+ return new _MockCallerStoreAdapter();
2576
+ }
2577
+ /**
2578
+ * Retrieves a cached value by key.
2579
+ *
2580
+ * @param key - Cache key (without prefix).
2581
+ * @returns Cached value or null.
2582
+ */
2583
+ async get(key) {
2584
+ this.calls.get += 1;
2585
+ this.history.get.push(key);
2586
+ return this.client.has(key) ? this.client.get(key) : null;
2587
+ }
2588
+ /**
2589
+ * Stores a cached value.
2590
+ *
2591
+ * @param key - Cache key (without prefix).
2592
+ * @param value - Value to store.
2593
+ * @param options - Cache options (ttl, etc).
2594
+ */
2595
+ async set(key, value, options) {
2596
+ this.calls.set += 1;
2597
+ this.history.set.push({ key, value, options });
2598
+ this.client.set(key, value);
2599
+ }
2600
+ /**
2601
+ * Removes a cached value.
2602
+ *
2603
+ * @param key - Cache key (without prefix).
2604
+ */
2605
+ async delete(key) {
2606
+ this.calls.delete += 1;
2607
+ this.history.delete.push(key);
2608
+ this.client.delete(key);
2609
+ }
2610
+ /**
2611
+ * Checks if a cached value exists.
2612
+ *
2613
+ * @param key - Cache key (without prefix).
2614
+ * @returns True when the key exists.
2615
+ */
2616
+ async has(key) {
2617
+ this.calls.has += 1;
2618
+ this.history.has.push(key);
2619
+ return this.client.has(key);
2620
+ }
2621
+ /**
2622
+ * Clears all tracked state.
2623
+ *
2624
+ * @returns Nothing.
2625
+ */
2626
+ clear() {
2627
+ this.client.clear();
2628
+ this.calls.get = 0;
2629
+ this.calls.set = 0;
2630
+ this.calls.delete = 0;
2631
+ this.calls.has = 0;
2632
+ this.history.get = [];
2633
+ this.history.set = [];
2634
+ this.history.delete = [];
2635
+ this.history.has = [];
2636
+ }
2637
+ };
1505
2638
 
1506
2639
  // src/utils/testing.ts
1507
- var IgniterCallerMock = class {
2640
+ var IgniterCallerHttpMock = class {
1508
2641
  /**
1509
2642
  * Creates a successful mock response.
2643
+ *
2644
+ * @param data - Mock response data.
1510
2645
  */
1511
2646
  static mockResponse(data) {
1512
2647
  return { data, error: void 0 };
1513
2648
  }
1514
2649
  /**
1515
2650
  * Creates an error mock response.
2651
+ *
2652
+ * @param code - Error code to use.
2653
+ * @param message - Optional error message.
1516
2654
  */
1517
2655
  static mockError(code, message = "Mock error") {
1518
2656
  return {
@@ -1526,6 +2664,9 @@ var IgniterCallerMock = class {
1526
2664
  }
1527
2665
  /**
1528
2666
  * Creates a successful file download mock.
2667
+ *
2668
+ * @param filename - File name for the mock.
2669
+ * @param content - File contents as string or Blob.
1529
2670
  */
1530
2671
  static mockFile(filename, content) {
1531
2672
  const blob = typeof content === "string" ? new Blob([content]) : content;
@@ -1534,6 +2675,8 @@ var IgniterCallerMock = class {
1534
2675
  }
1535
2676
  /**
1536
2677
  * Creates a failed file download mock.
2678
+ *
2679
+ * @param message - Optional error message.
1537
2680
  */
1538
2681
  static mockFileError(message = "Mock file error") {
1539
2682
  return {
@@ -1553,9 +2696,16 @@ exports.IgniterCallerBuilder = IgniterCallerBuilder;
1553
2696
  exports.IgniterCallerCacheUtils = IgniterCallerCacheUtils;
1554
2697
  exports.IgniterCallerError = IgniterCallerError;
1555
2698
  exports.IgniterCallerEvents = IgniterCallerEvents;
2699
+ exports.IgniterCallerHttpMock = IgniterCallerHttpMock;
2700
+ exports.IgniterCallerManager = IgniterCallerManager;
1556
2701
  exports.IgniterCallerMock = IgniterCallerMock;
2702
+ exports.IgniterCallerMockBuilder = IgniterCallerMockBuilder;
2703
+ exports.IgniterCallerMockManager = IgniterCallerMockManager;
1557
2704
  exports.IgniterCallerRequestBuilder = IgniterCallerRequestBuilder;
2705
+ exports.IgniterCallerSchema = IgniterCallerSchema;
2706
+ exports.IgniterCallerSchemaPathBuilder = IgniterCallerSchemaPathBuilder;
1558
2707
  exports.IgniterCallerSchemaUtils = IgniterCallerSchemaUtils;
1559
2708
  exports.IgniterCallerUrlUtils = IgniterCallerUrlUtils;
2709
+ exports.MockCallerStoreAdapter = MockCallerStoreAdapter;
1560
2710
  //# sourceMappingURL=index.js.map
1561
2711
  //# sourceMappingURL=index.js.map