@igniter-js/caller 0.1.4 → 0.1.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  'use strict';
2
2
 
3
- var core = require('@igniter-js/core');
3
+ var common = require('@igniter-js/common');
4
4
  var zod = require('zod');
5
5
 
6
6
  // src/errors/caller.error.ts
7
- var IgniterCallerError = class _IgniterCallerError extends core.IgniterError {
7
+ var IgniterCallerError = class _IgniterCallerError extends common.IgniterError {
8
8
  /**
9
9
  * Creates a new typed caller error.
10
10
  *
@@ -505,6 +505,7 @@ var IgniterCallerRequestBuilder = class {
505
505
  this.eventEmitter = params.eventEmitter;
506
506
  this.schemas = params.schemas;
507
507
  this.schemaValidation = params.schemaValidation;
508
+ this.mock = params.mock;
508
509
  }
509
510
  /**
510
511
  * Sets the HTTP method for this request.
@@ -529,7 +530,7 @@ var IgniterCallerRequestBuilder = class {
529
530
  /**
530
531
  * Overrides the logger for this request chain.
531
532
  *
532
- * @param logger - Logger implementation from `@igniter-js/core`.
533
+ * @param logger - Logger implementation from `@igniter-js/common`.
533
534
  */
534
535
  withLogger(logger) {
535
536
  this.logger = logger;
@@ -1026,6 +1027,11 @@ var IgniterCallerRequestBuilder = class {
1026
1027
  }
1027
1028
  }
1028
1029
  }
1030
+ const mockResult = await this.executeMockRequest(url, safeUrl);
1031
+ if (mockResult) {
1032
+ clearTimeout(timeoutId);
1033
+ return mockResult;
1034
+ }
1029
1035
  try {
1030
1036
  const httpResponse = await fetch(url, {
1031
1037
  ...requestInit,
@@ -1346,6 +1352,297 @@ var IgniterCallerRequestBuilder = class {
1346
1352
  timeoutId
1347
1353
  };
1348
1354
  }
1355
+ /**
1356
+ * Normalizes a URL into a path for mock matching.
1357
+ */
1358
+ normalizeMockPath(url, baseURL) {
1359
+ if (/^https?:\/\//i.test(url)) {
1360
+ try {
1361
+ return new URL(url).pathname;
1362
+ } catch {
1363
+ return url;
1364
+ }
1365
+ }
1366
+ if (baseURL && /^https?:\/\//i.test(baseURL)) {
1367
+ try {
1368
+ const resolved = IgniterCallerUrlUtils.buildUrl({ url, baseURL });
1369
+ return new URL(resolved).pathname;
1370
+ } catch {
1371
+ return url;
1372
+ }
1373
+ }
1374
+ return url.startsWith("/") ? url : `/${url}`;
1375
+ }
1376
+ /**
1377
+ * Resolves the final query object (merges GET/HEAD body into params).
1378
+ */
1379
+ getFinalQuery() {
1380
+ const { method, body, params } = this.options;
1381
+ if ((method === "GET" || method === "HEAD") && body && typeof body === "object") {
1382
+ const bodyParams = {};
1383
+ for (const [key, value] of Object.entries(body)) {
1384
+ if (value !== void 0 && value !== null) {
1385
+ bodyParams[key] = String(value);
1386
+ }
1387
+ }
1388
+ return { ...bodyParams, ...params || {} };
1389
+ }
1390
+ return params || {};
1391
+ }
1392
+ /**
1393
+ * Executes a mock handler when enabled and matched.
1394
+ */
1395
+ async executeMockRequest(url, safeUrl) {
1396
+ if (!this.mock?.enabled) return null;
1397
+ const path = this.normalizeMockPath(this.options.url, this.options.baseURL);
1398
+ const method = this.options.method;
1399
+ const resolved = this.mock.mock.resolve(path, method);
1400
+ if (!resolved) return null;
1401
+ const query = this.getFinalQuery();
1402
+ const mockRequest = {
1403
+ method,
1404
+ path: resolved.path,
1405
+ url,
1406
+ safeUrl,
1407
+ baseURL: this.options.baseURL,
1408
+ headers: this.options.headers || {},
1409
+ query,
1410
+ params: resolved.params || {},
1411
+ body: this.options.body,
1412
+ timeoutMs: this.options.timeout,
1413
+ cache: this.options.cache,
1414
+ cacheKey: this.cacheKey,
1415
+ staleTime: this.staleTime,
1416
+ responseTypeSchema: this.responseTypeSchema
1417
+ };
1418
+ const response = await this.resolveMockResponse(
1419
+ resolved.handler,
1420
+ mockRequest
1421
+ );
1422
+ const delayMs = response.delayMs ?? this.mock?.delay;
1423
+ if (delayMs && delayMs > 0) {
1424
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
1425
+ }
1426
+ const status = response.status;
1427
+ const headers = new Headers(response.headers);
1428
+ if (status >= 400) {
1429
+ return {
1430
+ data: void 0,
1431
+ error: new IgniterCallerError({
1432
+ code: "IGNITER_CALLER_MOCK_HTTP_ERROR",
1433
+ operation: "execute",
1434
+ message: response.errorMessage || `Mocked request failed with status ${status}`,
1435
+ statusCode: status,
1436
+ logger: this.logger,
1437
+ metadata: {
1438
+ method,
1439
+ url
1440
+ }
1441
+ }),
1442
+ status,
1443
+ headers
1444
+ };
1445
+ }
1446
+ let data = response.response;
1447
+ if (this.schemas) {
1448
+ const { schema: endpointSchema } = IgniterCallerSchemaUtils.findSchema(
1449
+ this.schemas,
1450
+ path,
1451
+ method
1452
+ );
1453
+ const responseSchema = endpointSchema?.responses?.[status];
1454
+ if (responseSchema) {
1455
+ try {
1456
+ data = await IgniterCallerSchemaUtils.validateResponse(
1457
+ data,
1458
+ responseSchema,
1459
+ status,
1460
+ this.schemaValidation,
1461
+ { url: safeUrl, method },
1462
+ this.logger
1463
+ );
1464
+ } catch (error) {
1465
+ const err = error;
1466
+ this.telemetry?.emit(
1467
+ "igniter.caller.validation.response.error",
1468
+ {
1469
+ level: "error",
1470
+ attributes: {
1471
+ "ctx.request.method": method,
1472
+ "ctx.request.url": safeUrl,
1473
+ "ctx.validation.type": "response",
1474
+ "ctx.validation.error": err.message,
1475
+ "ctx.response.status": status
1476
+ }
1477
+ }
1478
+ );
1479
+ this.logger?.error("IgniterCaller.response.validation failed", {
1480
+ method,
1481
+ url: safeUrl,
1482
+ status,
1483
+ error: err
1484
+ });
1485
+ return {
1486
+ data: void 0,
1487
+ error: err,
1488
+ status,
1489
+ headers
1490
+ };
1491
+ }
1492
+ }
1493
+ }
1494
+ if (this.responseTypeSchema) {
1495
+ if ("safeParse" in this.responseTypeSchema) {
1496
+ const zodSchema = this.responseTypeSchema;
1497
+ const result = zodSchema.safeParse(data);
1498
+ if (!result.success) {
1499
+ const err = new IgniterCallerError({
1500
+ code: "IGNITER_CALLER_RESPONSE_VALIDATION_FAILED",
1501
+ operation: "parseResponse",
1502
+ message: `Response validation failed: ${result.error.message}`,
1503
+ logger: this.logger,
1504
+ statusCode: status,
1505
+ metadata: {
1506
+ method,
1507
+ url
1508
+ },
1509
+ cause: result.error
1510
+ });
1511
+ this.telemetry?.emit(
1512
+ "igniter.caller.validation.response.error",
1513
+ {
1514
+ level: "error",
1515
+ attributes: {
1516
+ "ctx.request.method": method,
1517
+ "ctx.request.url": safeUrl,
1518
+ "ctx.validation.type": "response",
1519
+ "ctx.validation.error": err.message,
1520
+ "ctx.response.status": status
1521
+ }
1522
+ }
1523
+ );
1524
+ this.logger?.error("IgniterCaller.response.validation failed", {
1525
+ method,
1526
+ url: safeUrl,
1527
+ status,
1528
+ error: err
1529
+ });
1530
+ return {
1531
+ data: void 0,
1532
+ error: err,
1533
+ status,
1534
+ headers
1535
+ };
1536
+ }
1537
+ data = result.data;
1538
+ } else if ("~standard" in this.responseTypeSchema) {
1539
+ try {
1540
+ const standardSchema = this.responseTypeSchema;
1541
+ const result = await standardSchema["~standard"].validate(data);
1542
+ if (result.issues) {
1543
+ const err = new IgniterCallerError({
1544
+ code: "IGNITER_CALLER_RESPONSE_VALIDATION_FAILED",
1545
+ operation: "parseResponse",
1546
+ message: `Response validation failed`,
1547
+ logger: this.logger,
1548
+ statusCode: status,
1549
+ metadata: {
1550
+ method,
1551
+ url,
1552
+ issues: result.issues
1553
+ }
1554
+ });
1555
+ this.telemetry?.emit(
1556
+ "igniter.caller.validation.response.error",
1557
+ {
1558
+ level: "error",
1559
+ attributes: {
1560
+ "ctx.request.method": method,
1561
+ "ctx.request.url": safeUrl,
1562
+ "ctx.validation.type": "response",
1563
+ "ctx.validation.error": err.message,
1564
+ "ctx.response.status": status
1565
+ }
1566
+ }
1567
+ );
1568
+ this.logger?.error("IgniterCaller.response.validation failed", {
1569
+ method,
1570
+ url: safeUrl,
1571
+ status,
1572
+ error: err
1573
+ });
1574
+ return {
1575
+ data: void 0,
1576
+ error: err,
1577
+ status,
1578
+ headers
1579
+ };
1580
+ }
1581
+ data = result.value;
1582
+ } catch (error) {
1583
+ const err = error;
1584
+ this.telemetry?.emit(
1585
+ "igniter.caller.validation.response.error",
1586
+ {
1587
+ level: "error",
1588
+ attributes: {
1589
+ "ctx.request.method": method,
1590
+ "ctx.request.url": safeUrl,
1591
+ "ctx.validation.type": "response",
1592
+ "ctx.validation.error": err.message,
1593
+ "ctx.response.status": status
1594
+ }
1595
+ }
1596
+ );
1597
+ this.logger?.error("IgniterCaller.response.validation failed", {
1598
+ method,
1599
+ url: safeUrl,
1600
+ status,
1601
+ error: err
1602
+ });
1603
+ return {
1604
+ data: void 0,
1605
+ error: err,
1606
+ status,
1607
+ headers
1608
+ };
1609
+ }
1610
+ }
1611
+ }
1612
+ let responseResult = {
1613
+ data,
1614
+ error: void 0,
1615
+ status,
1616
+ headers
1617
+ };
1618
+ if (this.responseInterceptors && this.responseInterceptors.length > 0) {
1619
+ for (const interceptor of this.responseInterceptors) {
1620
+ responseResult = await interceptor(responseResult);
1621
+ }
1622
+ }
1623
+ return responseResult;
1624
+ }
1625
+ /**
1626
+ * Normalizes a mock handler result into a response payload with status.
1627
+ */
1628
+ async resolveMockResponse(handler, request) {
1629
+ const result = typeof handler === "function" ? await handler(request) : handler;
1630
+ const hasStatus = typeof result.status === "number";
1631
+ if (hasStatus) {
1632
+ return result;
1633
+ }
1634
+ const schemas = this.schemas;
1635
+ const schemaMatch = schemas ? IgniterCallerSchemaUtils.findSchema(
1636
+ schemas,
1637
+ request.path,
1638
+ request.method
1639
+ ).schema : void 0;
1640
+ const fallbackStatus = schemaMatch?.responses?.[200] ? 200 : schemaMatch?.responses?.[201] ? 201 : 200;
1641
+ return {
1642
+ ...result,
1643
+ status: fallbackStatus
1644
+ };
1645
+ }
1349
1646
  /**
1350
1647
  * Emits event for this response using injected emitter.
1351
1648
  */
@@ -1512,6 +1809,7 @@ var _IgniterCallerManager = class _IgniterCallerManager {
1512
1809
  this.responseInterceptors = opts?.responseInterceptors;
1513
1810
  this.schemas = opts?.schemas;
1514
1811
  this.schemaValidation = opts?.schemaValidation;
1812
+ this.mock = opts?.mock;
1515
1813
  }
1516
1814
  /**
1517
1815
  * Creates common request builder params.
@@ -1529,7 +1827,8 @@ var _IgniterCallerManager = class _IgniterCallerManager {
1529
1827
  await _IgniterCallerManager.emitEvent(url, method, result);
1530
1828
  },
1531
1829
  schemas: this.schemas,
1532
- schemaValidation: this.schemaValidation
1830
+ schemaValidation: this.schemaValidation,
1831
+ mock: this.mock
1533
1832
  };
1534
1833
  }
1535
1834
  get(url) {
@@ -1735,6 +2034,45 @@ var _IgniterCallerManager = class _IgniterCallerManager {
1735
2034
  _IgniterCallerManager.events = new IgniterCallerEvents();
1736
2035
  var IgniterCallerManager = _IgniterCallerManager;
1737
2036
 
2037
+ // src/core/mock.ts
2038
+ var IgniterCallerMockManager = class {
2039
+ constructor(registry) {
2040
+ this.registry = registry;
2041
+ }
2042
+ /**
2043
+ * Resolves a mock handler for a path+method pair.
2044
+ *
2045
+ * @param path - Request path (normalized).
2046
+ * @param method - HTTP method.
2047
+ * @returns Resolved handler info or null when no match is found.
2048
+ */
2049
+ resolve(path, method) {
2050
+ const direct = this.registry[path]?.[method];
2051
+ if (direct) {
2052
+ return {
2053
+ handler: direct,
2054
+ params: {},
2055
+ path,
2056
+ method
2057
+ };
2058
+ }
2059
+ for (const [registeredPath, methods] of Object.entries(this.registry)) {
2060
+ if (!methods) continue;
2061
+ const match = IgniterCallerSchemaUtils.matchPath(path, registeredPath);
2062
+ if (!match.matched) continue;
2063
+ const handler = methods[method];
2064
+ if (!handler) continue;
2065
+ return {
2066
+ handler,
2067
+ params: match.params || {},
2068
+ path: registeredPath,
2069
+ method
2070
+ };
2071
+ }
2072
+ return null;
2073
+ }
2074
+ };
2075
+
1738
2076
  // src/builders/main.builder.ts
1739
2077
  var IgniterCallerBuilder = class _IgniterCallerBuilder {
1740
2078
  constructor(state) {
@@ -1775,7 +2113,7 @@ var IgniterCallerBuilder = class _IgniterCallerBuilder {
1775
2113
  /**
1776
2114
  * Attaches a logger instance.
1777
2115
  *
1778
- * @param logger - Logger implementation from `@igniter-js/core`.
2116
+ * @param logger - Logger implementation from `@igniter-js/common`.
1779
2117
  */
1780
2118
  withLogger(logger) {
1781
2119
  return new _IgniterCallerBuilder({ ...this.state, logger });
@@ -1897,6 +2235,16 @@ var IgniterCallerBuilder = class _IgniterCallerBuilder {
1897
2235
  withTelemetry(telemetry) {
1898
2236
  return new _IgniterCallerBuilder({ ...this.state, telemetry });
1899
2237
  }
2238
+ /**
2239
+ * Enables request mocking using a mock registry.
2240
+ *
2241
+ * When enabled, matching requests are routed to the mock handlers instead of fetch.
2242
+ *
2243
+ * @param config - Mock configuration with registry and enable flag.
2244
+ */
2245
+ withMock(config) {
2246
+ return new _IgniterCallerBuilder({ ...this.state, mock: config });
2247
+ }
1900
2248
  /**
1901
2249
  * Builds the `IgniterCaller` instance.
1902
2250
  *
@@ -1914,7 +2262,8 @@ var IgniterCallerBuilder = class _IgniterCallerBuilder {
1914
2262
  requestInterceptors: this.state.requestInterceptors,
1915
2263
  responseInterceptors: this.state.responseInterceptors,
1916
2264
  schemas: this.state.schemas,
1917
- schemaValidation: this.state.schemaValidation
2265
+ schemaValidation: this.state.schemaValidation,
2266
+ mock: this.state.mock
1918
2267
  });
1919
2268
  this.state.logger?.info("IgniterCaller initialized", {
1920
2269
  baseURL: this.state.baseURL,
@@ -2154,6 +2503,54 @@ function ensureValidSchemaKey(key) {
2154
2503
  }
2155
2504
  }
2156
2505
 
2506
+ // src/builders/mock.builder.ts
2507
+ var IgniterCallerMockBuilder = class _IgniterCallerMockBuilder {
2508
+ constructor(state) {
2509
+ this.state = state;
2510
+ }
2511
+ /**
2512
+ * Creates a new mock builder.
2513
+ */
2514
+ static create() {
2515
+ return new _IgniterCallerMockBuilder({ registry: {} });
2516
+ }
2517
+ /**
2518
+ * Sets schemas to enable typed mock definitions.
2519
+ *
2520
+ * @param _schemas - Schema map or build result.
2521
+ */
2522
+ withSchemas(_schemas) {
2523
+ return new _IgniterCallerMockBuilder({
2524
+ registry: this.state.registry
2525
+ });
2526
+ }
2527
+ /**
2528
+ * Registers mock handlers for a path.
2529
+ *
2530
+ * @param path - Schema path.
2531
+ * @param handlers - Method handlers or static responses.
2532
+ */
2533
+ mock(path, handlers) {
2534
+ const registry = {
2535
+ ...this.state.registry,
2536
+ [path]: {
2537
+ ...this.state.registry[path] || {},
2538
+ ...handlers
2539
+ }
2540
+ };
2541
+ return new _IgniterCallerMockBuilder({ registry });
2542
+ }
2543
+ /**
2544
+ * Builds a mock manager instance.
2545
+ */
2546
+ build() {
2547
+ return new IgniterCallerMockManager(this.state.registry);
2548
+ }
2549
+ };
2550
+ var IgniterCallerMock = {
2551
+ create: IgniterCallerMockBuilder.create
2552
+ };
2553
+
2157
2554
  // src/adapters/mock.adapter.ts
2158
2555
  var MockCallerStoreAdapter = class _MockCallerStoreAdapter {
2159
2556
  constructor() {
@@ -2241,7 +2638,7 @@ var MockCallerStoreAdapter = class _MockCallerStoreAdapter {
2241
2638
  };
2242
2639
 
2243
2640
  // src/utils/testing.ts
2244
- var IgniterCallerMock = class {
2641
+ var IgniterCallerHttpMock = class {
2245
2642
  /**
2246
2643
  * Creates a successful mock response.
2247
2644
  *
@@ -2300,8 +2697,11 @@ exports.IgniterCallerBuilder = IgniterCallerBuilder;
2300
2697
  exports.IgniterCallerCacheUtils = IgniterCallerCacheUtils;
2301
2698
  exports.IgniterCallerError = IgniterCallerError;
2302
2699
  exports.IgniterCallerEvents = IgniterCallerEvents;
2700
+ exports.IgniterCallerHttpMock = IgniterCallerHttpMock;
2303
2701
  exports.IgniterCallerManager = IgniterCallerManager;
2304
2702
  exports.IgniterCallerMock = IgniterCallerMock;
2703
+ exports.IgniterCallerMockBuilder = IgniterCallerMockBuilder;
2704
+ exports.IgniterCallerMockManager = IgniterCallerMockManager;
2305
2705
  exports.IgniterCallerRequestBuilder = IgniterCallerRequestBuilder;
2306
2706
  exports.IgniterCallerSchema = IgniterCallerSchema;
2307
2707
  exports.IgniterCallerSchemaPathBuilder = IgniterCallerSchemaPathBuilder;