@igniter-js/caller 0.1.0 → 0.1.2

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
@@ -462,6 +462,52 @@ var IgniterCallerUrlUtils = class {
462
462
  };
463
463
 
464
464
  // src/builder/igniter-caller-request.builder.ts
465
+ var VALIDATABLE_CONTENT_TYPES = [
466
+ "json",
467
+ "xml",
468
+ "csv"
469
+ ];
470
+ function detectContentType(contentType) {
471
+ if (!contentType) return "text";
472
+ const ct = contentType.toLowerCase();
473
+ if (ct.includes("application/json")) return "json";
474
+ if (ct.includes("application/xml") || ct.includes("text/xml")) return "xml";
475
+ if (ct.includes("text/csv")) return "csv";
476
+ if (ct.includes("text/html")) return "html";
477
+ if (ct.includes("text/plain")) return "text";
478
+ if (ct.includes("multipart/form-data")) return "formdata";
479
+ if (ct.includes("application/octet-stream")) return "blob";
480
+ if (ct.includes("image/") || ct.includes("audio/") || ct.includes("video/") || ct.includes("application/pdf") || ct.includes("application/zip")) {
481
+ return "blob";
482
+ }
483
+ return "text";
484
+ }
485
+ function isValidatableContentType(contentType) {
486
+ return VALIDATABLE_CONTENT_TYPES.includes(
487
+ contentType
488
+ );
489
+ }
490
+ async function parseResponseByContentType(response, contentType) {
491
+ switch (contentType) {
492
+ case "json":
493
+ return response.json();
494
+ case "xml":
495
+ case "csv":
496
+ case "html":
497
+ case "text":
498
+ return response.text();
499
+ case "blob":
500
+ return response.blob();
501
+ case "stream":
502
+ return response.body;
503
+ case "arraybuffer":
504
+ return response.arrayBuffer();
505
+ case "formdata":
506
+ return response.formData();
507
+ default:
508
+ return response.text();
509
+ }
510
+ }
465
511
  var IgniterCallerRequestBuilder = class {
466
512
  constructor(params) {
467
513
  this.options = {
@@ -491,6 +537,22 @@ var IgniterCallerRequestBuilder = class {
491
537
  this.schemas = params.schemas;
492
538
  this.schemaValidation = params.schemaValidation;
493
539
  }
540
+ /**
541
+ * Sets the HTTP method for this request.
542
+ * @internal Used by IgniterCaller.request() for generic requests.
543
+ */
544
+ _setMethod(method) {
545
+ this.options.method = method;
546
+ return this;
547
+ }
548
+ /**
549
+ * Sets the URL for this request.
550
+ * @internal Used when URL is passed to HTTP method directly.
551
+ */
552
+ _setUrl(url) {
553
+ this.options.url = url;
554
+ return this;
555
+ }
494
556
  /**
495
557
  * Overrides the logger for this request chain.
496
558
  */
@@ -498,30 +560,45 @@ var IgniterCallerRequestBuilder = class {
498
560
  this.logger = logger;
499
561
  return this;
500
562
  }
501
- method(method) {
502
- this.options.method = method;
503
- return this;
504
- }
563
+ /**
564
+ * Sets the request URL.
565
+ */
505
566
  url(url) {
506
567
  this.options.url = url;
507
568
  return this;
508
569
  }
570
+ /**
571
+ * Sets the request body.
572
+ * For GET/HEAD requests, body will be automatically converted to query params.
573
+ */
509
574
  body(body) {
510
575
  this.options.body = body;
511
576
  return this;
512
577
  }
578
+ /**
579
+ * Sets URL query parameters.
580
+ */
513
581
  params(params) {
514
582
  this.options.params = params;
515
583
  return this;
516
584
  }
585
+ /**
586
+ * Merges additional headers into the request.
587
+ */
517
588
  headers(headers) {
518
589
  this.options.headers = { ...this.options.headers, ...headers };
519
590
  return this;
520
591
  }
592
+ /**
593
+ * Sets request timeout in milliseconds.
594
+ */
521
595
  timeout(timeout) {
522
596
  this.options.timeout = timeout;
523
597
  return this;
524
598
  }
599
+ /**
600
+ * Sets cache strategy and optional cache key.
601
+ */
525
602
  cache(cache, key) {
526
603
  this.options.cache = cache;
527
604
  this.cacheKey = key;
@@ -548,12 +625,32 @@ var IgniterCallerRequestBuilder = class {
548
625
  this.staleTime = milliseconds;
549
626
  return this;
550
627
  }
628
+ /**
629
+ * Sets the expected response type for TypeScript inference.
630
+ *
631
+ * - If a Zod/StandardSchema is passed, it will validate the response (only for JSON/XML/CSV)
632
+ * - If a type parameter is passed (e.g., `responseType<File>()`), it's for typing only
633
+ *
634
+ * The actual parsing is based on Content-Type headers, not this setting.
635
+ *
636
+ * @example
637
+ * ```ts
638
+ * // With Zod schema (validates JSON response)
639
+ * const result = await api.get('/users').responseType(UserSchema).execute()
640
+ *
641
+ * // With type marker (typing only, no validation)
642
+ * const result = await api.get('/file').responseType<Blob>().execute()
643
+ * ```
644
+ */
551
645
  responseType(schema) {
552
- this.options.responseSchema = schema;
646
+ if (schema) {
647
+ this.responseTypeSchema = schema;
648
+ }
553
649
  return this;
554
650
  }
555
651
  /**
556
652
  * Downloads a file via GET request.
653
+ * @deprecated Use `.responseType<File>().execute()` instead. The response type is auto-detected.
557
654
  */
558
655
  getFile(url) {
559
656
  this.options.method = "GET";
@@ -647,6 +744,19 @@ var IgniterCallerRequestBuilder = class {
647
744
  }
648
745
  };
649
746
  }
747
+ /**
748
+ * Executes the HTTP request.
749
+ *
750
+ * Response parsing is automatic based on Content-Type headers:
751
+ * - `application/json` → parsed as JSON
752
+ * - `text/xml`, `application/xml` → returned as text (parse with your XML library)
753
+ * - `text/csv` → returned as text
754
+ * - `text/html`, `text/plain` → returned as text
755
+ * - `image/*`, `audio/*`, `video/*`, `application/pdf`, etc. → returned as Blob
756
+ * - `application/octet-stream` → returned as Blob
757
+ *
758
+ * Schema validation (if configured) only runs for validatable content types (JSON, XML, CSV).
759
+ */
650
760
  async execute() {
651
761
  const effectiveCacheKey = this.cacheKey || this.options.url;
652
762
  if (effectiveCacheKey && this.staleTime) {
@@ -658,7 +768,10 @@ var IgniterCallerRequestBuilder = class {
658
768
  this.logger?.debug("IgniterCaller.execute cache hit", {
659
769
  key: effectiveCacheKey
660
770
  });
661
- const cachedResult = { data: cached, error: void 0 };
771
+ const cachedResult = {
772
+ data: cached,
773
+ error: void 0
774
+ };
662
775
  await this.emitEvent(cachedResult);
663
776
  return cachedResult;
664
777
  }
@@ -668,7 +781,10 @@ var IgniterCallerRequestBuilder = class {
668
781
  this.logger?.debug("IgniterCaller.execute applying fallback", {
669
782
  error: result.error
670
783
  });
671
- const fallbackResult = { data: this.fallbackFn(), error: void 0 };
784
+ const fallbackResult = {
785
+ data: this.fallbackFn(),
786
+ error: void 0
787
+ };
672
788
  await this.emitEvent(fallbackResult);
673
789
  return fallbackResult;
674
790
  }
@@ -720,7 +836,6 @@ var IgniterCallerRequestBuilder = class {
720
836
  };
721
837
  }
722
838
  async executeSingleRequest() {
723
- const { responseSchema } = this.options;
724
839
  let { url, requestInit, controller, timeoutId } = this.buildRequest();
725
840
  this.logger?.debug("IgniterCaller.execute started", {
726
841
  method: this.options.method,
@@ -754,6 +869,7 @@ var IgniterCallerRequestBuilder = class {
754
869
  this.logger
755
870
  );
756
871
  } catch (error) {
872
+ clearTimeout(timeoutId);
757
873
  return {
758
874
  data: void 0,
759
875
  error
@@ -782,70 +898,118 @@ var IgniterCallerRequestBuilder = class {
782
898
  method: this.options.method,
783
899
  url
784
900
  }
785
- })
901
+ }),
902
+ status: httpResponse.status,
903
+ headers: httpResponse.headers
786
904
  };
787
905
  }
788
- const contentType = httpResponse.headers.get("content-type");
789
- const data = contentType?.includes("application/json") ? await httpResponse.json() : await httpResponse.text();
790
- let validatedData = data;
791
- if (this.schemas) {
792
- const { schema: endpointSchema } = IgniterCallerSchemaUtils.findSchema(
793
- this.schemas,
794
- url,
795
- this.options.method
796
- );
797
- const responseSchema2 = endpointSchema?.responses?.[httpResponse.status];
798
- if (responseSchema2) {
799
- try {
800
- validatedData = await IgniterCallerSchemaUtils.validateResponse(
801
- data,
802
- responseSchema2,
803
- httpResponse.status,
804
- this.schemaValidation,
805
- { url, method: this.options.method },
806
- this.logger
807
- );
808
- } catch (error) {
809
- return {
810
- data: void 0,
811
- error
812
- };
906
+ const contentTypeHeader = httpResponse.headers.get("content-type");
907
+ const detectedContentType = detectContentType(contentTypeHeader);
908
+ let data = await parseResponseByContentType(httpResponse, detectedContentType);
909
+ const shouldValidate = isValidatableContentType(detectedContentType);
910
+ if (shouldValidate) {
911
+ if (this.schemas) {
912
+ const { schema: endpointSchema } = IgniterCallerSchemaUtils.findSchema(
913
+ this.schemas,
914
+ url,
915
+ this.options.method
916
+ );
917
+ const responseSchema = endpointSchema?.responses?.[httpResponse.status];
918
+ if (responseSchema) {
919
+ try {
920
+ data = await IgniterCallerSchemaUtils.validateResponse(
921
+ data,
922
+ responseSchema,
923
+ httpResponse.status,
924
+ this.schemaValidation,
925
+ { url, method: this.options.method },
926
+ this.logger
927
+ );
928
+ } catch (error) {
929
+ return {
930
+ data: void 0,
931
+ error,
932
+ status: httpResponse.status,
933
+ headers: httpResponse.headers
934
+ };
935
+ }
813
936
  }
814
937
  }
815
- }
816
- if (responseSchema) {
817
- const result = responseSchema.safeParse(data);
818
- if (!result.success) {
819
- return {
820
- data: void 0,
821
- error: new IgniterCallerError({
822
- code: "IGNITER_CALLER_RESPONSE_VALIDATION_FAILED",
823
- operation: "parseResponse",
824
- message: `Response validation failed: ${result.error.message}`,
825
- logger: this.logger,
826
- statusCode: 500,
827
- metadata: {
828
- method: this.options.method,
829
- url
830
- },
831
- cause: result.error
832
- })
833
- };
834
- }
835
- let response2 = {
836
- data: result.data,
837
- error: void 0
838
- };
839
- if (this.responseInterceptors && this.responseInterceptors.length > 0) {
840
- for (const interceptor of this.responseInterceptors) {
841
- response2 = await interceptor(response2);
938
+ if (this.responseTypeSchema) {
939
+ if ("safeParse" in this.responseTypeSchema) {
940
+ const zodSchema = this.responseTypeSchema;
941
+ const result = zodSchema.safeParse(data);
942
+ if (!result.success) {
943
+ return {
944
+ 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
+ }),
957
+ status: httpResponse.status,
958
+ headers: httpResponse.headers
959
+ };
960
+ }
961
+ data = result.data;
962
+ } else if ("~standard" in this.responseTypeSchema) {
963
+ try {
964
+ const standardSchema = this.responseTypeSchema;
965
+ const result = await standardSchema["~standard"].validate(data);
966
+ if (result.issues) {
967
+ return {
968
+ 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
+ }),
981
+ status: httpResponse.status,
982
+ headers: httpResponse.headers
983
+ };
984
+ }
985
+ data = result.value;
986
+ } catch (error) {
987
+ return {
988
+ 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
+ }),
1001
+ status: httpResponse.status,
1002
+ headers: httpResponse.headers
1003
+ };
1004
+ }
842
1005
  }
843
1006
  }
844
- return response2;
845
1007
  }
846
1008
  let response = {
847
- data: validatedData,
848
- error: void 0
1009
+ data,
1010
+ error: void 0,
1011
+ status: httpResponse.status,
1012
+ headers: httpResponse.headers
849
1013
  };
850
1014
  if (this.responseInterceptors && this.responseInterceptors.length > 0) {
851
1015
  for (const interceptor of this.responseInterceptors) {
@@ -891,21 +1055,32 @@ var IgniterCallerRequestBuilder = class {
891
1055
  }
892
1056
  buildRequest() {
893
1057
  const { method, url, body, params, headers, timeout, baseURL, cache } = this.options;
1058
+ let finalParams = params;
1059
+ if ((method === "GET" || method === "HEAD") && body && typeof body === "object") {
1060
+ const bodyParams = {};
1061
+ for (const [key, value] of Object.entries(body)) {
1062
+ if (value !== void 0 && value !== null) {
1063
+ bodyParams[key] = String(value);
1064
+ }
1065
+ }
1066
+ finalParams = { ...bodyParams, ...params };
1067
+ }
894
1068
  const fullUrl = IgniterCallerUrlUtils.buildUrl({
895
1069
  url,
896
1070
  baseURL,
897
- query: params
1071
+ query: finalParams
898
1072
  });
899
- const rawBody = IgniterCallerBodyUtils.isRawBody(body);
1073
+ const shouldIncludeBody = body && method !== "GET" && method !== "HEAD";
1074
+ const rawBody = shouldIncludeBody && IgniterCallerBodyUtils.isRawBody(body);
900
1075
  const finalHeaders = IgniterCallerBodyUtils.normalizeHeadersForBody(
901
1076
  headers,
902
- body
1077
+ shouldIncludeBody ? body : void 0
903
1078
  );
904
1079
  const requestInit = {
905
1080
  method,
906
1081
  headers: finalHeaders,
907
1082
  cache,
908
- ...body && method !== "GET" && method !== "HEAD" ? { body: rawBody ? body : JSON.stringify(body) } : {}
1083
+ ...shouldIncludeBody ? { body: rawBody ? body : JSON.stringify(body) } : {}
909
1084
  };
910
1085
  const controller = new AbortController();
911
1086
  const timeoutId = setTimeout(() => controller.abort(), timeout || 3e4);
@@ -1114,8 +1289,11 @@ var _IgniterCaller = class _IgniterCaller {
1114
1289
  this.cookies = cookies;
1115
1290
  return this;
1116
1291
  }
1117
- get() {
1118
- return new IgniterCallerRequestBuilder({
1292
+ /**
1293
+ * Creates common request builder params.
1294
+ */
1295
+ createBuilderParams() {
1296
+ return {
1119
1297
  baseURL: this.baseURL,
1120
1298
  defaultHeaders: this.headers,
1121
1299
  defaultCookies: this.cookies,
@@ -1127,80 +1305,192 @@ var _IgniterCaller = class _IgniterCaller {
1127
1305
  },
1128
1306
  schemas: this.schemas,
1129
1307
  schemaValidation: this.schemaValidation
1130
- }).method("GET");
1308
+ };
1131
1309
  }
1132
- post() {
1133
- return new IgniterCallerRequestBuilder({
1134
- baseURL: this.baseURL,
1135
- defaultHeaders: this.headers,
1136
- defaultCookies: this.cookies,
1137
- logger: this.logger,
1138
- requestInterceptors: this.requestInterceptors,
1139
- responseInterceptors: this.responseInterceptors,
1140
- eventEmitter: async (url, method, result) => {
1141
- await _IgniterCaller.emitEvent(url, method, result);
1142
- },
1143
- schemas: this.schemas,
1144
- schemaValidation: this.schemaValidation
1145
- }).method("POST");
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;
1146
1318
  }
1147
- put() {
1148
- return new IgniterCallerRequestBuilder({
1149
- baseURL: this.baseURL,
1150
- defaultHeaders: this.headers,
1151
- defaultCookies: this.cookies,
1152
- logger: this.logger,
1153
- requestInterceptors: this.requestInterceptors,
1154
- responseInterceptors: this.responseInterceptors,
1155
- eventEmitter: async (url, method, result) => {
1156
- await _IgniterCaller.emitEvent(url, method, result);
1157
- },
1158
- schemas: this.schemas,
1159
- schemaValidation: this.schemaValidation
1160
- }).method("PUT");
1319
+ /**
1320
+ * Creates a GET request.
1321
+ *
1322
+ * When a URL is provided and matches a schema, the response type is automatically inferred.
1323
+ *
1324
+ * @param url Optional URL for the request. Can also be set via `.url()`.
1325
+ *
1326
+ * @example
1327
+ * ```ts
1328
+ * // With typed schema - response type is inferred
1329
+ * const result = await api.get('/users').execute()
1330
+ * // result.data is typed based on schema
1331
+ *
1332
+ * // Without schema or URL set later
1333
+ * const result = await api.get().url('/users').execute()
1334
+ * ```
1335
+ */
1336
+ get(url) {
1337
+ const builder = new IgniterCallerRequestBuilder(this.createBuilderParams());
1338
+ builder._setMethod("GET");
1339
+ if (url) builder._setUrl(url);
1340
+ return builder;
1161
1341
  }
1162
- patch() {
1163
- return new IgniterCallerRequestBuilder({
1164
- baseURL: this.baseURL,
1165
- defaultHeaders: this.headers,
1166
- defaultCookies: this.cookies,
1167
- logger: this.logger,
1168
- requestInterceptors: this.requestInterceptors,
1169
- responseInterceptors: this.responseInterceptors,
1170
- eventEmitter: async (url, method, result) => {
1171
- await _IgniterCaller.emitEvent(url, method, result);
1172
- },
1173
- schemas: this.schemas,
1174
- schemaValidation: this.schemaValidation
1175
- }).method("PATCH");
1342
+ /**
1343
+ * Creates a POST request.
1344
+ *
1345
+ * When a URL is provided and matches a schema, the response type is automatically inferred.
1346
+ *
1347
+ * @param url Optional URL for the request. Can also be set via `.url()`.
1348
+ *
1349
+ * @example
1350
+ * ```ts
1351
+ * const result = await api.post('/users').body({ name: 'John' }).execute()
1352
+ * ```
1353
+ */
1354
+ post(url) {
1355
+ const builder = new IgniterCallerRequestBuilder(this.createBuilderParams());
1356
+ builder._setMethod("POST");
1357
+ if (url) builder._setUrl(url);
1358
+ return builder;
1176
1359
  }
1177
- delete() {
1178
- return new IgniterCallerRequestBuilder({
1179
- baseURL: this.baseURL,
1180
- defaultHeaders: this.headers,
1181
- defaultCookies: this.cookies,
1182
- logger: this.logger,
1183
- requestInterceptors: this.requestInterceptors,
1184
- responseInterceptors: this.responseInterceptors,
1185
- eventEmitter: async (url, method, result) => {
1186
- await _IgniterCaller.emitEvent(url, method, result);
1187
- },
1188
- schemas: this.schemas,
1189
- schemaValidation: this.schemaValidation
1190
- }).method("DELETE");
1360
+ /**
1361
+ * Creates a PUT request.
1362
+ *
1363
+ * When a URL is provided and matches a schema, the response type is automatically inferred.
1364
+ *
1365
+ * @param url Optional URL for the request. Can also be set via `.url()`.
1366
+ *
1367
+ * @example
1368
+ * ```ts
1369
+ * const result = await api.put('/users/1').body({ name: 'Jane' }).execute()
1370
+ * ```
1371
+ */
1372
+ put(url) {
1373
+ const builder = new IgniterCallerRequestBuilder(this.createBuilderParams());
1374
+ builder._setMethod("PUT");
1375
+ if (url) builder._setUrl(url);
1376
+ return builder;
1191
1377
  }
1192
- request() {
1193
- return new IgniterCallerRequestBuilder({
1194
- baseURL: this.baseURL,
1195
- defaultHeaders: this.headers,
1196
- defaultCookies: this.cookies,
1197
- logger: this.logger,
1198
- requestInterceptors: this.requestInterceptors,
1199
- responseInterceptors: this.responseInterceptors,
1200
- eventEmitter: async (url, method, result) => {
1201
- await _IgniterCaller.emitEvent(url, method, result);
1378
+ /**
1379
+ * Creates a PATCH request.
1380
+ *
1381
+ * When a URL is provided and matches a schema, the response type is automatically inferred.
1382
+ *
1383
+ * @param url Optional URL for the request. Can also be set via `.url()`.
1384
+ *
1385
+ * @example
1386
+ * ```ts
1387
+ * const result = await api.patch('/users/1').body({ name: 'Jane' }).execute()
1388
+ * ```
1389
+ */
1390
+ patch(url) {
1391
+ const builder = new IgniterCallerRequestBuilder(this.createBuilderParams());
1392
+ builder._setMethod("PATCH");
1393
+ if (url) builder._setUrl(url);
1394
+ return builder;
1395
+ }
1396
+ /**
1397
+ * Creates a DELETE request.
1398
+ *
1399
+ * When a URL is provided and matches a schema, the response type is automatically inferred.
1400
+ *
1401
+ * @param url Optional URL for the request. Can also be set via `.url()`.
1402
+ *
1403
+ * @example
1404
+ * ```ts
1405
+ * const result = await api.delete('/users/1').execute()
1406
+ * ```
1407
+ */
1408
+ delete(url) {
1409
+ const builder = new IgniterCallerRequestBuilder(this.createBuilderParams());
1410
+ builder._setMethod("DELETE");
1411
+ if (url) builder._setUrl(url);
1412
+ return builder;
1413
+ }
1414
+ /**
1415
+ * Creates a HEAD request.
1416
+ *
1417
+ * When a URL is provided and matches a schema, the response type is automatically inferred.
1418
+ *
1419
+ * @param url Optional URL for the request. Can also be set via `.url()`.
1420
+ */
1421
+ head(url) {
1422
+ const builder = new IgniterCallerRequestBuilder(this.createBuilderParams());
1423
+ builder._setMethod("HEAD");
1424
+ if (url) builder._setUrl(url);
1425
+ return builder;
1426
+ }
1427
+ /**
1428
+ * Executes a request directly with all options in one object (axios-style).
1429
+ *
1430
+ * This is a convenience method for making requests without using the builder pattern.
1431
+ * Useful for dynamic requests where options are constructed programmatically.
1432
+ *
1433
+ * @example
1434
+ * ```ts
1435
+ * const result = await api.request({
1436
+ * method: 'POST',
1437
+ * url: '/users',
1438
+ * body: { name: 'John' },
1439
+ * headers: { 'X-Custom': 'value' },
1440
+ * timeout: 5000,
1441
+ * })
1442
+ *
1443
+ * // With caching
1444
+ * const result = await api.request({
1445
+ * method: 'GET',
1446
+ * url: '/users',
1447
+ * staleTime: 30000,
1448
+ * })
1449
+ *
1450
+ * // With retry
1451
+ * const result = await api.request({
1452
+ * method: 'GET',
1453
+ * url: '/health',
1454
+ * retry: { maxAttempts: 3, backoff: 'exponential' },
1455
+ * })
1456
+ * ```
1457
+ */
1458
+ async request(options) {
1459
+ const builder = new IgniterCallerRequestBuilder({
1460
+ ...this.createBuilderParams(),
1461
+ // Override with request-specific options
1462
+ defaultHeaders: {
1463
+ ...this.headers,
1464
+ ...options.headers
1202
1465
  }
1203
1466
  });
1467
+ builder._setMethod(options.method);
1468
+ builder._setUrl(options.url);
1469
+ if (options.body !== void 0) {
1470
+ builder.body(options.body);
1471
+ }
1472
+ if (options.params) {
1473
+ builder.params(options.params);
1474
+ }
1475
+ if (options.timeout) {
1476
+ builder.timeout(options.timeout);
1477
+ }
1478
+ if (options.cache) {
1479
+ builder.cache(options.cache, options.cacheKey);
1480
+ }
1481
+ if (options.staleTime) {
1482
+ builder.stale(options.staleTime);
1483
+ }
1484
+ if (options.retry) {
1485
+ builder.retry(options.retry.maxAttempts, options.retry);
1486
+ }
1487
+ if (options.fallback) {
1488
+ builder.fallback(options.fallback);
1489
+ }
1490
+ if (options.responseSchema) {
1491
+ builder.responseType(options.responseSchema);
1492
+ }
1493
+ return builder.execute();
1204
1494
  }
1205
1495
  /**
1206
1496
  * Executes multiple requests in parallel and returns results as an array.
@@ -1251,7 +1541,7 @@ var _IgniterCaller = class _IgniterCaller {
1251
1541
  * @example
1252
1542
  * ```ts
1253
1543
  * // After creating a user
1254
- * await api.post().url('/users').body(newUser).execute()
1544
+ * await api.post('/users').body(newUser).execute()
1255
1545
  * await IgniterCaller.invalidate('/users') // Clear users list cache
1256
1546
  * ```
1257
1547
  */