@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/AGENTS.md +2382 -172
- package/CHANGELOG.md +13 -0
- package/README.md +226 -14
- package/dist/adapters/index.d.mts +70 -0
- package/dist/adapters/index.d.ts +70 -0
- package/dist/adapters/index.js +91 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/index.mjs +89 -0
- package/dist/adapters/index.mjs.map +1 -0
- package/dist/client/index.d.mts +212 -0
- package/dist/client/index.d.ts +212 -0
- package/dist/client/index.js +688 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/index.mjs +679 -0
- package/dist/client/index.mjs.map +1 -0
- package/dist/index.d.mts +415 -870
- package/dist/index.d.ts +415 -870
- package/dist/index.js +1447 -297
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1439 -296
- package/dist/index.mjs.map +1 -1
- package/dist/manager-CVi9utzy.d.ts +1121 -0
- package/dist/manager-DWvX2zsz.d.mts +1121 -0
- package/dist/store-D2p2dqGN.d.mts +54 -0
- package/dist/store-D2p2dqGN.d.ts +54 -0
- package/dist/telemetry/index.d.mts +100 -0
- package/dist/telemetry/index.d.ts +100 -0
- package/dist/telemetry/index.js +54 -0
- package/dist/telemetry/index.js.map +1 -0
- package/dist/telemetry/index.mjs +52 -0
- package/dist/telemetry/index.mjs.map +1 -0
- package/package.json +35 -6
package/dist/index.mjs
CHANGED
|
@@ -1,110 +1,13 @@
|
|
|
1
|
-
import { IgniterError } from '@igniter-js/
|
|
1
|
+
import { IgniterError } from '@igniter-js/common';
|
|
2
|
+
import { z } from 'zod';
|
|
2
3
|
|
|
3
|
-
// src/
|
|
4
|
-
var
|
|
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
|
-
*
|
|
7
|
+
* Creates a new typed caller error.
|
|
67
8
|
*
|
|
68
|
-
*
|
|
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
|
-
* @
|
|
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
|
-
* @
|
|
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/
|
|
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.
|
|
767
|
-
|
|
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
|
|
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.
|
|
816
|
-
attempt,
|
|
817
|
-
|
|
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.
|
|
839
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
1055
|
-
const { method, url, body, params,
|
|
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
|
|
1347
|
+
url,
|
|
1087
1348
|
requestInit,
|
|
1088
1349
|
controller,
|
|
1089
1350
|
timeoutId
|
|
1090
1351
|
};
|
|
1091
1352
|
}
|
|
1092
1353
|
/**
|
|
1093
|
-
*
|
|
1354
|
+
* Normalizes a URL into a path for mock matching.
|
|
1094
1355
|
*/
|
|
1095
|
-
|
|
1096
|
-
if (
|
|
1356
|
+
normalizeMockPath(url, baseURL) {
|
|
1357
|
+
if (/^https?:\/\//i.test(url)) {
|
|
1097
1358
|
try {
|
|
1098
|
-
|
|
1099
|
-
} catch
|
|
1100
|
-
|
|
1359
|
+
return new URL(url).pathname;
|
|
1360
|
+
} catch {
|
|
1361
|
+
return url;
|
|
1101
1362
|
}
|
|
1102
1363
|
}
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
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/
|
|
1232
|
-
var
|
|
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
2027
|
+
await _IgniterCallerManager.events.emit(url, method, result);
|
|
1498
2028
|
}
|
|
1499
2029
|
};
|
|
1500
2030
|
/** Global event emitter for observing HTTP responses */
|
|
1501
|
-
|
|
1502
|
-
var
|
|
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
|
|
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
|