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