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