recker 1.0.34 → 1.0.35-next.1b43fea

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.
@@ -16,6 +16,8 @@ import { dedupPlugin } from '../plugins/dedup.js';
16
16
  import { createXSRFMiddleware } from '../plugins/xsrf.js';
17
17
  import { createCompressionMiddleware } from '../plugins/compression.js';
18
18
  import { serializeXML } from '../plugins/xml.js';
19
+ import { serializeYaml } from '../plugins/yaml.js';
20
+ import { serializeCsv } from '../plugins/csv.js';
19
21
  import { MemoryStorage } from '../cache/memory-storage.js';
20
22
  import { FileStorage } from '../cache/basic-file-storage.js';
21
23
  import { RequestRunner } from '../runner/request-runner.js';
@@ -509,7 +511,7 @@ export class Client {
509
511
  }
510
512
  }
511
513
  actualOptions = actualOptions || {};
512
- const { json, form, xml, ...restOptions } = actualOptions;
514
+ const { json, form, xml, yaml, csv, ...restOptions } = actualOptions;
513
515
  let finalBody = actualBody;
514
516
  let explicitContentType;
515
517
  if (form !== undefined) {
@@ -524,6 +526,14 @@ export class Client {
524
526
  finalBody = '<?xml version="1.0" encoding="UTF-8"?>\n' + serializeXML(xml);
525
527
  explicitContentType = 'application/xml';
526
528
  }
529
+ else if (yaml !== undefined) {
530
+ finalBody = serializeYaml(yaml);
531
+ explicitContentType = 'application/yaml';
532
+ }
533
+ else if (csv !== undefined) {
534
+ finalBody = serializeCsv(csv);
535
+ explicitContentType = 'text/csv';
536
+ }
527
537
  else if (restOptions.body !== undefined) {
528
538
  finalBody = restOptions.body;
529
539
  }
@@ -0,0 +1,44 @@
1
+ export type ErrorSeverity = 'error' | 'warning' | 'info';
2
+ export interface ErrorCategory {
3
+ type: string;
4
+ label: string;
5
+ color: 'red' | 'yellow' | 'cyan' | 'gray' | 'magenta';
6
+ retriable: boolean;
7
+ retryDelay?: number;
8
+ }
9
+ export interface ClassifiedError {
10
+ original: Error | string;
11
+ category: ErrorCategory;
12
+ message: string;
13
+ explanation: string;
14
+ suggestion: string;
15
+ retry: {
16
+ should: boolean;
17
+ afterMs?: number;
18
+ reason?: string;
19
+ };
20
+ context?: Record<string, unknown>;
21
+ }
22
+ export declare const ERROR_CATEGORIES: Record<string, ErrorCategory>;
23
+ export declare function classifyError(error: Error | string | unknown, context?: Record<string, unknown>): ClassifiedError;
24
+ export declare function formatErrorForTerminal(classified: ClassifiedError, options?: {
25
+ verbose?: boolean;
26
+ colors?: {
27
+ red: (s: string) => string;
28
+ yellow: (s: string) => string;
29
+ cyan: (s: string) => string;
30
+ gray: (s: string) => string;
31
+ magenta: (s: string) => string;
32
+ bold: (s: string) => string;
33
+ green: (s: string) => string;
34
+ };
35
+ }): string;
36
+ export declare function formatErrorForJson(classified: ClassifiedError): Record<string, unknown>;
37
+ export declare function isRetriable(error: Error | string | unknown): boolean;
38
+ export declare function getRetryDelay(error: Error | string | unknown, context?: Record<string, unknown>): number;
39
+ export declare function getUserFriendlyMessage(error: Error | string | unknown): string;
40
+ export declare function createErrorReport(error: Error | string | unknown, context?: Record<string, unknown>): {
41
+ summary: string;
42
+ details: ClassifiedError;
43
+ formatted: string;
44
+ };
@@ -0,0 +1,520 @@
1
+ export const ERROR_CATEGORIES = {
2
+ HTTP_400: {
3
+ type: 'HTTP_400',
4
+ label: 'Bad Request',
5
+ color: 'yellow',
6
+ retriable: false,
7
+ },
8
+ HTTP_401: {
9
+ type: 'HTTP_401',
10
+ label: 'Unauthorized',
11
+ color: 'yellow',
12
+ retriable: false,
13
+ },
14
+ HTTP_403: {
15
+ type: 'HTTP_403',
16
+ label: 'Forbidden',
17
+ color: 'yellow',
18
+ retriable: false,
19
+ },
20
+ HTTP_404: {
21
+ type: 'HTTP_404',
22
+ label: 'Not Found',
23
+ color: 'yellow',
24
+ retriable: false,
25
+ },
26
+ HTTP_405: {
27
+ type: 'HTTP_405',
28
+ label: 'Method Not Allowed',
29
+ color: 'yellow',
30
+ retriable: false,
31
+ },
32
+ HTTP_408: {
33
+ type: 'HTTP_408',
34
+ label: 'Request Timeout',
35
+ color: 'yellow',
36
+ retriable: true,
37
+ retryDelay: 1000,
38
+ },
39
+ HTTP_429: {
40
+ type: 'HTTP_429',
41
+ label: 'Rate Limited',
42
+ color: 'magenta',
43
+ retriable: true,
44
+ retryDelay: 60000,
45
+ },
46
+ HTTP_5XX: {
47
+ type: 'HTTP_5XX',
48
+ label: 'Server Error',
49
+ color: 'red',
50
+ retriable: true,
51
+ retryDelay: 5000,
52
+ },
53
+ TIMEOUT: {
54
+ type: 'TIMEOUT',
55
+ label: 'Timeout',
56
+ color: 'yellow',
57
+ retriable: true,
58
+ retryDelay: 2000,
59
+ },
60
+ DNS: {
61
+ type: 'DNS',
62
+ label: 'DNS Error',
63
+ color: 'red',
64
+ retriable: true,
65
+ retryDelay: 5000,
66
+ },
67
+ CONNECTION_REFUSED: {
68
+ type: 'CONN_REFUSED',
69
+ label: 'Connection Refused',
70
+ color: 'red',
71
+ retriable: true,
72
+ retryDelay: 5000,
73
+ },
74
+ CONNECTION_RESET: {
75
+ type: 'CONN_RESET',
76
+ label: 'Connection Reset',
77
+ color: 'red',
78
+ retriable: true,
79
+ retryDelay: 2000,
80
+ },
81
+ CONNECTION_CLOSED: {
82
+ type: 'CONN_CLOSED',
83
+ label: 'Connection Closed',
84
+ color: 'red',
85
+ retriable: true,
86
+ retryDelay: 1000,
87
+ },
88
+ NETWORK_UNREACHABLE: {
89
+ type: 'NET_UNREACHABLE',
90
+ label: 'Network Unreachable',
91
+ color: 'red',
92
+ retriable: true,
93
+ retryDelay: 10000,
94
+ },
95
+ TLS_CERT_EXPIRED: {
96
+ type: 'TLS_EXPIRED',
97
+ label: 'Certificate Expired',
98
+ color: 'red',
99
+ retriable: false,
100
+ },
101
+ TLS_CERT_INVALID: {
102
+ type: 'TLS_INVALID',
103
+ label: 'Invalid Certificate',
104
+ color: 'red',
105
+ retriable: false,
106
+ },
107
+ TLS_HANDSHAKE: {
108
+ type: 'TLS_HANDSHAKE',
109
+ label: 'TLS Handshake Failed',
110
+ color: 'red',
111
+ retriable: true,
112
+ retryDelay: 2000,
113
+ },
114
+ TLS_PROTOCOL: {
115
+ type: 'TLS_PROTOCOL',
116
+ label: 'TLS Protocol Error',
117
+ color: 'red',
118
+ retriable: false,
119
+ },
120
+ AUTH_MISSING: {
121
+ type: 'AUTH_MISSING',
122
+ label: 'Missing Credentials',
123
+ color: 'yellow',
124
+ retriable: false,
125
+ },
126
+ AUTH_INVALID: {
127
+ type: 'AUTH_INVALID',
128
+ label: 'Invalid Credentials',
129
+ color: 'yellow',
130
+ retriable: false,
131
+ },
132
+ AUTH_EXPIRED: {
133
+ type: 'AUTH_EXPIRED',
134
+ label: 'Token Expired',
135
+ color: 'yellow',
136
+ retriable: true,
137
+ retryDelay: 0,
138
+ },
139
+ PARSE_JSON: {
140
+ type: 'PARSE_JSON',
141
+ label: 'Invalid JSON',
142
+ color: 'yellow',
143
+ retriable: false,
144
+ },
145
+ PARSE_XML: {
146
+ type: 'PARSE_XML',
147
+ label: 'Invalid XML',
148
+ color: 'yellow',
149
+ retriable: false,
150
+ },
151
+ PARSE_HTML: {
152
+ type: 'PARSE_HTML',
153
+ label: 'Invalid HTML',
154
+ color: 'yellow',
155
+ retriable: false,
156
+ },
157
+ PROTOCOL_FTP: {
158
+ type: 'FTP_ERROR',
159
+ label: 'FTP Error',
160
+ color: 'red',
161
+ retriable: true,
162
+ retryDelay: 2000,
163
+ },
164
+ PROTOCOL_WEBSOCKET: {
165
+ type: 'WS_ERROR',
166
+ label: 'WebSocket Error',
167
+ color: 'red',
168
+ retriable: true,
169
+ retryDelay: 1000,
170
+ },
171
+ PROTOCOL_GRAPHQL: {
172
+ type: 'GRAPHQL_ERROR',
173
+ label: 'GraphQL Error',
174
+ color: 'yellow',
175
+ retriable: false,
176
+ },
177
+ UNKNOWN: {
178
+ type: 'UNKNOWN',
179
+ label: 'Unknown Error',
180
+ color: 'red',
181
+ retriable: false,
182
+ },
183
+ };
184
+ const ERROR_SUGGESTIONS = {
185
+ HTTP_400: {
186
+ explanation: 'The server could not understand the request due to invalid syntax.',
187
+ suggestion: 'Check the request body, headers, and URL parameters. Ensure JSON is valid if sending JSON data.',
188
+ },
189
+ HTTP_401: {
190
+ explanation: 'Authentication is required but was not provided or is invalid.',
191
+ suggestion: 'Check your API key, token, or credentials. Ensure the Authorization header is correct.',
192
+ },
193
+ HTTP_403: {
194
+ explanation: 'You do not have permission to access this resource.',
195
+ suggestion: 'Verify you have the correct permissions. Check API scopes or contact the API administrator.',
196
+ },
197
+ HTTP_404: {
198
+ explanation: 'The requested resource was not found on the server.',
199
+ suggestion: 'Check the URL path for typos. The resource may have been moved or deleted.',
200
+ },
201
+ HTTP_405: {
202
+ explanation: 'The HTTP method is not allowed for this endpoint.',
203
+ suggestion: 'Check the API documentation for allowed methods (GET, POST, PUT, DELETE, etc.).',
204
+ },
205
+ HTTP_408: {
206
+ explanation: 'The server timed out waiting for the request.',
207
+ suggestion: 'Retry the request. If it persists, check your network connection or try later.',
208
+ },
209
+ HTTP_429: {
210
+ explanation: 'You have sent too many requests in a given time period (rate limited).',
211
+ suggestion: 'Wait before retrying. Check the Retry-After header for the wait time. Consider reducing request frequency.',
212
+ },
213
+ HTTP_5XX: {
214
+ explanation: 'The server encountered an internal error.',
215
+ suggestion: 'This is a server-side issue. Wait and retry. If persistent, contact the API provider.',
216
+ },
217
+ TIMEOUT: {
218
+ explanation: 'The request took too long and was aborted.',
219
+ suggestion: 'Increase the timeout setting, reduce payload size, or check if the server is responding slowly.',
220
+ },
221
+ DNS: {
222
+ explanation: 'The domain name could not be resolved to an IP address.',
223
+ suggestion: 'Check the domain spelling. Verify DNS configuration. Try a different DNS server (8.8.8.8).',
224
+ },
225
+ CONN_REFUSED: {
226
+ explanation: 'The server actively refused the connection.',
227
+ suggestion: 'Verify the server is running and the port is correct. Check firewall rules.',
228
+ },
229
+ CONN_RESET: {
230
+ explanation: 'The connection was forcibly closed by the server.',
231
+ suggestion: 'The server may be overloaded. Retry after a brief delay. Check for rate limiting.',
232
+ },
233
+ CONN_CLOSED: {
234
+ explanation: 'The connection was closed unexpectedly.',
235
+ suggestion: 'The server may have closed idle connections. Retry the request.',
236
+ },
237
+ NET_UNREACHABLE: {
238
+ explanation: 'The network is unreachable.',
239
+ suggestion: 'Check your internet connection. Verify VPN or proxy settings.',
240
+ },
241
+ TLS_EXPIRED: {
242
+ explanation: 'The server\'s SSL certificate has expired.',
243
+ suggestion: 'Contact the server administrator to renew the certificate. Do NOT bypass SSL verification in production.',
244
+ },
245
+ TLS_INVALID: {
246
+ explanation: 'The server\'s SSL certificate is invalid or self-signed.',
247
+ suggestion: 'Verify you\'re connecting to the correct server. For testing, you can use rejectUnauthorized: false.',
248
+ },
249
+ TLS_HANDSHAKE: {
250
+ explanation: 'The TLS handshake failed.',
251
+ suggestion: 'The server may have TLS configuration issues. Try again or check supported TLS versions.',
252
+ },
253
+ TLS_PROTOCOL: {
254
+ explanation: 'TLS protocol error occurred.',
255
+ suggestion: 'The server may not support your TLS version. Try specifying a different TLS version.',
256
+ },
257
+ AUTH_MISSING: {
258
+ explanation: 'No authentication credentials were provided.',
259
+ suggestion: 'Set the required environment variable (e.g., OPENAI_API_KEY) or pass credentials in the request.',
260
+ },
261
+ AUTH_INVALID: {
262
+ explanation: 'The provided credentials are invalid.',
263
+ suggestion: 'Regenerate your API key or token. Ensure you\'re using the correct credentials.',
264
+ },
265
+ AUTH_EXPIRED: {
266
+ explanation: 'The authentication token has expired.',
267
+ suggestion: 'Refresh your token and retry. Enable automatic token refresh if available.',
268
+ },
269
+ PARSE_JSON: {
270
+ explanation: 'The response is not valid JSON.',
271
+ suggestion: 'The server may have returned HTML error page or plain text. Check the raw response.',
272
+ },
273
+ PARSE_XML: {
274
+ explanation: 'The response is not valid XML.',
275
+ suggestion: 'Verify the endpoint returns XML. Check for encoding issues.',
276
+ },
277
+ PARSE_HTML: {
278
+ explanation: 'Failed to parse HTML content.',
279
+ suggestion: 'The HTML may be malformed. Try a more lenient parser or check the source.',
280
+ },
281
+ FTP_ERROR: {
282
+ explanation: 'An FTP operation failed.',
283
+ suggestion: 'Check credentials, path permissions, and server status. Ensure passive mode if behind NAT.',
284
+ },
285
+ WS_ERROR: {
286
+ explanation: 'WebSocket connection or operation failed.',
287
+ suggestion: 'Check the WebSocket URL (ws:// or wss://). Verify the server supports WebSocket.',
288
+ },
289
+ GRAPHQL_ERROR: {
290
+ explanation: 'The GraphQL query returned errors.',
291
+ suggestion: 'Check the query syntax and field names. Verify required variables are provided.',
292
+ },
293
+ UNKNOWN: {
294
+ explanation: 'An unexpected error occurred.',
295
+ suggestion: 'Check the error details. If the issue persists, report it with the full error message.',
296
+ },
297
+ };
298
+ export function classifyError(error, context) {
299
+ const errorStr = error instanceof Error ? error.message : String(error);
300
+ const errorLower = errorStr.toLowerCase();
301
+ const httpStatusMatch = errorStr.match(/(?:status|code)[:\s]*(\d{3})/i) ||
302
+ errorStr.match(/HTTP[\/\s]*\d*\.?\d*[:\s]*(\d{3})/i) ||
303
+ errorStr.match(/^(\d{3})\s/);
304
+ if (httpStatusMatch) {
305
+ const status = parseInt(httpStatusMatch[1]);
306
+ return classifyHttpError(status, error, context);
307
+ }
308
+ if (errorLower.includes('timeout') || errorLower.includes('etimedout') || errorLower.includes('timed out')) {
309
+ return buildClassifiedError('TIMEOUT', error, context);
310
+ }
311
+ if (errorLower.includes('enotfound') || errorLower.includes('getaddrinfo') ||
312
+ errorLower.includes('dns') && errorLower.includes('fail')) {
313
+ return buildClassifiedError('DNS', error, context);
314
+ }
315
+ if (errorLower.includes('econnrefused') || errorLower.includes('connection refused')) {
316
+ return buildClassifiedError('CONNECTION_REFUSED', error, context);
317
+ }
318
+ if (errorLower.includes('econnreset') || errorLower.includes('connection reset')) {
319
+ return buildClassifiedError('CONNECTION_RESET', error, context);
320
+ }
321
+ if (errorLower.includes('socket hang up') || errorLower.includes('epipe') ||
322
+ errorLower.includes('closed')) {
323
+ return buildClassifiedError('CONNECTION_CLOSED', error, context);
324
+ }
325
+ if (errorLower.includes('enetunreach') || errorLower.includes('network unreachable') ||
326
+ errorLower.includes('ehostunreach')) {
327
+ return buildClassifiedError('NETWORK_UNREACHABLE', error, context);
328
+ }
329
+ if (errorLower.includes('certificate') && errorLower.includes('expired')) {
330
+ return buildClassifiedError('TLS_CERT_EXPIRED', error, context);
331
+ }
332
+ if (errorLower.includes('self signed') || errorLower.includes('unable to verify') ||
333
+ (errorLower.includes('certificate') && errorLower.includes('invalid'))) {
334
+ return buildClassifiedError('TLS_CERT_INVALID', error, context);
335
+ }
336
+ if (errorLower.includes('handshake') || errorLower.includes('ssl_error_handshake')) {
337
+ return buildClassifiedError('TLS_HANDSHAKE', error, context);
338
+ }
339
+ if (errorLower.includes('ssl') || errorLower.includes('tls')) {
340
+ return buildClassifiedError('TLS_PROTOCOL', error, context);
341
+ }
342
+ if (errorLower.includes('api key') || errorLower.includes('apikey') ||
343
+ (errorLower.includes('missing') && errorLower.includes('credential'))) {
344
+ return buildClassifiedError('AUTH_MISSING', error, context);
345
+ }
346
+ if (errorLower.includes('invalid') && (errorLower.includes('key') || errorLower.includes('token'))) {
347
+ return buildClassifiedError('AUTH_INVALID', error, context);
348
+ }
349
+ if (errorLower.includes('expired') && (errorLower.includes('token') || errorLower.includes('auth'))) {
350
+ return buildClassifiedError('AUTH_EXPIRED', error, context);
351
+ }
352
+ if (errorLower.includes('json') && (errorLower.includes('parse') || errorLower.includes('unexpected'))) {
353
+ return buildClassifiedError('PARSE_JSON', error, context);
354
+ }
355
+ if (errorLower.includes('xml') && (errorLower.includes('parse') || errorLower.includes('invalid'))) {
356
+ return buildClassifiedError('PARSE_XML', error, context);
357
+ }
358
+ if (errorLower.includes('ftp')) {
359
+ return buildClassifiedError('PROTOCOL_FTP', error, context);
360
+ }
361
+ if (errorLower.includes('websocket') || errorLower.includes('ws://')) {
362
+ return buildClassifiedError('PROTOCOL_WEBSOCKET', error, context);
363
+ }
364
+ if (errorLower.includes('graphql')) {
365
+ return buildClassifiedError('PROTOCOL_GRAPHQL', error, context);
366
+ }
367
+ return buildClassifiedError('UNKNOWN', error, context);
368
+ }
369
+ function classifyHttpError(status, error, context) {
370
+ if (status === 400)
371
+ return buildClassifiedError('HTTP_400', error, context, { status });
372
+ if (status === 401)
373
+ return buildClassifiedError('HTTP_401', error, context, { status });
374
+ if (status === 403)
375
+ return buildClassifiedError('HTTP_403', error, context, { status });
376
+ if (status === 404)
377
+ return buildClassifiedError('HTTP_404', error, context, { status });
378
+ if (status === 405)
379
+ return buildClassifiedError('HTTP_405', error, context, { status });
380
+ if (status === 408)
381
+ return buildClassifiedError('HTTP_408', error, context, { status });
382
+ if (status === 429)
383
+ return buildClassifiedError('HTTP_429', error, context, { status });
384
+ if (status >= 500 && status < 600) {
385
+ return buildClassifiedError('HTTP_5XX', error, context, { status });
386
+ }
387
+ if (status >= 400 && status < 500) {
388
+ const category = {
389
+ type: `HTTP_${status}`,
390
+ label: `HTTP ${status}`,
391
+ color: 'yellow',
392
+ retriable: false,
393
+ };
394
+ return {
395
+ original: error instanceof Error ? error : new Error(String(error)),
396
+ category,
397
+ message: `HTTP ${status} error`,
398
+ explanation: `The server returned status code ${status}.`,
399
+ suggestion: 'Check the API documentation for this status code.',
400
+ retry: { should: false },
401
+ context,
402
+ };
403
+ }
404
+ return buildClassifiedError('UNKNOWN', error, context);
405
+ }
406
+ function buildClassifiedError(categoryKey, error, context, extra) {
407
+ const category = ERROR_CATEGORIES[categoryKey] || ERROR_CATEGORIES.UNKNOWN;
408
+ const suggestions = ERROR_SUGGESTIONS[category.type] || ERROR_SUGGESTIONS.UNKNOWN;
409
+ let retryAfterMs = category.retryDelay;
410
+ if (context?.retryAfter) {
411
+ const retryAfter = context.retryAfter;
412
+ if (typeof retryAfter === 'number') {
413
+ retryAfterMs = retryAfter * 1000;
414
+ }
415
+ else if (typeof retryAfter === 'string') {
416
+ const seconds = parseInt(retryAfter, 10);
417
+ if (!isNaN(seconds)) {
418
+ retryAfterMs = seconds * 1000;
419
+ }
420
+ else {
421
+ const date = new Date(retryAfter);
422
+ if (!isNaN(date.getTime())) {
423
+ retryAfterMs = Math.max(0, date.getTime() - Date.now());
424
+ }
425
+ }
426
+ }
427
+ }
428
+ return {
429
+ original: error instanceof Error ? error : new Error(String(error)),
430
+ category,
431
+ message: `${category.label}`,
432
+ explanation: suggestions.explanation,
433
+ suggestion: suggestions.suggestion,
434
+ retry: {
435
+ should: category.retriable,
436
+ afterMs: category.retriable ? retryAfterMs : undefined,
437
+ reason: category.retriable
438
+ ? `This error is usually temporary. Retry after ${formatRetryDelay(retryAfterMs)}.`
439
+ : 'This error requires fixing the request or configuration.',
440
+ },
441
+ context: { ...context, ...extra },
442
+ };
443
+ }
444
+ function formatRetryDelay(ms) {
445
+ if (!ms)
446
+ return 'a moment';
447
+ if (ms < 1000)
448
+ return `${ms}ms`;
449
+ if (ms < 60000)
450
+ return `${Math.ceil(ms / 1000)}s`;
451
+ if (ms < 3600000)
452
+ return `${Math.ceil(ms / 60000)} minute${ms >= 120000 ? 's' : ''}`;
453
+ return `${Math.ceil(ms / 3600000)} hour${ms >= 7200000 ? 's' : ''}`;
454
+ }
455
+ export function formatErrorForTerminal(classified, options = {}) {
456
+ const colors = options.colors || {
457
+ red: (s) => s,
458
+ yellow: (s) => s,
459
+ cyan: (s) => s,
460
+ gray: (s) => s,
461
+ magenta: (s) => s,
462
+ bold: (s) => s,
463
+ green: (s) => s,
464
+ };
465
+ const colorFn = colors[classified.category.color] || colors.red;
466
+ const lines = [];
467
+ lines.push(`${colorFn('Error:')} ${classified.message}`);
468
+ lines.push(`${colors.gray(classified.explanation)}`);
469
+ lines.push('');
470
+ lines.push(`${colors.cyan('Fix:')} ${classified.suggestion}`);
471
+ if (classified.retry.should) {
472
+ const retryMsg = classified.retry.afterMs
473
+ ? `Retry after ${formatRetryDelay(classified.retry.afterMs)}`
474
+ : 'Safe to retry immediately';
475
+ lines.push(`${colors.green('Retry:')} ${retryMsg}`);
476
+ }
477
+ if (options.verbose && classified.original instanceof Error) {
478
+ lines.push('');
479
+ lines.push(colors.gray('Original error:'));
480
+ lines.push(colors.gray(` ${classified.original.message}`));
481
+ if (classified.original.stack) {
482
+ const stackLines = classified.original.stack.split('\n').slice(1, 4);
483
+ stackLines.forEach((line) => lines.push(colors.gray(` ${line.trim()}`)));
484
+ }
485
+ }
486
+ return lines.join('\n');
487
+ }
488
+ export function formatErrorForJson(classified) {
489
+ return {
490
+ error: {
491
+ type: classified.category.type,
492
+ label: classified.category.label,
493
+ message: classified.message,
494
+ explanation: classified.explanation,
495
+ suggestion: classified.suggestion,
496
+ retriable: classified.retry.should,
497
+ retryAfterMs: classified.retry.afterMs,
498
+ context: classified.context,
499
+ },
500
+ };
501
+ }
502
+ export function isRetriable(error) {
503
+ return classifyError(error).retry.should;
504
+ }
505
+ export function getRetryDelay(error, context) {
506
+ const classified = classifyError(error, context);
507
+ return classified.retry.afterMs || 1000;
508
+ }
509
+ export function getUserFriendlyMessage(error) {
510
+ const classified = classifyError(error);
511
+ return `${classified.message}: ${classified.explanation}`;
512
+ }
513
+ export function createErrorReport(error, context) {
514
+ const classified = classifyError(error, context);
515
+ return {
516
+ summary: getUserFriendlyMessage(error),
517
+ details: classified,
518
+ formatted: formatErrorForTerminal(classified, { verbose: true }),
519
+ };
520
+ }
@@ -1,5 +1,7 @@
1
1
  import { ReckerResponse, SSEEvent, ProgressEvent } from '../types/index.js';
2
2
  import type { ZodSchema } from 'zod';
3
+ import { type YamlParseOptions } from '../plugins/yaml.js';
4
+ import { type CsvParseOptions } from '../plugins/csv.js';
3
5
  export declare class RequestPromise<T = unknown> implements Promise<ReckerResponse<T>> {
4
6
  private promise;
5
7
  private abortController?;
@@ -10,6 +12,8 @@ export declare class RequestPromise<T = unknown> implements Promise<ReckerRespon
10
12
  finally(onfinally?: (() => void) | null): Promise<ReckerResponse<T>>;
11
13
  cancel(): void;
12
14
  json<R = T>(): Promise<R>;
15
+ yaml<R = T>(options?: YamlParseOptions): Promise<R>;
16
+ csv<R = Record<string, string>>(options?: CsvParseOptions): Promise<R[]>;
13
17
  text(): Promise<string>;
14
18
  cleanText(): Promise<string>;
15
19
  blob(): Promise<Blob>;
@@ -3,6 +3,8 @@ import { pipeline } from 'node:stream/promises';
3
3
  import { Readable } from 'node:stream';
4
4
  import { tryFn } from '../utils/try-fn.js';
5
5
  import { StreamError } from './errors.js';
6
+ import { parseYaml } from '../plugins/yaml.js';
7
+ import { parseCsv } from '../plugins/csv.js';
6
8
  export class RequestPromise {
7
9
  promise;
8
10
  abortController;
@@ -31,6 +33,16 @@ export class RequestPromise {
31
33
  const response = await this.promise;
32
34
  return response.json();
33
35
  }
36
+ async yaml(options) {
37
+ const response = await this.promise;
38
+ const text = await response.text();
39
+ return parseYaml(text, options);
40
+ }
41
+ async csv(options) {
42
+ const response = await this.promise;
43
+ const text = await response.text();
44
+ return parseCsv(text, options);
45
+ }
34
46
  async text() {
35
47
  const response = await this.promise;
36
48
  return response.text();
@@ -3,6 +3,8 @@ import { Dispatcher } from 'undici';
3
3
  import { type HeaderInfo, type CacheInfo, type RateLimitInfo } from '../utils/header-parser.js';
4
4
  import { type LinkHeaderParser } from '../utils/link-header.js';
5
5
  import type { Readable } from 'node:stream';
6
+ import { type YamlParseOptions } from '../plugins/yaml.js';
7
+ import { type CsvParseOptions } from '../plugins/csv.js';
6
8
  export declare class HttpResponse<T = unknown> implements ReckerResponse<T> {
7
9
  readonly timings?: Timings;
8
10
  readonly connection?: ConnectionInfo;
@@ -21,6 +23,8 @@ export declare class HttpResponse<T = unknown> implements ReckerResponse<T> {
21
23
  links(): LinkHeaderParser | null;
22
24
  get headerInfo(): HeaderInfo;
23
25
  json<R = T>(): Promise<R>;
26
+ yaml<R = T>(options?: YamlParseOptions): Promise<R>;
27
+ csv<R = Record<string, string>>(options?: CsvParseOptions): Promise<R[]>;
24
28
  text(): Promise<string>;
25
29
  cleanText(): Promise<string>;
26
30
  blob(): Promise<Blob>;
@@ -4,6 +4,8 @@ import { webToNodeStream } from '../utils/streaming.js';
4
4
  import { parseHeaders } from '../utils/header-parser.js';
5
5
  import { parseLinkHeader } from '../utils/link-header.js';
6
6
  import { StreamError } from './errors.js';
7
+ import { parseYaml } from '../plugins/yaml.js';
8
+ import { parseCsv } from '../plugins/csv.js';
7
9
  export class HttpResponse {
8
10
  timings;
9
11
  connection;
@@ -52,6 +54,14 @@ export class HttpResponse {
52
54
  async json() {
53
55
  return (await this.raw.json());
54
56
  }
57
+ async yaml(options) {
58
+ const text = await this.raw.text();
59
+ return parseYaml(text, options);
60
+ }
61
+ async csv(options) {
62
+ const text = await this.raw.text();
63
+ return parseCsv(text, options);
64
+ }
55
65
  async text() {
56
66
  return this.raw.text();
57
67
  }