recker 1.0.72 → 1.0.75-next.2e5a94f
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/README.md +5 -18
- package/dist/browser/core/client.d.ts +14 -8
- package/dist/browser/core/client.js +199 -17
- package/dist/browser/core/errors.d.ts +15 -1
- package/dist/browser/core/errors.js +140 -9
- package/dist/browser/core/request.d.ts +5 -0
- package/dist/browser/core/request.js +33 -2
- package/dist/browser/core-runtime/plugin-manifest.d.ts +24 -0
- package/dist/browser/core-runtime/plugin-manifest.js +159 -0
- package/dist/browser/core-runtime/request-context.d.ts +13 -0
- package/dist/browser/core-runtime/request-context.js +24 -0
- package/dist/browser/core-runtime/typed-events.d.ts +89 -0
- package/dist/browser/core-runtime/typed-events.js +34 -0
- package/dist/browser/index.iife.min.js +79 -79
- package/dist/browser/index.min.js +79 -79
- package/dist/browser/index.mini.iife.js +913 -97
- package/dist/browser/index.mini.iife.min.js +46 -46
- package/dist/browser/index.mini.min.js +46 -46
- package/dist/browser/index.mini.umd.js +913 -97
- package/dist/browser/index.mini.umd.min.js +46 -46
- package/dist/browser/index.umd.min.js +79 -79
- package/dist/browser/plugins/auth/aws-sigv4.d.ts +1 -0
- package/dist/browser/plugins/auth/aws-sigv4.js +19 -2
- package/dist/browser/plugins/retry.js +29 -1
- package/dist/browser/presets/aws.d.ts +1 -0
- package/dist/browser/presets/aws.js +62 -1
- package/dist/browser/runner/request-runner.d.ts +15 -5
- package/dist/browser/runner/request-runner.js +164 -30
- package/dist/browser/scrape/parser/nodes/html.d.ts +6 -0
- package/dist/browser/scrape/parser/nodes/html.js +70 -18
- package/dist/browser/scrape/parser/nodes/node.d.ts +1 -0
- package/dist/browser/scrape/parser/nodes/node.js +5 -0
- package/dist/browser/scrape/spider.d.ts +1 -0
- package/dist/browser/scrape/spider.js +39 -26
- package/dist/browser/seo/analyzer.d.ts +1 -1
- package/dist/browser/seo/analyzer.js +73 -42
- package/dist/browser/seo/index.d.ts +1 -1
- package/dist/browser/seo/rules/types.d.ts +2 -0
- package/dist/browser/seo/seo-spider.d.ts +2 -3
- package/dist/browser/seo/seo-spider.js +26 -202
- package/dist/browser/seo/types.d.ts +4 -0
- package/dist/browser/seo/validators/sitemap.js +9 -2
- package/dist/browser/transport/fetch.js +38 -5
- package/dist/browser/transport/undici.js +73 -11
- package/dist/browser/transport/worker.d.ts +0 -1
- package/dist/browser/transport/worker.js +1 -3
- package/dist/browser/types/index.d.ts +24 -0
- package/dist/cli/commands/mcp.js +5 -3
- package/dist/core/client.d.ts +14 -8
- package/dist/core/client.js +199 -17
- package/dist/core/errors.d.ts +15 -1
- package/dist/core/errors.js +140 -9
- package/dist/core/request.d.ts +5 -0
- package/dist/core/request.js +33 -2
- package/dist/core-runtime/plugin-manifest.d.ts +24 -0
- package/dist/core-runtime/plugin-manifest.js +159 -0
- package/dist/core-runtime/request-context.d.ts +13 -0
- package/dist/core-runtime/request-context.js +24 -0
- package/dist/core-runtime/typed-events.d.ts +89 -0
- package/dist/core-runtime/typed-events.js +34 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/mcp/cli.js +10 -8
- package/dist/mcp/profiles.d.ts +1 -1
- package/dist/mcp/profiles.js +31 -6
- package/dist/mcp/tools/categories.js +0 -1
- package/dist/mcp/tools/seo.js +320 -4
- package/dist/plugins/auth/aws-sigv4.d.ts +1 -0
- package/dist/plugins/auth/aws-sigv4.js +19 -2
- package/dist/plugins/retry.js +29 -1
- package/dist/presets/aws.d.ts +1 -0
- package/dist/presets/aws.js +62 -1
- package/dist/recker.d.ts +3 -0
- package/dist/recker.js +5 -0
- package/dist/runner/request-runner.d.ts +15 -5
- package/dist/runner/request-runner.js +164 -30
- package/dist/scrape/parser/nodes/html.d.ts +6 -0
- package/dist/scrape/parser/nodes/html.js +70 -18
- package/dist/scrape/parser/nodes/node.d.ts +1 -0
- package/dist/scrape/parser/nodes/node.js +5 -0
- package/dist/scrape/spider.d.ts +1 -0
- package/dist/scrape/spider.js +39 -26
- package/dist/search/google.d.ts +67 -0
- package/dist/search/google.js +480 -0
- package/dist/search/index.d.ts +3 -0
- package/dist/search/index.js +1 -0
- package/dist/seo/analyzer.d.ts +1 -1
- package/dist/seo/analyzer.js +73 -42
- package/dist/seo/index.d.ts +1 -1
- package/dist/seo/rules/types.d.ts +2 -0
- package/dist/seo/seo-spider.d.ts +2 -3
- package/dist/seo/seo-spider.js +26 -202
- package/dist/seo/types.d.ts +4 -0
- package/dist/seo/validators/sitemap.js +9 -2
- package/dist/transport/fetch.js +38 -5
- package/dist/transport/undici.js +73 -11
- package/dist/transport/worker.d.ts +0 -1
- package/dist/transport/worker.js +1 -3
- package/dist/types/index.d.ts +24 -0
- package/dist/version.js +1 -1
- package/package.json +9 -1
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
Nine protocols unified: HTTP, WebSocket, DNS, WHOIS, RDAP, FTP, SFTP, Telnet, HLS.
|
|
8
8
|
<br>
|
|
9
|
-
AI-native: OpenAI, Anthropic, Google, Ollama + MCP server with
|
|
9
|
+
AI-native: OpenAI, Anthropic, Google, Ollama + MCP server with 70 tools.
|
|
10
10
|
<br>
|
|
11
11
|
48 service presets built-in. Zero config.
|
|
12
12
|
|
|
@@ -121,7 +121,7 @@ recker.ws('wss://example.com/socket'); // WebSocket
|
|
|
121
121
|
| **SEO** | 400+ rules, 19 categories, site-wide spider crawler |
|
|
122
122
|
| **Testing** | 10 mock servers (HTTP, Proxy, WebSocket, DNS, FTP, HLS, SSE...) |
|
|
123
123
|
| **CLI** | `rek` - curl replacement with superpowers |
|
|
124
|
-
| **MCP** |
|
|
124
|
+
| **MCP** | 70 tools for AI assistants (Claude, Cursor, Windsurf) |
|
|
125
125
|
|
|
126
126
|
## Highlights
|
|
127
127
|
|
|
@@ -253,7 +253,7 @@ rek shell
|
|
|
253
253
|
|
|
254
254
|
## MCP Server
|
|
255
255
|
|
|
256
|
-
|
|
256
|
+
70 tools for AI assistants like Claude Code, Cursor, and Windsurf:
|
|
257
257
|
|
|
258
258
|
```bash
|
|
259
259
|
# One-liner for Claude Code (uses minimal category by default)
|
|
@@ -262,11 +262,11 @@ claude mcp add recker npx recker@latest mcp
|
|
|
262
262
|
# Add more categories as needed
|
|
263
263
|
claude mcp add recker npx recker@latest mcp --category=minimal,video,seo
|
|
264
264
|
|
|
265
|
-
# Enable all
|
|
265
|
+
# Enable all 70 tools
|
|
266
266
|
claude mcp add recker npx recker@latest mcp --category=full
|
|
267
267
|
```
|
|
268
268
|
|
|
269
|
-
**Categories:** `minimal` (default) `docs` `network` `dns` `security` `seo` `scrape` `video` `ai` `protocols` `parsing` `streaming` `full`
|
|
269
|
+
**Categories:** `minimal` (default) `docs` `network` `dns` `security` `seo` `scrape` `video` `ai` `protocols` `parsing` `streaming` `template` `full`
|
|
270
270
|
|
|
271
271
|
[→ MCP Documentation](https://forattini-dev.github.io/recker/#/getting-started/mcp)
|
|
272
272
|
|
|
@@ -285,19 +285,6 @@ claude mcp add recker npx recker@latest mcp --category=full
|
|
|
285
285
|
| Mock Servers | [→ Testing](https://forattini-dev.github.io/recker/#/cli/08-mock-servers) |
|
|
286
286
|
| API Reference | [→ Full API](https://forattini-dev.github.io/recker/#/reference/01-api) |
|
|
287
287
|
|
|
288
|
-
## Numbers
|
|
289
|
-
|
|
290
|
-
| Metric | Value |
|
|
291
|
-
|:-------|:------|
|
|
292
|
-
| Protocols | 9 (HTTP, WS, DNS, WHOIS, RDAP, FTP, SFTP, Telnet, HLS) |
|
|
293
|
-
| Plugins | 30+ |
|
|
294
|
-
| Auth Methods | 15 |
|
|
295
|
-
| API Presets | 48 |
|
|
296
|
-
| MCP Tools | 65 |
|
|
297
|
-
| SEO Rules | 400+ |
|
|
298
|
-
| Mock Servers | 10 |
|
|
299
|
-
| Tests | 4200+ |
|
|
300
|
-
|
|
301
288
|
## License
|
|
302
289
|
|
|
303
290
|
MIT © [Forattini](https://github.com/forattini-dev)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ClientOptions, Middleware, ReckerRequest, ReckerResponse, RequestOptions, CacheStorage, PageResult } from '../types/index.js';
|
|
2
2
|
import type { ClientAI, ClientOptionsWithAI } from '../types/ai-client.js';
|
|
3
3
|
import { RequestPromise } from './request-promise.js';
|
|
4
|
+
import { type RuntimeEventName, type RuntimeEventPayloads, type TypedEventBus } from '../core-runtime/typed-events.js';
|
|
4
5
|
import { type VersionInfo } from '../version.js';
|
|
5
6
|
import { PaginationOptions } from '../plugins/pagination.js';
|
|
6
7
|
import { RetryOptions } from '../plugins/retry.js';
|
|
@@ -15,6 +16,12 @@ interface ClientCacheConfig extends Omit<CacheOptions, 'storage'> {
|
|
|
15
16
|
driver?: 'memory' | 'file';
|
|
16
17
|
fileStoragePath?: string;
|
|
17
18
|
}
|
|
19
|
+
interface BatchRequestOptions<T = ReckerResponse> {
|
|
20
|
+
concurrency?: number;
|
|
21
|
+
mapResponse?: (res: ReckerResponse) => Promise<T> | T;
|
|
22
|
+
signal?: AbortSignal;
|
|
23
|
+
deadlineMs?: number;
|
|
24
|
+
}
|
|
18
25
|
export interface ExtendedClientOptions extends ClientOptions {
|
|
19
26
|
retry?: RetryOptions;
|
|
20
27
|
cache?: ClientCacheConfig;
|
|
@@ -41,6 +48,10 @@ export declare class Client {
|
|
|
41
48
|
private concurrencyConfig;
|
|
42
49
|
private requestPool?;
|
|
43
50
|
private maxResponseSize?;
|
|
51
|
+
readonly runtimeEventBus: TypedEventBus;
|
|
52
|
+
private shouldEmitRuntimeEvents;
|
|
53
|
+
private runtimeEventBusProviderAttached;
|
|
54
|
+
private runtimeEventListenerCount;
|
|
44
55
|
private cookieJar?;
|
|
45
56
|
private cookieIgnoreInvalid;
|
|
46
57
|
private defaultTimeout?;
|
|
@@ -63,6 +74,7 @@ export declare class Client {
|
|
|
63
74
|
beforeRequest(hook: (req: ReckerRequest) => ReckerRequest | void | Promise<ReckerRequest | void>): this;
|
|
64
75
|
afterResponse(hook: (req: ReckerRequest, res: ReckerResponse) => ReckerResponse | void | Promise<ReckerResponse | void>): this;
|
|
65
76
|
onError(hook: (error: Error, req: ReckerRequest) => ReckerResponse | void | Promise<ReckerResponse | void>): this;
|
|
77
|
+
onRuntimeEvent<K extends RuntimeEventName>(name: K, handler: (event: RuntimeEventPayloads[K]) => void): () => void;
|
|
66
78
|
private buildUrl;
|
|
67
79
|
request<T = unknown>(path: string, options?: RequestOptions): RequestPromise<T>;
|
|
68
80
|
get<T = unknown>(path: string, options?: Omit<RequestOptions, 'method'>): RequestPromise<T>;
|
|
@@ -70,10 +82,7 @@ export declare class Client {
|
|
|
70
82
|
batch<T = ReckerResponse>(requests: Array<{
|
|
71
83
|
path: string;
|
|
72
84
|
options?: RequestOptions;
|
|
73
|
-
}>, options?: {
|
|
74
|
-
concurrency?: number;
|
|
75
|
-
mapResponse?: (res: ReckerResponse) => Promise<T> | T;
|
|
76
|
-
}): Promise<{
|
|
85
|
+
}>, options?: BatchRequestOptions<T>): Promise<{
|
|
77
86
|
results: (T | Error)[];
|
|
78
87
|
stats: {
|
|
79
88
|
total: number;
|
|
@@ -85,10 +94,7 @@ export declare class Client {
|
|
|
85
94
|
multi<T = ReckerResponse>(requests: Array<{
|
|
86
95
|
path: string;
|
|
87
96
|
options?: RequestOptions;
|
|
88
|
-
}>, options?: {
|
|
89
|
-
concurrency?: number;
|
|
90
|
-
mapResponse?: (res: ReckerResponse) => Promise<T> | T;
|
|
91
|
-
}): Promise<{
|
|
97
|
+
}>, options?: BatchRequestOptions<T>): Promise<{
|
|
92
98
|
results: (Error | T)[];
|
|
93
99
|
stats: {
|
|
94
100
|
total: number;
|
|
@@ -5,6 +5,9 @@ import { RequestPromise } from './request-promise.js';
|
|
|
5
5
|
import { HttpError, MaxSizeExceededError, ConfigurationError, ValidationError, TimeoutError, UnsupportedError } from '../core/errors.js';
|
|
6
6
|
import { processBody, createFormData, isPlainObject } from '../utils/body.js';
|
|
7
7
|
import { RequestPool } from '../utils/request-pool.js';
|
|
8
|
+
import { createRequestContext, attachRequestContext, getRequestContext } from '../core-runtime/request-context.js';
|
|
9
|
+
import { createNoopEventBus } from '../core-runtime/typed-events.js';
|
|
10
|
+
import { attachPluginManifest, getPluginManifest, normalizePlugins, toSortedPlugins } from '../core-runtime/plugin-manifest.js';
|
|
8
11
|
import { normalizeConcurrency, expandHTTP2Options } from '../utils/concurrency.js';
|
|
9
12
|
import { getGlobalProtocolCache } from '../utils/protocol-cache.js';
|
|
10
13
|
import { getDefaultUserAgent } from '../utils/user-agent.js';
|
|
@@ -188,6 +191,10 @@ export class Client {
|
|
|
188
191
|
concurrencyConfig;
|
|
189
192
|
requestPool;
|
|
190
193
|
maxResponseSize;
|
|
194
|
+
runtimeEventBus;
|
|
195
|
+
shouldEmitRuntimeEvents;
|
|
196
|
+
runtimeEventBusProviderAttached = false;
|
|
197
|
+
runtimeEventListenerCount = 0;
|
|
191
198
|
cookieJar;
|
|
192
199
|
cookieIgnoreInvalid = false;
|
|
193
200
|
defaultTimeout;
|
|
@@ -215,6 +222,18 @@ export class Client {
|
|
|
215
222
|
this.defaultParams = options.defaults?.params || {};
|
|
216
223
|
this.paginationConfig = options.pagination;
|
|
217
224
|
this.maxResponseSize = options.maxResponseSize;
|
|
225
|
+
const runtimeEventBus = options.runtimeEventBus;
|
|
226
|
+
if (runtimeEventBus) {
|
|
227
|
+
this.runtimeEventBus = {
|
|
228
|
+
on: (name, handler) => runtimeEventBus.on(name, handler),
|
|
229
|
+
emit: (name, event) => runtimeEventBus.emit(name, event),
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
this.runtimeEventBus = createNoopEventBus();
|
|
234
|
+
}
|
|
235
|
+
this.shouldEmitRuntimeEvents = runtimeEventBus !== undefined;
|
|
236
|
+
this.runtimeEventBusProviderAttached = runtimeEventBus !== undefined;
|
|
218
237
|
this.debugEnabled = options.debug === true;
|
|
219
238
|
if (this.debugEnabled) {
|
|
220
239
|
this.logger = options.logger ?? consoleLogger;
|
|
@@ -276,8 +295,20 @@ export class Client {
|
|
|
276
295
|
this.transport = new FetchTransport();
|
|
277
296
|
this.transportKind = 'fetch';
|
|
278
297
|
}
|
|
298
|
+
const registerPlugin = (plugin, manifest = {}) => {
|
|
299
|
+
const existingManifest = getPluginManifest(plugin);
|
|
300
|
+
pluginRegistrations.push(attachPluginManifest(plugin, {
|
|
301
|
+
...existingManifest,
|
|
302
|
+
...manifest
|
|
303
|
+
}));
|
|
304
|
+
};
|
|
305
|
+
const pluginRegistrations = [];
|
|
279
306
|
if (options.retry) {
|
|
280
|
-
retryPlugin(options.retry)
|
|
307
|
+
registerPlugin(retryPlugin(options.retry), {
|
|
308
|
+
name: 'recker:retry',
|
|
309
|
+
priority: 120,
|
|
310
|
+
scope: 'request'
|
|
311
|
+
});
|
|
281
312
|
}
|
|
282
313
|
if (this.concurrencyConfig.max < Infinity || this.concurrencyConfig.requestsPerInterval < Infinity) {
|
|
283
314
|
this.requestPool = new RequestPool({
|
|
@@ -285,7 +316,13 @@ export class Client {
|
|
|
285
316
|
requestsPerInterval: this.concurrencyConfig.requestsPerInterval,
|
|
286
317
|
interval: this.concurrencyConfig.interval
|
|
287
318
|
});
|
|
288
|
-
|
|
319
|
+
registerPlugin((client) => {
|
|
320
|
+
client.middlewares.unshift(this.requestPool.asMiddleware());
|
|
321
|
+
}, {
|
|
322
|
+
name: 'recker:request-pool',
|
|
323
|
+
priority: 130,
|
|
324
|
+
scope: 'runtime'
|
|
325
|
+
});
|
|
289
326
|
if (this.debugEnabled && this.logger) {
|
|
290
327
|
this.logger.debug(`Global concurrency limit: ${this.concurrencyConfig.max} concurrent requests`);
|
|
291
328
|
}
|
|
@@ -296,7 +333,11 @@ export class Client {
|
|
|
296
333
|
}
|
|
297
334
|
}
|
|
298
335
|
if (options.dedup) {
|
|
299
|
-
dedupPlugin(options.dedup)
|
|
336
|
+
registerPlugin(dedupPlugin(options.dedup), {
|
|
337
|
+
name: 'recker:dedup',
|
|
338
|
+
priority: 110,
|
|
339
|
+
scope: 'request'
|
|
340
|
+
});
|
|
300
341
|
}
|
|
301
342
|
if (options.cache) {
|
|
302
343
|
let storage;
|
|
@@ -309,13 +350,31 @@ export class Client {
|
|
|
309
350
|
else {
|
|
310
351
|
storage = createDefaultCacheStorage();
|
|
311
352
|
}
|
|
312
|
-
cachePlugin({
|
|
353
|
+
registerPlugin(cachePlugin({
|
|
313
354
|
...options.cache,
|
|
314
355
|
storage
|
|
315
|
-
})
|
|
356
|
+
}), {
|
|
357
|
+
name: 'recker:cache',
|
|
358
|
+
priority: 100,
|
|
359
|
+
scope: 'request'
|
|
360
|
+
});
|
|
316
361
|
}
|
|
317
362
|
if (options.plugins) {
|
|
318
|
-
options.plugins.forEach((plugin) =>
|
|
363
|
+
options.plugins.forEach((plugin, index) => {
|
|
364
|
+
const existingManifest = getPluginManifest(plugin);
|
|
365
|
+
registerPlugin(plugin, {
|
|
366
|
+
name: existingManifest?.name || `user-plugin:${index}`,
|
|
367
|
+
priority: existingManifest?.priority ?? 90 - index,
|
|
368
|
+
scope: existingManifest?.scope || 'request'
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
const { ordered, debugOrder } = normalizePlugins(toSortedPlugins(pluginRegistrations));
|
|
373
|
+
ordered.forEach(({ plugin }) => {
|
|
374
|
+
plugin(this);
|
|
375
|
+
});
|
|
376
|
+
if (this.debugEnabled && this.logger) {
|
|
377
|
+
this.logger.debug(`Applied plugins in order: ${debugOrder.join(' | ')}`);
|
|
319
378
|
}
|
|
320
379
|
if (options.compression) {
|
|
321
380
|
const compressionMiddleware = createCompressionMiddleware(options.compression);
|
|
@@ -458,13 +517,46 @@ export class Client {
|
|
|
458
517
|
return header.split(/,(?=\s*[a-zA-Z0-9_-]+=)/g).map(s => s.trim());
|
|
459
518
|
}
|
|
460
519
|
async dispatch(req) {
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
520
|
+
const context = getRequestContext(req);
|
|
521
|
+
const transportStart = context ? Date.now() : 0;
|
|
522
|
+
if (context) {
|
|
523
|
+
this.runtimeEventBus.emit('transport:start', { context, req });
|
|
524
|
+
}
|
|
525
|
+
try {
|
|
526
|
+
if (req.useCurl && this.transportKind !== 'curl') {
|
|
527
|
+
if (!this.curlTransport) {
|
|
528
|
+
this.curlTransport = createLazyCurlTransport();
|
|
529
|
+
}
|
|
530
|
+
const response = await this.curlTransport.dispatch(req);
|
|
531
|
+
if (context) {
|
|
532
|
+
this.runtimeEventBus.emit('transport:finish', {
|
|
533
|
+
context,
|
|
534
|
+
req,
|
|
535
|
+
durationMs: Date.now() - transportStart
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
return response;
|
|
539
|
+
}
|
|
540
|
+
const response = await this.transport.dispatch(req);
|
|
541
|
+
if (context) {
|
|
542
|
+
this.runtimeEventBus.emit('transport:finish', {
|
|
543
|
+
context,
|
|
544
|
+
req,
|
|
545
|
+
durationMs: Date.now() - transportStart
|
|
546
|
+
});
|
|
464
547
|
}
|
|
465
|
-
return
|
|
548
|
+
return response;
|
|
549
|
+
}
|
|
550
|
+
catch (error) {
|
|
551
|
+
if (context) {
|
|
552
|
+
this.runtimeEventBus.emit('transport:error', {
|
|
553
|
+
context,
|
|
554
|
+
req,
|
|
555
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
throw error;
|
|
466
559
|
}
|
|
467
|
-
return this.transport.dispatch(req);
|
|
468
560
|
}
|
|
469
561
|
composeMiddlewares() {
|
|
470
562
|
const chain = [...this.middlewares];
|
|
@@ -563,6 +655,20 @@ export class Client {
|
|
|
563
655
|
this.canFastPath = false;
|
|
564
656
|
return this;
|
|
565
657
|
}
|
|
658
|
+
onRuntimeEvent(name, handler) {
|
|
659
|
+
this.shouldEmitRuntimeEvents = true;
|
|
660
|
+
this.runtimeEventListenerCount += 1;
|
|
661
|
+
const unsubscribe = this.runtimeEventBus.on(name, handler);
|
|
662
|
+
return () => {
|
|
663
|
+
unsubscribe();
|
|
664
|
+
if (this.runtimeEventListenerCount > 0) {
|
|
665
|
+
this.runtimeEventListenerCount -= 1;
|
|
666
|
+
}
|
|
667
|
+
if (!this.runtimeEventBusProviderAttached && this.runtimeEventListenerCount === 0) {
|
|
668
|
+
this.shouldEmitRuntimeEvents = false;
|
|
669
|
+
}
|
|
670
|
+
};
|
|
671
|
+
}
|
|
566
672
|
buildUrl(path, requestParams) {
|
|
567
673
|
const hasRequestParams = requestParams && Object.keys(requestParams).length > 0;
|
|
568
674
|
const hasDefaultParams = Object.keys(this.defaultParams).length > 0;
|
|
@@ -581,7 +687,7 @@ export class Client {
|
|
|
581
687
|
const mergedParams = { ...this.defaultParams, ...requestParams };
|
|
582
688
|
const usedParams = new Set();
|
|
583
689
|
if (finalPath.includes(':')) {
|
|
584
|
-
finalPath = finalPath.replace(/:([a-zA-Z0-9_]+)/g, (
|
|
690
|
+
finalPath = finalPath.replace(/:([a-zA-Z0-9_]+)/g, (_match, paramName) => {
|
|
585
691
|
if (mergedParams && paramName in mergedParams) {
|
|
586
692
|
usedParams.add(paramName);
|
|
587
693
|
return encodeURIComponent(String(mergedParams[paramName]));
|
|
@@ -616,19 +722,62 @@ export class Client {
|
|
|
616
722
|
}
|
|
617
723
|
request(path, options = {}) {
|
|
618
724
|
const url = this.buildUrl(path, options.params);
|
|
725
|
+
const canRequestMutateHeaders = (this.hooks.beforeRequest?.length ?? 0) > 0 ||
|
|
726
|
+
this.middlewares.length > 0;
|
|
727
|
+
const shouldAttachContext = this.shouldEmitRuntimeEvents ||
|
|
728
|
+
!!options.correlationId ||
|
|
729
|
+
!!options.tenant ||
|
|
730
|
+
!!options.traceId ||
|
|
731
|
+
!!options.policySource ||
|
|
732
|
+
(options.policyTags?.length ?? 0) > 0;
|
|
733
|
+
const createContextSeed = () => createRequestContext({
|
|
734
|
+
correlationId: options.correlationId,
|
|
735
|
+
tenant: options.tenant,
|
|
736
|
+
policyTags: options.policyTags,
|
|
737
|
+
policySource: options.policySource,
|
|
738
|
+
traceId: options.traceId
|
|
739
|
+
});
|
|
619
740
|
const usesFastPath = this.canFastPath &&
|
|
620
741
|
!options.headers &&
|
|
621
742
|
!options.timeout &&
|
|
622
743
|
!options.signal &&
|
|
623
744
|
options.maxResponseSize === undefined;
|
|
624
745
|
if (usesFastPath) {
|
|
625
|
-
|
|
746
|
+
let req = new HttpRequest(url, {
|
|
626
747
|
method: options.method || 'GET',
|
|
627
748
|
body: options.body,
|
|
628
749
|
headers: this.defaultHeadersObj,
|
|
629
750
|
throwHttpErrors: options.throwHttpErrors,
|
|
630
751
|
});
|
|
631
|
-
|
|
752
|
+
if (shouldAttachContext) {
|
|
753
|
+
req = attachRequestContext(req, createContextSeed());
|
|
754
|
+
}
|
|
755
|
+
const requestContext = getRequestContext(req);
|
|
756
|
+
const startTime = Date.now();
|
|
757
|
+
if (requestContext) {
|
|
758
|
+
this.runtimeEventBus.emit('request:start', { context: requestContext, req });
|
|
759
|
+
}
|
|
760
|
+
let responsePromise = this.fastHandler(req);
|
|
761
|
+
if (requestContext) {
|
|
762
|
+
responsePromise = responsePromise.then((response) => {
|
|
763
|
+
this.runtimeEventBus.emit('request:success', {
|
|
764
|
+
context: requestContext,
|
|
765
|
+
req,
|
|
766
|
+
res: response,
|
|
767
|
+
durationMs: Date.now() - startTime
|
|
768
|
+
});
|
|
769
|
+
return response;
|
|
770
|
+
}, (error) => {
|
|
771
|
+
const requestError = error instanceof Error ? error : new Error(String(error));
|
|
772
|
+
this.runtimeEventBus.emit('request:failed', {
|
|
773
|
+
context: requestContext,
|
|
774
|
+
req,
|
|
775
|
+
error: requestError,
|
|
776
|
+
durationMs: Date.now() - startTime
|
|
777
|
+
});
|
|
778
|
+
throw error;
|
|
779
|
+
});
|
|
780
|
+
}
|
|
632
781
|
return new RequestPromise(responsePromise);
|
|
633
782
|
}
|
|
634
783
|
let mergedHeaders;
|
|
@@ -639,6 +788,9 @@ export class Client {
|
|
|
639
788
|
: new Headers(options.headers);
|
|
640
789
|
optHeaders.forEach((value, key) => mergedHeaders.set(key, value));
|
|
641
790
|
}
|
|
791
|
+
else if (canRequestMutateHeaders) {
|
|
792
|
+
mergedHeaders = new Headers(this.defaultHeadersObj);
|
|
793
|
+
}
|
|
642
794
|
else {
|
|
643
795
|
mergedHeaders = this.defaultHeadersObj;
|
|
644
796
|
}
|
|
@@ -672,13 +824,41 @@ export class Client {
|
|
|
672
824
|
}
|
|
673
825
|
}
|
|
674
826
|
}
|
|
675
|
-
|
|
827
|
+
let req = new HttpRequest(url, {
|
|
676
828
|
...options,
|
|
677
829
|
headers: mergedHeaders,
|
|
678
830
|
signal,
|
|
679
831
|
maxResponseSize: options.maxResponseSize ?? this.maxResponseSize
|
|
680
832
|
});
|
|
681
|
-
|
|
833
|
+
if (shouldAttachContext) {
|
|
834
|
+
req = attachRequestContext(req, createContextSeed());
|
|
835
|
+
}
|
|
836
|
+
const requestContext = getRequestContext(req);
|
|
837
|
+
const startTime = Date.now();
|
|
838
|
+
if (requestContext) {
|
|
839
|
+
this.runtimeEventBus.emit('request:start', { context: requestContext, req });
|
|
840
|
+
}
|
|
841
|
+
let responsePromise = this.handler(req);
|
|
842
|
+
if (requestContext) {
|
|
843
|
+
responsePromise = responsePromise.then((response) => {
|
|
844
|
+
this.runtimeEventBus.emit('request:success', {
|
|
845
|
+
context: requestContext,
|
|
846
|
+
req,
|
|
847
|
+
res: response,
|
|
848
|
+
durationMs: Date.now() - startTime
|
|
849
|
+
});
|
|
850
|
+
return response;
|
|
851
|
+
}, (error) => {
|
|
852
|
+
const requestError = error instanceof Error ? error : new Error(String(error));
|
|
853
|
+
this.runtimeEventBus.emit('request:failed', {
|
|
854
|
+
context: requestContext,
|
|
855
|
+
req,
|
|
856
|
+
error: requestError,
|
|
857
|
+
durationMs: Date.now() - startTime
|
|
858
|
+
});
|
|
859
|
+
throw error;
|
|
860
|
+
});
|
|
861
|
+
}
|
|
682
862
|
if (timeoutId || externalAbortCleanup) {
|
|
683
863
|
responsePromise.finally(() => {
|
|
684
864
|
if (timeoutId)
|
|
@@ -708,6 +888,9 @@ export class Client {
|
|
|
708
888
|
const runnerResult = await runner.run(requests, async (item) => {
|
|
709
889
|
const res = await this.request(item.path, item.options);
|
|
710
890
|
return mapResponse(res);
|
|
891
|
+
}, {
|
|
892
|
+
signal: options.signal,
|
|
893
|
+
deadlineMs: options.deadlineMs
|
|
711
894
|
});
|
|
712
895
|
return runnerResult;
|
|
713
896
|
}
|
|
@@ -857,7 +1040,6 @@ export class Client {
|
|
|
857
1040
|
}
|
|
858
1041
|
page(path, pageNumber, options = {}) {
|
|
859
1042
|
const pageParam = options.pageParam || this.paginationConfig?.pageParam || 'page';
|
|
860
|
-
const url = new URL(path.startsWith('http') ? path : `http://base${path}`);
|
|
861
1043
|
const params = { ...options.params, [pageParam]: pageNumber };
|
|
862
1044
|
return this.request(path, { ...options, params });
|
|
863
1045
|
}
|
|
@@ -1,10 +1,23 @@
|
|
|
1
1
|
import { ReckerRequest, ReckerResponse } from '../types/index.js';
|
|
2
|
+
export type CanonicalErrorCategory = 'http' | 'network' | 'timeout' | 'protocol' | 'validation' | 'state' | 'filesystem' | 'resource' | 'policy' | 'queue' | 'unknown';
|
|
3
|
+
export type ErrorSource = 'client' | 'transport' | 'server' | 'upstream';
|
|
4
|
+
export type CanonicalErrorSeverity = 'low' | 'medium' | 'high';
|
|
5
|
+
export interface CanonicalErrorMetadata {
|
|
6
|
+
category: CanonicalErrorCategory;
|
|
7
|
+
source: ErrorSource;
|
|
8
|
+
severity: CanonicalErrorSeverity;
|
|
9
|
+
canRetry: boolean;
|
|
10
|
+
reason: string;
|
|
11
|
+
statusCode?: number;
|
|
12
|
+
retryAfterMs?: number;
|
|
13
|
+
}
|
|
2
14
|
export declare class ReckerError extends Error {
|
|
3
15
|
request?: ReckerRequest;
|
|
4
16
|
response?: ReckerResponse;
|
|
5
17
|
suggestions: string[];
|
|
6
18
|
retriable: boolean;
|
|
7
|
-
|
|
19
|
+
classification?: CanonicalErrorMetadata;
|
|
20
|
+
constructor(message: string, request?: ReckerRequest, response?: ReckerResponse, suggestions?: string[], retriable?: boolean, classification?: CanonicalErrorMetadata);
|
|
8
21
|
}
|
|
9
22
|
export declare class HttpError extends ReckerError {
|
|
10
23
|
status: number;
|
|
@@ -40,6 +53,7 @@ export declare class Http2Error extends ReckerError {
|
|
|
40
53
|
});
|
|
41
54
|
}
|
|
42
55
|
export declare function parseHttp2Error(error: Error): Http2Error | null;
|
|
56
|
+
export declare function classifyTransportError(error: unknown): CanonicalErrorMetadata | undefined;
|
|
43
57
|
export declare class MaxSizeExceededError extends ReckerError {
|
|
44
58
|
maxSize: number;
|
|
45
59
|
actualSize?: number;
|