@igniter-js/caller 0.1.4 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,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,296 @@ 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
+ if (response.delayMs && response.delayMs > 0) {
1423
+ await new Promise((resolve) => setTimeout(resolve, response.delayMs));
1424
+ }
1425
+ const status = response.status;
1426
+ const headers = new Headers(response.headers);
1427
+ if (status >= 400) {
1428
+ return {
1429
+ data: void 0,
1430
+ error: new IgniterCallerError({
1431
+ code: "IGNITER_CALLER_MOCK_HTTP_ERROR",
1432
+ operation: "execute",
1433
+ message: response.errorMessage || `Mocked request failed with status ${status}`,
1434
+ statusCode: status,
1435
+ logger: this.logger,
1436
+ metadata: {
1437
+ method,
1438
+ url
1439
+ }
1440
+ }),
1441
+ status,
1442
+ headers
1443
+ };
1444
+ }
1445
+ let data = response.response;
1446
+ if (this.schemas) {
1447
+ const { schema: endpointSchema } = IgniterCallerSchemaUtils.findSchema(
1448
+ this.schemas,
1449
+ path,
1450
+ method
1451
+ );
1452
+ const responseSchema = endpointSchema?.responses?.[status];
1453
+ if (responseSchema) {
1454
+ try {
1455
+ data = await IgniterCallerSchemaUtils.validateResponse(
1456
+ data,
1457
+ responseSchema,
1458
+ status,
1459
+ this.schemaValidation,
1460
+ { url: safeUrl, method },
1461
+ this.logger
1462
+ );
1463
+ } catch (error) {
1464
+ const err = error;
1465
+ this.telemetry?.emit(
1466
+ "igniter.caller.validation.response.error",
1467
+ {
1468
+ level: "error",
1469
+ attributes: {
1470
+ "ctx.request.method": method,
1471
+ "ctx.request.url": safeUrl,
1472
+ "ctx.validation.type": "response",
1473
+ "ctx.validation.error": err.message,
1474
+ "ctx.response.status": status
1475
+ }
1476
+ }
1477
+ );
1478
+ this.logger?.error("IgniterCaller.response.validation failed", {
1479
+ method,
1480
+ url: safeUrl,
1481
+ status,
1482
+ error: err
1483
+ });
1484
+ return {
1485
+ data: void 0,
1486
+ error: err,
1487
+ status,
1488
+ headers
1489
+ };
1490
+ }
1491
+ }
1492
+ }
1493
+ if (this.responseTypeSchema) {
1494
+ if ("safeParse" in this.responseTypeSchema) {
1495
+ const zodSchema = this.responseTypeSchema;
1496
+ const result = zodSchema.safeParse(data);
1497
+ if (!result.success) {
1498
+ const err = new IgniterCallerError({
1499
+ code: "IGNITER_CALLER_RESPONSE_VALIDATION_FAILED",
1500
+ operation: "parseResponse",
1501
+ message: `Response validation failed: ${result.error.message}`,
1502
+ logger: this.logger,
1503
+ statusCode: status,
1504
+ metadata: {
1505
+ method,
1506
+ url
1507
+ },
1508
+ cause: result.error
1509
+ });
1510
+ this.telemetry?.emit(
1511
+ "igniter.caller.validation.response.error",
1512
+ {
1513
+ level: "error",
1514
+ attributes: {
1515
+ "ctx.request.method": method,
1516
+ "ctx.request.url": safeUrl,
1517
+ "ctx.validation.type": "response",
1518
+ "ctx.validation.error": err.message,
1519
+ "ctx.response.status": status
1520
+ }
1521
+ }
1522
+ );
1523
+ this.logger?.error("IgniterCaller.response.validation failed", {
1524
+ method,
1525
+ url: safeUrl,
1526
+ status,
1527
+ error: err
1528
+ });
1529
+ return {
1530
+ data: void 0,
1531
+ error: err,
1532
+ status,
1533
+ headers
1534
+ };
1535
+ }
1536
+ data = result.data;
1537
+ } else if ("~standard" in this.responseTypeSchema) {
1538
+ try {
1539
+ const standardSchema = this.responseTypeSchema;
1540
+ const result = await standardSchema["~standard"].validate(data);
1541
+ if (result.issues) {
1542
+ const err = new IgniterCallerError({
1543
+ code: "IGNITER_CALLER_RESPONSE_VALIDATION_FAILED",
1544
+ operation: "parseResponse",
1545
+ message: `Response validation failed`,
1546
+ logger: this.logger,
1547
+ statusCode: status,
1548
+ metadata: {
1549
+ method,
1550
+ url,
1551
+ issues: result.issues
1552
+ }
1553
+ });
1554
+ this.telemetry?.emit(
1555
+ "igniter.caller.validation.response.error",
1556
+ {
1557
+ level: "error",
1558
+ attributes: {
1559
+ "ctx.request.method": method,
1560
+ "ctx.request.url": safeUrl,
1561
+ "ctx.validation.type": "response",
1562
+ "ctx.validation.error": err.message,
1563
+ "ctx.response.status": status
1564
+ }
1565
+ }
1566
+ );
1567
+ this.logger?.error("IgniterCaller.response.validation failed", {
1568
+ method,
1569
+ url: safeUrl,
1570
+ status,
1571
+ error: err
1572
+ });
1573
+ return {
1574
+ data: void 0,
1575
+ error: err,
1576
+ status,
1577
+ headers
1578
+ };
1579
+ }
1580
+ data = result.value;
1581
+ } catch (error) {
1582
+ const err = error;
1583
+ this.telemetry?.emit(
1584
+ "igniter.caller.validation.response.error",
1585
+ {
1586
+ level: "error",
1587
+ attributes: {
1588
+ "ctx.request.method": method,
1589
+ "ctx.request.url": safeUrl,
1590
+ "ctx.validation.type": "response",
1591
+ "ctx.validation.error": err.message,
1592
+ "ctx.response.status": status
1593
+ }
1594
+ }
1595
+ );
1596
+ this.logger?.error("IgniterCaller.response.validation failed", {
1597
+ method,
1598
+ url: safeUrl,
1599
+ status,
1600
+ error: err
1601
+ });
1602
+ return {
1603
+ data: void 0,
1604
+ error: err,
1605
+ status,
1606
+ headers
1607
+ };
1608
+ }
1609
+ }
1610
+ }
1611
+ let responseResult = {
1612
+ data,
1613
+ error: void 0,
1614
+ status,
1615
+ headers
1616
+ };
1617
+ if (this.responseInterceptors && this.responseInterceptors.length > 0) {
1618
+ for (const interceptor of this.responseInterceptors) {
1619
+ responseResult = await interceptor(responseResult);
1620
+ }
1621
+ }
1622
+ return responseResult;
1623
+ }
1624
+ /**
1625
+ * Normalizes a mock handler result into a response payload with status.
1626
+ */
1627
+ async resolveMockResponse(handler, request) {
1628
+ const result = typeof handler === "function" ? await handler(request) : handler;
1629
+ const hasStatus = typeof result.status === "number";
1630
+ if (hasStatus) {
1631
+ return result;
1632
+ }
1633
+ const schemas = this.schemas;
1634
+ const schemaMatch = schemas ? IgniterCallerSchemaUtils.findSchema(
1635
+ schemas,
1636
+ request.path,
1637
+ request.method
1638
+ ).schema : void 0;
1639
+ const fallbackStatus = schemaMatch?.responses?.[200] ? 200 : schemaMatch?.responses?.[201] ? 201 : 200;
1640
+ return {
1641
+ ...result,
1642
+ status: fallbackStatus
1643
+ };
1644
+ }
1349
1645
  /**
1350
1646
  * Emits event for this response using injected emitter.
1351
1647
  */
