@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.js
CHANGED
|
@@ -1,112 +1,15 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var common = require('@igniter-js/common');
|
|
4
|
+
var zod = require('zod');
|
|
4
5
|
|
|
5
|
-
// src/
|
|
6
|
-
var
|
|
7
|
-
constructor(state, factory) {
|
|
8
|
-
this.state = state;
|
|
9
|
-
this.factory = factory;
|
|
10
|
-
}
|
|
11
|
-
/**
|
|
12
|
-
* Creates a new builder instance.
|
|
13
|
-
*/
|
|
14
|
-
static create(factory) {
|
|
15
|
-
return new _IgniterCallerBuilder({}, factory);
|
|
16
|
-
}
|
|
17
|
-
/** Sets the base URL for all requests. */
|
|
18
|
-
withBaseUrl(baseURL) {
|
|
19
|
-
return new _IgniterCallerBuilder({ ...this.state, baseURL }, this.factory);
|
|
20
|
-
}
|
|
21
|
-
/** Merges default headers for all requests. */
|
|
22
|
-
withHeaders(headers) {
|
|
23
|
-
return new _IgniterCallerBuilder({ ...this.state, headers }, this.factory);
|
|
24
|
-
}
|
|
25
|
-
/** Sets default cookies (sent as the `Cookie` header). */
|
|
26
|
-
withCookies(cookies) {
|
|
27
|
-
return new _IgniterCallerBuilder({ ...this.state, cookies }, this.factory);
|
|
28
|
-
}
|
|
29
|
-
/** Attaches a logger instance. */
|
|
30
|
-
withLogger(logger) {
|
|
31
|
-
return new _IgniterCallerBuilder({ ...this.state, logger }, this.factory);
|
|
32
|
-
}
|
|
33
|
-
/** Adds a request interceptor that runs before each request. */
|
|
34
|
-
withRequestInterceptor(interceptor) {
|
|
35
|
-
const requestInterceptors = [
|
|
36
|
-
...this.state.requestInterceptors || [],
|
|
37
|
-
interceptor
|
|
38
|
-
];
|
|
39
|
-
return new _IgniterCallerBuilder(
|
|
40
|
-
{ ...this.state, requestInterceptors },
|
|
41
|
-
this.factory
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
/** Adds a response interceptor that runs after each request. */
|
|
45
|
-
withResponseInterceptor(interceptor) {
|
|
46
|
-
const responseInterceptors = [
|
|
47
|
-
...this.state.responseInterceptors || [],
|
|
48
|
-
interceptor
|
|
49
|
-
];
|
|
50
|
-
return new _IgniterCallerBuilder(
|
|
51
|
-
{ ...this.state, responseInterceptors },
|
|
52
|
-
this.factory
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Configures a persistent store adapter for caching.
|
|
57
|
-
*
|
|
58
|
-
* When configured, cache operations will use the store (e.g., Redis)
|
|
59
|
-
* instead of in-memory cache, enabling persistent cache across deployments.
|
|
60
|
-
*/
|
|
61
|
-
withStore(store, options) {
|
|
62
|
-
return new _IgniterCallerBuilder(
|
|
63
|
-
{ ...this.state, store, storeOptions: options },
|
|
64
|
-
this.factory
|
|
65
|
-
);
|
|
66
|
-
}
|
|
6
|
+
// src/errors/caller.error.ts
|
|
7
|
+
var IgniterCallerError = class _IgniterCallerError extends common.IgniterError {
|
|
67
8
|
/**
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
* Enables automatic type inference for requests/responses based on
|
|
71
|
-
* route and method, with optional runtime validation via Zod.
|
|
9
|
+
* Creates a new typed caller error.
|
|
72
10
|
*
|
|
73
|
-
* @
|
|
74
|
-
* ```ts
|
|
75
|
-
* const api = IgniterCaller.create()
|
|
76
|
-
* .withSchemas({
|
|
77
|
-
* '/users': {
|
|
78
|
-
* GET: {
|
|
79
|
-
* responses: {
|
|
80
|
-
* 200: z.array(UserSchema),
|
|
81
|
-
* 401: ErrorSchema,
|
|
82
|
-
* },
|
|
83
|
-
* },
|
|
84
|
-
* POST: {
|
|
85
|
-
* request: CreateUserSchema,
|
|
86
|
-
* responses: {
|
|
87
|
-
* 201: UserSchema,
|
|
88
|
-
* 400: ValidationErrorSchema,
|
|
89
|
-
* },
|
|
90
|
-
* },
|
|
91
|
-
* },
|
|
92
|
-
* })
|
|
93
|
-
* .build()
|
|
94
|
-
* ```
|
|
95
|
-
*/
|
|
96
|
-
withSchemas(schemas, validation) {
|
|
97
|
-
return new _IgniterCallerBuilder(
|
|
98
|
-
{ ...this.state, schemas, schemaValidation: validation },
|
|
99
|
-
this.factory
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
|
-
/**
|
|
103
|
-
* Builds the `IgniterCaller` instance.
|
|
11
|
+
* @param payload - Error payload with code, message, and metadata.
|
|
104
12
|
*/
|
|
105
|
-
build() {
|
|
106
|
-
return this.factory(this.state);
|
|
107
|
-
}
|
|
108
|
-
};
|
|
109
|
-
var IgniterCallerError = class _IgniterCallerError extends core.IgniterError {
|
|
110
13
|
constructor(payload) {
|
|
111
14
|
const metadata = {
|
|
112
15
|
...payload.metadata,
|
|
@@ -121,13 +24,19 @@ var IgniterCallerError = class _IgniterCallerError extends core.IgniterError {
|
|
|
121
24
|
causer: "@igniter-js/caller",
|
|
122
25
|
details,
|
|
123
26
|
metadata,
|
|
124
|
-
logger: payload.logger
|
|
27
|
+
logger: payload.logger,
|
|
28
|
+
cause: payload.cause
|
|
125
29
|
});
|
|
126
30
|
this.name = "IgniterCallerError";
|
|
127
31
|
this.operation = payload.operation;
|
|
128
32
|
this.statusText = payload.statusText;
|
|
129
33
|
this.cause = payload.cause;
|
|
130
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* Type guard for `IgniterCallerError`.
|
|
37
|
+
*
|
|
38
|
+
* @param error - Value to check.
|
|
39
|
+
*/
|
|
131
40
|
static is(error) {
|
|
132
41
|
return error instanceof _IgniterCallerError;
|
|
133
42
|
}
|
|
@@ -137,6 +46,9 @@ var IgniterCallerError = class _IgniterCallerError extends core.IgniterError {
|
|
|
137
46
|
var IgniterCallerBodyUtils = class {
|
|
138
47
|
/**
|
|
139
48
|
* Returns true when the request body should be passed to `fetch` as-is.
|
|
49
|
+
*
|
|
50
|
+
* @param body - Request body to inspect.
|
|
51
|
+
* @returns True if the body should be sent as raw data.
|
|
140
52
|
*/
|
|
141
53
|
static isRawBody(body) {
|
|
142
54
|
if (!body) return false;
|
|
@@ -155,6 +67,10 @@ var IgniterCallerBodyUtils = class {
|
|
|
155
67
|
}
|
|
156
68
|
/**
|
|
157
69
|
* Removes Content-Type for FormData so fetch can set boundaries automatically.
|
|
70
|
+
*
|
|
71
|
+
* @param headers - Request headers map.
|
|
72
|
+
* @param body - Request body.
|
|
73
|
+
* @returns Updated headers without Content-Type when needed.
|
|
158
74
|
*/
|
|
159
75
|
static normalizeHeadersForBody(headers, body) {
|
|
160
76
|
if (!headers) return headers;
|
|
@@ -173,6 +89,9 @@ var _IgniterCallerCacheUtils = class _IgniterCallerCacheUtils {
|
|
|
173
89
|
*
|
|
174
90
|
* When configured, cache operations will use the store (e.g., Redis)
|
|
175
91
|
* instead of in-memory cache, enabling persistent cache across deployments.
|
|
92
|
+
*
|
|
93
|
+
* @param store - Store adapter implementation.
|
|
94
|
+
* @param options - Store options such as ttl and key prefix.
|
|
176
95
|
*/
|
|
177
96
|
static setStore(store, options) {
|
|
178
97
|
_IgniterCallerCacheUtils.store = store;
|
|
@@ -185,12 +104,18 @@ var _IgniterCallerCacheUtils = class _IgniterCallerCacheUtils {
|
|
|
185
104
|
}
|
|
186
105
|
/**
|
|
187
106
|
* Gets the configured store adapter.
|
|
107
|
+
*
|
|
108
|
+
* @returns Store adapter or null when unset.
|
|
188
109
|
*/
|
|
189
110
|
static getStore() {
|
|
190
111
|
return _IgniterCallerCacheUtils.store;
|
|
191
112
|
}
|
|
192
113
|
/**
|
|
193
114
|
* Gets cached data if it exists and is not stale.
|
|
115
|
+
*
|
|
116
|
+
* @param key - Cache key (without prefix).
|
|
117
|
+
* @param staleTime - Optional stale time in milliseconds.
|
|
118
|
+
* @returns Cached value or undefined when missing/stale.
|
|
194
119
|
*/
|
|
195
120
|
static async get(key, staleTime) {
|
|
196
121
|
const prefixedKey = _IgniterCallerCacheUtils.getPrefixedKey(key);
|
|
@@ -214,6 +139,10 @@ var _IgniterCallerCacheUtils = class _IgniterCallerCacheUtils {
|
|
|
214
139
|
}
|
|
215
140
|
/**
|
|
216
141
|
* Stores data in cache with current timestamp.
|
|
142
|
+
*
|
|
143
|
+
* @param key - Cache key (without prefix).
|
|
144
|
+
* @param data - Data to cache.
|
|
145
|
+
* @param ttl - Optional TTL override in seconds.
|
|
217
146
|
*/
|
|
218
147
|
static async set(key, data, ttl) {
|
|
219
148
|
const prefixedKey = _IgniterCallerCacheUtils.getPrefixedKey(key);
|
|
@@ -235,6 +164,8 @@ var _IgniterCallerCacheUtils = class _IgniterCallerCacheUtils {
|
|
|
235
164
|
}
|
|
236
165
|
/**
|
|
237
166
|
* Clears a specific cache entry.
|
|
167
|
+
*
|
|
168
|
+
* @param key - Cache key (without prefix).
|
|
238
169
|
*/
|
|
239
170
|
static async clear(key) {
|
|
240
171
|
const prefixedKey = _IgniterCallerCacheUtils.getPrefixedKey(key);
|
|
@@ -268,6 +199,8 @@ var _IgniterCallerCacheUtils = class _IgniterCallerCacheUtils {
|
|
|
268
199
|
}
|
|
269
200
|
/**
|
|
270
201
|
* Clears all cache entries.
|
|
202
|
+
*
|
|
203
|
+
* @returns Promise that resolves when in-memory cache is cleared.
|
|
271
204
|
*/
|
|
272
205
|
static async clearAll() {
|
|
273
206
|
_IgniterCallerCacheUtils.cache.clear();
|
|
@@ -306,6 +239,10 @@ var IgniterCallerSchemaUtils = class _IgniterCallerSchemaUtils {
|
|
|
306
239
|
/**
|
|
307
240
|
* Matches a URL path against schema map paths (supports path parameters).
|
|
308
241
|
*
|
|
242
|
+
* @param actualPath - Incoming request path.
|
|
243
|
+
* @param schemaPath - Schema path pattern.
|
|
244
|
+
* @returns Match result with params when matched.
|
|
245
|
+
*
|
|
309
246
|
* @example
|
|
310
247
|
* ```ts
|
|
311
248
|
* matchPath('/users/123', '/users/:id') // { matched: true, params: { id: '123' } }
|
|
@@ -334,6 +271,11 @@ var IgniterCallerSchemaUtils = class _IgniterCallerSchemaUtils {
|
|
|
334
271
|
}
|
|
335
272
|
/**
|
|
336
273
|
* Finds the schema for a given path and method from the schema map.
|
|
274
|
+
*
|
|
275
|
+
* @param schemaMap - Schema map from the builder.
|
|
276
|
+
* @param path - Request path to match.
|
|
277
|
+
* @param method - HTTP method to match.
|
|
278
|
+
* @returns Matching schema and extracted params.
|
|
337
279
|
*/
|
|
338
280
|
static findSchema(schemaMap, path, method) {
|
|
339
281
|
if (!schemaMap) {
|
|
@@ -359,6 +301,10 @@ var IgniterCallerSchemaUtils = class _IgniterCallerSchemaUtils {
|
|
|
359
301
|
*
|
|
360
302
|
* If the schema provides `~standard.validate`, it will be used.
|
|
361
303
|
* Otherwise, returns the input as-is.
|
|
304
|
+
*
|
|
305
|
+
* @param schema - StandardSchema instance.
|
|
306
|
+
* @param input - Input value to validate.
|
|
307
|
+
* @returns Validated input value.
|
|
362
308
|
*/
|
|
363
309
|
static async validateWithStandardSchema(schema, input) {
|
|
364
310
|
const standard = schema?.["~standard"];
|
|
@@ -377,7 +323,12 @@ var IgniterCallerSchemaUtils = class _IgniterCallerSchemaUtils {
|
|
|
377
323
|
/**
|
|
378
324
|
* Validates request body against schema.
|
|
379
325
|
*
|
|
380
|
-
* @
|
|
326
|
+
* @param data - Request body data.
|
|
327
|
+
* @param schema - Request schema (if any).
|
|
328
|
+
* @param options - Validation options.
|
|
329
|
+
* @param context - Request context for error reporting.
|
|
330
|
+
* @param logger - Optional logger instance.
|
|
331
|
+
* @returns Validated data or throws/logs error based on validation mode.
|
|
381
332
|
*/
|
|
382
333
|
static async validateRequest(data, schema, options, context, logger) {
|
|
383
334
|
if (!schema || options?.mode === "off") {
|
|
@@ -410,7 +361,13 @@ var IgniterCallerSchemaUtils = class _IgniterCallerSchemaUtils {
|
|
|
410
361
|
/**
|
|
411
362
|
* Validates response data against schema.
|
|
412
363
|
*
|
|
413
|
-
* @
|
|
364
|
+
* @param data - Response payload to validate.
|
|
365
|
+
* @param schema - Response schema (if any).
|
|
366
|
+
* @param statusCode - HTTP status code.
|
|
367
|
+
* @param options - Validation options.
|
|
368
|
+
* @param context - Request context for error reporting.
|
|
369
|
+
* @param logger - Optional logger instance.
|
|
370
|
+
* @returns Validated data or throws/logs error based on validation mode.
|
|
414
371
|
*/
|
|
415
372
|
static async validateResponse(data, schema, statusCode, options, context, logger) {
|
|
416
373
|
if (!schema || options?.mode === "off") {
|
|
@@ -445,6 +402,12 @@ var IgniterCallerSchemaUtils = class _IgniterCallerSchemaUtils {
|
|
|
445
402
|
|
|
446
403
|
// src/utils/url.ts
|
|
447
404
|
var IgniterCallerUrlUtils = class {
|
|
405
|
+
/**
|
|
406
|
+
* Builds a full URL with optional base URL and query parameters.
|
|
407
|
+
*
|
|
408
|
+
* @param params - URL construction parameters.
|
|
409
|
+
* @returns Full URL string.
|
|
410
|
+
*/
|
|
448
411
|
static buildUrl(params) {
|
|
449
412
|
const { url, baseURL, query } = params;
|
|
450
413
|
let fullUrl = url;
|
|
@@ -461,7 +424,7 @@ var IgniterCallerUrlUtils = class {
|
|
|
461
424
|
}
|
|
462
425
|
};
|
|
463
426
|
|
|
464
|
-
// src/
|
|
427
|
+
// src/builders/request.builder.ts
|
|
465
428
|
var VALIDATABLE_CONTENT_TYPES = [
|
|
466
429
|
"json",
|
|
467
430
|
"xml",
|
|
@@ -509,6 +472,11 @@ async function parseResponseByContentType(response, contentType) {
|
|
|
509
472
|
}
|
|
510
473
|
}
|
|
511
474
|
var IgniterCallerRequestBuilder = class {
|
|
475
|
+
/**
|
|
476
|
+
* Creates a new request builder instance.
|
|
477
|
+
*
|
|
478
|
+
* @param params - Builder configuration from the manager.
|
|
479
|
+
*/
|
|
512
480
|
constructor(params) {
|
|
513
481
|
this.options = {
|
|
514
482
|
method: "GET",
|
|
@@ -531,15 +499,19 @@ var IgniterCallerRequestBuilder = class {
|
|
|
531
499
|
this.options.headers = { ...this.options.headers, Cookie: cookieStr };
|
|
532
500
|
}
|
|
533
501
|
this.logger = params.logger;
|
|
502
|
+
this.telemetry = params.telemetry;
|
|
534
503
|
this.requestInterceptors = params.requestInterceptors;
|
|
535
504
|
this.responseInterceptors = params.responseInterceptors;
|
|
536
505
|
this.eventEmitter = params.eventEmitter;
|
|
537
506
|
this.schemas = params.schemas;
|
|
538
507
|
this.schemaValidation = params.schemaValidation;
|
|
508
|
+
this.mock = params.mock;
|
|
539
509
|
}
|
|
540
510
|
/**
|
|
541
511
|
* Sets the HTTP method for this request.
|
|
542
512
|
* @internal Used by IgniterCaller.request() for generic requests.
|
|
513
|
+
*
|
|
514
|
+
* @param method - HTTP method for the request.
|
|
543
515
|
*/
|
|
544
516
|
_setMethod(method) {
|
|
545
517
|
this.options.method = method;
|
|
@@ -548,6 +520,8 @@ var IgniterCallerRequestBuilder = class {
|
|
|
548
520
|
/**
|
|
549
521
|
* Sets the URL for this request.
|
|
550
522
|
* @internal Used when URL is passed to HTTP method directly.
|
|
523
|
+
*
|
|
524
|
+
* @param url - Request URL or path.
|
|
551
525
|
*/
|
|
552
526
|
_setUrl(url) {
|
|
553
527
|
this.options.url = url;
|
|
@@ -555,6 +529,8 @@ var IgniterCallerRequestBuilder = class {
|
|
|
555
529
|
}
|
|
556
530
|
/**
|
|
557
531
|
* Overrides the logger for this request chain.
|
|
532
|
+
*
|
|
533
|
+
* @param logger - Logger implementation from `@igniter-js/common`.
|
|
558
534
|
*/
|
|
559
535
|
withLogger(logger) {
|
|
560
536
|
this.logger = logger;
|
|
@@ -562,6 +538,8 @@ var IgniterCallerRequestBuilder = class {
|
|
|
562
538
|
}
|
|
563
539
|
/**
|
|
564
540
|
* Sets the request URL.
|
|
541
|
+
*
|
|
542
|
+
* @param url - Request URL or path.
|
|
565
543
|
*/
|
|
566
544
|
url(url) {
|
|
567
545
|
this.options.url = url;
|
|
@@ -570,6 +548,8 @@ var IgniterCallerRequestBuilder = class {
|
|
|
570
548
|
/**
|
|
571
549
|
* Sets the request body.
|
|
572
550
|
* For GET/HEAD requests, body will be automatically converted to query params.
|
|
551
|
+
*
|
|
552
|
+
* @param body - Body payload for the request.
|
|
573
553
|
*/
|
|
574
554
|
body(body) {
|
|
575
555
|
this.options.body = body;
|
|
@@ -577,6 +557,8 @@ var IgniterCallerRequestBuilder = class {
|
|
|
577
557
|
}
|
|
578
558
|
/**
|
|
579
559
|
* Sets URL query parameters.
|
|
560
|
+
*
|
|
561
|
+
* @param params - Query string parameters.
|
|
580
562
|
*/
|
|
581
563
|
params(params) {
|
|
582
564
|
this.options.params = params;
|
|
@@ -584,6 +566,8 @@ var IgniterCallerRequestBuilder = class {
|
|
|
584
566
|
}
|
|
585
567
|
/**
|
|
586
568
|
* Merges additional headers into the request.
|
|
569
|
+
*
|
|
570
|
+
* @param headers - Header map merged into existing headers.
|
|
587
571
|
*/
|
|
588
572
|
headers(headers) {
|
|
589
573
|
this.options.headers = { ...this.options.headers, ...headers };
|
|
@@ -591,6 +575,8 @@ var IgniterCallerRequestBuilder = class {
|
|
|
591
575
|
}
|
|
592
576
|
/**
|
|
593
577
|
* Sets request timeout in milliseconds.
|
|
578
|
+
*
|
|
579
|
+
* @param timeout - Timeout in milliseconds.
|
|
594
580
|
*/
|
|
595
581
|
timeout(timeout) {
|
|
596
582
|
this.options.timeout = timeout;
|
|
@@ -598,6 +584,9 @@ var IgniterCallerRequestBuilder = class {
|
|
|
598
584
|
}
|
|
599
585
|
/**
|
|
600
586
|
* Sets cache strategy and optional cache key.
|
|
587
|
+
*
|
|
588
|
+
* @param cache - Cache strategy for the request.
|
|
589
|
+
* @param key - Optional cache key override.
|
|
601
590
|
*/
|
|
602
591
|
cache(cache, key) {
|
|
603
592
|
this.options.cache = cache;
|
|
@@ -606,6 +595,9 @@ var IgniterCallerRequestBuilder = class {
|
|
|
606
595
|
}
|
|
607
596
|
/**
|
|
608
597
|
* Configures retry behavior for failed requests.
|
|
598
|
+
*
|
|
599
|
+
* @param maxAttempts - Maximum number of attempts.
|
|
600
|
+
* @param options - Retry options excluding `maxAttempts`.
|
|
609
601
|
*/
|
|
610
602
|
retry(maxAttempts, options) {
|
|
611
603
|
this.retryOptions = { maxAttempts, ...options };
|
|
@@ -613,6 +605,8 @@ var IgniterCallerRequestBuilder = class {
|
|
|
613
605
|
}
|
|
614
606
|
/**
|
|
615
607
|
* Provides a fallback value if the request fails.
|
|
608
|
+
*
|
|
609
|
+
* @param fn - Fallback factory called when the request fails.
|
|
616
610
|
*/
|
|
617
611
|
fallback(fn) {
|
|
618
612
|
this.fallbackFn = fn;
|
|
@@ -620,6 +614,8 @@ var IgniterCallerRequestBuilder = class {
|
|
|
620
614
|
}
|
|
621
615
|
/**
|
|
622
616
|
* Sets cache stale time in milliseconds.
|
|
617
|
+
*
|
|
618
|
+
* @param milliseconds - Stale time in milliseconds.
|
|
623
619
|
*/
|
|
624
620
|
stale(milliseconds) {
|
|
625
621
|
this.staleTime = milliseconds;
|
|
@@ -633,6 +629,8 @@ var IgniterCallerRequestBuilder = class {
|
|
|
633
629
|
*
|
|
634
630
|
* The actual parsing is based on Content-Type headers, not this setting.
|
|
635
631
|
*
|
|
632
|
+
* @param schema - Zod/StandardSchema instance for validation (optional).
|
|
633
|
+
*
|
|
636
634
|
* @example
|
|
637
635
|
* ```ts
|
|
638
636
|
* // With Zod schema (validates JSON response)
|
|
@@ -651,6 +649,8 @@ var IgniterCallerRequestBuilder = class {
|
|
|
651
649
|
/**
|
|
652
650
|
* Downloads a file via GET request.
|
|
653
651
|
* @deprecated Use `.responseType<File>().execute()` instead. The response type is auto-detected.
|
|
652
|
+
*
|
|
653
|
+
* @param url - URL or path to download.
|
|
654
654
|
*/
|
|
655
655
|
getFile(url) {
|
|
656
656
|
this.options.method = "GET";
|
|
@@ -725,7 +725,7 @@ var IgniterCallerRequestBuilder = class {
|
|
|
725
725
|
statusCode: 408,
|
|
726
726
|
logger: this.logger,
|
|
727
727
|
metadata: { url: finalUrl },
|
|
728
|
-
cause: error
|
|
728
|
+
cause: error instanceof Error ? error : void 0
|
|
729
729
|
})
|
|
730
730
|
};
|
|
731
731
|
}
|
|
@@ -737,7 +737,7 @@ var IgniterCallerRequestBuilder = class {
|
|
|
737
737
|
message: error?.message || "Failed to download file",
|
|
738
738
|
logger: this.logger,
|
|
739
739
|
metadata: { url: finalUrl },
|
|
740
|
-
cause: error
|
|
740
|
+
cause: error instanceof Error ? error : void 0
|
|
741
741
|
})
|
|
742
742
|
};
|
|
743
743
|
}
|
|
@@ -756,8 +756,32 @@ var IgniterCallerRequestBuilder = class {
|
|
|
756
756
|
* - `application/octet-stream` → returned as Blob
|
|
757
757
|
*
|
|
758
758
|
* Schema validation (if configured) only runs for validatable content types (JSON, XML, CSV).
|
|
759
|
+
*
|
|
760
|
+
* @returns Response envelope with data or error.
|
|
759
761
|
*/
|
|
760
762
|
async execute() {
|
|
763
|
+
const startTime = Date.now();
|
|
764
|
+
const { safeUrl } = this.resolveUrl();
|
|
765
|
+
const method = this.options.method;
|
|
766
|
+
const baseURL = this.options.baseURL;
|
|
767
|
+
const timeoutMs = this.options.timeout;
|
|
768
|
+
this.telemetry?.emit(
|
|
769
|
+
"igniter.caller.request.execute.started",
|
|
770
|
+
{
|
|
771
|
+
level: "debug",
|
|
772
|
+
attributes: {
|
|
773
|
+
"ctx.request.method": method,
|
|
774
|
+
"ctx.request.url": safeUrl,
|
|
775
|
+
"ctx.request.baseUrl": baseURL,
|
|
776
|
+
"ctx.request.timeoutMs": timeoutMs
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
);
|
|
780
|
+
this.logger?.debug("IgniterCaller.request.execute started", {
|
|
781
|
+
method,
|
|
782
|
+
url: safeUrl,
|
|
783
|
+
baseURL
|
|
784
|
+
});
|
|
761
785
|
const effectiveCacheKey = this.cacheKey || this.options.url;
|
|
762
786
|
if (effectiveCacheKey && this.staleTime) {
|
|
763
787
|
const cached = await IgniterCallerCacheUtils.get(
|
|
@@ -765,8 +789,36 @@ var IgniterCallerRequestBuilder = class {
|
|
|
765
789
|
this.staleTime
|
|
766
790
|
);
|
|
767
791
|
if (cached !== void 0) {
|
|
768
|
-
this.
|
|
769
|
-
|
|
792
|
+
this.telemetry?.emit(
|
|
793
|
+
"igniter.caller.cache.read.hit",
|
|
794
|
+
{
|
|
795
|
+
level: "debug",
|
|
796
|
+
attributes: {
|
|
797
|
+
"ctx.request.method": method,
|
|
798
|
+
"ctx.request.url": safeUrl,
|
|
799
|
+
"ctx.cache.key": effectiveCacheKey,
|
|
800
|
+
"ctx.cache.staleTime": this.staleTime
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
);
|
|
804
|
+
const durationMs2 = Date.now() - startTime;
|
|
805
|
+
this.telemetry?.emit(
|
|
806
|
+
"igniter.caller.request.execute.success",
|
|
807
|
+
{
|
|
808
|
+
level: "info",
|
|
809
|
+
attributes: {
|
|
810
|
+
"ctx.request.method": method,
|
|
811
|
+
"ctx.request.url": safeUrl,
|
|
812
|
+
"ctx.request.durationMs": durationMs2,
|
|
813
|
+
"ctx.cache.hit": true
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
);
|
|
817
|
+
this.logger?.info("IgniterCaller.request.execute success (cache)", {
|
|
818
|
+
key: effectiveCacheKey,
|
|
819
|
+
method,
|
|
820
|
+
url: safeUrl,
|
|
821
|
+
durationMs: durationMs2
|
|
770
822
|
});
|
|
771
823
|
const cachedResult = {
|
|
772
824
|
data: cached,
|
|
@@ -778,13 +830,33 @@ var IgniterCallerRequestBuilder = class {
|
|
|
778
830
|
}
|
|
779
831
|
const result = await this.executeWithRetry();
|
|
780
832
|
if (result.error && this.fallbackFn) {
|
|
781
|
-
this.logger?.debug("IgniterCaller.execute applying fallback", {
|
|
833
|
+
this.logger?.debug("IgniterCaller.request.execute applying fallback", {
|
|
834
|
+
method,
|
|
835
|
+
url: safeUrl,
|
|
782
836
|
error: result.error
|
|
783
837
|
});
|
|
784
838
|
const fallbackResult = {
|
|
785
839
|
data: this.fallbackFn(),
|
|
786
840
|
error: void 0
|
|
787
841
|
};
|
|
842
|
+
const durationMs2 = Date.now() - startTime;
|
|
843
|
+
this.telemetry?.emit(
|
|
844
|
+
"igniter.caller.request.execute.success",
|
|
845
|
+
{
|
|
846
|
+
level: "info",
|
|
847
|
+
attributes: {
|
|
848
|
+
"ctx.request.method": method,
|
|
849
|
+
"ctx.request.url": safeUrl,
|
|
850
|
+
"ctx.request.durationMs": durationMs2,
|
|
851
|
+
"ctx.request.fallback": true
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
);
|
|
855
|
+
this.logger?.info("IgniterCaller.request.execute success (fallback)", {
|
|
856
|
+
method,
|
|
857
|
+
url: safeUrl,
|
|
858
|
+
durationMs: durationMs2
|
|
859
|
+
});
|
|
788
860
|
await this.emitEvent(fallbackResult);
|
|
789
861
|
return fallbackResult;
|
|
790
862
|
}
|
|
@@ -795,12 +867,57 @@ var IgniterCallerRequestBuilder = class {
|
|
|
795
867
|
this.staleTime
|
|
796
868
|
);
|
|
797
869
|
}
|
|
870
|
+
const durationMs = Date.now() - startTime;
|
|
871
|
+
if (result.error) {
|
|
872
|
+
this.telemetry?.emit(
|
|
873
|
+
"igniter.caller.request.execute.error",
|
|
874
|
+
{
|
|
875
|
+
level: "error",
|
|
876
|
+
attributes: {
|
|
877
|
+
"ctx.request.method": method,
|
|
878
|
+
"ctx.request.url": safeUrl,
|
|
879
|
+
"ctx.request.durationMs": durationMs,
|
|
880
|
+
"ctx.error.code": result.error instanceof IgniterCallerError ? result.error.code : "IGNITER_CALLER_UNKNOWN_ERROR",
|
|
881
|
+
"ctx.error.message": result.error.message,
|
|
882
|
+
"ctx.response.status": result.status
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
);
|
|
886
|
+
this.logger?.error("IgniterCaller.request.execute failed", {
|
|
887
|
+
method,
|
|
888
|
+
url: safeUrl,
|
|
889
|
+
durationMs,
|
|
890
|
+
error: result.error
|
|
891
|
+
});
|
|
892
|
+
} else {
|
|
893
|
+
const contentType = result.headers?.get("content-type") || void 0;
|
|
894
|
+
this.telemetry?.emit(
|
|
895
|
+
"igniter.caller.request.execute.success",
|
|
896
|
+
{
|
|
897
|
+
level: "info",
|
|
898
|
+
attributes: {
|
|
899
|
+
"ctx.request.method": method,
|
|
900
|
+
"ctx.request.url": safeUrl,
|
|
901
|
+
"ctx.request.durationMs": durationMs,
|
|
902
|
+
"ctx.response.status": result.status,
|
|
903
|
+
"ctx.response.contentType": contentType,
|
|
904
|
+
"ctx.cache.hit": false
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
);
|
|
908
|
+
this.logger?.info("IgniterCaller.request.execute success", {
|
|
909
|
+
method,
|
|
910
|
+
url: safeUrl,
|
|
911
|
+
durationMs,
|
|
912
|
+
status: result.status
|
|
913
|
+
});
|
|
914
|
+
}
|
|
798
915
|
await this.emitEvent(result);
|
|
799
916
|
return result;
|
|
800
917
|
}
|
|
801
918
|
async executeWithRetry() {
|
|
802
919
|
const maxAttempts = this.retryOptions?.maxAttempts || 1;
|
|
803
|
-
const baseDelay = this.retryOptions?.baseDelay
|
|
920
|
+
const baseDelay = this.retryOptions?.baseDelay ?? 1e3;
|
|
804
921
|
const backoff = this.retryOptions?.backoff || "linear";
|
|
805
922
|
const retryOnStatus = this.retryOptions?.retryOnStatus || [
|
|
806
923
|
408,
|
|
@@ -810,13 +927,30 @@ var IgniterCallerRequestBuilder = class {
|
|
|
810
927
|
503,
|
|
811
928
|
504
|
|
812
929
|
];
|
|
930
|
+
const { safeUrl } = this.resolveUrl();
|
|
931
|
+
const method = this.options.method;
|
|
813
932
|
let lastError;
|
|
814
933
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
815
934
|
if (attempt > 0) {
|
|
816
935
|
const delay = backoff === "exponential" ? baseDelay * 2 ** (attempt - 1) : baseDelay * attempt;
|
|
817
|
-
this.
|
|
818
|
-
attempt,
|
|
819
|
-
|
|
936
|
+
this.telemetry?.emit(
|
|
937
|
+
"igniter.caller.retry.attempt.started",
|
|
938
|
+
{
|
|
939
|
+
level: "debug",
|
|
940
|
+
attributes: {
|
|
941
|
+
"ctx.request.method": method,
|
|
942
|
+
"ctx.request.url": safeUrl,
|
|
943
|
+
"ctx.retry.attempt": attempt + 1,
|
|
944
|
+
"ctx.retry.maxAttempts": maxAttempts,
|
|
945
|
+
"ctx.retry.delayMs": delay
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
);
|
|
949
|
+
this.logger?.debug("IgniterCaller.request.execute retrying", {
|
|
950
|
+
method,
|
|
951
|
+
url: safeUrl,
|
|
952
|
+
attempt: attempt + 1,
|
|
953
|
+
delayMs: delay
|
|
820
954
|
});
|
|
821
955
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
822
956
|
}
|
|
@@ -837,10 +971,8 @@ var IgniterCallerRequestBuilder = class {
|
|
|
837
971
|
}
|
|
838
972
|
async executeSingleRequest() {
|
|
839
973
|
let { url, requestInit, controller, timeoutId } = this.buildRequest();
|
|
840
|
-
this.
|
|
841
|
-
|
|
842
|
-
url
|
|
843
|
-
});
|
|
974
|
+
const { safeUrl } = this.resolveUrl();
|
|
975
|
+
const method = this.options.method;
|
|
844
976
|
if (this.requestInterceptors && this.requestInterceptors.length > 0) {
|
|
845
977
|
let modifiedOptions = { ...this.options, url };
|
|
846
978
|
for (const interceptor of this.requestInterceptors) {
|
|
@@ -870,13 +1002,36 @@ var IgniterCallerRequestBuilder = class {
|
|
|
870
1002
|
);
|
|
871
1003
|
} catch (error) {
|
|
872
1004
|
clearTimeout(timeoutId);
|
|
1005
|
+
const err = error;
|
|
1006
|
+
this.telemetry?.emit(
|
|
1007
|
+
"igniter.caller.validation.request.error",
|
|
1008
|
+
{
|
|
1009
|
+
level: "error",
|
|
1010
|
+
attributes: {
|
|
1011
|
+
"ctx.request.method": method,
|
|
1012
|
+
"ctx.request.url": safeUrl,
|
|
1013
|
+
"ctx.validation.type": "request",
|
|
1014
|
+
"ctx.validation.error": err.message
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
);
|
|
1018
|
+
this.logger?.error("IgniterCaller.request.validation failed", {
|
|
1019
|
+
method,
|
|
1020
|
+
url: safeUrl,
|
|
1021
|
+
error: err
|
|
1022
|
+
});
|
|
873
1023
|
return {
|
|
874
1024
|
data: void 0,
|
|
875
|
-
error
|
|
1025
|
+
error: err
|
|
876
1026
|
};
|
|
877
1027
|
}
|
|
878
1028
|
}
|
|
879
1029
|
}
|
|
1030
|
+
const mockResult = await this.executeMockRequest(url, safeUrl);
|
|
1031
|
+
if (mockResult) {
|
|
1032
|
+
clearTimeout(timeoutId);
|
|
1033
|
+
return mockResult;
|
|
1034
|
+
}
|
|
880
1035
|
try {
|
|
881
1036
|
const httpResponse = await fetch(url, {
|
|
882
1037
|
...requestInit,
|
|
@@ -926,9 +1081,29 @@ var IgniterCallerRequestBuilder = class {
|
|
|
926
1081
|
this.logger
|
|
927
1082
|
);
|
|
928
1083
|
} catch (error) {
|
|
1084
|
+
const err = error;
|
|
1085
|
+
this.telemetry?.emit(
|
|
1086
|
+
"igniter.caller.validation.response.error",
|
|
1087
|
+
{
|
|
1088
|
+
level: "error",
|
|
1089
|
+
attributes: {
|
|
1090
|
+
"ctx.request.method": method,
|
|
1091
|
+
"ctx.request.url": safeUrl,
|
|
1092
|
+
"ctx.validation.type": "response",
|
|
1093
|
+
"ctx.validation.error": err.message,
|
|
1094
|
+
"ctx.response.status": httpResponse.status
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
);
|
|
1098
|
+
this.logger?.error("IgniterCaller.response.validation failed", {
|
|
1099
|
+
method,
|
|
1100
|
+
url: safeUrl,
|
|
1101
|
+
status: httpResponse.status,
|
|
1102
|
+
error: err
|
|
1103
|
+
});
|
|
929
1104
|
return {
|
|
930
1105
|
data: void 0,
|
|
931
|
-
error,
|
|
1106
|
+
error: err,
|
|
932
1107
|
status: httpResponse.status,
|
|
933
1108
|
headers: httpResponse.headers
|
|
934
1109
|
};
|
|
@@ -940,20 +1115,40 @@ var IgniterCallerRequestBuilder = class {
|
|
|
940
1115
|
const zodSchema = this.responseTypeSchema;
|
|
941
1116
|
const result = zodSchema.safeParse(data);
|
|
942
1117
|
if (!result.success) {
|
|
1118
|
+
const err = new IgniterCallerError({
|
|
1119
|
+
code: "IGNITER_CALLER_RESPONSE_VALIDATION_FAILED",
|
|
1120
|
+
operation: "parseResponse",
|
|
1121
|
+
message: `Response validation failed: ${result.error.message}`,
|
|
1122
|
+
logger: this.logger,
|
|
1123
|
+
statusCode: httpResponse.status,
|
|
1124
|
+
metadata: {
|
|
1125
|
+
method: this.options.method,
|
|
1126
|
+
url
|
|
1127
|
+
},
|
|
1128
|
+
cause: result.error
|
|
1129
|
+
});
|
|
1130
|
+
this.telemetry?.emit(
|
|
1131
|
+
"igniter.caller.validation.response.error",
|
|
1132
|
+
{
|
|
1133
|
+
level: "error",
|
|
1134
|
+
attributes: {
|
|
1135
|
+
"ctx.request.method": method,
|
|
1136
|
+
"ctx.request.url": safeUrl,
|
|
1137
|
+
"ctx.validation.type": "response",
|
|
1138
|
+
"ctx.validation.error": err.message,
|
|
1139
|
+
"ctx.response.status": httpResponse.status
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
);
|
|
1143
|
+
this.logger?.error("IgniterCaller.response.validation failed", {
|
|
1144
|
+
method,
|
|
1145
|
+
url: safeUrl,
|
|
1146
|
+
status: httpResponse.status,
|
|
1147
|
+
error: err
|
|
1148
|
+
});
|
|
943
1149
|
return {
|
|
944
1150
|
data: void 0,
|
|
945
|
-
error:
|
|
946
|
-
code: "IGNITER_CALLER_RESPONSE_VALIDATION_FAILED",
|
|
947
|
-
operation: "parseResponse",
|
|
948
|
-
message: `Response validation failed: ${result.error.message}`,
|
|
949
|
-
logger: this.logger,
|
|
950
|
-
statusCode: httpResponse.status,
|
|
951
|
-
metadata: {
|
|
952
|
-
method: this.options.method,
|
|
953
|
-
url
|
|
954
|
-
},
|
|
955
|
-
cause: result.error
|
|
956
|
-
}),
|
|
1151
|
+
error: err,
|
|
957
1152
|
status: httpResponse.status,
|
|
958
1153
|
headers: httpResponse.headers
|
|
959
1154
|
};
|
|
@@ -964,40 +1159,80 @@ var IgniterCallerRequestBuilder = class {
|
|
|
964
1159
|
const standardSchema = this.responseTypeSchema;
|
|
965
1160
|
const result = await standardSchema["~standard"].validate(data);
|
|
966
1161
|
if (result.issues) {
|
|
1162
|
+
const err = new IgniterCallerError({
|
|
1163
|
+
code: "IGNITER_CALLER_RESPONSE_VALIDATION_FAILED",
|
|
1164
|
+
operation: "parseResponse",
|
|
1165
|
+
message: `Response validation failed`,
|
|
1166
|
+
logger: this.logger,
|
|
1167
|
+
statusCode: httpResponse.status,
|
|
1168
|
+
metadata: {
|
|
1169
|
+
method: this.options.method,
|
|
1170
|
+
url,
|
|
1171
|
+
issues: result.issues
|
|
1172
|
+
}
|
|
1173
|
+
});
|
|
1174
|
+
this.telemetry?.emit(
|
|
1175
|
+
"igniter.caller.validation.response.error",
|
|
1176
|
+
{
|
|
1177
|
+
level: "error",
|
|
1178
|
+
attributes: {
|
|
1179
|
+
"ctx.request.method": method,
|
|
1180
|
+
"ctx.request.url": safeUrl,
|
|
1181
|
+
"ctx.validation.type": "response",
|
|
1182
|
+
"ctx.validation.error": err.message,
|
|
1183
|
+
"ctx.response.status": httpResponse.status
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
);
|
|
1187
|
+
this.logger?.error("IgniterCaller.response.validation failed", {
|
|
1188
|
+
method,
|
|
1189
|
+
url: safeUrl,
|
|
1190
|
+
status: httpResponse.status,
|
|
1191
|
+
error: err
|
|
1192
|
+
});
|
|
967
1193
|
return {
|
|
968
1194
|
data: void 0,
|
|
969
|
-
error:
|
|
970
|
-
code: "IGNITER_CALLER_RESPONSE_VALIDATION_FAILED",
|
|
971
|
-
operation: "parseResponse",
|
|
972
|
-
message: `Response validation failed`,
|
|
973
|
-
logger: this.logger,
|
|
974
|
-
statusCode: httpResponse.status,
|
|
975
|
-
metadata: {
|
|
976
|
-
method: this.options.method,
|
|
977
|
-
url,
|
|
978
|
-
issues: result.issues
|
|
979
|
-
}
|
|
980
|
-
}),
|
|
1195
|
+
error: err,
|
|
981
1196
|
status: httpResponse.status,
|
|
982
1197
|
headers: httpResponse.headers
|
|
983
1198
|
};
|
|
984
1199
|
}
|
|
985
1200
|
data = result.value;
|
|
986
1201
|
} catch (error) {
|
|
1202
|
+
const err = new IgniterCallerError({
|
|
1203
|
+
code: "IGNITER_CALLER_RESPONSE_VALIDATION_FAILED",
|
|
1204
|
+
operation: "parseResponse",
|
|
1205
|
+
message: error?.message || "Response validation failed",
|
|
1206
|
+
logger: this.logger,
|
|
1207
|
+
statusCode: httpResponse.status,
|
|
1208
|
+
metadata: {
|
|
1209
|
+
method: this.options.method,
|
|
1210
|
+
url
|
|
1211
|
+
},
|
|
1212
|
+
cause: error instanceof Error ? error : void 0
|
|
1213
|
+
});
|
|
1214
|
+
this.telemetry?.emit(
|
|
1215
|
+
"igniter.caller.validation.response.error",
|
|
1216
|
+
{
|
|
1217
|
+
level: "error",
|
|
1218
|
+
attributes: {
|
|
1219
|
+
"ctx.request.method": method,
|
|
1220
|
+
"ctx.request.url": safeUrl,
|
|
1221
|
+
"ctx.validation.type": "response",
|
|
1222
|
+
"ctx.validation.error": err.message,
|
|
1223
|
+
"ctx.response.status": httpResponse.status
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
);
|
|
1227
|
+
this.logger?.error("IgniterCaller.response.validation failed", {
|
|
1228
|
+
method,
|
|
1229
|
+
url: safeUrl,
|
|
1230
|
+
status: httpResponse.status,
|
|
1231
|
+
error: err
|
|
1232
|
+
});
|
|
987
1233
|
return {
|
|
988
1234
|
data: void 0,
|
|
989
|
-
error:
|
|
990
|
-
code: "IGNITER_CALLER_RESPONSE_VALIDATION_FAILED",
|
|
991
|
-
operation: "parseResponse",
|
|
992
|
-
message: error?.message || "Response validation failed",
|
|
993
|
-
logger: this.logger,
|
|
994
|
-
statusCode: httpResponse.status,
|
|
995
|
-
metadata: {
|
|
996
|
-
method: this.options.method,
|
|
997
|
-
url
|
|
998
|
-
},
|
|
999
|
-
cause: error
|
|
1000
|
-
}),
|
|
1235
|
+
error: err,
|
|
1001
1236
|
status: httpResponse.status,
|
|
1002
1237
|
headers: httpResponse.headers
|
|
1003
1238
|
};
|
|
@@ -1020,20 +1255,38 @@ var IgniterCallerRequestBuilder = class {
|
|
|
1020
1255
|
} catch (error) {
|
|
1021
1256
|
clearTimeout(timeoutId);
|
|
1022
1257
|
if (error instanceof Error && error.name === "AbortError") {
|
|
1258
|
+
const err = new IgniterCallerError({
|
|
1259
|
+
code: "IGNITER_CALLER_TIMEOUT",
|
|
1260
|
+
operation: "execute",
|
|
1261
|
+
message: `Request timeout after ${this.options.timeout || 3e4}ms`,
|
|
1262
|
+
statusCode: 408,
|
|
1263
|
+
logger: this.logger,
|
|
1264
|
+
metadata: {
|
|
1265
|
+
method: this.options.method,
|
|
1266
|
+
url
|
|
1267
|
+
},
|
|
1268
|
+
cause: error instanceof Error ? error : void 0
|
|
1269
|
+
});
|
|
1270
|
+
this.telemetry?.emit(
|
|
1271
|
+
"igniter.caller.request.timeout.error",
|
|
1272
|
+
{
|
|
1273
|
+
level: "error",
|
|
1274
|
+
attributes: {
|
|
1275
|
+
"ctx.request.method": method,
|
|
1276
|
+
"ctx.request.url": safeUrl,
|
|
1277
|
+
"ctx.request.timeoutMs": this.options.timeout || 3e4
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
);
|
|
1281
|
+
this.logger?.error("IgniterCaller.request.execute timeout", {
|
|
1282
|
+
method,
|
|
1283
|
+
url: safeUrl,
|
|
1284
|
+
timeoutMs: this.options.timeout || 3e4,
|
|
1285
|
+
error: err
|
|
1286
|
+
});
|
|
1023
1287
|
return {
|
|
1024
1288
|
data: void 0,
|
|
1025
|
-
error:
|
|
1026
|
-
code: "IGNITER_CALLER_TIMEOUT",
|
|
1027
|
-
operation: "execute",
|
|
1028
|
-
message: `Request timeout after ${this.options.timeout || 3e4}ms`,
|
|
1029
|
-
statusCode: 408,
|
|
1030
|
-
logger: this.logger,
|
|
1031
|
-
metadata: {
|
|
1032
|
-
method: this.options.method,
|
|
1033
|
-
url
|
|
1034
|
-
},
|
|
1035
|
-
cause: error
|
|
1036
|
-
})
|
|
1289
|
+
error: err
|
|
1037
1290
|
};
|
|
1038
1291
|
}
|
|
1039
1292
|
return {
|
|
@@ -1048,13 +1301,13 @@ var IgniterCallerRequestBuilder = class {
|
|
|
1048
1301
|
method: this.options.method,
|
|
1049
1302
|
url
|
|
1050
1303
|
},
|
|
1051
|
-
cause: error
|
|
1304
|
+
cause: error instanceof Error ? error : void 0
|
|
1052
1305
|
})
|
|
1053
1306
|
};
|
|
1054
1307
|
}
|
|
1055
1308
|
}
|
|
1056
|
-
|
|
1057
|
-
const { method, url, body, params,
|
|
1309
|
+
resolveUrl() {
|
|
1310
|
+
const { method, url, body, params, baseURL } = this.options;
|
|
1058
1311
|
let finalParams = params;
|
|
1059
1312
|
if ((method === "GET" || method === "HEAD") && body && typeof body === "object") {
|
|
1060
1313
|
const bodyParams = {};
|
|
@@ -1070,6 +1323,14 @@ var IgniterCallerRequestBuilder = class {
|
|
|
1070
1323
|
baseURL,
|
|
1071
1324
|
query: finalParams
|
|
1072
1325
|
});
|
|
1326
|
+
return {
|
|
1327
|
+
url: fullUrl,
|
|
1328
|
+
safeUrl: fullUrl.split("?")[0]
|
|
1329
|
+
};
|
|
1330
|
+
}
|
|
1331
|
+
buildRequest() {
|
|
1332
|
+
const { method, body, headers, timeout, cache } = this.options;
|
|
1333
|
+
const { url } = this.resolveUrl();
|
|
1073
1334
|
const shouldIncludeBody = body && method !== "GET" && method !== "HEAD";
|
|
1074
1335
|
const rawBody = shouldIncludeBody && IgniterCallerBodyUtils.isRawBody(body);
|
|
1075
1336
|
const finalHeaders = IgniterCallerBodyUtils.normalizeHeadersForBody(
|
|
@@ -1085,76 +1346,366 @@ var IgniterCallerRequestBuilder = class {
|
|
|
1085
1346
|
const controller = new AbortController();
|
|
1086
1347
|
const timeoutId = setTimeout(() => controller.abort(), timeout || 3e4);
|
|
1087
1348
|
return {
|
|
1088
|
-
url
|
|
1349
|
+
url,
|
|
1089
1350
|
requestInit,
|
|
1090
1351
|
controller,
|
|
1091
1352
|
timeoutId
|
|
1092
1353
|
};
|
|
1093
1354
|
}
|
|
1094
1355
|
/**
|
|
1095
|
-
*
|
|
1356
|
+
* Normalizes a URL into a path for mock matching.
|
|
1096
1357
|
*/
|
|
1097
|
-
|
|
1098
|
-
if (
|
|
1358
|
+
normalizeMockPath(url, baseURL) {
|
|
1359
|
+
if (/^https?:\/\//i.test(url)) {
|
|
1099
1360
|
try {
|
|
1100
|
-
|
|
1101
|
-
} catch
|
|
1102
|
-
|
|
1361
|
+
return new URL(url).pathname;
|
|
1362
|
+
} catch {
|
|
1363
|
+
return url;
|
|
1103
1364
|
}
|
|
1104
1365
|
}
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
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}`;
|
|
1113
1375
|
}
|
|
1114
1376
|
/**
|
|
1115
|
-
*
|
|
1116
|
-
*
|
|
1117
|
-
* @param pattern URL string (exact match) or RegExp pattern
|
|
1118
|
-
* @param callback Function to execute when a response matches
|
|
1119
|
-
* @returns Cleanup function to remove the listener
|
|
1120
|
-
*
|
|
1121
|
-
* @example
|
|
1122
|
-
* ```ts
|
|
1123
|
-
* // Listen to specific endpoint
|
|
1124
|
-
* const cleanup = api.on('/users', (result) => {
|
|
1125
|
-
* console.log('Users fetched:', result.data)
|
|
1126
|
-
* })
|
|
1127
|
-
*
|
|
1128
|
-
* // Listen to pattern
|
|
1129
|
-
* api.on(/^\/users\/\d+$/, (result) => {
|
|
1130
|
-
* console.log('User detail fetched')
|
|
1131
|
-
* })
|
|
1132
|
-
*
|
|
1133
|
-
* // Cleanup when done
|
|
1134
|
-
* cleanup()
|
|
1135
|
-
* ```
|
|
1377
|
+
* Resolves the final query object (merges GET/HEAD body into params).
|
|
1136
1378
|
*/
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
callbacks2.add(callback);
|
|
1145
|
-
}
|
|
1146
|
-
return () => {
|
|
1147
|
-
const callbacks3 = this.listeners.get(pattern);
|
|
1148
|
-
if (callbacks3) {
|
|
1149
|
-
callbacks3.delete(callback);
|
|
1150
|
-
if (callbacks3.size === 0) {
|
|
1151
|
-
this.listeners.delete(pattern);
|
|
1152
|
-
}
|
|
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);
|
|
1153
1386
|
}
|
|
1154
|
-
}
|
|
1387
|
+
}
|
|
1388
|
+
return { ...bodyParams, ...params || {} };
|
|
1155
1389
|
}
|
|
1156
|
-
|
|
1157
|
-
|
|
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
|
+
}
|
|
1645
|
+
/**
|
|
1646
|
+
* Emits event for this response using injected emitter.
|
|
1647
|
+
*/
|
|
1648
|
+
async emitEvent(result) {
|
|
1649
|
+
if (this.eventEmitter) {
|
|
1650
|
+
try {
|
|
1651
|
+
await this.eventEmitter(this.options.url, this.options.method, result);
|
|
1652
|
+
} catch (error) {
|
|
1653
|
+
this.logger?.debug("Failed to emit event", { error });
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
};
|
|
1658
|
+
|
|
1659
|
+
// src/core/events.ts
|
|
1660
|
+
var IgniterCallerEvents = class {
|
|
1661
|
+
constructor() {
|
|
1662
|
+
this.listeners = /* @__PURE__ */ new Map();
|
|
1663
|
+
this.patternListeners = /* @__PURE__ */ new Map();
|
|
1664
|
+
}
|
|
1665
|
+
/**
|
|
1666
|
+
* Registers a listener for a specific URL or pattern.
|
|
1667
|
+
*
|
|
1668
|
+
* @param pattern URL string (exact match) or RegExp pattern
|
|
1669
|
+
* @param callback Function to execute when a response matches
|
|
1670
|
+
* @returns Cleanup function to remove the listener
|
|
1671
|
+
*
|
|
1672
|
+
* @example
|
|
1673
|
+
* ```ts
|
|
1674
|
+
* // Listen to specific endpoint
|
|
1675
|
+
* const cleanup = api.on('/users', (result) => {
|
|
1676
|
+
* console.log('Users fetched:', result.data)
|
|
1677
|
+
* })
|
|
1678
|
+
*
|
|
1679
|
+
* // Listen to pattern
|
|
1680
|
+
* api.on(/^\/users\/\d+$/, (result) => {
|
|
1681
|
+
* console.log('User detail fetched')
|
|
1682
|
+
* })
|
|
1683
|
+
*
|
|
1684
|
+
* // Cleanup when done
|
|
1685
|
+
* cleanup()
|
|
1686
|
+
* ```
|
|
1687
|
+
*/
|
|
1688
|
+
on(pattern, callback) {
|
|
1689
|
+
if (typeof pattern === "string") {
|
|
1690
|
+
if (!this.listeners.has(pattern)) {
|
|
1691
|
+
this.listeners.set(pattern, /* @__PURE__ */ new Set());
|
|
1692
|
+
}
|
|
1693
|
+
const callbacks2 = this.listeners.get(pattern);
|
|
1694
|
+
if (callbacks2) {
|
|
1695
|
+
callbacks2.add(callback);
|
|
1696
|
+
}
|
|
1697
|
+
return () => {
|
|
1698
|
+
const callbacks3 = this.listeners.get(pattern);
|
|
1699
|
+
if (callbacks3) {
|
|
1700
|
+
callbacks3.delete(callback);
|
|
1701
|
+
if (callbacks3.size === 0) {
|
|
1702
|
+
this.listeners.delete(pattern);
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
};
|
|
1706
|
+
}
|
|
1707
|
+
if (!this.patternListeners.has(pattern)) {
|
|
1708
|
+
this.patternListeners.set(pattern, /* @__PURE__ */ new Set());
|
|
1158
1709
|
}
|
|
1159
1710
|
const callbacks = this.patternListeners.get(pattern);
|
|
1160
1711
|
if (callbacks) {
|
|
@@ -1172,6 +1723,9 @@ var IgniterCallerEvents = class {
|
|
|
1172
1723
|
}
|
|
1173
1724
|
/**
|
|
1174
1725
|
* Removes a specific listener or all listeners for a pattern.
|
|
1726
|
+
*
|
|
1727
|
+
* @param pattern - URL string or RegExp pattern.
|
|
1728
|
+
* @param callback - Optional specific callback to remove.
|
|
1175
1729
|
*/
|
|
1176
1730
|
off(pattern, callback) {
|
|
1177
1731
|
if (typeof pattern === "string") {
|
|
@@ -1192,6 +1746,10 @@ var IgniterCallerEvents = class {
|
|
|
1192
1746
|
* Emits an event to all matching listeners.
|
|
1193
1747
|
*
|
|
1194
1748
|
* @internal
|
|
1749
|
+
*
|
|
1750
|
+
* @param url - Request URL to match listeners against.
|
|
1751
|
+
* @param method - HTTP method.
|
|
1752
|
+
* @param result - Response envelope.
|
|
1195
1753
|
*/
|
|
1196
1754
|
async emit(url, method, result) {
|
|
1197
1755
|
const context = {
|
|
@@ -1223,6 +1781,8 @@ var IgniterCallerEvents = class {
|
|
|
1223
1781
|
}
|
|
1224
1782
|
/**
|
|
1225
1783
|
* Removes all listeners.
|
|
1784
|
+
*
|
|
1785
|
+
* @returns Nothing.
|
|
1226
1786
|
*/
|
|
1227
1787
|
clear() {
|
|
1228
1788
|
this.listeners.clear();
|
|
@@ -1230,64 +1790,25 @@ var IgniterCallerEvents = class {
|
|
|
1230
1790
|
}
|
|
1231
1791
|
};
|
|
1232
1792
|
|
|
1233
|
-
// src/core/
|
|
1234
|
-
var
|
|
1793
|
+
// src/core/manager.ts
|
|
1794
|
+
var _IgniterCallerManager = class _IgniterCallerManager {
|
|
1795
|
+
/**
|
|
1796
|
+
* Creates a new manager instance.
|
|
1797
|
+
*
|
|
1798
|
+
* @param baseURL - Base URL prefix for requests.
|
|
1799
|
+
* @param opts - Optional configuration (headers, cookies, telemetry, schemas).
|
|
1800
|
+
*/
|
|
1235
1801
|
constructor(baseURL, opts) {
|
|
1236
1802
|
this.baseURL = baseURL;
|
|
1237
1803
|
this.headers = opts?.headers;
|
|
1238
1804
|
this.cookies = opts?.cookies;
|
|
1239
1805
|
this.logger = opts?.logger;
|
|
1806
|
+
this.telemetry = opts?.telemetry;
|
|
1240
1807
|
this.requestInterceptors = opts?.requestInterceptors;
|
|
1241
1808
|
this.responseInterceptors = opts?.responseInterceptors;
|
|
1242
1809
|
this.schemas = opts?.schemas;
|
|
1243
1810
|
this.schemaValidation = opts?.schemaValidation;
|
|
1244
|
-
|
|
1245
|
-
/**
|
|
1246
|
-
* Canonical initialization entrypoint.
|
|
1247
|
-
*
|
|
1248
|
-
* This is designed to remain stable when extracted to `@igniter-js/caller`.
|
|
1249
|
-
*/
|
|
1250
|
-
static create() {
|
|
1251
|
-
return IgniterCallerBuilder.create((state) => {
|
|
1252
|
-
if (state.store) {
|
|
1253
|
-
IgniterCallerCacheUtils.setStore(state.store, state.storeOptions);
|
|
1254
|
-
}
|
|
1255
|
-
return new _IgniterCaller(state.baseURL, {
|
|
1256
|
-
headers: state.headers,
|
|
1257
|
-
cookies: state.cookies,
|
|
1258
|
-
logger: state.logger,
|
|
1259
|
-
requestInterceptors: state.requestInterceptors,
|
|
1260
|
-
responseInterceptors: state.responseInterceptors,
|
|
1261
|
-
schemas: state.schemas,
|
|
1262
|
-
schemaValidation: state.schemaValidation
|
|
1263
|
-
});
|
|
1264
|
-
});
|
|
1265
|
-
}
|
|
1266
|
-
/**
|
|
1267
|
-
* Returns a new client with the same config and a new logger.
|
|
1268
|
-
*/
|
|
1269
|
-
withLogger(logger) {
|
|
1270
|
-
return new _IgniterCaller(this.baseURL, {
|
|
1271
|
-
headers: this.headers,
|
|
1272
|
-
cookies: this.cookies,
|
|
1273
|
-
logger,
|
|
1274
|
-
requestInterceptors: this.requestInterceptors,
|
|
1275
|
-
responseInterceptors: this.responseInterceptors,
|
|
1276
|
-
schemas: this.schemas,
|
|
1277
|
-
schemaValidation: this.schemaValidation
|
|
1278
|
-
});
|
|
1279
|
-
}
|
|
1280
|
-
setBaseURL(baseURL) {
|
|
1281
|
-
this.baseURL = baseURL;
|
|
1282
|
-
return this;
|
|
1283
|
-
}
|
|
1284
|
-
setHeaders(headers) {
|
|
1285
|
-
this.headers = headers;
|
|
1286
|
-
return this;
|
|
1287
|
-
}
|
|
1288
|
-
setCookies(cookies) {
|
|
1289
|
-
this.cookies = cookies;
|
|
1290
|
-
return this;
|
|
1811
|
+
this.mock = opts?.mock;
|
|
1291
1812
|
}
|
|
1292
1813
|
/**
|
|
1293
1814
|
* Creates common request builder params.
|
|
@@ -1298,24 +1819,17 @@ var _IgniterCaller = class _IgniterCaller {
|
|
|
1298
1819
|
defaultHeaders: this.headers,
|
|
1299
1820
|
defaultCookies: this.cookies,
|
|
1300
1821
|
logger: this.logger,
|
|
1822
|
+
telemetry: this.telemetry,
|
|
1301
1823
|
requestInterceptors: this.requestInterceptors,
|
|
1302
1824
|
responseInterceptors: this.responseInterceptors,
|
|
1303
1825
|
eventEmitter: async (url, method, result) => {
|
|
1304
|
-
await
|
|
1826
|
+
await _IgniterCallerManager.emitEvent(url, method, result);
|
|
1305
1827
|
},
|
|
1306
1828
|
schemas: this.schemas,
|
|
1307
|
-
schemaValidation: this.schemaValidation
|
|
1829
|
+
schemaValidation: this.schemaValidation,
|
|
1830
|
+
mock: this.mock
|
|
1308
1831
|
};
|
|
1309
1832
|
}
|
|
1310
|
-
/**
|
|
1311
|
-
* Resolves the full URL path by prepending baseURL if needed.
|
|
1312
|
-
*/
|
|
1313
|
-
resolveSchemaPath(url) {
|
|
1314
|
-
if (this.baseURL && !url.startsWith("http")) {
|
|
1315
|
-
return `${this.baseURL}${url}`;
|
|
1316
|
-
}
|
|
1317
|
-
return url;
|
|
1318
|
-
}
|
|
1319
1833
|
get(url) {
|
|
1320
1834
|
const builder = new IgniterCallerRequestBuilder(this.createBuilderParams());
|
|
1321
1835
|
builder._setMethod("GET");
|
|
@@ -1358,6 +1872,9 @@ var _IgniterCaller = class _IgniterCaller {
|
|
|
1358
1872
|
* This is a convenience method for making requests without using the builder pattern.
|
|
1359
1873
|
* Useful for dynamic requests where options are constructed programmatically.
|
|
1360
1874
|
*
|
|
1875
|
+
* @param options - Request configuration for method, url, and behavior.
|
|
1876
|
+
* @returns Response envelope with data or error.
|
|
1877
|
+
*
|
|
1361
1878
|
* @example
|
|
1362
1879
|
* ```ts
|
|
1363
1880
|
* const result = await api.request({
|
|
@@ -1424,6 +1941,9 @@ var _IgniterCaller = class _IgniterCaller {
|
|
|
1424
1941
|
* Executes multiple requests in parallel and returns results as an array.
|
|
1425
1942
|
*
|
|
1426
1943
|
* This is useful for batching independent API calls.
|
|
1944
|
+
*
|
|
1945
|
+
* @param requests - Array of request promises.
|
|
1946
|
+
* @returns Array of resolved results in the same order.
|
|
1427
1947
|
*/
|
|
1428
1948
|
static async batch(requests) {
|
|
1429
1949
|
return Promise.all(requests);
|
|
@@ -1444,7 +1964,7 @@ var _IgniterCaller = class _IgniterCaller {
|
|
|
1444
1964
|
* @example
|
|
1445
1965
|
* ```ts
|
|
1446
1966
|
* // Listen to all user endpoints
|
|
1447
|
-
* const cleanup =
|
|
1967
|
+
* const cleanup = IgniterCallerManager.on(/^\/users/, (result, context) => {
|
|
1448
1968
|
* console.log(`${context.method} ${context.url}`, result)
|
|
1449
1969
|
* })
|
|
1450
1970
|
*
|
|
@@ -1453,24 +1973,29 @@ var _IgniterCaller = class _IgniterCaller {
|
|
|
1453
1973
|
* ```
|
|
1454
1974
|
*/
|
|
1455
1975
|
static on(pattern, callback) {
|
|
1456
|
-
return
|
|
1976
|
+
return _IgniterCallerManager.events.on(pattern, callback);
|
|
1457
1977
|
}
|
|
1458
1978
|
/**
|
|
1459
1979
|
* Removes event listeners for a pattern.
|
|
1980
|
+
*
|
|
1981
|
+
* @param pattern - URL string or RegExp pattern.
|
|
1982
|
+
* @param callback - Callback to remove (optional).
|
|
1460
1983
|
*/
|
|
1461
1984
|
static off(pattern, callback) {
|
|
1462
|
-
|
|
1985
|
+
_IgniterCallerManager.events.off(pattern, callback);
|
|
1463
1986
|
}
|
|
1464
1987
|
/**
|
|
1465
1988
|
* Invalidates a specific cache entry.
|
|
1466
1989
|
*
|
|
1467
1990
|
* This is useful after mutations to ensure fresh data on next fetch.
|
|
1468
1991
|
*
|
|
1992
|
+
* @param key - Cache key to invalidate.
|
|
1993
|
+
*
|
|
1469
1994
|
* @example
|
|
1470
1995
|
* ```ts
|
|
1471
1996
|
* // After creating a user
|
|
1472
1997
|
* await api.post('/users').body(newUser).execute()
|
|
1473
|
-
* await
|
|
1998
|
+
* await IgniterCallerManager.invalidate('/users') // Clear users list cache
|
|
1474
1999
|
* ```
|
|
1475
2000
|
*/
|
|
1476
2001
|
static async invalidate(key) {
|
|
@@ -1480,11 +2005,12 @@ var _IgniterCaller = class _IgniterCaller {
|
|
|
1480
2005
|
* Invalidates all cache entries matching a pattern.
|
|
1481
2006
|
*
|
|
1482
2007
|
* @param pattern Glob pattern (e.g., '/users/*') or exact key
|
|
2008
|
+
* @returns Promise that resolves when invalidation completes.
|
|
1483
2009
|
*
|
|
1484
2010
|
* @example
|
|
1485
2011
|
* ```ts
|
|
1486
2012
|
* // Invalidate all user-related caches
|
|
1487
|
-
* await
|
|
2013
|
+
* await IgniterCallerManager.invalidatePattern('/users/*')
|
|
1488
2014
|
* ```
|
|
1489
2015
|
*/
|
|
1490
2016
|
static async invalidatePattern(pattern) {
|
|
@@ -1494,25 +2020,637 @@ var _IgniterCaller = class _IgniterCaller {
|
|
|
1494
2020
|
* Emits an event to all registered listeners.
|
|
1495
2021
|
*
|
|
1496
2022
|
* @internal
|
|
2023
|
+
*
|
|
2024
|
+
* @param url - Request URL (resolved).
|
|
2025
|
+
* @param method - HTTP method.
|
|
2026
|
+
* @param result - Response envelope.
|
|
1497
2027
|
*/
|
|
1498
2028
|
static async emitEvent(url, method, result) {
|
|
1499
|
-
await
|
|
2029
|
+
await _IgniterCallerManager.events.emit(url, method, result);
|
|
1500
2030
|
}
|
|
1501
2031
|
};
|
|
1502
2032
|
/** Global event emitter for observing HTTP responses */
|
|
1503
|
-
|
|
1504
|
-
var
|
|
2033
|
+
_IgniterCallerManager.events = new IgniterCallerEvents();
|
|
2034
|
+
var IgniterCallerManager = _IgniterCallerManager;
|
|
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
|
+
|
|
2075
|
+
// src/builders/main.builder.ts
|
|
2076
|
+
var IgniterCallerBuilder = class _IgniterCallerBuilder {
|
|
2077
|
+
constructor(state) {
|
|
2078
|
+
this.state = state;
|
|
2079
|
+
}
|
|
2080
|
+
/**
|
|
2081
|
+
* Creates a new builder instance.
|
|
2082
|
+
*
|
|
2083
|
+
* @returns New builder instance with empty state.
|
|
2084
|
+
*/
|
|
2085
|
+
static create() {
|
|
2086
|
+
return new _IgniterCallerBuilder({});
|
|
2087
|
+
}
|
|
2088
|
+
/**
|
|
2089
|
+
* Sets the base URL for all requests.
|
|
2090
|
+
*
|
|
2091
|
+
* @param baseURL - Base URL prefix for outgoing requests.
|
|
2092
|
+
*/
|
|
2093
|
+
withBaseUrl(baseURL) {
|
|
2094
|
+
return new _IgniterCallerBuilder({ ...this.state, baseURL });
|
|
2095
|
+
}
|
|
2096
|
+
/**
|
|
2097
|
+
* Merges default headers for all requests.
|
|
2098
|
+
*
|
|
2099
|
+
* @param headers - Header map merged into every request.
|
|
2100
|
+
*/
|
|
2101
|
+
withHeaders(headers) {
|
|
2102
|
+
return new _IgniterCallerBuilder({ ...this.state, headers });
|
|
2103
|
+
}
|
|
2104
|
+
/**
|
|
2105
|
+
* Sets default cookies (sent as the `Cookie` header).
|
|
2106
|
+
*
|
|
2107
|
+
* @param cookies - Cookie key/value pairs.
|
|
2108
|
+
*/
|
|
2109
|
+
withCookies(cookies) {
|
|
2110
|
+
return new _IgniterCallerBuilder({ ...this.state, cookies });
|
|
2111
|
+
}
|
|
2112
|
+
/**
|
|
2113
|
+
* Attaches a logger instance.
|
|
2114
|
+
*
|
|
2115
|
+
* @param logger - Logger implementation from `@igniter-js/common`.
|
|
2116
|
+
*/
|
|
2117
|
+
withLogger(logger) {
|
|
2118
|
+
return new _IgniterCallerBuilder({ ...this.state, logger });
|
|
2119
|
+
}
|
|
2120
|
+
/**
|
|
2121
|
+
* Adds a request interceptor that runs before each request.
|
|
2122
|
+
*
|
|
2123
|
+
* @param interceptor - Interceptor called with request options.
|
|
2124
|
+
*/
|
|
2125
|
+
withRequestInterceptor(interceptor) {
|
|
2126
|
+
const requestInterceptors = [
|
|
2127
|
+
...this.state.requestInterceptors || [],
|
|
2128
|
+
interceptor
|
|
2129
|
+
];
|
|
2130
|
+
return new _IgniterCallerBuilder({ ...this.state, requestInterceptors });
|
|
2131
|
+
}
|
|
2132
|
+
/**
|
|
2133
|
+
* Adds a response interceptor that runs after each request.
|
|
2134
|
+
*
|
|
2135
|
+
* @param interceptor - Interceptor called with the response result.
|
|
2136
|
+
*/
|
|
2137
|
+
withResponseInterceptor(interceptor) {
|
|
2138
|
+
const responseInterceptors = [
|
|
2139
|
+
...this.state.responseInterceptors || [],
|
|
2140
|
+
interceptor
|
|
2141
|
+
];
|
|
2142
|
+
return new _IgniterCallerBuilder({ ...this.state, responseInterceptors });
|
|
2143
|
+
}
|
|
2144
|
+
/**
|
|
2145
|
+
* Configures a persistent store adapter for caching.
|
|
2146
|
+
*
|
|
2147
|
+
* When configured, cache operations will use the store (e.g., Redis)
|
|
2148
|
+
* instead of in-memory cache, enabling persistent cache across deployments.
|
|
2149
|
+
*
|
|
2150
|
+
* @param store - Store adapter implementation.
|
|
2151
|
+
* @param options - Store options (ttl, keyPrefix, fallback).
|
|
2152
|
+
*/
|
|
2153
|
+
withStore(store, options) {
|
|
2154
|
+
return new _IgniterCallerBuilder({
|
|
2155
|
+
...this.state,
|
|
2156
|
+
store,
|
|
2157
|
+
storeOptions: options
|
|
2158
|
+
});
|
|
2159
|
+
}
|
|
2160
|
+
/**
|
|
2161
|
+
* Configures schema-based type safety and validation.
|
|
2162
|
+
*
|
|
2163
|
+
* Enables automatic type inference for requests/responses based on
|
|
2164
|
+
* route and method, with optional runtime validation via StandardSchemaV1
|
|
2165
|
+
* (Zod is supported).
|
|
2166
|
+
*
|
|
2167
|
+
* @param schemas - Schema map keyed by URL path and method.
|
|
2168
|
+
* @param validation - Validation options for request/response checks.
|
|
2169
|
+
*
|
|
2170
|
+
* @example
|
|
2171
|
+
* ```ts
|
|
2172
|
+
* const api = IgniterCaller.create()
|
|
2173
|
+
* .withSchemas({
|
|
2174
|
+
* '/users': {
|
|
2175
|
+
* GET: {
|
|
2176
|
+
* responses: {
|
|
2177
|
+
* 200: z.array(UserSchema),
|
|
2178
|
+
* 401: ErrorSchema,
|
|
2179
|
+
* },
|
|
2180
|
+
* },
|
|
2181
|
+
* POST: {
|
|
2182
|
+
* request: CreateUserSchema,
|
|
2183
|
+
* responses: {
|
|
2184
|
+
* 201: UserSchema,
|
|
2185
|
+
* 400: ValidationErrorSchema,
|
|
2186
|
+
* },
|
|
2187
|
+
* },
|
|
2188
|
+
* },
|
|
2189
|
+
* })
|
|
2190
|
+
* .build()
|
|
2191
|
+
* ```
|
|
2192
|
+
*/
|
|
2193
|
+
withSchemas(schemas, validation) {
|
|
2194
|
+
const nextState = {
|
|
2195
|
+
...this.state,
|
|
2196
|
+
schemas,
|
|
2197
|
+
schemaValidation: validation
|
|
2198
|
+
};
|
|
2199
|
+
return new _IgniterCallerBuilder(nextState);
|
|
2200
|
+
}
|
|
2201
|
+
/**
|
|
2202
|
+
* Attaches telemetry for request monitoring and observability.
|
|
2203
|
+
*
|
|
2204
|
+
* Telemetry is optional and only emits events when a manager is provided.
|
|
2205
|
+
*
|
|
2206
|
+
* Telemetry events emitted by the caller package include:
|
|
2207
|
+
* - `request.execute.started`
|
|
2208
|
+
* - `request.execute.success`
|
|
2209
|
+
* - `request.execute.error`
|
|
2210
|
+
* - `request.timeout.error`
|
|
2211
|
+
* - `cache.read.hit`
|
|
2212
|
+
* - `retry.attempt.started`
|
|
2213
|
+
* - `validation.request.error`
|
|
2214
|
+
* - `validation.response.error`
|
|
2215
|
+
*
|
|
2216
|
+
* @param telemetry - Telemetry manager instance.
|
|
2217
|
+
*
|
|
2218
|
+
* @example
|
|
2219
|
+
* ```ts
|
|
2220
|
+
* import { IgniterTelemetry } from '@igniter-js/telemetry'
|
|
2221
|
+
* import { IgniterCallerTelemetryEvents } from '@igniter-js/caller/telemetry'
|
|
2222
|
+
*
|
|
2223
|
+
* const telemetry = IgniterTelemetry.create()
|
|
2224
|
+
* .withService('my-api')
|
|
2225
|
+
* .addEvents(IgniterCallerTelemetryEvents)
|
|
2226
|
+
* .build()
|
|
2227
|
+
*
|
|
2228
|
+
* const api = IgniterCaller.create()
|
|
2229
|
+
* .withBaseUrl('https://api.example.com')
|
|
2230
|
+
* .withTelemetry(telemetry)
|
|
2231
|
+
* .build()
|
|
2232
|
+
* ```
|
|
2233
|
+
*/
|
|
2234
|
+
withTelemetry(telemetry) {
|
|
2235
|
+
return new _IgniterCallerBuilder({ ...this.state, telemetry });
|
|
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
|
+
}
|
|
2247
|
+
/**
|
|
2248
|
+
* Builds the `IgniterCaller` instance.
|
|
2249
|
+
*
|
|
2250
|
+
* @returns Configured manager instance.
|
|
2251
|
+
*/
|
|
2252
|
+
build() {
|
|
2253
|
+
if (this.state.store) {
|
|
2254
|
+
IgniterCallerCacheUtils.setStore(this.state.store, this.state.storeOptions);
|
|
2255
|
+
}
|
|
2256
|
+
const manager = new IgniterCallerManager(this.state.baseURL, {
|
|
2257
|
+
headers: this.state.headers,
|
|
2258
|
+
cookies: this.state.cookies,
|
|
2259
|
+
logger: this.state.logger,
|
|
2260
|
+
telemetry: this.state.telemetry,
|
|
2261
|
+
requestInterceptors: this.state.requestInterceptors,
|
|
2262
|
+
responseInterceptors: this.state.responseInterceptors,
|
|
2263
|
+
schemas: this.state.schemas,
|
|
2264
|
+
schemaValidation: this.state.schemaValidation,
|
|
2265
|
+
mock: this.state.mock
|
|
2266
|
+
});
|
|
2267
|
+
this.state.logger?.info("IgniterCaller initialized", {
|
|
2268
|
+
baseURL: this.state.baseURL,
|
|
2269
|
+
hasTelemetry: Boolean(this.state.telemetry),
|
|
2270
|
+
hasStore: Boolean(this.state.store),
|
|
2271
|
+
hasSchemas: Boolean(this.state.schemas)
|
|
2272
|
+
});
|
|
2273
|
+
return manager;
|
|
2274
|
+
}
|
|
2275
|
+
};
|
|
2276
|
+
var IgniterCaller = {
|
|
2277
|
+
create: IgniterCallerBuilder.create
|
|
2278
|
+
};
|
|
2279
|
+
var IgniterCallerSchemaPathBuilder = class _IgniterCallerSchemaPathBuilder {
|
|
2280
|
+
constructor(methods, registry) {
|
|
2281
|
+
this.methods = methods;
|
|
2282
|
+
this.registry = registry;
|
|
2283
|
+
}
|
|
2284
|
+
/**
|
|
2285
|
+
* Creates a new path builder for the provided registry.
|
|
2286
|
+
*/
|
|
2287
|
+
static create(registry) {
|
|
2288
|
+
return new _IgniterCallerSchemaPathBuilder({}, registry);
|
|
2289
|
+
}
|
|
2290
|
+
/**
|
|
2291
|
+
* Returns a registry reference helper for a given key.
|
|
2292
|
+
* The helper exposes optional Zod-based wrappers (array/optional/nullable/record).
|
|
2293
|
+
*/
|
|
2294
|
+
ref(key) {
|
|
2295
|
+
const schema = this.registry[key];
|
|
2296
|
+
const zodSchema = schema;
|
|
2297
|
+
return {
|
|
2298
|
+
schema,
|
|
2299
|
+
array: () => zod.z.array(zodSchema),
|
|
2300
|
+
nullable: () => zodSchema.nullable(),
|
|
2301
|
+
optional: () => zodSchema.optional(),
|
|
2302
|
+
record: (keyType) => zod.z.record(
|
|
2303
|
+
zod.z.any(),
|
|
2304
|
+
zodSchema
|
|
2305
|
+
)
|
|
2306
|
+
};
|
|
2307
|
+
}
|
|
2308
|
+
/**
|
|
2309
|
+
* Defines a GET endpoint.
|
|
2310
|
+
*/
|
|
2311
|
+
get(config) {
|
|
2312
|
+
return this.addMethod("GET", config);
|
|
2313
|
+
}
|
|
2314
|
+
/**
|
|
2315
|
+
* Defines a POST endpoint.
|
|
2316
|
+
*/
|
|
2317
|
+
post(config) {
|
|
2318
|
+
return this.addMethod("POST", config);
|
|
2319
|
+
}
|
|
2320
|
+
/**
|
|
2321
|
+
* Defines a PUT endpoint.
|
|
2322
|
+
*/
|
|
2323
|
+
put(config) {
|
|
2324
|
+
return this.addMethod("PUT", config);
|
|
2325
|
+
}
|
|
2326
|
+
/**
|
|
2327
|
+
* Defines a PATCH endpoint.
|
|
2328
|
+
*/
|
|
2329
|
+
patch(config) {
|
|
2330
|
+
return this.addMethod("PATCH", config);
|
|
2331
|
+
}
|
|
2332
|
+
/**
|
|
2333
|
+
* Defines a DELETE endpoint.
|
|
2334
|
+
*/
|
|
2335
|
+
delete(config) {
|
|
2336
|
+
return this.addMethod("DELETE", config);
|
|
2337
|
+
}
|
|
2338
|
+
/**
|
|
2339
|
+
* Defines a HEAD endpoint.
|
|
2340
|
+
*/
|
|
2341
|
+
head(config) {
|
|
2342
|
+
return this.addMethod("HEAD", config);
|
|
2343
|
+
}
|
|
2344
|
+
/**
|
|
2345
|
+
* Builds the accumulated method map for the path.
|
|
2346
|
+
*/
|
|
2347
|
+
build() {
|
|
2348
|
+
return this.methods;
|
|
2349
|
+
}
|
|
2350
|
+
addMethod(method, config) {
|
|
2351
|
+
if (method in this.methods) {
|
|
2352
|
+
throw new IgniterCallerError({
|
|
2353
|
+
code: "IGNITER_CALLER_SCHEMA_DUPLICATE",
|
|
2354
|
+
operation: "buildSchema",
|
|
2355
|
+
message: `Schema for method "${method}" is already defined on this path.`,
|
|
2356
|
+
statusCode: 400,
|
|
2357
|
+
metadata: { method }
|
|
2358
|
+
});
|
|
2359
|
+
}
|
|
2360
|
+
return new _IgniterCallerSchemaPathBuilder(
|
|
2361
|
+
{
|
|
2362
|
+
...this.methods,
|
|
2363
|
+
[method]: {
|
|
2364
|
+
...config
|
|
2365
|
+
}
|
|
2366
|
+
},
|
|
2367
|
+
this.registry
|
|
2368
|
+
);
|
|
2369
|
+
}
|
|
2370
|
+
};
|
|
2371
|
+
|
|
2372
|
+
// src/builders/schema.builder.ts
|
|
2373
|
+
var IgniterCallerSchema = class _IgniterCallerSchema {
|
|
2374
|
+
constructor(schemas, registry) {
|
|
2375
|
+
this.schemas = schemas;
|
|
2376
|
+
this.registry = registry;
|
|
2377
|
+
}
|
|
2378
|
+
/**
|
|
2379
|
+
* Creates a new empty schema builder.
|
|
2380
|
+
*/
|
|
2381
|
+
static create() {
|
|
2382
|
+
return new _IgniterCallerSchema({}, {});
|
|
2383
|
+
}
|
|
2384
|
+
/**
|
|
2385
|
+
* Registers a reusable schema in the registry.
|
|
2386
|
+
*/
|
|
2387
|
+
schema(key, schema, options) {
|
|
2388
|
+
ensureValidSchemaKey(key);
|
|
2389
|
+
if (key in this.registry) {
|
|
2390
|
+
throw new IgniterCallerError({
|
|
2391
|
+
code: "IGNITER_CALLER_SCHEMA_DUPLICATE",
|
|
2392
|
+
operation: "buildSchema",
|
|
2393
|
+
message: `Schema registry key "${key}" is already defined.`,
|
|
2394
|
+
statusCode: 400,
|
|
2395
|
+
metadata: { key }
|
|
2396
|
+
});
|
|
2397
|
+
}
|
|
2398
|
+
const nextRegistry = {
|
|
2399
|
+
...this.registry,
|
|
2400
|
+
[key]: schema
|
|
2401
|
+
};
|
|
2402
|
+
void options?.internal;
|
|
2403
|
+
return new _IgniterCallerSchema(this.schemas, nextRegistry);
|
|
2404
|
+
}
|
|
2405
|
+
/**
|
|
2406
|
+
* Defines a path with its methods using a fluent builder.
|
|
2407
|
+
*/
|
|
2408
|
+
path(path, builder) {
|
|
2409
|
+
ensureValidPath(path);
|
|
2410
|
+
const pathBuilder = IgniterCallerSchemaPathBuilder.create(this.registry);
|
|
2411
|
+
const builtMethods = builder(pathBuilder).build();
|
|
2412
|
+
const existing = this.schemas[path] ?? {};
|
|
2413
|
+
for (const method of Object.keys(
|
|
2414
|
+
builtMethods
|
|
2415
|
+
)) {
|
|
2416
|
+
if (method in existing) {
|
|
2417
|
+
throw new IgniterCallerError({
|
|
2418
|
+
code: "IGNITER_CALLER_SCHEMA_DUPLICATE",
|
|
2419
|
+
operation: "buildSchema",
|
|
2420
|
+
message: `Schema for "${path}" with method "${method}" is already defined.`,
|
|
2421
|
+
statusCode: 400,
|
|
2422
|
+
metadata: { path, method }
|
|
2423
|
+
});
|
|
2424
|
+
}
|
|
2425
|
+
}
|
|
2426
|
+
const merged = {
|
|
2427
|
+
...existing,
|
|
2428
|
+
...builtMethods
|
|
2429
|
+
};
|
|
2430
|
+
const nextSchemas = {
|
|
2431
|
+
...this.schemas,
|
|
2432
|
+
[path]: merged
|
|
2433
|
+
};
|
|
2434
|
+
return new _IgniterCallerSchema(nextSchemas, this.registry);
|
|
2435
|
+
}
|
|
2436
|
+
/**
|
|
2437
|
+
* Builds the schema map and attaches inference + runtime helpers.
|
|
2438
|
+
*/
|
|
2439
|
+
build() {
|
|
2440
|
+
const result = {
|
|
2441
|
+
...this.schemas
|
|
2442
|
+
};
|
|
2443
|
+
const inferHelpers = createInferHelpers();
|
|
2444
|
+
const getHelpers = createGetHelpers(this.schemas, this.registry);
|
|
2445
|
+
Object.defineProperty(result, "$Infer", {
|
|
2446
|
+
value: inferHelpers,
|
|
2447
|
+
enumerable: false
|
|
2448
|
+
});
|
|
2449
|
+
Object.defineProperty(result, "get", {
|
|
2450
|
+
value: getHelpers,
|
|
2451
|
+
enumerable: false
|
|
2452
|
+
});
|
|
2453
|
+
return result;
|
|
2454
|
+
}
|
|
2455
|
+
};
|
|
2456
|
+
function createInferHelpers() {
|
|
2457
|
+
return {
|
|
2458
|
+
Path: void 0,
|
|
2459
|
+
Endpoint: (() => void 0),
|
|
2460
|
+
Request: (() => void 0),
|
|
2461
|
+
Response: (() => void 0),
|
|
2462
|
+
Responses: (() => void 0),
|
|
2463
|
+
Schema: (() => void 0)
|
|
2464
|
+
};
|
|
2465
|
+
}
|
|
2466
|
+
function createGetHelpers(schemas, registry) {
|
|
2467
|
+
return {
|
|
2468
|
+
path: (path) => schemas[path],
|
|
2469
|
+
endpoint: (path, method) => schemas[path][method],
|
|
2470
|
+
request: (path, method) => schemas[path][method]?.request,
|
|
2471
|
+
response: (path, method, status) => schemas[path][method]?.responses?.[status],
|
|
2472
|
+
schema: (key) => registry[key]
|
|
2473
|
+
};
|
|
2474
|
+
}
|
|
2475
|
+
function ensureValidPath(path) {
|
|
2476
|
+
if (!path || path.trim().length === 0) {
|
|
2477
|
+
throw new IgniterCallerError({
|
|
2478
|
+
code: "IGNITER_CALLER_SCHEMA_INVALID",
|
|
2479
|
+
operation: "buildSchema",
|
|
2480
|
+
message: "Path cannot be empty.",
|
|
2481
|
+
statusCode: 400
|
|
2482
|
+
});
|
|
2483
|
+
}
|
|
2484
|
+
if (!path.startsWith("/")) {
|
|
2485
|
+
throw new IgniterCallerError({
|
|
2486
|
+
code: "IGNITER_CALLER_SCHEMA_INVALID",
|
|
2487
|
+
operation: "buildSchema",
|
|
2488
|
+
message: `Path "${path}" must start with "/".`,
|
|
2489
|
+
statusCode: 400,
|
|
2490
|
+
metadata: { path }
|
|
2491
|
+
});
|
|
2492
|
+
}
|
|
2493
|
+
}
|
|
2494
|
+
function ensureValidSchemaKey(key) {
|
|
2495
|
+
if (!key || key.trim().length === 0) {
|
|
2496
|
+
throw new IgniterCallerError({
|
|
2497
|
+
code: "IGNITER_CALLER_SCHEMA_INVALID",
|
|
2498
|
+
operation: "buildSchema",
|
|
2499
|
+
message: "Schema registry key cannot be empty.",
|
|
2500
|
+
statusCode: 400
|
|
2501
|
+
});
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
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
|
+
|
|
2553
|
+
// src/adapters/mock.adapter.ts
|
|
2554
|
+
var MockCallerStoreAdapter = class _MockCallerStoreAdapter {
|
|
2555
|
+
constructor() {
|
|
2556
|
+
/** Underlying in-memory store. */
|
|
2557
|
+
this.client = /* @__PURE__ */ new Map();
|
|
2558
|
+
/** Tracks all calls for assertions. */
|
|
2559
|
+
this.calls = {
|
|
2560
|
+
get: 0,
|
|
2561
|
+
set: 0,
|
|
2562
|
+
delete: 0,
|
|
2563
|
+
has: 0
|
|
2564
|
+
};
|
|
2565
|
+
/** Captures recent operations. */
|
|
2566
|
+
this.history = {
|
|
2567
|
+
get: [],
|
|
2568
|
+
set: [],
|
|
2569
|
+
delete: [],
|
|
2570
|
+
has: []
|
|
2571
|
+
};
|
|
2572
|
+
}
|
|
2573
|
+
/** Creates a new mock adapter instance. */
|
|
2574
|
+
static create() {
|
|
2575
|
+
return new _MockCallerStoreAdapter();
|
|
2576
|
+
}
|
|
2577
|
+
/**
|
|
2578
|
+
* Retrieves a cached value by key.
|
|
2579
|
+
*
|
|
2580
|
+
* @param key - Cache key (without prefix).
|
|
2581
|
+
* @returns Cached value or null.
|
|
2582
|
+
*/
|
|
2583
|
+
async get(key) {
|
|
2584
|
+
this.calls.get += 1;
|
|
2585
|
+
this.history.get.push(key);
|
|
2586
|
+
return this.client.has(key) ? this.client.get(key) : null;
|
|
2587
|
+
}
|
|
2588
|
+
/**
|
|
2589
|
+
* Stores a cached value.
|
|
2590
|
+
*
|
|
2591
|
+
* @param key - Cache key (without prefix).
|
|
2592
|
+
* @param value - Value to store.
|
|
2593
|
+
* @param options - Cache options (ttl, etc).
|
|
2594
|
+
*/
|
|
2595
|
+
async set(key, value, options) {
|
|
2596
|
+
this.calls.set += 1;
|
|
2597
|
+
this.history.set.push({ key, value, options });
|
|
2598
|
+
this.client.set(key, value);
|
|
2599
|
+
}
|
|
2600
|
+
/**
|
|
2601
|
+
* Removes a cached value.
|
|
2602
|
+
*
|
|
2603
|
+
* @param key - Cache key (without prefix).
|
|
2604
|
+
*/
|
|
2605
|
+
async delete(key) {
|
|
2606
|
+
this.calls.delete += 1;
|
|
2607
|
+
this.history.delete.push(key);
|
|
2608
|
+
this.client.delete(key);
|
|
2609
|
+
}
|
|
2610
|
+
/**
|
|
2611
|
+
* Checks if a cached value exists.
|
|
2612
|
+
*
|
|
2613
|
+
* @param key - Cache key (without prefix).
|
|
2614
|
+
* @returns True when the key exists.
|
|
2615
|
+
*/
|
|
2616
|
+
async has(key) {
|
|
2617
|
+
this.calls.has += 1;
|
|
2618
|
+
this.history.has.push(key);
|
|
2619
|
+
return this.client.has(key);
|
|
2620
|
+
}
|
|
2621
|
+
/**
|
|
2622
|
+
* Clears all tracked state.
|
|
2623
|
+
*
|
|
2624
|
+
* @returns Nothing.
|
|
2625
|
+
*/
|
|
2626
|
+
clear() {
|
|
2627
|
+
this.client.clear();
|
|
2628
|
+
this.calls.get = 0;
|
|
2629
|
+
this.calls.set = 0;
|
|
2630
|
+
this.calls.delete = 0;
|
|
2631
|
+
this.calls.has = 0;
|
|
2632
|
+
this.history.get = [];
|
|
2633
|
+
this.history.set = [];
|
|
2634
|
+
this.history.delete = [];
|
|
2635
|
+
this.history.has = [];
|
|
2636
|
+
}
|
|
2637
|
+
};
|
|
1505
2638
|
|
|
1506
2639
|
// src/utils/testing.ts
|
|
1507
|
-
var
|
|
2640
|
+
var IgniterCallerHttpMock = class {
|
|
1508
2641
|
/**
|
|
1509
2642
|
* Creates a successful mock response.
|
|
2643
|
+
*
|
|
2644
|
+
* @param data - Mock response data.
|
|
1510
2645
|
*/
|
|
1511
2646
|
static mockResponse(data) {
|
|
1512
2647
|
return { data, error: void 0 };
|
|
1513
2648
|
}
|
|
1514
2649
|
/**
|
|
1515
2650
|
* Creates an error mock response.
|
|
2651
|
+
*
|
|
2652
|
+
* @param code - Error code to use.
|
|
2653
|
+
* @param message - Optional error message.
|
|
1516
2654
|
*/
|
|
1517
2655
|
static mockError(code, message = "Mock error") {
|
|
1518
2656
|
return {
|
|
@@ -1526,6 +2664,9 @@ var IgniterCallerMock = class {
|
|
|
1526
2664
|
}
|
|
1527
2665
|
/**
|
|
1528
2666
|
* Creates a successful file download mock.
|
|
2667
|
+
*
|
|
2668
|
+
* @param filename - File name for the mock.
|
|
2669
|
+
* @param content - File contents as string or Blob.
|
|
1529
2670
|
*/
|
|
1530
2671
|
static mockFile(filename, content) {
|
|
1531
2672
|
const blob = typeof content === "string" ? new Blob([content]) : content;
|
|
@@ -1534,6 +2675,8 @@ var IgniterCallerMock = class {
|
|
|
1534
2675
|
}
|
|
1535
2676
|
/**
|
|
1536
2677
|
* Creates a failed file download mock.
|
|
2678
|
+
*
|
|
2679
|
+
* @param message - Optional error message.
|
|
1537
2680
|
*/
|
|
1538
2681
|
static mockFileError(message = "Mock file error") {
|
|
1539
2682
|
return {
|
|
@@ -1553,9 +2696,16 @@ exports.IgniterCallerBuilder = IgniterCallerBuilder;
|
|
|
1553
2696
|
exports.IgniterCallerCacheUtils = IgniterCallerCacheUtils;
|
|
1554
2697
|
exports.IgniterCallerError = IgniterCallerError;
|
|
1555
2698
|
exports.IgniterCallerEvents = IgniterCallerEvents;
|
|
2699
|
+
exports.IgniterCallerHttpMock = IgniterCallerHttpMock;
|
|
2700
|
+
exports.IgniterCallerManager = IgniterCallerManager;
|
|
1556
2701
|
exports.IgniterCallerMock = IgniterCallerMock;
|
|
2702
|
+
exports.IgniterCallerMockBuilder = IgniterCallerMockBuilder;
|
|
2703
|
+
exports.IgniterCallerMockManager = IgniterCallerMockManager;
|
|
1557
2704
|
exports.IgniterCallerRequestBuilder = IgniterCallerRequestBuilder;
|
|
2705
|
+
exports.IgniterCallerSchema = IgniterCallerSchema;
|
|
2706
|
+
exports.IgniterCallerSchemaPathBuilder = IgniterCallerSchemaPathBuilder;
|
|
1558
2707
|
exports.IgniterCallerSchemaUtils = IgniterCallerSchemaUtils;
|
|
1559
2708
|
exports.IgniterCallerUrlUtils = IgniterCallerUrlUtils;
|
|
2709
|
+
exports.MockCallerStoreAdapter = MockCallerStoreAdapter;
|
|
1560
2710
|
//# sourceMappingURL=index.js.map
|
|
1561
2711
|
//# sourceMappingURL=index.js.map
|