@@ -1512,6 +1808,7 @@ var _IgniterCallerManager = class _IgniterCallerManager {
1512
1808
  this.responseInterceptors = opts?.responseInterceptors;
1513
1809
  this.schemas = opts?.schemas;
1514
1810
  this.schemaValidation = opts?.schemaValidation;
1811
+ this.mock = opts?.mock;
1515
1812
  }
1516
1813
  /**
1517
1814
  * Creates common request builder params.
@@ -1529,7 +1826,8 @@ var _IgniterCallerManager = class _IgniterCallerManager {
1529
1826
  await _IgniterCallerManager.emitEvent(url, method, result);
1530
1827
  },
1531
1828
  schemas: this.schemas,
1532
- schemaValidation: this.schemaValidation
1829
+ schemaValidation: this.schemaValidation,
1830
+ mock: this.mock
1533
1831
  };
1534
1832
  }
1535
1833
  get(url) {
@@ -1735,6 +2033,45 @@ var _IgniterCallerManager = class _IgniterCallerManager {
1735
2033
  _IgniterCallerManager.events = new IgniterCallerEvents();
1736
2034
  var IgniterCallerManager = _IgniterCallerManager;
1737
2035
 
2036
+ // src/core/mock.ts
2037
+ var IgniterCallerMockManager = class {
2038
+ constructor(registry) {
2039
+ this.registry = registry;
2040
+ }
2041
+ /**
2042
+ * Resolves a mock handler for a path+method pair.
2043
+ *
2044
+ * @param path - Request path (normalized).
2045
+ * @param method - HTTP method.
2046
+ * @returns Resolved handler info or null when no match is found.
2047
+ */
2048
+ resolve(path, method) {
2049
+ const direct = this.registry[path]?.[method];
2050
+ if (direct) {
2051
+ return {
2052
+ handler: direct,
2053
+ params: {},
2054
+ path,
2055
+ method
2056
+ };
2057
+ }
2058
+ for (const [registeredPath, methods] of Object.entries(this.registry)) {
2059
+ if (!methods) continue;
2060
+ const match = IgniterCallerSchemaUtils.matchPath(path, registeredPath);
2061
+ if (!match.matched) continue;
2062
+ const handler = methods[method];
2063
+ if (!handler) continue;
2064
+ return {
2065
+ handler,
2066
+ params: match.params || {},
2067
+ path: registeredPath,
2068
+ method
2069
+ };
2070
+ }
2071
+ return null;
2072
+ }
2073
+ };
2074
+
1738
2075
  // src/builders/main.builder.ts
1739
2076
  var IgniterCallerBuilder = class _IgniterCallerBuilder {
1740
2077
  constructor(state) {
@@ -1775,7 +2112,7 @@ var IgniterCallerBuilder = class _IgniterCallerBuilder {
1775
2112
  /**
1776
2113
  * Attaches a logger instance.
1777
2114
  *
1778
- * @param logger - Logger implementation from `@igniter-js/core`.
2115
+ * @param logger - Logger implementation from `@igniter-js/common`.
1779
2116
  */
1780
2117
  withLogger(logger) {
1781
2118
  return new _IgniterCallerBuilder({ ...this.state, logger });
@@ -1897,6 +2234,16 @@ var IgniterCallerBuilder = class _IgniterCallerBuilder {
1897
2234
  withTelemetry(telemetry) {
1898
2235
  return new _IgniterCallerBuilder({ ...this.state, telemetry });
1899
2236
  }
2237
+ /**
2238
+ * Enables request mocking using a mock registry.
2239
+ *
2240
+ * When enabled, matching requests are routed to the mock handlers instead of fetch.
2241
+ *
2242
+ * @param config - Mock configuration with registry and enable flag.
2243
+ */
2244
+ withMock(config) {
2245
+ return new _IgniterCallerBuilder({ ...this.state, mock: config });
2246
+ }
1900
2247
  /**
1901
2248
  * Builds the `IgniterCaller` instance.
1902
2249
  *
@@ -1914,7 +2261,8 @@ var IgniterCallerBuilder = class _IgniterCallerBuilder {
1914
2261
  requestInterceptors: this.state.requestInterceptors,
1915
2262
  responseInterceptors: this.state.responseInterceptors,
1916
2263
  schemas: this.state.schemas,
1917
- schemaValidation: this.state.schemaValidation
2264
+ schemaValidation: this.state.schemaValidation,
2265
+ mock: this.state.mock
1918
2266
  });
1919
2267
  this.state.logger?.info("IgniterCaller initialized", {
1920
2268
  baseURL: this.state.baseURL,
@@ -2154,6 +2502,54 @@ function ensureValidSchemaKey(key) {
2154
2502
  }
2155
2503
  }
2156
2504
 
2505
+ // src/builders/mock.builder.ts
2506
+ var IgniterCallerMockBuilder = class _IgniterCallerMockBuilder {
2507
+ constructor(state) {
2508
+ this.state = state;
2509
+ }
2510
+ /**
2511
+ * Creates a new mock builder.
2512
+ */
2513
+ static create() {
2514
+ return new _IgniterCallerMockBuilder({ registry: {} });
2515
+ }
2516
+ /**
2517
+ * Sets schemas to enable typed mock definitions.
2518
+ *
2519
+ * @param _schemas - Schema map or build result.
2520
+ */
2521
+ withSchemas(_schemas) {
2522
+ return new _IgniterCallerMockBuilder({
2523
+ registry: this.state.registry
2524
+ });
2525
+ }
2526
+ /**
2527
+ * Registers mock handlers for a path.
2528
+ *
2529
+ * @param path - Schema path.
2530
+ * @param handlers - Method handlers or static responses.
2531
+ */
2532
+ mock(path, handlers) {
2533
+ const registry = {
2534
+ ...this.state.registry,
2535
+ [path]: {
2536
+ ...this.state.registry[path] || {},
2537
+ ...handlers
2538
+ }
2539
+ };
2540
+ return new _IgniterCallerMockBuilder({ registry });
2541
+ }
2542
+ /**
2543
+ * Builds a mock manager instance.
2544
+ */
2545
+ build() {
2546
+ return new IgniterCallerMockManager(this.state.registry);
2547
+ }
2548
+ };
2549
+ var IgniterCallerMock = {
2550
+ create: IgniterCallerMockBuilder.create
2551
+ };
2552
+
2157
2553
  // src/adapters/mock.adapter.ts
2158
2554
  var MockCallerStoreAdapter = class _MockCallerStoreAdapter {
2159
2555
  constructor() {
@@ -2241,7 +2637,7 @@ var MockCallerStoreAdapter = class _MockCallerStoreAdapter {
2241
2637
  };
2242
2638
 
2243
2639
  // src/utils/testing.ts
2244
- var IgniterCallerMock = class {
2640
+ var IgniterCallerHttpMock = class {
2245
2641
  /**
2246
2642
  * Creates a successful mock response.
2247
2643
  *
@@ -2300,8 +2696,11 @@ exports.IgniterCallerBuilder = IgniterCallerBuilder;
2300
2696
  exports.IgniterCallerCacheUtils = IgniterCallerCacheUtils;
2301
2697
  exports.IgniterCallerError = IgniterCallerError;
2302
2698
  exports.IgniterCallerEvents = IgniterCallerEvents;
2699
+ exports.IgniterCallerHttpMock = IgniterCallerHttpMock;
2303
2700
  exports.IgniterCallerManager = IgniterCallerManager;
2304
2701
  exports.IgniterCallerMock = IgniterCallerMock;
2702
+ exports.IgniterCallerMockBuilder = IgniterCallerMockBuilder;
2703
+ exports.IgniterCallerMockManager = IgniterCallerMockManager;
2305
2704
  exports.IgniterCallerRequestBuilder = IgniterCallerRequestBuilder;
2306
2705
  exports.IgniterCallerSchema = IgniterCallerSchema;
2307
2706
  exports.IgniterCallerSchemaPathBuilder = IgniterCallerSchemaPathBuilder;