@unireq/core 0.0.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Olivier Orabona
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,828 @@
1
+ /**
2
+ * Core types for the unireq framework
3
+ */
4
+ /** Request context passed through the policy chain */
5
+ interface RequestContext {
6
+ readonly url: string;
7
+ readonly method: string;
8
+ readonly headers: Record<string, string>;
9
+ readonly body?: unknown;
10
+ readonly signal?: AbortSignal;
11
+ readonly policies?: ReadonlyArray<Policy>;
12
+ readonly [key: string]: unknown;
13
+ }
14
+ /** Response from a transport or policy */
15
+ interface Response<T = unknown> {
16
+ readonly status: number;
17
+ readonly statusText: string;
18
+ readonly headers: Record<string, string>;
19
+ readonly data: T;
20
+ readonly ok: boolean;
21
+ }
22
+ /** Policy function that transforms request context */
23
+ type Policy = (ctx: RequestContext, next: (ctx: RequestContext) => Promise<Response>) => Promise<Response>;
24
+ /** Transport capabilities declaration */
25
+ interface TransportCapabilities {
26
+ readonly streams?: boolean;
27
+ readonly multipartFormData?: boolean;
28
+ readonly randomAccess?: boolean;
29
+ readonly [key: string]: boolean | undefined;
30
+ }
31
+ /** Connector interface for pluggable transport implementations */
32
+ interface Connector<TClient = unknown> {
33
+ connect(uri: string): Promise<TClient> | TClient;
34
+ request(client: TClient, ctx: RequestContext): Promise<Response>;
35
+ disconnect(client: TClient): Promise<void> | void;
36
+ }
37
+ /** Transport function that executes the actual I/O */
38
+ type Transport = (ctx: RequestContext) => Promise<Response>;
39
+ /** Transport with capabilities metadata */
40
+ interface TransportWithCapabilities {
41
+ readonly transport: Transport;
42
+ readonly capabilities: TransportCapabilities;
43
+ }
44
+ /** Slot types for compile/runtime checks */
45
+ declare enum SlotType {
46
+ Transport = "transport",
47
+ Auth = "auth",
48
+ Parser = "parser"
49
+ }
50
+ /** Slot metadata for policy registration */
51
+ interface SlotMetadata {
52
+ readonly type: SlotType;
53
+ readonly name: string;
54
+ readonly requiredCapabilities?: ReadonlyArray<string>;
55
+ }
56
+ /** Client configuration options */
57
+ interface ClientOptions {
58
+ readonly base?: string;
59
+ readonly defaultScheme?: 'http' | 'https' | 'ftp' | 'imap';
60
+ readonly policies?: ReadonlyArray<Policy>;
61
+ }
62
+ /** Client instance */
63
+ interface Client {
64
+ readonly request: <T = unknown>(url: string, ...policies: ReadonlyArray<Policy>) => Promise<Response<T>>;
65
+ readonly get: <T = unknown>(url: string, ...policies: ReadonlyArray<Policy>) => Promise<Response<T>>;
66
+ readonly head: <T = unknown>(url: string, ...policies: ReadonlyArray<Policy>) => Promise<Response<T>>;
67
+ readonly post: <T = unknown>(url: string, body?: unknown, ...policies: ReadonlyArray<Policy>) => Promise<Response<T>>;
68
+ readonly put: <T = unknown>(url: string, body?: unknown, ...policies: ReadonlyArray<Policy>) => Promise<Response<T>>;
69
+ readonly delete: <T = unknown>(url: string, ...policies: ReadonlyArray<Policy>) => Promise<Response<T>>;
70
+ readonly patch: <T = unknown>(url: string, body?: unknown, ...policies: ReadonlyArray<Policy>) => Promise<Response<T>>;
71
+ readonly options: <T = unknown>(url: string, ...policies: ReadonlyArray<Policy>) => Promise<Response<T>>;
72
+ }
73
+ /** Predicate function for conditional branching */
74
+ type Predicate<T = unknown> = (ctx: RequestContext) => T | Promise<T>;
75
+ /** Either branch configuration */
76
+ interface EitherBranch<T> {
77
+ readonly predicate: Predicate<T>;
78
+ readonly then: Policy;
79
+ readonly else?: Policy;
80
+ }
81
+ /** Body descriptor for request serialization */
82
+ interface BodyDescriptor {
83
+ readonly __brand: 'BodyDescriptor';
84
+ readonly data: unknown;
85
+ readonly contentType?: string;
86
+ readonly serialize: () => string | Blob | ArrayBuffer | FormData | ReadableStream<Uint8Array>;
87
+ readonly filename?: string;
88
+ readonly contentLength?: number;
89
+ }
90
+ /** Multipart part configuration */
91
+ interface MultipartPart {
92
+ readonly name: string;
93
+ readonly part: BodyDescriptor;
94
+ readonly filename?: string;
95
+ }
96
+ /** Logger interface for structured logging */
97
+ interface Logger {
98
+ debug(message: string, meta?: Record<string, unknown>): void;
99
+ info(message: string, meta?: Record<string, unknown>): void;
100
+ warn(message: string, meta?: Record<string, unknown>): void;
101
+ error(message: string, meta?: Record<string, unknown>): void;
102
+ }
103
+
104
+ /**
105
+ * Structured audit logging for security events (OWASP A09:2021)
106
+ *
107
+ * @see https://owasp.org/Top10/A09_2021-Security_Logging_and_Monitoring_Failures/
108
+ */
109
+
110
+ /**
111
+ * Security event types for audit logging
112
+ */
113
+ type SecurityEventType = 'auth_success' | 'auth_failure' | 'auth_token_refresh' | 'auth_token_expired' | 'access_denied' | 'rate_limit_exceeded' | 'validation_failure' | 'request_started' | 'request_completed' | 'request_failed' | 'suspicious_activity';
114
+ /**
115
+ * Structured audit log entry
116
+ */
117
+ interface AuditLogEntry {
118
+ /** ISO 8601 timestamp */
119
+ readonly timestamp: string;
120
+ /** Unique correlation ID for request tracing */
121
+ readonly correlationId: string;
122
+ /** Security event type */
123
+ readonly eventType: SecurityEventType;
124
+ /** Event severity level */
125
+ readonly severity: 'info' | 'warn' | 'error' | 'critical';
126
+ /** HTTP method */
127
+ readonly method: string;
128
+ /** Request URL (sanitized) */
129
+ readonly url: string;
130
+ /** HTTP status code (if available) */
131
+ readonly statusCode?: number;
132
+ /** Request duration in milliseconds */
133
+ readonly durationMs?: number;
134
+ /** Client IP address (if available) */
135
+ readonly clientIp?: string;
136
+ /** User agent (if available) */
137
+ readonly userAgent?: string;
138
+ /** User ID or identifier (if authenticated) */
139
+ readonly userId?: string;
140
+ /** Session ID (if available) */
141
+ readonly sessionId?: string;
142
+ /** Additional context */
143
+ readonly context?: Record<string, unknown>;
144
+ /** Error message (sanitized, no stack traces) */
145
+ readonly errorMessage?: string;
146
+ /** Error code */
147
+ readonly errorCode?: string;
148
+ }
149
+ /**
150
+ * Audit logger interface
151
+ */
152
+ interface AuditLogger {
153
+ readonly log: (entry: AuditLogEntry) => void | Promise<void>;
154
+ }
155
+ /**
156
+ * Audit logging options
157
+ */
158
+ interface AuditOptions {
159
+ /** Audit logger implementation */
160
+ readonly logger: AuditLogger;
161
+ /** Function to extract user ID from context */
162
+ readonly getUserId?: (ctx: RequestContext) => string | undefined;
163
+ /** Function to extract session ID from context */
164
+ readonly getSessionId?: (ctx: RequestContext) => string | undefined;
165
+ /** Function to extract client IP from context */
166
+ readonly getClientIp?: (ctx: RequestContext) => string | undefined;
167
+ /** Headers to redact from logs */
168
+ readonly redactHeaders?: ReadonlyArray<string>;
169
+ /** Log successful requests (default: true) */
170
+ readonly logSuccess?: boolean;
171
+ /** Log request bodies (default: false) */
172
+ readonly logBody?: boolean;
173
+ /** Custom correlation ID generator */
174
+ readonly correlationIdGenerator?: () => string;
175
+ /** Suspicious activity detector */
176
+ readonly detectSuspiciousActivity?: (ctx: RequestContext, response?: Response, error?: Error) => boolean;
177
+ }
178
+ /**
179
+ * Creates a structured audit logging policy (OWASP A09:2021)
180
+ *
181
+ * Provides comprehensive security event logging with:
182
+ * - Structured JSON format
183
+ * - Correlation IDs for request tracing
184
+ * - Sensitive data redaction
185
+ * - Security event classification
186
+ * - Suspicious activity detection
187
+ *
188
+ * @param options - Audit logging configuration
189
+ * @returns Policy that logs security events
190
+ *
191
+ * @example
192
+ * ```ts
193
+ * const auditLogger: AuditLogger = {
194
+ * log: (entry) => console.log(JSON.stringify(entry)),
195
+ * };
196
+ *
197
+ * const api = client(
198
+ * http('https://api.example.com'),
199
+ * audit({
200
+ * logger: auditLogger,
201
+ * getUserId: (ctx) => ctx.headers['x-user-id'],
202
+ * }),
203
+ * parse.json()
204
+ * );
205
+ * ```
206
+ */
207
+ declare function audit(options: AuditOptions): Policy;
208
+ /**
209
+ * Creates a console-based audit logger for development
210
+ * @returns AuditLogger that logs to console in JSON format
211
+ */
212
+ declare function createConsoleAuditLogger(): AuditLogger;
213
+ /**
214
+ * Creates an audit logger that uses a standard Logger interface
215
+ * @param logger - Standard logger implementation
216
+ * @returns AuditLogger that delegates to the standard logger
217
+ */
218
+ declare function createLoggerAdapter(logger: Logger): AuditLogger;
219
+
220
+ /**
221
+ * Generic transport-agnostic retry flow control primitive
222
+ */
223
+
224
+ /**
225
+ * Retry predicate function that determines if a retry should occur
226
+ * Returns true to retry, false to stop retrying
227
+ */
228
+ type RetryPredicate<T = Response> = (result: T | null, error: Error | null, attempt: number, context: RequestContext) => boolean | Promise<boolean>;
229
+ /**
230
+ * Retry delay strategy that calculates the delay before next retry
231
+ * Returns delay in milliseconds, or undefined to skip delay calculation
232
+ */
233
+ type RetryDelayStrategy<T = Response> = {
234
+ readonly getDelay: (result: T | null, error: Error | null, attempt: number) => number | undefined | Promise<number | undefined>;
235
+ };
236
+ /**
237
+ * Generic retry options
238
+ */
239
+ interface RetryOptions<T = Response> {
240
+ /** Number of retry attempts (default: 3) */
241
+ readonly tries?: number;
242
+ /** Callback before retry */
243
+ readonly onRetry?: (attempt: number, error: Error | null, result: T | null) => void | Promise<void>;
244
+ }
245
+ /**
246
+ * Creates a generic transport-agnostic retry policy
247
+ *
248
+ * This is a pure flow control primitive that works with any protocol (HTTP, FTP, IMAP, etc.).
249
+ * It executes a predicate to determine if retry should occur, applies delay strategies,
250
+ * and invokes callbacks.
251
+ *
252
+ * @param predicate - Function to determine if retry should occur
253
+ * @param strategies - Delay strategies to calculate wait time before retry
254
+ * @param options - Retry options
255
+ * @returns Policy that retries based on predicate
256
+ *
257
+ * @example
258
+ * ```ts
259
+ * // HTTP-specific retry with status codes
260
+ * const httpRetry = retry(
261
+ * (result, error) => error !== null || (result?.status >= 500 && result?.status < 600),
262
+ * [backoff({ initial: 100, max: 5000 })],
263
+ * { tries: 3 }
264
+ * );
265
+ * ```
266
+ *
267
+ * @example
268
+ * ```ts
269
+ * // Generic retry for any operation
270
+ * const genericRetry = retry(
271
+ * (result, error) => error !== null,
272
+ * [exponentialBackoff()],
273
+ * { tries: 5 }
274
+ * );
275
+ * ```
276
+ */
277
+ declare function retry<T = Response>(predicate: RetryPredicate<T>, strategies: ReadonlyArray<RetryDelayStrategy<T>>, options?: RetryOptions<T>): Policy;
278
+
279
+ /**
280
+ * Exponential backoff strategy for retry delays
281
+ * Provides exponential backoff with optional jitter
282
+ * Transport-agnostic - works with any protocol
283
+ */
284
+
285
+ /**
286
+ * Backoff options
287
+ */
288
+ interface BackoffOptions {
289
+ /** Initial backoff in milliseconds (default: 1000) */
290
+ readonly initial?: number;
291
+ /** Maximum backoff in milliseconds (default: 30000) */
292
+ readonly max?: number;
293
+ /** Multiplier for exponential backoff (default: 2) */
294
+ readonly multiplier?: number;
295
+ /** Add jitter to backoff (default: true) */
296
+ readonly jitter?: boolean;
297
+ }
298
+ /**
299
+ * Creates an exponential backoff delay strategy
300
+ *
301
+ * @param options - Backoff configuration
302
+ * @returns Delay strategy that calculates exponential backoff
303
+ *
304
+ * @example
305
+ * ```ts
306
+ * import { retry } from '@unireq/core';
307
+ * import { backoff } from '@unireq/core';
308
+ *
309
+ * const policy = retry(
310
+ * myPredicate,
311
+ * [backoff({ initial: 100, max: 5000, jitter: true })],
312
+ * { tries: 3 }
313
+ * );
314
+ * ```
315
+ */
316
+ declare function backoff<T = unknown>(options?: BackoffOptions): RetryDelayStrategy<T>;
317
+
318
+ /**
319
+ * DX-focused error types for unireq
320
+ */
321
+
322
+ /** Base error class for unireq */
323
+ declare class UnireqError extends Error {
324
+ readonly code: string;
325
+ readonly cause?: unknown | undefined;
326
+ constructor(message: string, code: string, cause?: unknown | undefined);
327
+ }
328
+ /** Error thrown when a network request fails (e.g. DNS, connection refused) */
329
+ declare class NetworkError extends UnireqError {
330
+ constructor(message: string, cause?: unknown);
331
+ }
332
+ /** Error thrown when a request times out */
333
+ declare class TimeoutError extends UnireqError {
334
+ readonly timeoutMs: number;
335
+ constructor(timeoutMs: number, cause?: unknown);
336
+ }
337
+ /** Error thrown when an HTTP response has an error status code (4xx, 5xx) */
338
+ declare class HttpError extends UnireqError {
339
+ readonly status: number;
340
+ readonly statusText: string;
341
+ readonly headers: Record<string, string | string[] | undefined>;
342
+ readonly data: unknown;
343
+ constructor(response: Response);
344
+ }
345
+ /** Error thrown when serialization or parsing fails */
346
+ declare class SerializationError extends UnireqError {
347
+ constructor(message: string, cause?: unknown);
348
+ }
349
+ /** Error thrown when duplicate policies are detected */
350
+ declare class DuplicatePolicyError extends UnireqError {
351
+ readonly policyName: string;
352
+ constructor(policyName: string);
353
+ }
354
+ /** Error thrown when authentication is not supported by transport */
355
+ declare class UnsupportedAuthForTransport extends UnireqError {
356
+ readonly authType: string;
357
+ readonly transportType: string;
358
+ constructor(authType: string, transportType: string);
359
+ }
360
+ /** HTTP 406 Not Acceptable error */
361
+ declare class NotAcceptableError extends UnireqError {
362
+ readonly acceptedTypes: ReadonlyArray<string>;
363
+ readonly receivedType?: string | undefined;
364
+ constructor(acceptedTypes: ReadonlyArray<string>, receivedType?: string | undefined);
365
+ }
366
+ /** HTTP 415 Unsupported Media Type error */
367
+ declare class UnsupportedMediaTypeError extends UnireqError {
368
+ readonly supportedTypes: ReadonlyArray<string>;
369
+ readonly sentType?: string | undefined;
370
+ constructor(supportedTypes: ReadonlyArray<string>, sentType?: string | undefined);
371
+ }
372
+ /** Error thrown when required capability is missing */
373
+ declare class MissingCapabilityError extends UnireqError {
374
+ readonly capability: string;
375
+ readonly transportType: string;
376
+ constructor(capability: string, transportType: string);
377
+ }
378
+ /** Error thrown for invalid slot configuration */
379
+ declare class InvalidSlotError extends UnireqError {
380
+ readonly slotType: string;
381
+ constructor(slotType: string, message: string);
382
+ }
383
+ /** Error thrown when URL normalization fails */
384
+ declare class URLNormalizationError extends UnireqError {
385
+ readonly url: string;
386
+ readonly reason: string;
387
+ constructor(url: string, reason: string);
388
+ }
389
+
390
+ /**
391
+ * Circuit Breaker policy
392
+ * Prevents cascading failures by stopping requests to a failing service
393
+ */
394
+
395
+ interface CircuitBreakerOptions {
396
+ /** Number of failures before opening the circuit (default: 5) */
397
+ readonly failureThreshold?: number;
398
+ /** Time in ms to wait before trying again (default: 30000) */
399
+ readonly resetTimeout?: number;
400
+ /** Optional predicate to determine if an error should trigger failure */
401
+ readonly shouldFail?: (error: unknown) => boolean;
402
+ }
403
+ /** Error thrown when circuit is open */
404
+ declare class CircuitBreakerOpenError extends UnireqError {
405
+ readonly resetTime: number;
406
+ constructor(resetTime: number);
407
+ }
408
+ declare function circuitBreaker(options?: CircuitBreakerOptions): Policy;
409
+
410
+ /**
411
+ * Client factory and request execution
412
+ */
413
+
414
+ /**
415
+ * Creates a client with the given transport and policies
416
+ * @param transport - The transport function or transport with capabilities
417
+ * @param policies - Variadic list of policies to apply
418
+ * @returns A configured client instance
419
+ */
420
+ declare function client(transport: Transport | TransportWithCapabilities, ...policies: ReadonlyArray<Policy>): Client;
421
+
422
+ /**
423
+ * Policy composition utilities
424
+ */
425
+
426
+ /**
427
+ * Composes multiple policies into a single policy chain (onion model)
428
+ * @param policies - Array of policies to compose
429
+ * @returns A single composed policy
430
+ */
431
+ declare function compose(...policies: ReadonlyArray<Policy>): Policy;
432
+
433
+ /**
434
+ * Conditional branching utilities
435
+ */
436
+
437
+ /**
438
+ * Creates a policy that executes different policies based on a predicate
439
+ * Useful for conditional execution (e.g., content negotiation)
440
+ *
441
+ * @param predicate - Function that evaluates the condition
442
+ * @param thenPolicy - Policy to execute if predicate is truthy
443
+ * @param elsePolicy - Optional policy to execute if predicate is falsy
444
+ * @returns A policy that branches based on the predicate
445
+ *
446
+ * @example
447
+ * ```ts
448
+ * const contentNegotiation = either(
449
+ * (ctx) => ctx.headers['accept']?.includes('application/json'),
450
+ * json(),
451
+ * xml()
452
+ * );
453
+ * ```
454
+ */
455
+ declare function either<T = unknown>(predicate: Predicate<T>, thenPolicy: Policy, elsePolicy?: Policy): Policy;
456
+ /**
457
+ * Creates a policy that matches against multiple predicates
458
+ * Executes the first matching policy
459
+ *
460
+ * @param branches - Array of predicate-policy pairs
461
+ * @param defaultPolicy - Optional default policy if no predicates match
462
+ * @returns A policy that matches multiple conditions
463
+ *
464
+ * @example
465
+ * ```ts
466
+ * const parser = match(
467
+ * [
468
+ * [(ctx) => ctx.headers['content-type']?.includes('json'), json()],
469
+ * [(ctx) => ctx.headers['content-type']?.includes('xml'), xml()],
470
+ * ],
471
+ * text() // default
472
+ * );
473
+ * ```
474
+ */
475
+ declare function match<T = unknown>(branches: ReadonlyArray<readonly [Predicate<T>, Policy]>, defaultPolicy?: Policy): Policy;
476
+
477
+ /**
478
+ * Policy introspection utilities
479
+ * Enables inspection of policy composition for debugging and testing
480
+ */
481
+
482
+ /**
483
+ * Policy kind/slot classification
484
+ */
485
+ type Kind = 'transport' | 'auth' | 'parser' | 'headers' | 'retry' | 'timeout' | 'files' | 'predicate' | 'strategy' | 'other';
486
+ /**
487
+ * Unified metadata for any inspectable (policy, predicate, strategy, etc.)
488
+ * All inspectable items share this base structure
489
+ */
490
+ interface InspectableMeta {
491
+ /** Unique stable ID for snapshot tests */
492
+ readonly id: string;
493
+ /** Function/object name */
494
+ readonly name: string;
495
+ /** Kind/type classification - unified for all inspectables */
496
+ readonly kind: Kind;
497
+ /** Redacted configuration options */
498
+ readonly options?: Record<string, unknown>;
499
+ /** Child inspectables (for composed functions) */
500
+ readonly children?: ReadonlyArray<InspectableMeta>;
501
+ /** Branch structure (for either/match policies) */
502
+ readonly branch?: {
503
+ readonly predicate: string;
504
+ readonly thenBranch: ReadonlyArray<InspectableMeta>;
505
+ readonly elseBranch: ReadonlyArray<InspectableMeta>;
506
+ };
507
+ }
508
+ /**
509
+ * Handler type (policy chain result)
510
+ */
511
+ type Handler = (ctx: RequestContext) => Promise<Response>;
512
+ /**
513
+ * Symbol for handler graph attachment
514
+ */
515
+ declare const HANDLER_GRAPH: unique symbol;
516
+ /**
517
+ * Symbol for inspectable metadata attachment
518
+ */
519
+ declare const INSPECTABLE_META: unique symbol;
520
+ /**
521
+ * Reset ID counter (for deterministic tests)
522
+ */
523
+ declare function resetIdCounter(): void;
524
+ /**
525
+ * Redacts sensitive options for safe inspection
526
+ * @param opts - Options object to redact
527
+ * @param extraKeys - Additional keys to redact
528
+ * @returns Redacted options clone
529
+ */
530
+ declare function redactOptions(opts: Record<string, unknown>, extraKeys?: string[]): Record<string, unknown>;
531
+ /**
532
+ * Creates a tagged policy with metadata for introspection
533
+ * @param fn - Policy function
534
+ * @param meta - Policy metadata (without id)
535
+ * @returns Tagged policy function
536
+ *
537
+ * @example
538
+ * ```ts
539
+ * export function timeout(ms: number): Policy {
540
+ * return policy(
541
+ * async (ctx, next) => {
542
+ * // ... implementation
543
+ * },
544
+ * { name: 'timeout', kind: 'timeout', options: { ms } }
545
+ * );
546
+ * }
547
+ * ```
548
+ */
549
+ declare function policy<T extends Policy>(fn: T, meta: Omit<InspectableMeta, 'id'> & {
550
+ options?: Record<string, unknown>;
551
+ }): T;
552
+ /**
553
+ * Attaches policy metadata to handler graph
554
+ * @param handler - Handler to attach graph to
555
+ * @param meta - Policy metadata to attach
556
+ */
557
+ declare function attachToGraph(handler: Handler, meta: InspectableMeta): void;
558
+ /**
559
+ * Gets handler graph
560
+ * @param handler - Handler to get graph from
561
+ * @returns Policy metadata graph
562
+ */
563
+ declare function getHandlerGraph(handler: Handler): ReadonlyArray<InspectableMeta>;
564
+ /**
565
+ * Creates an inspectable function/object with metadata
566
+ * Generic version that works with any type of function (predicate, strategy, etc.)
567
+ *
568
+ * @param fn - Function to make inspectable
569
+ * @param meta - Metadata (without id)
570
+ * @returns Tagged function
571
+ *
572
+ * @example
573
+ * ```ts
574
+ * export function backoff(options: BackoffOptions): RetryDelayStrategy {
575
+ * const strategy = {
576
+ * getDelay: (result, error, attempt) => { ... }
577
+ * };
578
+ * return inspectable(strategy, {
579
+ * name: 'backoff',
580
+ * kind: 'strategy',
581
+ * options: { initial: options.initial, max: options.max }
582
+ * });
583
+ * }
584
+ * ```
585
+ */
586
+ declare function inspectable<T>(fn: T, meta: Omit<InspectableMeta, 'id'> & {
587
+ options?: Record<string, unknown>;
588
+ }): T;
589
+ /**
590
+ * Extracts inspectable metadata from any function/object
591
+ * @param fn - Function/object to extract metadata from
592
+ * @returns Metadata or undefined if not inspectable
593
+ */
594
+ declare function getInspectableMeta(fn: unknown): InspectableMeta | undefined;
595
+ /**
596
+ * Checks if a function/object has inspectable metadata
597
+ * @param fn - Function/object to check
598
+ * @returns True if inspectable
599
+ */
600
+ declare function isInspectable(fn: unknown): boolean;
601
+
602
+ /**
603
+ * Policy inspection and formatting utilities
604
+ */
605
+
606
+ /**
607
+ * Inspection output format
608
+ */
609
+ type InspectFormat = 'json' | 'tree';
610
+ /**
611
+ * Inspection options
612
+ */
613
+ interface InspectOptions {
614
+ /** Output format (default: 'json') */
615
+ readonly format?: InspectFormat;
616
+ /** Whether to redact sensitive values (default: true) */
617
+ readonly redact?: boolean;
618
+ }
619
+ /**
620
+ * Inspects a policy or handler and returns its composition graph
621
+ * @param target - Policy or handler to inspect
622
+ * @param options - Inspection options
623
+ * @returns Formatted policy graph
624
+ *
625
+ * @example
626
+ * ```ts
627
+ * const pipeline = compose(
628
+ * retry(backoff(), { tries: 3 }),
629
+ * json()
630
+ * );
631
+ *
632
+ * // JSON format
633
+ * console.log(inspect(pipeline));
634
+ *
635
+ * // Tree format
636
+ * console.log(inspect(pipeline, { format: 'tree' }));
637
+ * ```
638
+ */
639
+ declare function inspect(target: Handler | Policy, options?: InspectOptions): string;
640
+ declare namespace inspect {
641
+ var json: (target: Handler | Policy) => string;
642
+ var tree: (target: Handler | Policy) => string;
643
+ }
644
+ /**
645
+ * Asserts that a policy or handler has a specific policy kind
646
+ * Useful for testing presets
647
+ *
648
+ * @param target - Policy or handler to check
649
+ * @param kind - Expected policy kind
650
+ * @throws Error if kind is not found
651
+ *
652
+ * @example
653
+ * ```ts
654
+ * const pipeline = compose(auth(), json());
655
+ * assertHas(pipeline, 'auth'); // throws if no auth policy
656
+ * assertHas(pipeline, 'parser'); // throws if no parser policy
657
+ * ```
658
+ */
659
+ declare function assertHas(target: Handler | Policy, kind: Kind): void;
660
+
661
+ /**
662
+ * Logging policy
663
+ */
664
+
665
+ interface LogOptions {
666
+ readonly logger: Logger;
667
+ readonly redactHeaders?: ReadonlyArray<string>;
668
+ readonly logBody?: boolean;
669
+ }
670
+ declare function log(options: LogOptions): Policy;
671
+
672
+ /**
673
+ * Serialization middleware for BodyDescriptor handling
674
+ */
675
+
676
+ /**
677
+ * Type guard to check if a value is a BodyDescriptor
678
+ * @param value - Value to check
679
+ * @returns True if value is a BodyDescriptor
680
+ */
681
+ declare function isBodyDescriptor(value: unknown): value is BodyDescriptor;
682
+ /**
683
+ * Serialization policy that converts BodyDescriptor to serialized form
684
+ * This middleware should be applied first in the policy chain to handle
685
+ * body descriptors before other policies process the request
686
+ *
687
+ * @returns Policy that handles BodyDescriptor serialization
688
+ */
689
+ declare function serializationPolicy(): Policy;
690
+
691
+ /**
692
+ * Slot system for transport, auth, and parser policies
693
+ * Provides compile-time and runtime checks for capability requirements
694
+ */
695
+
696
+ /**
697
+ * Registers slot metadata for a policy
698
+ * @param policy - The policy to register
699
+ * @param metadata - Slot metadata
700
+ */
701
+ declare function registerSlot(policy: Policy, metadata: SlotMetadata): void;
702
+ /**
703
+ * Gets slot metadata for a policy
704
+ * @param policy - The policy to query
705
+ * @returns Slot metadata or undefined
706
+ */
707
+ declare function getSlotMetadata(policy: Policy): SlotMetadata | undefined;
708
+ /**
709
+ * Validates a policy chain for slot conflicts and capability requirements
710
+ * @param policies - Array of policies to validate
711
+ * @param transportCapabilities - Transport capabilities
712
+ * @throws {DuplicatePolicyError} If duplicate slots detected
713
+ * @throws {MissingCapabilityError} If required capabilities missing
714
+ * @throws {InvalidSlotError} If slot configuration is invalid
715
+ */
716
+ declare function validatePolicyChain(policies: ReadonlyArray<Policy>, transportCapabilities?: TransportCapabilities): void;
717
+ /**
718
+ * Creates a slot decorator for policies
719
+ * @param metadata - Slot metadata
720
+ * @returns Decorator function
721
+ */
722
+ declare function slot(metadata: SlotMetadata): (policy: Policy) => Policy;
723
+ /**
724
+ * Checks if a policy has a specific slot type
725
+ * @param policy - The policy to check
726
+ * @param type - The slot type to match
727
+ * @returns True if policy has the slot type
728
+ */
729
+ declare function hasSlotType(policy: Policy, type: SlotType): boolean;
730
+
731
+ /**
732
+ * Throttle policy (Client-side Rate Limiter)
733
+ * Controls the rate of outgoing requests using Token Bucket algorithm
734
+ */
735
+
736
+ interface ThrottleOptions {
737
+ /** Number of requests allowed per interval */
738
+ readonly limit: number;
739
+ /** Interval in milliseconds (default: 1000) */
740
+ readonly interval?: number;
741
+ }
742
+ declare function throttle(options: ThrottleOptions): Policy;
743
+
744
+ /**
745
+ * URL normalization and header utilities
746
+ */
747
+
748
+ /**
749
+ * Normalizes a URL with base and default scheme support
750
+ * @param url - The URL to normalize (absolute or relative)
751
+ * @param options - Client options containing base and defaultScheme
752
+ * @returns Normalized absolute URL
753
+ */
754
+ declare function normalizeURL(url: string, options?: ClientOptions): string;
755
+ /**
756
+ * Appends query parameters to a URL
757
+ * @param url - The base URL
758
+ * @param params - Query parameters to append
759
+ * @returns URL with query parameters
760
+ */
761
+ declare function appendQueryParams(url: string, params: Record<string, string | number | boolean | undefined>): string;
762
+ /**
763
+ * Normalizes HTTP headers to lowercase (per HTTP/2 spec)
764
+ * HTTP/2 requires all header names to be lowercase
765
+ * HTTP/1.1 is case-insensitive, so lowercase is safe everywhere
766
+ *
767
+ * @param headers - Headers object with potentially mixed case keys
768
+ * @returns Headers object with all lowercase keys
769
+ *
770
+ * @example
771
+ * ```ts
772
+ * normalizeHeaders({ 'Content-Type': 'application/json', 'Accept': 'text/html' })
773
+ * // Returns: { 'content-type': 'application/json', 'accept': 'text/html' }
774
+ * ```
775
+ */
776
+ declare function normalizeHeaders(headers: Record<string, string>): Record<string, string>;
777
+ /**
778
+ * Gets a header value with case-insensitive lookup
779
+ * Useful for reading headers before normalization
780
+ *
781
+ * @param headers - Headers object
782
+ * @param name - Header name (case-insensitive)
783
+ * @returns Header value or undefined
784
+ *
785
+ * @example
786
+ * ```ts
787
+ * getHeader({ 'Content-Type': 'application/json' }, 'content-type') // 'application/json'
788
+ * getHeader({ 'content-type': 'application/json' }, 'Content-Type') // 'application/json'
789
+ * ```
790
+ */
791
+ declare function getHeader(headers: Record<string, string>, name: string): string | undefined;
792
+ /**
793
+ * Sets a header value, removing any existing variants with different casing
794
+ * Prevents duplicate headers with different casing (e.g., 'accept' and 'Accept')
795
+ *
796
+ * @param headers - Headers object to modify
797
+ * @param name - Header name (will be normalized to lowercase)
798
+ * @param value - Header value
799
+ * @returns New headers object with the header set (immutable)
800
+ *
801
+ * @example
802
+ * ```ts
803
+ * setHeader({ Accept: 'text/html' }, 'accept', 'application/json')
804
+ * // Returns: { accept: 'application/json' } (removed 'Accept')
805
+ * ```
806
+ */
807
+ declare function setHeader(headers: Record<string, string>, name: string, value: string): Record<string, string>;
808
+
809
+ /**
810
+ * Validation policy using Adapter Pattern
811
+ */
812
+
813
+ /**
814
+ * Validation adapter interface
815
+ * Adapts any validation library (Zod, Valibot, Joi, etc.) to unireq
816
+ */
817
+ interface ValidationAdapter<TSchema, TOutput> {
818
+ validate(schema: TSchema, data: unknown): Promise<TOutput> | TOutput;
819
+ }
820
+ /**
821
+ * Validates response data using a schema and an adapter
822
+ * @param schema - The schema to validate against
823
+ * @param adapter - The adapter for the validation library
824
+ * @returns Policy that validates response data
825
+ */
826
+ declare function validate<TSchema, TOutput>(schema: TSchema, adapter: ValidationAdapter<TSchema, TOutput>): Policy;
827
+
828
+ export { type AuditLogEntry, type AuditLogger, type AuditOptions, type BackoffOptions, type BodyDescriptor, CircuitBreakerOpenError, type CircuitBreakerOptions, type Client, type ClientOptions, type Connector, DuplicatePolicyError, type EitherBranch, HANDLER_GRAPH, type Handler, HttpError, INSPECTABLE_META, type InspectFormat, type InspectOptions, type InspectableMeta, InvalidSlotError, type Kind, type LogOptions, type Logger, MissingCapabilityError, type MultipartPart, NetworkError, NotAcceptableError, type Policy, type Predicate, type RequestContext, type Response, type RetryDelayStrategy, type RetryOptions, type RetryPredicate, type SecurityEventType, SerializationError, type SlotMetadata, SlotType, type ThrottleOptions, TimeoutError, type Transport, type TransportCapabilities, type TransportWithCapabilities, URLNormalizationError, UnireqError, UnsupportedAuthForTransport, UnsupportedMediaTypeError, type ValidationAdapter, appendQueryParams, assertHas, attachToGraph, audit, backoff, circuitBreaker, client, compose, createConsoleAuditLogger, createLoggerAdapter, either, getHandlerGraph, getHeader, getInspectableMeta, getSlotMetadata, hasSlotType, inspect, inspectable, isBodyDescriptor, isInspectable, log, match, normalizeHeaders, normalizeURL, policy, redactOptions, registerSlot, resetIdCounter, retry, serializationPolicy, setHeader, slot, throttle, validate, validatePolicyChain };
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ import {randomUUID}from'crypto';var C=Symbol("unireq.handlerGraph"),S=Symbol("unireq.inspectable"),ce=["token","accessToken","refreshToken","clientSecret","clientId","password","privateKey","secret","apiKey","authorization"],ee=0;function le(){ee=0;}function te(e){return process.env.NODE_ENV==="test"||process.env.VITEST?`${e}#${ee++}`:`${e}#${randomUUID().slice(0,8)}`}function _(e){if(e===null||typeof e!="object")return e;if(Array.isArray(e))return e.map(_);let t={};for(let[n,r]of Object.entries(e))t[n]=_(r);return t}function $(e,t=[]){if(!e||typeof e!="object")return {};let n=new Set([...ce,...t]),r=_(e);for(let o of Object.keys(r))n.has(o)&&(r[o]="***redacted***"),(o.toLowerCase().includes("secret")||o.toLowerCase().includes("token"))&&(r[o]="***redacted***");return r}function q(e,t,n){e[t]=n;}function j(e,t){return e[t]}function p(e,t){let n={...t,id:te(t.name),options:t.options?$(t.options):void 0};return q(e,S,n),e}function de(e,t){let n=j(e,C),r=n?[...n,t]:[t];q(e,C,r);}function I(e){return j(e,C)??[]}function U(e,t){let n={...t,id:te(t.name),options:t.options?$(t.options):void 0};return q(e,S,n),e}function m(e){if(!(!e||typeof e!="function"&&typeof e!="object"))return j(e,S)}function pe(e){return m(e)!==void 0}var ye=["authorization","cookie","set-cookie","x-api-key","x-auth-token","x-csrf-token","x-session-id"];function fe(){return randomUUID()}function H(e){try{let t=new URL(e),n=["token","api_key","apikey","secret","password","auth"];for(let r of n)t.searchParams.has(r)&&t.searchParams.set(r,"[REDACTED]");return t.toString()}catch{return e}}function me(e){return e>=500?"error":e===401||e===403||e>=400?"warn":"info"}function ge(e){let{logger:t,getUserId:n,getSessionId:r,getClientIp:o,redactHeaders:i=ye,logSuccess:s=true,correlationIdGenerator:c=fe,detectSuspiciousActivity:l}=e;return p(async(a,d)=>{let y=c(),f=Date.now(),h=new Date().toISOString(),O=n?.(a),L=r?.(a),M=o?.(a),D=a.headers["user-agent"];await t.log({timestamp:h,correlationId:y,eventType:"request_started",severity:"info",method:a.method,url:H(a.url),userId:O,sessionId:L,clientIp:M,userAgent:D});try{let g=await d(a),v=Date.now()-f,R="request_completed",b=me(g.status);return g.status===401?(R="auth_failure",b="warn"):g.status===403?(R="access_denied",b="warn"):g.status===429&&(R="rate_limit_exceeded",b="warn"),l?.(a,g,void 0)&&(R="suspicious_activity",b="critical"),(s||g.status>=400)&&await t.log({timestamp:new Date().toISOString(),correlationId:y,eventType:R,severity:b,method:a.method,url:H(a.url),statusCode:g.status,durationMs:v,userId:O,sessionId:L,clientIp:M,userAgent:D}),g}catch(g){let v=Date.now()-f,R=g instanceof Error?g:new Error(String(g)),b="request_failed",X="error";throw l?.(a,void 0,R)&&(b="suspicious_activity",X="critical"),await t.log({timestamp:new Date().toISOString(),correlationId:y,eventType:b,severity:X,method:a.method,url:H(a.url),durationMs:v,userId:O,sessionId:L,clientIp:M,userAgent:D,errorMessage:R.message,errorCode:R.code}),g}},{name:"audit",kind:"other",options:{logSuccess:s,redactHeaders:i}})}function he(){return {log:e=>{console.log(JSON.stringify(e));}}}function Re(e){return {log:t=>{let n=`[${t.eventType}] ${t.method} ${t.url}`,r={...t};switch(t.severity){case "critical":case "error":e.error(n,r);break;case "warn":e.warn(n,r);break;default:e.info(n,r);}}}}function be(e,t,n,r,o){let i=t*r**e,s=Math.min(i,n);return o?Math.floor(Math.random()*s):s}function Te(e={}){let{initial:t=1e3,max:n=3e4,multiplier:r=2,jitter:o=true}=e;return U({getDelay:(s,c,l)=>be(l,t,n,r,o)},{name:"backoff",kind:"strategy",options:{initial:t,max:n,multiplier:r,jitter:o}})}var u=class extends Error{constructor(n,r,o){super(n);this.code=r;this.cause=o;this.name="UnireqError";}},B=class extends u{constructor(t,n){super(t,"NETWORK_ERROR",n),this.name="NetworkError";}},N=class extends u{constructor(n,r){super(`Request timed out after ${n}ms`,"TIMEOUT",r);this.timeoutMs=n;this.name="TimeoutError";}},z=class extends u{status;statusText;headers;data;constructor(t){super(`HTTP Error ${t.status}: ${t.statusText}`,"HTTP_ERROR"),this.name="HttpError",this.status=t.status,this.statusText=t.statusText,this.headers=t.headers,this.data=t.data;}},P=class extends u{constructor(t,n){super(t,"SERIALIZATION_ERROR",n),this.name="SerializationError";}},x=class extends u{constructor(n){super(`Duplicate policy detected: ${n}. Each policy can only be registered once in the chain.`,"DUPLICATE_POLICY");this.policyName=n;this.name="DuplicatePolicyError";}},F=class extends u{constructor(n,r){super(`Authentication type "${n}" is not supported by transport "${r}".`,"UNSUPPORTED_AUTH");this.authType=n;this.transportType=r;this.name="UnsupportedAuthForTransport";}},G=class extends u{constructor(n,r){let o=r?` (received: ${r})`:"";super(`Server cannot produce a response matching the Accept header. Accepted types: ${n.join(", ")}${o}`,"NOT_ACCEPTABLE");this.acceptedTypes=n;this.receivedType=r;this.name="NotAcceptableError";}},K=class extends u{constructor(n,r){let o=r?` (sent: ${r})`:"";super(`Server cannot process the request payload media type. Supported types: ${n.join(", ")}${o}`,"UNSUPPORTED_MEDIA_TYPE");this.supportedTypes=n;this.sentType=r;this.name="UnsupportedMediaTypeError";}},k=class extends u{constructor(n,r){super(`Transport "${r}" does not support required capability: ${n}`,"MISSING_CAPABILITY");this.capability=n;this.transportType=r;this.name="MissingCapabilityError";}},w=class extends u{constructor(n,r){super(`Invalid slot configuration for ${n}: ${r}`,"INVALID_SLOT");this.slotType=n;this.name="InvalidSlotError";}},T=class extends u{constructor(n,r){super(`Failed to normalize URL "${n}": ${r}`,"URL_NORMALIZATION_FAILED");this.url=n;this.reason=r;this.name="URLNormalizationError";}};var V=class extends u{constructor(n){super("Circuit breaker is OPEN","CIRCUIT_BREAKER_OPEN");this.resetTime=n;this.name="CircuitBreakerOpenError";}};function Je(e={}){let{failureThreshold:t=5,resetTimeout:n=3e4,shouldFail:r=()=>true}=e,o="CLOSED",i=0,s=0;return p(async(c,l)=>{let a=Date.now();if(o==="OPEN")if(a>=s)o="HALF_OPEN";else throw new V(s);try{let d=await l(c);return o==="HALF_OPEN"&&(o="CLOSED"),i=0,d}catch(d){throw r(d)&&(o==="HALF_OPEN"?(o="OPEN",s=Date.now()+n):(i++,i>=t&&(o="OPEN",s=Date.now()+n))),d}},{name:"circuitBreaker",kind:"other",options:{failureThreshold:t,resetTimeout:n,state:o}})}function E(...e){if(e.length===0)return async(r,o)=>o(r);if(e.length===1){let r=e[0];return r||(async(o,i)=>i(o))}let t=async(r,o)=>{let i=async(s,c)=>{let l=e[s];return l?l(c,a=>i(s+1,a)):o(c)};return i(0,r)},n=e.filter(r=>r!==void 0).map(r=>m(r)).filter(r=>r!==void 0);return p(t,{name:"compose",kind:"other",children:n})}function re(e){return typeof e=="object"&&e!==null&&"__brand"in e&&e.__brand==="BodyDescriptor"&&"serialize"in e&&typeof e.serialize=="function"}function W(){return async(e,t)=>{if(re(e.body)){let n=e.body,r=n.serialize(),o=n.contentType&&!e.headers["content-type"]&&!e.headers["Content-Type"]&&!(r instanceof FormData),i={...e,body:r,headers:{...e.headers,...o?{"content-type":n.contentType}:{}}};return t(i)}return t(e)}}var ne=new WeakMap;function oe(e,t){ne.set(e,t);}function Y(e){return ne.get(e)}function J(e,t){let n=new Map;for(let r of e){let o=Y(r);if(!o)continue;let i=`${o.type}:${o.name}`;if(n.get(i))throw new x(o.name);if(n.set(i,o),t&&o.requiredCapabilities){for(let c of o.requiredCapabilities)if(!t[c])throw new k(c,"current")}}we(Array.from(n.values()));}function we(e){let t=-1,n=-1,r=-1;if(e.forEach((o,i)=>{switch(o.type){case "transport":t=i;break;case "auth":n=i;break;case "parser":r=i;break}}),t!==-1&&t!==e.length-1)throw new w("transport","Transport slot must be the last policy in the chain");if(n!==-1&&r!==-1&&n>r)throw new w("auth","Auth slot must come before parser slot in the chain")}function Pe(e){return t=>(oe(t,e),t)}function xe(e,t){return Y(e)?.type===t}function Q(e,t={}){let{base:n,defaultScheme:r="https"}=t;try{if(e.includes("://"))return new URL(e).toString();if(e.startsWith("//"))return new URL(`${r}:${e}`).toString();if(n){let i=n.includes("://")?n:`${r}://${n}`;return new URL(e,i).toString()}if(!e.match(/^[a-z][a-z0-9+.-]*:/i))throw new T(e,'Relative URL requires URI in transport. Use http("https://api.com") or provide absolute URL.');return new URL(e).toString()}catch(o){if(o instanceof T)throw o;let i=o instanceof Error?o.message:String(o);throw new T(e,i)}}function ke(e,t){let n=new URL(e);for(let[r,o]of Object.entries(t))o!==void 0&&n.searchParams.append(r,String(o));return n.toString()}function Ee(e){let t={};for(let[n,r]of Object.entries(e))t[n.toLowerCase()]=r;return t}function Ae(e,t){let n=t.toLowerCase();if(n in e)return e[n];for(let[r,o]of Object.entries(e))if(r.toLowerCase()===n)return o}function Ce(e,t,n){let r=t.toLowerCase(),o={};for(let[i,s]of Object.entries(e))i.toLowerCase()!==r&&(o[i]=s);return o[r]=n,o}function Se(e,...t){let n=typeof e=="function"?e:e.transport,r=typeof e=="function"?void 0:e.capabilities;J(t,r);let o=E(W(),...t),i=async(s,c,l,a)=>{if(!s||typeof s!="string"||s.trim().length===0)throw new u("URL must be a non-empty string","INVALID_URL");let y={url:s.includes("://")?Q(s,{}):s,method:c,headers:{},body:l};if(a.length>0){let f=E(...a);return E(o,f)(y,n)}return o(y,n)};return {request:(s,...c)=>i(s,"GET",void 0,c),get:(s,...c)=>i(s,"GET",void 0,c),head:(s,...c)=>i(s,"HEAD",void 0,c),post:(s,c,...l)=>i(s,"POST",c,l),put:(s,c,...l)=>i(s,"PUT",c,l),delete:(s,...c)=>i(s,"DELETE",void 0,c),patch:(s,c,...l)=>i(s,"PATCH",c,l),options:(s,...c)=>i(s,"OPTIONS",void 0,c)}}function Ie(e,t,n){let r=async(a,d)=>await Promise.resolve(e(a))?t(a,d):n?n(a,d):d(a),o=e.name||"anonymous predicate",i=m(t),s=i?[i]:[],c=n?m(n):void 0;return p(r,{name:"either",kind:"other",branch:{predicate:o,thenBranch:s,elseBranch:c?[c]:[]}})}function Oe(e,t){let n=async(o,i)=>{for(let[s,c]of e)if(await Promise.resolve(s(o)))return c(o,i);return t?t(o,i):i(o)},r=e.map(([o,i],s)=>{let c=o.name||`branch ${s+1}`,l=m(i);return {id:`match-branch-${s}`,name:c,kind:"other",children:l?[l]:[]}});if(t){let o=m(t);r.push({id:"match-default",name:"default",kind:"other",children:o?[o]:[]});}return p(n,{name:"match",kind:"other",children:r})}function A(e,t={}){let{format:n="json"}=t,r=I(e);if(r.length===0){let o=m(e);o&&o.name!=="anonymous"&&(r=[o]);}return r.length===0?n==="tree"?"(empty policy chain)":"[]":n==="tree"?Me(r):Le(r)}A.json=e=>A(e,{format:"json"});A.tree=e=>A(e,{format:"tree"});function Le(e){return JSON.stringify(e,null,2)}function Me(e){let t=[],n=(r,o,i,s="")=>{let c="";o>0&&(c=i?"\u2514\u2500 ":"\u251C\u2500 ");let l=De(r.options);if(t.push(`${s}${c}${r.name} (${r.kind})${l}`),r.children&&r.children.length>0){let a=s+(o>0?i?" ":"\u2502 ":""),d=r.children.length;r.children.forEach((y,f)=>{n(y,o+1,f===d-1,a);});}if(r.branch){let a=s+(o>0?i?" ":"\u2502 ":"");if(t.push(`${a} ? ${r.branch.predicate}`),r.branch.thenBranch.length>0){t.push(`${a} \u251C\u2500 then:`);let d=`${a} \u2502 `,y=r.branch.thenBranch.length;r.branch.thenBranch.forEach((f,h)=>{n(f,o+2,h===y-1,d);});}if(r.branch.elseBranch.length>0){t.push(`${a} \u2514\u2500 else:`);let d=`${a} `,y=r.branch.elseBranch.length;r.branch.elseBranch.forEach((f,h)=>{n(f,o+2,h===y-1,d);});}}};return e.forEach((r,o)=>{n(r,0,o===e.length-1,"");}),t.join(`
2
+ `)}function De(e){return !e||Object.keys(e).length===0?"":` [${Object.entries(e).map(([n,r])=>`${n}=${Z(r)}`).join(", ")}]`}function Z(e){if(e==null)return "null";if(typeof e=="string")return `"${e}"`;if(typeof e=="boolean"||typeof e=="number")return String(e);if(Array.isArray(e))return e.length===0?"[]":e.length<=3?`[${e.map(Z).join(", ")}]`:`[${e.slice(0,2).map(Z).join(", ")}, ... +${e.length-2}]`;if(typeof e=="object"){let t=Object.keys(e);if(t.length===0)return "{}";let n=t.slice(0,2).join(", ");return t.length>2?`{${n}, ...}`:`{${n}}`}return String(e)}function ve(e,t){let n=I(e);if(n.length===0){let o=m(e);o&&o.name!=="anonymous"&&(n=[o]);}let r=o=>{for(let i of o)if(i.kind===t||i.children&&r(i.children)||i.branch&&(r(i.branch.thenBranch)||r(i.branch.elseBranch)))return true;return false};if(!r(n))throw new Error(`Expected policy kind "${t}" not found in handler graph`)}var $e=["authorization","cookie","set-cookie","x-api-key"];function qe(e){let{logger:t,redactHeaders:n=$e,logBody:r=false}=e,o=i=>{let s={...i};for(let c of n){let l=Object.keys(s).find(a=>a.toLowerCase()===c.toLowerCase());l&&(s[l]="[REDACTED]");}return s};return p(async(i,s)=>{let c=Date.now(),l=randomUUID();t.info(`Request ${l} started`,{requestId:l,method:i.method,url:i.url,headers:o(i.headers),body:r?i.body:void 0});try{let a=await s(i),d=Date.now()-c;return t.info(`Request ${l} completed`,{requestId:l,status:a.status,duration:d,headers:o(a.headers),data:r?a.data:void 0}),a}catch(a){let d=Date.now()-c;throw t.error(`Request ${l} failed`,{requestId:l,duration:d,error:a}),a}},{name:"log",kind:"other",options:{redactHeaders:n,logBody:r}})}async function ie(e,t,n,r){for(let o of e){let i=await o.getDelay(t,n,r);if(i!==void 0)return i}return 0}function je(e,t,n){let{tries:r=3,onRetry:o}=n??{};return p(async(i,s)=>{let c,l=null;for(let a=0;a<r;a++)try{let d=await s(i);if(c=d,!await Promise.resolve(e(d,null,a,i))||a===r-1)return d;o&&await Promise.resolve(o(a+1,null,d));let f=await ie(t,d,null,a);f>0&&await new Promise(h=>setTimeout(h,f));}catch(d){if(l=d instanceof Error?d:new Error(String(d)),a===r-1||!await Promise.resolve(e(null,l,a,i)))throw l;o&&await Promise.resolve(o(a+1,l,null));let f=await ie(t,null,l,a);f>0&&await new Promise(h=>setTimeout(h,f));}return c},{name:"retry",kind:"retry",options:{tries:r},children:[...(()=>{let i=m(e);return i?[i]:[]})(),...t.map(i=>m(i)).filter(i=>i!==void 0)]})}function wt(e){let{limit:t,interval:n=1e3}=e,r=t,o=Date.now(),i=t/n,s=()=>{let c=Date.now(),a=(c-o)*i;a>0&&(r=Math.min(t,r+a),o=c);};return p(async(c,l)=>{if(s(),r<1){let a=1-r,d=Math.ceil(a/i);await new Promise(y=>setTimeout(y,d)),s();}return r-=1,l(c)},{name:"throttle",kind:"other",options:{limit:t,interval:n}})}var se=(r=>(r.Transport="transport",r.Auth="auth",r.Parser="parser",r))(se||{});function Ue(e,t){return p(async(n,r)=>{let o=await r(n);try{let i=await t.validate(e,o.data);return {...o,data:i}}catch(i){throw new P(`Validation failed: ${i.message}`,i)}},{name:"validate",kind:"other",options:{schema:e}})}export{V as CircuitBreakerOpenError,x as DuplicatePolicyError,C as HANDLER_GRAPH,z as HttpError,S as INSPECTABLE_META,w as InvalidSlotError,k as MissingCapabilityError,B as NetworkError,G as NotAcceptableError,P as SerializationError,se as SlotType,N as TimeoutError,T as URLNormalizationError,u as UnireqError,F as UnsupportedAuthForTransport,K as UnsupportedMediaTypeError,ke as appendQueryParams,ve as assertHas,de as attachToGraph,ge as audit,Te as backoff,Je as circuitBreaker,Se as client,E as compose,he as createConsoleAuditLogger,Re as createLoggerAdapter,Ie as either,I as getHandlerGraph,Ae as getHeader,m as getInspectableMeta,Y as getSlotMetadata,xe as hasSlotType,A as inspect,U as inspectable,re as isBodyDescriptor,pe as isInspectable,qe as log,Oe as match,Ee as normalizeHeaders,Q as normalizeURL,p as policy,$ as redactOptions,oe as registerSlot,le as resetIdCounter,je as retry,W as serializationPolicy,Ce as setHeader,Pe as slot,wt as throttle,Ue as validate,J as validatePolicyChain};//# sourceMappingURL=index.js.map
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/introspection.ts","../src/audit.ts","../src/backoff.ts","../src/errors.ts","../src/circuit-breaker.ts","../src/compose.ts","../src/serialization.ts","../src/slots.ts","../src/url.ts","../src/client.ts","../src/either.ts","../src/inspect.ts","../src/logging.ts","../src/retry.ts","../src/throttle.ts","../src/types.ts","../src/validation.ts"],"names":["HANDLER_GRAPH","INSPECTABLE_META","SECRET_KEYS","idCounter","resetIdCounter","generateId","name","randomUUID","deepClone","obj","result","key","value","redactOptions","opts","extraKeys","keys","clone","attachMetadata","target","symbol","meta","getMetadata","policy","fn","withId","attachToGraph","handler","prev","next","getHandlerGraph","inspectable","getInspectableMeta","isInspectable","DEFAULT_REDACT_HEADERS","generateCorrelationId","sanitizeUrl","url","parsed","sensitiveParams","param","getSeverityFromStatus","status","audit","options","logger","getUserId","getSessionId","getClientIp","redactHeaders","logSuccess","correlationIdGenerator","detectSuspiciousActivity","ctx","correlationId","startTime","timestamp","userId","sessionId","clientIp","userAgent","response","durationMs","eventType","severity","error","err","createConsoleAuditLogger","entry","createLoggerAdapter","message","calculateBackoff","attempt","baseMs","maxMs","multiplier","jitter","exponential","capped","backoff","initial","max","_result","_error","UnireqError","code","cause","NetworkError","TimeoutError","timeoutMs","HttpError","SerializationError","DuplicatePolicyError","policyName","UnsupportedAuthForTransport","authType","transportType","NotAcceptableError","acceptedTypes","receivedType","received","UnsupportedMediaTypeError","supportedTypes","sentType","sent","MissingCapabilityError","capability","InvalidSlotError","slotType","URLNormalizationError","reason","CircuitBreakerOpenError","resetTime","circuitBreaker","failureThreshold","resetTimeout","shouldFail","state","failureCount","nextAttempt","now","compose","policies","_ctx","singlePolicy","impl","dispatch","i","context","nextCtx","children","p","m","isBodyDescriptor","serializationPolicy","descriptor","serialized","shouldSetContentType","updatedCtx","slotRegistry","registerSlot","metadata","getSlotMetadata","validatePolicyChain","transportCapabilities","seenSlots","slotKey","validateSlotOrdering","slots","transportIndex","authIndex","parserIndex","slot","hasSlotType","type","normalizeURL","base","defaultScheme","baseURL","appendQueryParams","params","urlObj","normalizeHeaders","headers","normalized","getHeader","lowerName","setHeader","newHeaders","val","client","transport","actualTransport","capabilities","composedPolicy","executeRequest","method","body","perRequestPolicies","perRequestPolicy","either","predicate","thenPolicy","elsePolicy","predicateDesc","thenMetaRaw","thenMeta","elseMetaRaw","match","branches","defaultPolicy","idx","defaultMeta","inspect","format","graph","toTree","toJson","nodes","lines","walk","depth","isLast","parentPrefix","prefix","optionsStr","formatOptions","childPrefix","childrenLength","child","branchPrefix","thenPrefix","thenLength","elsePrefix","elseLength","node","prettyValue","keyList","assertHas","kind","hasKind","log","logBody","redact","redacted","header","k","start","requestId","duration","calculateDelay","strategies","strategy","strategyDelay","retry","tries","onRetry","lastResponse","lastError","delay","resolve","s","throttle","limit","interval","tokens","lastRefill","refillRate","refill","newTokens","missingTokens","waitTime","SlotType","validate","schema","adapter","validatedData"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Policy introspection utilities\n * Enables inspection of policy composition for debugging and testing\n */\n\nimport { randomUUID } from 'node:crypto';\nimport type { Policy, RequestContext, Response } from './types.js';\n\n/**\n * Policy kind/slot classification\n */\nexport type Kind =\n | 'transport'\n | 'auth'\n | 'parser'\n | 'headers'\n | 'retry'\n | 'timeout'\n | 'files'\n | 'predicate'\n | 'strategy'\n | 'other';\n\n/**\n * Unified metadata for any inspectable (policy, predicate, strategy, etc.)\n * All inspectable items share this base structure\n */\nexport interface InspectableMeta {\n /** Unique stable ID for snapshot tests */\n readonly id: string;\n /** Function/object name */\n readonly name: string;\n /** Kind/type classification - unified for all inspectables */\n readonly kind: Kind;\n /** Redacted configuration options */\n readonly options?: Record<string, unknown>;\n /** Child inspectables (for composed functions) */\n readonly children?: ReadonlyArray<InspectableMeta>;\n /** Branch structure (for either/match policies) */\n readonly branch?: {\n readonly predicate: string;\n readonly thenBranch: ReadonlyArray<InspectableMeta>;\n readonly elseBranch: ReadonlyArray<InspectableMeta>;\n };\n}\n\n/**\n * Handler type (policy chain result)\n */\nexport type Handler = (ctx: RequestContext) => Promise<Response>;\n\n/**\n * Symbol for handler graph attachment\n */\nexport const HANDLER_GRAPH = Symbol('unireq.handlerGraph');\n\n/**\n * Symbol for inspectable metadata attachment\n */\nexport const INSPECTABLE_META = Symbol('unireq.inspectable');\n\n/**\n * Keys that should be redacted from options (secrets)\n */\nconst SECRET_KEYS = [\n 'token',\n 'accessToken',\n 'refreshToken',\n 'clientSecret',\n 'clientId',\n 'password',\n 'privateKey',\n 'secret',\n 'apiKey',\n 'authorization',\n] as const;\n\n/**\n * Counter for deterministic IDs in tests\n */\nlet idCounter = 0;\n\n/**\n * Reset ID counter (for deterministic tests)\n */\nexport function resetIdCounter(): void {\n idCounter = 0;\n}\n\n/**\n * Generate stable ID for policy metadata\n */\nfunction generateId(name: string): string {\n // In test mode, use deterministic counter\n if (process.env['NODE_ENV'] === 'test' || process.env['VITEST']) {\n return `${name}#${idCounter++}`;\n }\n // In production, use cryptographically secure random UUID\n return `${name}#${randomUUID().slice(0, 8)}`;\n}\n\n/**\n * Deep clones an object while preserving functions and other non-JSON types\n */\nfunction deepClone<T>(obj: T): T {\n if (obj === null || typeof obj !== 'object') {\n return obj;\n }\n\n if (Array.isArray(obj)) {\n return obj.map(deepClone) as unknown as T;\n }\n\n const result = {} as Record<string, unknown>;\n for (const [key, value] of Object.entries(obj as Record<string, unknown>)) {\n result[key] = deepClone(value);\n }\n return result as T;\n}\n\n/**\n * Redacts sensitive options for safe inspection\n * @param opts - Options object to redact\n * @param extraKeys - Additional keys to redact\n * @returns Redacted options clone\n */\nexport function redactOptions(opts: Record<string, unknown>, extraKeys: string[] = []): Record<string, unknown> {\n if (!opts || typeof opts !== 'object') {\n return {};\n }\n\n const keys = new Set([...SECRET_KEYS, ...extraKeys]);\n const clone = deepClone(opts);\n\n for (const key of Object.keys(clone)) {\n if (keys.has(key)) {\n clone[key] = '***redacted***';\n }\n // Also redact nested objects with \"secret\" in the key name\n if (key.toLowerCase().includes('secret') || key.toLowerCase().includes('token')) {\n clone[key] = '***redacted***';\n }\n }\n\n return clone;\n}\n\n/**\n * Type-safe metadata attachment helper\n * Attaches metadata to a function/object via symbol\n */\nfunction attachMetadata<T>(target: T, symbol: symbol, meta: InspectableMeta): void {\n // Use Record<symbol, unknown> to safely index with symbol\n (target as Record<symbol, unknown>)[symbol] = meta;\n}\n\n/**\n * Type-safe metadata retrieval helper\n * Retrieves metadata from a function/object via symbol\n */\nfunction getMetadata<T>(target: T, symbol: symbol): InspectableMeta | undefined {\n // Use Record<symbol, unknown> to safely index with symbol\n return (target as Record<symbol, unknown>)[symbol] as InspectableMeta | undefined;\n}\n\n/**\n * Creates a tagged policy with metadata for introspection\n * @param fn - Policy function\n * @param meta - Policy metadata (without id)\n * @returns Tagged policy function\n *\n * @example\n * ```ts\n * export function timeout(ms: number): Policy {\n * return policy(\n * async (ctx, next) => {\n * // ... implementation\n * },\n * { name: 'timeout', kind: 'timeout', options: { ms } }\n * );\n * }\n * ```\n */\nexport function policy<T extends Policy>(\n fn: T,\n meta: Omit<InspectableMeta, 'id'> & { options?: Record<string, unknown> },\n): T {\n const withId: InspectableMeta = {\n ...meta,\n id: generateId(meta.name),\n options: meta.options ? redactOptions(meta.options) : undefined,\n };\n\n // Attach metadata to function using typed helper\n attachMetadata(fn, INSPECTABLE_META, withId);\n\n return fn;\n}\n\n/**\n * Attaches policy metadata to handler graph\n * @param handler - Handler to attach graph to\n * @param meta - Policy metadata to attach\n */\nexport function attachToGraph(handler: Handler, meta: InspectableMeta): void {\n const prev = getMetadata(handler, HANDLER_GRAPH) as InspectableMeta[] | undefined;\n const next = prev ? [...prev, meta] : [meta];\n attachMetadata(handler, HANDLER_GRAPH, next as unknown as InspectableMeta);\n}\n\n/**\n * Gets handler graph\n * @param handler - Handler to get graph from\n * @returns Policy metadata graph\n */\nexport function getHandlerGraph(handler: Handler): ReadonlyArray<InspectableMeta> {\n const graph = getMetadata(handler, HANDLER_GRAPH);\n return (graph as unknown as InspectableMeta[]) ?? [];\n}\n\n/**\n * Creates an inspectable function/object with metadata\n * Generic version that works with any type of function (predicate, strategy, etc.)\n *\n * @param fn - Function to make inspectable\n * @param meta - Metadata (without id)\n * @returns Tagged function\n *\n * @example\n * ```ts\n * export function backoff(options: BackoffOptions): RetryDelayStrategy {\n * const strategy = {\n * getDelay: (result, error, attempt) => { ... }\n * };\n * return inspectable(strategy, {\n * name: 'backoff',\n * kind: 'strategy',\n * options: { initial: options.initial, max: options.max }\n * });\n * }\n * ```\n */\nexport function inspectable<T>(fn: T, meta: Omit<InspectableMeta, 'id'> & { options?: Record<string, unknown> }): T {\n const withId: InspectableMeta = {\n ...meta,\n id: generateId(meta.name),\n options: meta.options ? redactOptions(meta.options) : undefined,\n };\n\n // Attach metadata to function/object using typed helper\n attachMetadata(fn, INSPECTABLE_META, withId);\n\n return fn;\n}\n\n/**\n * Extracts inspectable metadata from any function/object\n * @param fn - Function/object to extract metadata from\n * @returns Metadata or undefined if not inspectable\n */\nexport function getInspectableMeta(fn: unknown): InspectableMeta | undefined {\n if (!fn || (typeof fn !== 'function' && typeof fn !== 'object')) {\n return undefined;\n }\n return getMetadata(fn, INSPECTABLE_META);\n}\n\n/**\n * Checks if a function/object has inspectable metadata\n * @param fn - Function/object to check\n * @returns True if inspectable\n */\nexport function isInspectable(fn: unknown): boolean {\n return getInspectableMeta(fn) !== undefined;\n}\n","/**\n * Structured audit logging for security events (OWASP A09:2021)\n *\n * @see https://owasp.org/Top10/A09_2021-Security_Logging_and_Monitoring_Failures/\n */\n\nimport { randomUUID } from 'node:crypto';\nimport { policy } from './introspection.js';\nimport type { Logger, Policy, RequestContext, Response } from './types.js';\n\n/**\n * Security event types for audit logging\n */\nexport type SecurityEventType =\n | 'auth_success'\n | 'auth_failure'\n | 'auth_token_refresh'\n | 'auth_token_expired'\n | 'access_denied'\n | 'rate_limit_exceeded'\n | 'validation_failure'\n | 'request_started'\n | 'request_completed'\n | 'request_failed'\n | 'suspicious_activity';\n\n/**\n * Structured audit log entry\n */\nexport interface AuditLogEntry {\n /** ISO 8601 timestamp */\n readonly timestamp: string;\n /** Unique correlation ID for request tracing */\n readonly correlationId: string;\n /** Security event type */\n readonly eventType: SecurityEventType;\n /** Event severity level */\n readonly severity: 'info' | 'warn' | 'error' | 'critical';\n /** HTTP method */\n readonly method: string;\n /** Request URL (sanitized) */\n readonly url: string;\n /** HTTP status code (if available) */\n readonly statusCode?: number;\n /** Request duration in milliseconds */\n readonly durationMs?: number;\n /** Client IP address (if available) */\n readonly clientIp?: string;\n /** User agent (if available) */\n readonly userAgent?: string;\n /** User ID or identifier (if authenticated) */\n readonly userId?: string;\n /** Session ID (if available) */\n readonly sessionId?: string;\n /** Additional context */\n readonly context?: Record<string, unknown>;\n /** Error message (sanitized, no stack traces) */\n readonly errorMessage?: string;\n /** Error code */\n readonly errorCode?: string;\n}\n\n/**\n * Audit logger interface\n */\nexport interface AuditLogger {\n readonly log: (entry: AuditLogEntry) => void | Promise<void>;\n}\n\n/**\n * Audit logging options\n */\nexport interface AuditOptions {\n /** Audit logger implementation */\n readonly logger: AuditLogger;\n /** Function to extract user ID from context */\n readonly getUserId?: (ctx: RequestContext) => string | undefined;\n /** Function to extract session ID from context */\n readonly getSessionId?: (ctx: RequestContext) => string | undefined;\n /** Function to extract client IP from context */\n readonly getClientIp?: (ctx: RequestContext) => string | undefined;\n /** Headers to redact from logs */\n readonly redactHeaders?: ReadonlyArray<string>;\n /** Log successful requests (default: true) */\n readonly logSuccess?: boolean;\n /** Log request bodies (default: false) */\n readonly logBody?: boolean;\n /** Custom correlation ID generator */\n readonly correlationIdGenerator?: () => string;\n /** Suspicious activity detector */\n readonly detectSuspiciousActivity?: (ctx: RequestContext, response?: Response, error?: Error) => boolean;\n}\n\n/**\n * Default headers to redact for security\n */\nconst DEFAULT_REDACT_HEADERS = [\n 'authorization',\n 'cookie',\n 'set-cookie',\n 'x-api-key',\n 'x-auth-token',\n 'x-csrf-token',\n 'x-session-id',\n];\n\n/**\n * Generate a correlation ID using cryptographically secure random\n */\nfunction generateCorrelationId(): string {\n return randomUUID();\n}\n\n/**\n * Sanitize URL by removing sensitive query parameters\n */\nfunction sanitizeUrl(url: string): string {\n try {\n const parsed = new URL(url);\n const sensitiveParams = ['token', 'api_key', 'apikey', 'secret', 'password', 'auth'];\n\n for (const param of sensitiveParams) {\n if (parsed.searchParams.has(param)) {\n parsed.searchParams.set(param, '[REDACTED]');\n }\n }\n\n return parsed.toString();\n } catch {\n return url;\n }\n}\n\n/**\n * Get severity based on status code\n */\nfunction getSeverityFromStatus(status: number): 'info' | 'warn' | 'error' | 'critical' {\n if (status >= 500) return 'error';\n if (status === 401 || status === 403) return 'warn';\n if (status >= 400) return 'warn';\n return 'info';\n}\n\n/**\n * Creates a structured audit logging policy (OWASP A09:2021)\n *\n * Provides comprehensive security event logging with:\n * - Structured JSON format\n * - Correlation IDs for request tracing\n * - Sensitive data redaction\n * - Security event classification\n * - Suspicious activity detection\n *\n * @param options - Audit logging configuration\n * @returns Policy that logs security events\n *\n * @example\n * ```ts\n * const auditLogger: AuditLogger = {\n * log: (entry) => console.log(JSON.stringify(entry)),\n * };\n *\n * const api = client(\n * http('https://api.example.com'),\n * audit({\n * logger: auditLogger,\n * getUserId: (ctx) => ctx.headers['x-user-id'],\n * }),\n * parse.json()\n * );\n * ```\n */\nexport function audit(options: AuditOptions): Policy {\n const {\n logger,\n getUserId,\n getSessionId,\n getClientIp,\n redactHeaders = DEFAULT_REDACT_HEADERS,\n logSuccess = true,\n correlationIdGenerator = generateCorrelationId,\n detectSuspiciousActivity,\n } = options;\n\n return policy(\n async (ctx, next) => {\n const correlationId = correlationIdGenerator();\n const startTime = Date.now();\n const timestamp = new Date().toISOString();\n\n // Extract context information\n const userId = getUserId?.(ctx);\n const sessionId = getSessionId?.(ctx);\n const clientIp = getClientIp?.(ctx);\n const userAgent = ctx.headers['user-agent'];\n\n // Log request started\n await logger.log({\n timestamp,\n correlationId,\n eventType: 'request_started',\n severity: 'info',\n method: ctx.method,\n url: sanitizeUrl(ctx.url),\n userId,\n sessionId,\n clientIp,\n userAgent,\n });\n\n try {\n const response = await next(ctx);\n const durationMs = Date.now() - startTime;\n\n // Determine event type based on response\n let eventType: SecurityEventType = 'request_completed';\n let severity = getSeverityFromStatus(response.status);\n\n if (response.status === 401) {\n eventType = 'auth_failure';\n severity = 'warn';\n } else if (response.status === 403) {\n eventType = 'access_denied';\n severity = 'warn';\n } else if (response.status === 429) {\n eventType = 'rate_limit_exceeded';\n severity = 'warn';\n }\n\n // Check for suspicious activity\n if (detectSuspiciousActivity?.(ctx, response, undefined)) {\n eventType = 'suspicious_activity';\n severity = 'critical';\n }\n\n // Log completion (unless it's a success and logSuccess is false)\n if (logSuccess || response.status >= 400) {\n await logger.log({\n timestamp: new Date().toISOString(),\n correlationId,\n eventType,\n severity,\n method: ctx.method,\n url: sanitizeUrl(ctx.url),\n statusCode: response.status,\n durationMs,\n userId,\n sessionId,\n clientIp,\n userAgent,\n });\n }\n\n return response;\n } catch (error) {\n const durationMs = Date.now() - startTime;\n const err = error instanceof Error ? error : new Error(String(error));\n\n // Check for suspicious activity\n let eventType: SecurityEventType = 'request_failed';\n let severity: 'error' | 'critical' = 'error';\n\n if (detectSuspiciousActivity?.(ctx, undefined, err)) {\n eventType = 'suspicious_activity';\n severity = 'critical';\n }\n\n // Log failure with sanitized error message (no stack trace)\n await logger.log({\n timestamp: new Date().toISOString(),\n correlationId,\n eventType,\n severity,\n method: ctx.method,\n url: sanitizeUrl(ctx.url),\n durationMs,\n userId,\n sessionId,\n clientIp,\n userAgent,\n errorMessage: err.message,\n errorCode: (err as { code?: string }).code,\n });\n\n throw error;\n }\n },\n {\n name: 'audit',\n kind: 'other',\n options: {\n logSuccess,\n redactHeaders,\n },\n },\n );\n}\n\n/**\n * Creates a console-based audit logger for development\n * @returns AuditLogger that logs to console in JSON format\n */\nexport function createConsoleAuditLogger(): AuditLogger {\n return {\n log: (entry) => {\n console.log(JSON.stringify(entry));\n },\n };\n}\n\n/**\n * Creates an audit logger that uses a standard Logger interface\n * @param logger - Standard logger implementation\n * @returns AuditLogger that delegates to the standard logger\n */\nexport function createLoggerAdapter(logger: Logger): AuditLogger {\n return {\n log: (entry) => {\n const message = `[${entry.eventType}] ${entry.method} ${entry.url}`;\n const meta: Record<string, unknown> = { ...entry };\n\n switch (entry.severity) {\n case 'critical':\n case 'error':\n logger.error(message, meta);\n break;\n case 'warn':\n logger.warn(message, meta);\n break;\n default:\n logger.info(message, meta);\n }\n },\n };\n}\n","/**\n * Exponential backoff strategy for retry delays\n * Provides exponential backoff with optional jitter\n * Transport-agnostic - works with any protocol\n */\n\nimport { inspectable } from './introspection.js';\nimport type { RetryDelayStrategy } from './retry.js';\n\n/**\n * Backoff options\n */\nexport interface BackoffOptions {\n /** Initial backoff in milliseconds (default: 1000) */\n readonly initial?: number;\n /** Maximum backoff in milliseconds (default: 30000) */\n readonly max?: number;\n /** Multiplier for exponential backoff (default: 2) */\n readonly multiplier?: number;\n /** Add jitter to backoff (default: true) */\n readonly jitter?: boolean;\n}\n\n/**\n * Calculates backoff delay with exponential backoff and optional jitter\n * @param attempt - Current attempt number (0-indexed)\n * @param baseMs - Base backoff in milliseconds\n * @param maxMs - Maximum backoff in milliseconds\n * @param multiplier - Exponential multiplier\n * @param jitter - Whether to add jitter\n * @returns Delay in milliseconds\n */\nfunction calculateBackoff(attempt: number, baseMs: number, maxMs: number, multiplier: number, jitter: boolean): number {\n const exponential = baseMs * multiplier ** attempt;\n const capped = Math.min(exponential, maxMs);\n\n if (!jitter) {\n return capped;\n }\n\n // Add jitter: random value between 0 and capped\n return Math.floor(Math.random() * capped);\n}\n\n/**\n * Creates an exponential backoff delay strategy\n *\n * @param options - Backoff configuration\n * @returns Delay strategy that calculates exponential backoff\n *\n * @example\n * ```ts\n * import { retry } from '@unireq/core';\n * import { backoff } from '@unireq/core';\n *\n * const policy = retry(\n * myPredicate,\n * [backoff({ initial: 100, max: 5000, jitter: true })],\n * { tries: 3 }\n * );\n * ```\n */\nexport function backoff<T = unknown>(options: BackoffOptions = {}): RetryDelayStrategy<T> {\n const { initial = 1000, max = 30000, multiplier = 2, jitter = true } = options;\n\n const strategy: RetryDelayStrategy<T> = {\n getDelay: (_result: T | null, _error: Error | null, attempt: number) => {\n return calculateBackoff(attempt, initial, max, multiplier, jitter);\n },\n };\n\n return inspectable(strategy, {\n name: 'backoff',\n kind: 'strategy',\n options: { initial, max, multiplier, jitter },\n });\n}\n","/**\n * DX-focused error types for unireq\n */\n\nimport type { Response } from './types.js';\n\n/** Base error class for unireq */\nexport class UnireqError extends Error {\n constructor(\n message: string,\n public readonly code: string,\n public override readonly cause?: unknown,\n ) {\n super(message);\n this.name = 'UnireqError';\n }\n}\n\n/** Error thrown when a network request fails (e.g. DNS, connection refused) */\nexport class NetworkError extends UnireqError {\n constructor(message: string, cause?: unknown) {\n super(message, 'NETWORK_ERROR', cause);\n this.name = 'NetworkError';\n }\n}\n\n/** Error thrown when a request times out */\nexport class TimeoutError extends UnireqError {\n constructor(\n public readonly timeoutMs: number,\n cause?: unknown,\n ) {\n super(`Request timed out after ${timeoutMs}ms`, 'TIMEOUT', cause);\n this.name = 'TimeoutError';\n }\n}\n\n/** Error thrown when an HTTP response has an error status code (4xx, 5xx) */\nexport class HttpError extends UnireqError {\n public readonly status: number;\n public readonly statusText: string;\n public readonly headers: Record<string, string | string[] | undefined>;\n public readonly data: unknown;\n\n constructor(response: Response) {\n super(`HTTP Error ${response.status}: ${response.statusText}`, 'HTTP_ERROR');\n this.name = 'HttpError';\n this.status = response.status;\n this.statusText = response.statusText;\n this.headers = response.headers;\n this.data = response.data;\n }\n}\n\n/** Error thrown when serialization or parsing fails */\nexport class SerializationError extends UnireqError {\n constructor(message: string, cause?: unknown) {\n super(message, 'SERIALIZATION_ERROR', cause);\n this.name = 'SerializationError';\n }\n}\n\n/** Error thrown when duplicate policies are detected */\nexport class DuplicatePolicyError extends UnireqError {\n constructor(public readonly policyName: string) {\n super(\n `Duplicate policy detected: ${policyName}. Each policy can only be registered once in the chain.`,\n 'DUPLICATE_POLICY',\n );\n this.name = 'DuplicatePolicyError';\n }\n}\n\n/** Error thrown when authentication is not supported by transport */\nexport class UnsupportedAuthForTransport extends UnireqError {\n constructor(\n public readonly authType: string,\n public readonly transportType: string,\n ) {\n super(`Authentication type \"${authType}\" is not supported by transport \"${transportType}\".`, 'UNSUPPORTED_AUTH');\n this.name = 'UnsupportedAuthForTransport';\n }\n}\n\n/** HTTP 406 Not Acceptable error */\nexport class NotAcceptableError extends UnireqError {\n constructor(\n public readonly acceptedTypes: ReadonlyArray<string>,\n public readonly receivedType?: string,\n ) {\n const received = receivedType ? ` (received: ${receivedType})` : '';\n super(\n `Server cannot produce a response matching the Accept header. Accepted types: ${acceptedTypes.join(', ')}${received}`,\n 'NOT_ACCEPTABLE',\n );\n this.name = 'NotAcceptableError';\n }\n}\n\n/** HTTP 415 Unsupported Media Type error */\nexport class UnsupportedMediaTypeError extends UnireqError {\n constructor(\n public readonly supportedTypes: ReadonlyArray<string>,\n public readonly sentType?: string,\n ) {\n const sent = sentType ? ` (sent: ${sentType})` : '';\n super(\n `Server cannot process the request payload media type. Supported types: ${supportedTypes.join(', ')}${sent}`,\n 'UNSUPPORTED_MEDIA_TYPE',\n );\n this.name = 'UnsupportedMediaTypeError';\n }\n}\n\n/** Error thrown when required capability is missing */\nexport class MissingCapabilityError extends UnireqError {\n constructor(\n public readonly capability: string,\n public readonly transportType: string,\n ) {\n super(`Transport \"${transportType}\" does not support required capability: ${capability}`, 'MISSING_CAPABILITY');\n this.name = 'MissingCapabilityError';\n }\n}\n\n/** Error thrown for invalid slot configuration */\nexport class InvalidSlotError extends UnireqError {\n constructor(\n public readonly slotType: string,\n message: string,\n ) {\n super(`Invalid slot configuration for ${slotType}: ${message}`, 'INVALID_SLOT');\n this.name = 'InvalidSlotError';\n }\n}\n\n/** Error thrown when URL normalization fails */\nexport class URLNormalizationError extends UnireqError {\n constructor(\n public readonly url: string,\n public readonly reason: string,\n ) {\n super(`Failed to normalize URL \"${url}\": ${reason}`, 'URL_NORMALIZATION_FAILED');\n this.name = 'URLNormalizationError';\n }\n}\n","/**\n * Circuit Breaker policy\n * Prevents cascading failures by stopping requests to a failing service\n */\n\nimport { UnireqError } from './errors.js';\nimport { policy } from './introspection.js';\nimport type { Policy } from './types.js';\n\nexport interface CircuitBreakerOptions {\n /** Number of failures before opening the circuit (default: 5) */\n readonly failureThreshold?: number;\n /** Time in ms to wait before trying again (default: 30000) */\n readonly resetTimeout?: number;\n /** Optional predicate to determine if an error should trigger failure */\n readonly shouldFail?: (error: unknown) => boolean;\n}\n\nenum CircuitState {\n CLOSED = 'CLOSED',\n OPEN = 'OPEN',\n HALF_OPEN = 'HALF_OPEN',\n}\n\n/** Error thrown when circuit is open */\nexport class CircuitBreakerOpenError extends UnireqError {\n constructor(public readonly resetTime: number) {\n super('Circuit breaker is OPEN', 'CIRCUIT_BREAKER_OPEN');\n this.name = 'CircuitBreakerOpenError';\n }\n}\n\nexport function circuitBreaker(options: CircuitBreakerOptions = {}): Policy {\n const { failureThreshold = 5, resetTimeout = 30000, shouldFail = () => true } = options;\n\n let state = CircuitState.CLOSED;\n let failureCount = 0;\n let nextAttempt = 0;\n\n return policy(\n async (ctx, next) => {\n const now = Date.now();\n\n if (state === CircuitState.OPEN) {\n if (now >= nextAttempt) {\n state = CircuitState.HALF_OPEN;\n } else {\n throw new CircuitBreakerOpenError(nextAttempt);\n }\n }\n\n try {\n const response = await next(ctx);\n\n if (state === CircuitState.HALF_OPEN) {\n /* v8 ignore next 2 */\n state = CircuitState.CLOSED;\n failureCount = 0;\n } else {\n // Must be CLOSED\n failureCount = 0; // Reset on success\n }\n\n return response;\n } catch (error) {\n if (shouldFail(error)) {\n if (state === CircuitState.HALF_OPEN) {\n /* v8 ignore next 2 */\n state = CircuitState.OPEN;\n nextAttempt = Date.now() + resetTimeout;\n } else {\n // Must be CLOSED\n failureCount++;\n if (failureCount >= failureThreshold) {\n state = CircuitState.OPEN;\n nextAttempt = Date.now() + resetTimeout;\n }\n }\n }\n throw error;\n }\n },\n {\n name: 'circuitBreaker',\n kind: 'other',\n options: { failureThreshold, resetTimeout, state },\n },\n );\n}\n","/**\n * Policy composition utilities\n */\n\nimport type { InspectableMeta } from './introspection.js';\nimport { getInspectableMeta, policy as tagPolicy } from './introspection.js';\nimport type { Policy, RequestContext, Response } from './types.js';\n\n/**\n * Composes multiple policies into a single policy chain (onion model)\n * @param policies - Array of policies to compose\n * @returns A single composed policy\n */\nexport function compose(...policies: ReadonlyArray<Policy>): Policy {\n if (policies.length === 0) {\n return async (_ctx, next) => next(_ctx);\n }\n\n if (policies.length === 1) {\n const singlePolicy = policies[0];\n if (!singlePolicy) {\n return async (_ctx, next) => next(_ctx);\n }\n return singlePolicy;\n }\n\n const impl = async (ctx: RequestContext, next: (ctx: RequestContext) => Promise<Response>) => {\n const dispatch = async (i: number, context: RequestContext): Promise<Response> => {\n const policy = policies[i];\n if (!policy) {\n return next(context);\n }\n\n return policy(context, (nextCtx) => dispatch(i + 1, nextCtx));\n };\n\n return dispatch(0, ctx);\n };\n\n // Build children metadata from input policies (filter out undefined)\n const children: InspectableMeta[] = policies\n .filter((p) => p !== undefined)\n .map((p) => getInspectableMeta(p))\n .filter((m): m is InspectableMeta => m !== undefined);\n\n return tagPolicy(impl, {\n name: 'compose',\n kind: 'other',\n children,\n });\n}\n","/**\n * Serialization middleware for BodyDescriptor handling\n */\n\nimport type { BodyDescriptor, Policy } from './types.js';\n\n/**\n * Type guard to check if a value is a BodyDescriptor\n * @param value - Value to check\n * @returns True if value is a BodyDescriptor\n */\nexport function isBodyDescriptor(value: unknown): value is BodyDescriptor {\n return (\n typeof value === 'object' &&\n value !== null &&\n '__brand' in value &&\n (value as BodyDescriptor).__brand === 'BodyDescriptor' &&\n 'serialize' in value &&\n typeof (value as BodyDescriptor).serialize === 'function'\n );\n}\n\n/**\n * Serialization policy that converts BodyDescriptor to serialized form\n * This middleware should be applied first in the policy chain to handle\n * body descriptors before other policies process the request\n *\n * @returns Policy that handles BodyDescriptor serialization\n */\nexport function serializationPolicy(): Policy {\n return async (ctx, next) => {\n // Check if body is a BodyDescriptor\n if (isBodyDescriptor(ctx.body)) {\n const descriptor = ctx.body;\n\n // Serialize the body\n const serialized = descriptor.serialize();\n\n // Create updated context with serialized body and Content-Type header\n // IMPORTANT: For FormData, we must NOT set Content-Type manually\n // because fetch needs to set it with the correct boundary\n const shouldSetContentType =\n descriptor.contentType &&\n !ctx.headers['content-type'] &&\n !ctx.headers['Content-Type'] &&\n !(serialized instanceof FormData); // Let fetch handle FormData Content-Type\n\n const updatedCtx = {\n ...ctx,\n body: serialized,\n headers: {\n ...ctx.headers,\n ...(shouldSetContentType ? { 'content-type': descriptor.contentType } : {}),\n },\n };\n\n return next(updatedCtx);\n }\n\n // Not a BodyDescriptor, pass through unchanged\n return next(ctx);\n };\n}\n","/**\n * Slot system for transport, auth, and parser policies\n * Provides compile-time and runtime checks for capability requirements\n */\n\nimport { DuplicatePolicyError, InvalidSlotError, MissingCapabilityError } from './errors.js';\nimport type { Policy, SlotMetadata, SlotType, TransportCapabilities } from './types.js';\n\n/** Registry for slot metadata */\nconst slotRegistry = new WeakMap<Policy, SlotMetadata>();\n\n/**\n * Registers slot metadata for a policy\n * @param policy - The policy to register\n * @param metadata - Slot metadata\n */\nexport function registerSlot(policy: Policy, metadata: SlotMetadata): void {\n slotRegistry.set(policy, metadata);\n}\n\n/**\n * Gets slot metadata for a policy\n * @param policy - The policy to query\n * @returns Slot metadata or undefined\n */\nexport function getSlotMetadata(policy: Policy): SlotMetadata | undefined {\n return slotRegistry.get(policy);\n}\n\n/**\n * Validates a policy chain for slot conflicts and capability requirements\n * @param policies - Array of policies to validate\n * @param transportCapabilities - Transport capabilities\n * @throws {DuplicatePolicyError} If duplicate slots detected\n * @throws {MissingCapabilityError} If required capabilities missing\n * @throws {InvalidSlotError} If slot configuration is invalid\n */\nexport function validatePolicyChain(\n policies: ReadonlyArray<Policy>,\n transportCapabilities?: TransportCapabilities,\n): void {\n const seenSlots = new Map<string, SlotMetadata>();\n\n for (const policy of policies) {\n const metadata = getSlotMetadata(policy);\n if (!metadata) continue;\n\n // Check for duplicate slots of the same type\n const slotKey = `${metadata.type}:${metadata.name}`;\n const existing = seenSlots.get(slotKey);\n if (existing) {\n throw new DuplicatePolicyError(metadata.name);\n }\n seenSlots.set(slotKey, metadata);\n\n // Validate required capabilities\n if (transportCapabilities && metadata.requiredCapabilities) {\n for (const capability of metadata.requiredCapabilities) {\n if (!transportCapabilities[capability]) {\n throw new MissingCapabilityError(capability, 'current');\n }\n }\n }\n }\n\n // Validate slot ordering constraints\n validateSlotOrdering(Array.from(seenSlots.values()));\n}\n\n/**\n * Validates slot ordering (e.g., auth before parser)\n * @param slots - Array of slot metadata\n * @throws {InvalidSlotError} If ordering is invalid\n */\nfunction validateSlotOrdering(slots: ReadonlyArray<SlotMetadata>): void {\n let transportIndex = -1;\n let authIndex = -1;\n let parserIndex = -1;\n\n slots.forEach((slot, i) => {\n switch (slot.type) {\n case 'transport':\n transportIndex = i;\n break;\n case 'auth':\n authIndex = i;\n break;\n case 'parser':\n parserIndex = i;\n break;\n }\n });\n\n // Transport should be last (if present)\n if (transportIndex !== -1 && transportIndex !== slots.length - 1) {\n throw new InvalidSlotError('transport', 'Transport slot must be the last policy in the chain');\n }\n\n // Auth should come before parser (if both present)\n if (authIndex !== -1 && parserIndex !== -1 && authIndex > parserIndex) {\n throw new InvalidSlotError('auth', 'Auth slot must come before parser slot in the chain');\n }\n}\n\n/**\n * Creates a slot decorator for policies\n * @param metadata - Slot metadata\n * @returns Decorator function\n */\nexport function slot(metadata: SlotMetadata) {\n return (policy: Policy): Policy => {\n registerSlot(policy, metadata);\n return policy;\n };\n}\n\n/**\n * Checks if a policy has a specific slot type\n * @param policy - The policy to check\n * @param type - The slot type to match\n * @returns True if policy has the slot type\n */\nexport function hasSlotType(policy: Policy, type: SlotType): boolean {\n const metadata = getSlotMetadata(policy);\n return metadata?.type === type;\n}\n","/**\n * URL normalization and header utilities\n */\n\nimport { URLNormalizationError } from './errors.js';\nimport type { ClientOptions } from './types.js';\n\n/**\n * Normalizes a URL with base and default scheme support\n * @param url - The URL to normalize (absolute or relative)\n * @param options - Client options containing base and defaultScheme\n * @returns Normalized absolute URL\n */\nexport function normalizeURL(url: string, options: ClientOptions = {}): string {\n const { base, defaultScheme = 'https' } = options;\n\n try {\n // If URL is already absolute, use it directly\n if (url.includes('://')) {\n return new URL(url).toString();\n }\n\n // If URL starts with //, treat as protocol-relative\n if (url.startsWith('//')) {\n return new URL(`${defaultScheme}:${url}`).toString();\n }\n\n // If base is provided, resolve relative to base\n if (base) {\n const baseURL = base.includes('://') ? base : `${defaultScheme}://${base}`;\n return new URL(url, baseURL).toString();\n }\n\n // If no base provided and URL is relative → Error\n // Check if URL is relative (starts with / or doesn't have scheme)\n const isRelative = !url.match(/^[a-z][a-z0-9+.-]*:/i);\n\n if (isRelative) {\n throw new URLNormalizationError(\n url,\n 'Relative URL requires URI in transport. Use http(\"https://api.com\") or provide absolute URL.',\n );\n }\n\n return new URL(url).toString();\n } catch (error) {\n if (error instanceof URLNormalizationError) {\n throw error;\n }\n const message = error instanceof Error ? error.message : String(error);\n throw new URLNormalizationError(url, message);\n }\n}\n\n/**\n * Appends query parameters to a URL\n * @param url - The base URL\n * @param params - Query parameters to append\n * @returns URL with query parameters\n */\nexport function appendQueryParams(url: string, params: Record<string, string | number | boolean | undefined>): string {\n const urlObj = new URL(url);\n for (const [key, value] of Object.entries(params)) {\n if (value !== undefined) {\n urlObj.searchParams.append(key, String(value));\n }\n }\n return urlObj.toString();\n}\n\n/**\n * Normalizes HTTP headers to lowercase (per HTTP/2 spec)\n * HTTP/2 requires all header names to be lowercase\n * HTTP/1.1 is case-insensitive, so lowercase is safe everywhere\n *\n * @param headers - Headers object with potentially mixed case keys\n * @returns Headers object with all lowercase keys\n *\n * @example\n * ```ts\n * normalizeHeaders({ 'Content-Type': 'application/json', 'Accept': 'text/html' })\n * // Returns: { 'content-type': 'application/json', 'accept': 'text/html' }\n * ```\n */\nexport function normalizeHeaders(headers: Record<string, string>): Record<string, string> {\n const normalized: Record<string, string> = {};\n for (const [key, value] of Object.entries(headers)) {\n normalized[key.toLowerCase()] = value;\n }\n return normalized;\n}\n\n/**\n * Gets a header value with case-insensitive lookup\n * Useful for reading headers before normalization\n *\n * @param headers - Headers object\n * @param name - Header name (case-insensitive)\n * @returns Header value or undefined\n *\n * @example\n * ```ts\n * getHeader({ 'Content-Type': 'application/json' }, 'content-type') // 'application/json'\n * getHeader({ 'content-type': 'application/json' }, 'Content-Type') // 'application/json'\n * ```\n */\nexport function getHeader(headers: Record<string, string>, name: string): string | undefined {\n const lowerName = name.toLowerCase();\n // Try lowercase first (most common)\n if (lowerName in headers) {\n return headers[lowerName];\n }\n // Fallback: case-insensitive search\n for (const [key, value] of Object.entries(headers)) {\n if (key.toLowerCase() === lowerName) {\n return value;\n }\n }\n return undefined;\n}\n\n/**\n * Sets a header value, removing any existing variants with different casing\n * Prevents duplicate headers with different casing (e.g., 'accept' and 'Accept')\n *\n * @param headers - Headers object to modify\n * @param name - Header name (will be normalized to lowercase)\n * @param value - Header value\n * @returns New headers object with the header set (immutable)\n *\n * @example\n * ```ts\n * setHeader({ Accept: 'text/html' }, 'accept', 'application/json')\n * // Returns: { accept: 'application/json' } (removed 'Accept')\n * ```\n */\nexport function setHeader(headers: Record<string, string>, name: string, value: string): Record<string, string> {\n const lowerName = name.toLowerCase();\n const newHeaders: Record<string, string> = {};\n\n // Copy all headers except variants of the target header\n for (const [key, val] of Object.entries(headers)) {\n if (key.toLowerCase() !== lowerName) {\n newHeaders[key] = val;\n }\n }\n\n // Set the new header value with normalized lowercase name\n newHeaders[lowerName] = value;\n\n return newHeaders;\n}\n","/**\n * Client factory and request execution\n */\n\nimport { compose } from './compose.js';\nimport { UnireqError } from './errors.js';\nimport { serializationPolicy } from './serialization.js';\nimport { validatePolicyChain } from './slots.js';\nimport type { Client, Policy, RequestContext, Response, Transport, TransportWithCapabilities } from './types.js';\nimport { normalizeURL } from './url.js';\n\n/**\n * Creates a client with the given transport and policies\n * @param transport - The transport function or transport with capabilities\n * @param policies - Variadic list of policies to apply\n * @returns A configured client instance\n */\nexport function client(transport: Transport | TransportWithCapabilities, ...policies: ReadonlyArray<Policy>): Client {\n // Extract transport and capabilities\n const actualTransport = typeof transport === 'function' ? transport : transport.transport;\n const capabilities = typeof transport === 'function' ? undefined : transport.capabilities;\n\n // Validate policy chain\n validatePolicyChain(policies, capabilities);\n\n // Compose all policies with serialization middleware first\n const composedPolicy = compose(serializationPolicy(), ...policies);\n\n // Base request executor with per-request policies\n const executeRequest = async <T = unknown>(\n url: string,\n method: string,\n body: unknown | undefined,\n perRequestPolicies: ReadonlyArray<Policy>,\n ): Promise<Response<T>> => {\n // Validate URL input\n if (!url || typeof url !== 'string' || url.trim().length === 0) {\n throw new UnireqError('URL must be a non-empty string', 'INVALID_URL');\n }\n\n // Pass URL as-is to transport - let transport handle URI resolution\n // Only normalize absolute URLs to ensure consistent format\n const normalizedURL = url.includes('://') ? normalizeURL(url, {}) : url;\n\n const ctx: RequestContext = {\n url: normalizedURL,\n method,\n headers: {},\n body,\n };\n\n // Compose global policies with per-request policies\n if (perRequestPolicies.length > 0) {\n const perRequestPolicy = compose(...perRequestPolicies);\n const combinedPolicy = compose(composedPolicy, perRequestPolicy);\n return combinedPolicy(ctx, actualTransport) as Promise<Response<T>>;\n }\n\n // Execute through global policy chain only\n return composedPolicy(ctx, actualTransport) as Promise<Response<T>>;\n };\n\n // Return client interface with variadic policies\n return {\n request: <T = unknown>(url: string, ...perRequestPolicies: ReadonlyArray<Policy>) =>\n executeRequest<T>(url, 'GET', undefined, perRequestPolicies),\n\n get: <T = unknown>(url: string, ...perRequestPolicies: ReadonlyArray<Policy>) =>\n executeRequest<T>(url, 'GET', undefined, perRequestPolicies),\n\n head: <T = unknown>(url: string, ...perRequestPolicies: ReadonlyArray<Policy>) =>\n executeRequest<T>(url, 'HEAD', undefined, perRequestPolicies),\n\n post: <T = unknown>(url: string, body?: unknown, ...perRequestPolicies: ReadonlyArray<Policy>) =>\n executeRequest<T>(url, 'POST', body, perRequestPolicies),\n\n put: <T = unknown>(url: string, body?: unknown, ...perRequestPolicies: ReadonlyArray<Policy>) =>\n executeRequest<T>(url, 'PUT', body, perRequestPolicies),\n\n delete: <T = unknown>(url: string, ...perRequestPolicies: ReadonlyArray<Policy>) =>\n executeRequest<T>(url, 'DELETE', undefined, perRequestPolicies),\n\n patch: <T = unknown>(url: string, body?: unknown, ...perRequestPolicies: ReadonlyArray<Policy>) =>\n executeRequest<T>(url, 'PATCH', body, perRequestPolicies),\n\n options: <T = unknown>(url: string, ...perRequestPolicies: ReadonlyArray<Policy>) =>\n executeRequest<T>(url, 'OPTIONS', undefined, perRequestPolicies),\n };\n}\n","/**\n * Conditional branching utilities\n */\n\nimport { getInspectableMeta, policy as tagPolicy } from './introspection.js';\nimport type { Policy, Predicate, RequestContext, Response } from './types.js';\n\n/**\n * Creates a policy that executes different policies based on a predicate\n * Useful for conditional execution (e.g., content negotiation)\n *\n * @param predicate - Function that evaluates the condition\n * @param thenPolicy - Policy to execute if predicate is truthy\n * @param elsePolicy - Optional policy to execute if predicate is falsy\n * @returns A policy that branches based on the predicate\n *\n * @example\n * ```ts\n * const contentNegotiation = either(\n * (ctx) => ctx.headers['accept']?.includes('application/json'),\n * json(),\n * xml()\n * );\n * ```\n */\nexport function either<T = unknown>(predicate: Predicate<T>, thenPolicy: Policy, elsePolicy?: Policy): Policy {\n const impl = async (ctx: RequestContext, next: (ctx: RequestContext) => Promise<Response>) => {\n const result = await Promise.resolve(predicate(ctx));\n\n if (result) {\n return thenPolicy(ctx, next);\n }\n\n if (elsePolicy) {\n return elsePolicy(ctx, next);\n }\n\n // If no else branch and predicate is false, pass through\n return next(ctx);\n };\n\n // Build branch metadata\n const predicateDesc = predicate.name || 'anonymous predicate';\n const thenMetaRaw = getInspectableMeta(thenPolicy);\n const thenMeta = thenMetaRaw ? [thenMetaRaw] : [];\n const elseMetaRaw = elsePolicy ? getInspectableMeta(elsePolicy) : undefined;\n const elseMeta = elseMetaRaw ? [elseMetaRaw] : [];\n\n return tagPolicy(impl, {\n name: 'either',\n kind: 'other',\n branch: {\n predicate: predicateDesc,\n thenBranch: thenMeta,\n elseBranch: elseMeta,\n },\n });\n}\n\n/**\n * Creates a policy that matches against multiple predicates\n * Executes the first matching policy\n *\n * @param branches - Array of predicate-policy pairs\n * @param defaultPolicy - Optional default policy if no predicates match\n * @returns A policy that matches multiple conditions\n *\n * @example\n * ```ts\n * const parser = match(\n * [\n * [(ctx) => ctx.headers['content-type']?.includes('json'), json()],\n * [(ctx) => ctx.headers['content-type']?.includes('xml'), xml()],\n * ],\n * text() // default\n * );\n * ```\n */\nexport function match<T = unknown>(\n branches: ReadonlyArray<readonly [Predicate<T>, Policy]>,\n defaultPolicy?: Policy,\n): Policy {\n const impl = async (ctx: RequestContext, next: (ctx: RequestContext) => Promise<Response>) => {\n for (const [predicate, policy] of branches) {\n const result = await Promise.resolve(predicate(ctx));\n if (result) {\n return policy(ctx, next);\n }\n }\n\n if (defaultPolicy) {\n return defaultPolicy(ctx, next);\n }\n\n return next(ctx);\n };\n\n // Build children from all branches (match is a composition of either)\n const children = branches.map(([predicate, policy], idx) => {\n const predicateDesc = predicate.name || `branch ${idx + 1}`;\n const meta = getInspectableMeta(policy);\n return {\n id: `match-branch-${idx}`,\n name: predicateDesc,\n kind: 'other' as const,\n children: meta ? [meta] : [],\n };\n });\n\n if (defaultPolicy) {\n const defaultMeta = getInspectableMeta(defaultPolicy);\n children.push({\n id: 'match-default',\n name: 'default',\n kind: 'other' as const,\n children: defaultMeta ? [defaultMeta] : [],\n });\n }\n\n return tagPolicy(impl, {\n name: 'match',\n kind: 'other',\n children,\n });\n}\n","/**\n * Policy inspection and formatting utilities\n */\n\nimport type { Handler, InspectableMeta, Kind } from './introspection.js';\nimport { getHandlerGraph, getInspectableMeta } from './introspection.js';\nimport type { Policy } from './types.js';\n\n/**\n * Inspection output format\n */\nexport type InspectFormat = 'json' | 'tree';\n\n/**\n * Inspection options\n */\nexport interface InspectOptions {\n /** Output format (default: 'json') */\n readonly format?: InspectFormat;\n /** Whether to redact sensitive values (default: true) */\n readonly redact?: boolean;\n}\n\n/**\n * Inspects a policy or handler and returns its composition graph\n * @param target - Policy or handler to inspect\n * @param options - Inspection options\n * @returns Formatted policy graph\n *\n * @example\n * ```ts\n * const pipeline = compose(\n * retry(backoff(), { tries: 3 }),\n * json()\n * );\n *\n * // JSON format\n * console.log(inspect(pipeline));\n *\n * // Tree format\n * console.log(inspect(pipeline, { format: 'tree' }));\n * ```\n */\nexport function inspect(target: Handler | Policy, options: InspectOptions = {}): string {\n const { format = 'json' } = options;\n\n // Try to get graph from handler first\n let graph = getHandlerGraph(target as Handler);\n\n // If no graph, try to get metadata from policy\n if (graph.length === 0) {\n const meta = getInspectableMeta(target as Policy);\n if (meta && meta.name !== 'anonymous') {\n graph = [meta];\n }\n }\n\n if (graph.length === 0) {\n return format === 'tree' ? '(empty policy chain)' : '[]';\n }\n\n return format === 'tree' ? toTree(graph) : toJson(graph);\n}\n\n/**\n * Shortcut for JSON format inspection\n */\ninspect.json = (target: Handler | Policy): string => inspect(target, { format: 'json' });\n\n/**\n * Shortcut for tree format inspection\n */\ninspect.tree = (target: Handler | Policy): string => inspect(target, { format: 'tree' });\n\n/**\n * Converts policy graph to JSON string\n */\nfunction toJson(nodes: ReadonlyArray<InspectableMeta>): string {\n return JSON.stringify(nodes, null, 2);\n}\n\n/**\n * Converts policy graph to tree using box-drawing characters\n */\nfunction toTree(nodes: ReadonlyArray<InspectableMeta>): string {\n const lines: string[] = [];\n\n const walk = (meta: InspectableMeta, depth: number, isLast: boolean, parentPrefix = ''): void => {\n // Build indentation based on depth\n let prefix = '';\n if (depth > 0) {\n // For nested items, add proper tree connectors\n prefix = isLast ? '└─ ' : '├─ ';\n }\n\n const optionsStr = formatOptions(meta.options);\n lines.push(`${parentPrefix}${prefix}${meta.name} (${meta.kind})${optionsStr}`);\n\n // Handle children (compose)\n if (meta.children && meta.children.length > 0) {\n const childPrefix = parentPrefix + (depth > 0 ? (isLast ? ' ' : '│ ') : '');\n const childrenLength = meta.children.length;\n meta.children.forEach((child, idx) => {\n walk(child, depth + 1, idx === childrenLength - 1, childPrefix);\n });\n }\n\n // Handle branch (either/match)\n if (meta.branch) {\n const branchPrefix = parentPrefix + (depth > 0 ? (isLast ? ' ' : '│ ') : '');\n lines.push(`${branchPrefix} ? ${meta.branch.predicate}`);\n\n if (meta.branch.thenBranch.length > 0) {\n lines.push(`${branchPrefix} ├─ then:`);\n const thenPrefix = `${branchPrefix} │ `;\n const thenLength = meta.branch.thenBranch.length;\n meta.branch.thenBranch.forEach((child, idx) => {\n walk(child, depth + 2, idx === thenLength - 1, thenPrefix);\n });\n }\n\n if (meta.branch.elseBranch.length > 0) {\n lines.push(`${branchPrefix} └─ else:`);\n const elsePrefix = `${branchPrefix} `;\n const elseLength = meta.branch.elseBranch.length;\n meta.branch.elseBranch.forEach((child, idx) => {\n walk(child, depth + 2, idx === elseLength - 1, elsePrefix);\n });\n }\n }\n };\n\n nodes.forEach((node, idx) => {\n walk(node, 0, idx === nodes.length - 1, '');\n });\n\n return lines.join('\\n');\n}\n\n/**\n * Formats options for tree display\n */\nfunction formatOptions(opts?: Record<string, unknown>): string {\n if (!opts || Object.keys(opts).length === 0) {\n return '';\n }\n\n const entries = Object.entries(opts)\n .map(([key, value]) => `${key}=${prettyValue(value)}`)\n .join(', ');\n\n return ` [${entries}]`;\n}\n\n/**\n * Pretty-prints a value for tree display\n */\nfunction prettyValue(value: unknown): string {\n if (value === null || value === undefined) {\n return 'null';\n }\n\n if (typeof value === 'string') {\n return `\"${value}\"`;\n }\n\n if (typeof value === 'boolean' || typeof value === 'number') {\n return String(value);\n }\n\n if (Array.isArray(value)) {\n if (value.length === 0) {\n return '[]';\n }\n if (value.length <= 3) {\n return `[${value.map(prettyValue).join(', ')}]`;\n }\n return `[${value.slice(0, 2).map(prettyValue).join(', ')}, ... +${value.length - 2}]`;\n }\n\n if (typeof value === 'object') {\n const keys = Object.keys(value);\n if (keys.length === 0) {\n return '{}';\n }\n const keyList = keys.slice(0, 2).join(', ');\n if (keys.length > 2) {\n return `{${keyList}, ...}`;\n }\n /* v8 ignore next 2 */\n return `{${keyList}}`;\n }\n\n // Unreachable in practice since JSON.parse() can't produce non-JSON types\n /* v8 ignore next 2 */\n return String(value);\n}\n\n/**\n * Asserts that a policy or handler has a specific policy kind\n * Useful for testing presets\n *\n * @param target - Policy or handler to check\n * @param kind - Expected policy kind\n * @throws Error if kind is not found\n *\n * @example\n * ```ts\n * const pipeline = compose(auth(), json());\n * assertHas(pipeline, 'auth'); // throws if no auth policy\n * assertHas(pipeline, 'parser'); // throws if no parser policy\n * ```\n */\nexport function assertHas(target: Handler | Policy, kind: Kind): void {\n // Try to get graph from handler first\n let graph = getHandlerGraph(target as Handler);\n\n // If no graph, try to get metadata from policy\n if (graph.length === 0) {\n const meta = getInspectableMeta(target as Policy);\n if (meta && meta.name !== 'anonymous') {\n graph = [meta];\n }\n }\n\n const hasKind = (nodes: ReadonlyArray<InspectableMeta>): boolean => {\n for (const node of nodes) {\n if (node.kind === kind) {\n return true;\n }\n if (node.children && hasKind(node.children)) {\n return true;\n }\n if (node.branch) {\n if (hasKind(node.branch.thenBranch) || hasKind(node.branch.elseBranch)) {\n return true;\n }\n }\n }\n return false;\n };\n\n if (!hasKind(graph)) {\n throw new Error(`Expected policy kind \"${kind}\" not found in handler graph`);\n }\n}\n","/**\n * Logging policy\n */\n\nimport { randomUUID } from 'node:crypto';\nimport { policy } from './introspection.js';\nimport type { Logger, Policy } from './types.js';\n\nexport interface LogOptions {\n readonly logger: Logger;\n readonly redactHeaders?: ReadonlyArray<string>;\n readonly logBody?: boolean;\n}\n\nconst DEFAULT_REDACT_HEADERS = ['authorization', 'cookie', 'set-cookie', 'x-api-key'];\n\nexport function log(options: LogOptions): Policy {\n const { logger, redactHeaders = DEFAULT_REDACT_HEADERS, logBody = false } = options;\n\n const redact = (headers: Record<string, string>): Record<string, string> => {\n const redacted = { ...headers };\n for (const header of redactHeaders) {\n const key = Object.keys(redacted).find((k) => k.toLowerCase() === header.toLowerCase());\n if (key) {\n redacted[key] = '[REDACTED]';\n }\n }\n return redacted;\n };\n\n return policy(\n async (ctx, next) => {\n const start = Date.now();\n const requestId = randomUUID();\n\n logger.info(`Request ${requestId} started`, {\n requestId,\n method: ctx.method,\n url: ctx.url,\n headers: redact(ctx.headers),\n body: logBody ? ctx.body : undefined,\n });\n\n try {\n const response = await next(ctx);\n const duration = Date.now() - start;\n\n logger.info(`Request ${requestId} completed`, {\n requestId,\n status: response.status,\n duration,\n headers: redact(response.headers),\n data: logBody ? response.data : undefined,\n });\n\n return response;\n } catch (error) {\n const duration = Date.now() - start;\n logger.error(`Request ${requestId} failed`, {\n requestId,\n duration,\n error,\n });\n throw error;\n }\n },\n {\n name: 'log',\n kind: 'other',\n options: { redactHeaders, logBody },\n },\n );\n}\n","/**\n * Generic transport-agnostic retry flow control primitive\n */\n\nimport type { InspectableMeta } from './introspection.js';\nimport { getInspectableMeta, policy } from './introspection.js';\nimport type { Policy, RequestContext, Response } from './types.js';\n\n/**\n * Retry predicate function that determines if a retry should occur\n * Returns true to retry, false to stop retrying\n */\nexport type RetryPredicate<T = Response> = (\n result: T | null,\n error: Error | null,\n attempt: number,\n context: RequestContext,\n) => boolean | Promise<boolean>;\n\n/**\n * Retry delay strategy that calculates the delay before next retry\n * Returns delay in milliseconds, or undefined to skip delay calculation\n */\nexport type RetryDelayStrategy<T = Response> = {\n readonly getDelay: (\n result: T | null,\n error: Error | null,\n attempt: number,\n ) => number | undefined | Promise<number | undefined>;\n};\n\n/**\n * Generic retry options\n */\nexport interface RetryOptions<T = Response> {\n /** Number of retry attempts (default: 3) */\n readonly tries?: number;\n /** Callback before retry */\n readonly onRetry?: (attempt: number, error: Error | null, result: T | null) => void | Promise<void>;\n}\n\n/**\n * Calculate delay using strategies in order (first match wins)\n * @internal\n */\nasync function calculateDelay<T>(\n strategies: ReadonlyArray<RetryDelayStrategy<T>>,\n result: T | null,\n error: Error | null,\n attempt: number,\n): Promise<number> {\n for (const strategy of strategies) {\n const strategyDelay = await strategy.getDelay(result, error, attempt);\n if (strategyDelay !== undefined) {\n return strategyDelay;\n }\n }\n return 0;\n}\n\n/**\n * Creates a generic transport-agnostic retry policy\n *\n * This is a pure flow control primitive that works with any protocol (HTTP, FTP, IMAP, etc.).\n * It executes a predicate to determine if retry should occur, applies delay strategies,\n * and invokes callbacks.\n *\n * @param predicate - Function to determine if retry should occur\n * @param strategies - Delay strategies to calculate wait time before retry\n * @param options - Retry options\n * @returns Policy that retries based on predicate\n *\n * @example\n * ```ts\n * // HTTP-specific retry with status codes\n * const httpRetry = retry(\n * (result, error) => error !== null || (result?.status >= 500 && result?.status < 600),\n * [backoff({ initial: 100, max: 5000 })],\n * { tries: 3 }\n * );\n * ```\n *\n * @example\n * ```ts\n * // Generic retry for any operation\n * const genericRetry = retry(\n * (result, error) => error !== null,\n * [exponentialBackoff()],\n * { tries: 5 }\n * );\n * ```\n */\nexport function retry<T = Response>(\n predicate: RetryPredicate<T>,\n strategies: ReadonlyArray<RetryDelayStrategy<T>>,\n options?: RetryOptions<T>,\n): Policy {\n const { tries = 3, onRetry } = options ?? {};\n\n return policy(\n async (ctx, next) => {\n let lastResponse: Response | undefined;\n let lastError: Error | null = null;\n\n for (let attempt = 0; attempt < tries; attempt++) {\n try {\n const response = await next(ctx);\n lastResponse = response;\n\n // Check if we should retry using predicate\n const shouldRetry = await Promise.resolve(predicate(response as T, null, attempt, ctx));\n\n // If predicate says no retry, or last attempt, return response\n if (!shouldRetry || attempt === tries - 1) {\n return response;\n }\n\n // Call onRetry callback\n if (onRetry) {\n await Promise.resolve(onRetry(attempt + 1, null, response as T));\n }\n\n // Calculate and apply delay\n const delay = await calculateDelay(strategies, response as T, null, attempt);\n if (delay > 0) {\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n\n // Last attempt - throw error\n if (attempt === tries - 1) {\n throw lastError;\n }\n\n // Check if we should retry on error using predicate\n const shouldRetry = await Promise.resolve(predicate(null, lastError, attempt, ctx));\n\n if (!shouldRetry) {\n throw lastError;\n }\n\n // Call onRetry callback\n if (onRetry) {\n await Promise.resolve(onRetry(attempt + 1, lastError, null));\n }\n\n // Calculate and apply delay\n const delay = await calculateDelay(strategies, null, lastError, attempt);\n if (delay > 0) {\n await new Promise((resolve) => setTimeout(resolve, delay));\n }\n }\n }\n\n return lastResponse as Response;\n },\n {\n name: 'retry',\n kind: 'retry',\n options: {\n tries,\n },\n children: [\n // Add predicate metadata\n ...(() => {\n const meta = getInspectableMeta(predicate);\n return meta ? [meta] : [];\n })(),\n // Add strategies metadata\n ...strategies.map((s) => getInspectableMeta(s)).filter((m): m is InspectableMeta => m !== undefined),\n ],\n },\n );\n}\n","/**\n * Throttle policy (Client-side Rate Limiter)\n * Controls the rate of outgoing requests using Token Bucket algorithm\n */\n\nimport { policy } from './introspection.js';\nimport type { Policy } from './types.js';\n\nexport interface ThrottleOptions {\n /** Number of requests allowed per interval */\n readonly limit: number;\n /** Interval in milliseconds (default: 1000) */\n readonly interval?: number;\n}\n\nexport function throttle(options: ThrottleOptions): Policy {\n const { limit, interval = 1000 } = options;\n\n let tokens = limit;\n let lastRefill = Date.now();\n const refillRate = limit / interval; // tokens per ms\n\n const refill = () => {\n const now = Date.now();\n const elapsed = now - lastRefill;\n const newTokens = elapsed * refillRate;\n\n if (newTokens > 0) {\n tokens = Math.min(limit, tokens + newTokens);\n lastRefill = now;\n }\n };\n\n return policy(\n async (ctx, next) => {\n refill();\n\n if (tokens < 1) {\n // Calculate wait time\n const missingTokens = 1 - tokens;\n const waitTime = Math.ceil(missingTokens / refillRate);\n\n await new Promise((resolve) => setTimeout(resolve, waitTime));\n\n // Refill after wait\n refill();\n }\n\n tokens -= 1;\n return next(ctx);\n },\n {\n name: 'throttle',\n kind: 'other',\n options: { limit, interval },\n },\n );\n}\n","/**\n * Core types for the unireq framework\n */\n\n/** Request context passed through the policy chain */\nexport interface RequestContext {\n readonly url: string;\n readonly method: string;\n readonly headers: Record<string, string>;\n readonly body?: unknown;\n readonly signal?: AbortSignal;\n readonly policies?: ReadonlyArray<Policy>;\n readonly [key: string]: unknown;\n}\n\n/** Response from a transport or policy */\nexport interface Response<T = unknown> {\n readonly status: number;\n readonly statusText: string;\n readonly headers: Record<string, string>;\n readonly data: T;\n readonly ok: boolean;\n}\n\n/** Policy function that transforms request context */\nexport type Policy = (ctx: RequestContext, next: (ctx: RequestContext) => Promise<Response>) => Promise<Response>;\n\n/** Transport capabilities declaration */\nexport interface TransportCapabilities {\n readonly streams?: boolean;\n readonly multipartFormData?: boolean;\n readonly randomAccess?: boolean;\n readonly [key: string]: boolean | undefined;\n}\n\n/** Connector interface for pluggable transport implementations */\nexport interface Connector<TClient = unknown> {\n connect(uri: string): Promise<TClient> | TClient;\n request(client: TClient, ctx: RequestContext): Promise<Response>;\n disconnect(client: TClient): Promise<void> | void;\n}\n\n/** Transport function that executes the actual I/O */\nexport type Transport = (ctx: RequestContext) => Promise<Response>;\n\n/** Transport with capabilities metadata */\nexport interface TransportWithCapabilities {\n readonly transport: Transport;\n readonly capabilities: TransportCapabilities;\n}\n\n/** Slot types for compile/runtime checks */\nexport enum SlotType {\n Transport = 'transport',\n Auth = 'auth',\n Parser = 'parser',\n}\n\n/** Slot metadata for policy registration */\nexport interface SlotMetadata {\n readonly type: SlotType;\n readonly name: string;\n readonly requiredCapabilities?: ReadonlyArray<string>;\n}\n\n/** Client configuration options */\nexport interface ClientOptions {\n readonly base?: string;\n readonly defaultScheme?: 'http' | 'https' | 'ftp' | 'imap';\n readonly policies?: ReadonlyArray<Policy>;\n}\n\n/** Client instance */\nexport interface Client {\n readonly request: <T = unknown>(url: string, ...policies: ReadonlyArray<Policy>) => Promise<Response<T>>;\n readonly get: <T = unknown>(url: string, ...policies: ReadonlyArray<Policy>) => Promise<Response<T>>;\n readonly head: <T = unknown>(url: string, ...policies: ReadonlyArray<Policy>) => Promise<Response<T>>;\n readonly post: <T = unknown>(url: string, body?: unknown, ...policies: ReadonlyArray<Policy>) => Promise<Response<T>>;\n readonly put: <T = unknown>(url: string, body?: unknown, ...policies: ReadonlyArray<Policy>) => Promise<Response<T>>;\n readonly delete: <T = unknown>(url: string, ...policies: ReadonlyArray<Policy>) => Promise<Response<T>>;\n readonly patch: <T = unknown>(\n url: string,\n body?: unknown,\n ...policies: ReadonlyArray<Policy>\n ) => Promise<Response<T>>;\n readonly options: <T = unknown>(url: string, ...policies: ReadonlyArray<Policy>) => Promise<Response<T>>;\n}\n\n/** Predicate function for conditional branching */\nexport type Predicate<T = unknown> = (ctx: RequestContext) => T | Promise<T>;\n\n/** Either branch configuration */\nexport interface EitherBranch<T> {\n readonly predicate: Predicate<T>;\n readonly then: Policy;\n readonly else?: Policy;\n}\n\n/** Body descriptor for request serialization */\nexport interface BodyDescriptor {\n readonly __brand: 'BodyDescriptor';\n readonly data: unknown;\n readonly contentType?: string;\n readonly serialize: () => string | Blob | ArrayBuffer | FormData | ReadableStream<Uint8Array>;\n readonly filename?: string;\n readonly contentLength?: number;\n}\n\n/** Multipart part configuration */\nexport interface MultipartPart {\n readonly name: string;\n readonly part: BodyDescriptor;\n readonly filename?: string;\n}\n\n/** Logger interface for structured logging */\nexport interface Logger {\n debug(message: string, meta?: Record<string, unknown>): void;\n info(message: string, meta?: Record<string, unknown>): void;\n warn(message: string, meta?: Record<string, unknown>): void;\n error(message: string, meta?: Record<string, unknown>): void;\n}\n","/**\n * Validation policy using Adapter Pattern\n */\n\nimport { SerializationError } from './errors.js';\nimport { policy } from './introspection.js';\nimport type { Policy } from './types.js';\n\n/**\n * Validation adapter interface\n * Adapts any validation library (Zod, Valibot, Joi, etc.) to unireq\n */\nexport interface ValidationAdapter<TSchema, TOutput> {\n validate(schema: TSchema, data: unknown): Promise<TOutput> | TOutput;\n}\n\n/**\n * Validates response data using a schema and an adapter\n * @param schema - The schema to validate against\n * @param adapter - The adapter for the validation library\n * @returns Policy that validates response data\n */\nexport function validate<TSchema, TOutput>(schema: TSchema, adapter: ValidationAdapter<TSchema, TOutput>): Policy {\n return policy(\n async (ctx, next) => {\n const response = await next(ctx);\n\n try {\n const validatedData = await adapter.validate(schema, response.data);\n return {\n ...response,\n data: validatedData,\n };\n } catch (error) {\n throw new SerializationError(`Validation failed: ${(error as Error).message}`, error);\n }\n },\n {\n name: 'validate',\n kind: 'other',\n options: { schema },\n },\n );\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@unireq/core",
3
+ "version": "0.0.1",
4
+ "description": "Core client and composition utilities for unireq",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "keywords": [
19
+ "http",
20
+ "client",
21
+ "compose",
22
+ "pipeline"
23
+ ],
24
+ "author": "Olivier Orabona",
25
+ "license": "MIT",
26
+ "devDependencies": {
27
+ "typescript": "^5.9.3",
28
+ "tsup": "^8.5.1",
29
+ "vitest": "^4.0.16"
30
+ },
31
+ "engines": {
32
+ "node": ">=18.0.0"
33
+ },
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "https://github.com/oorabona/unireq",
37
+ "directory": "packages/core"
38
+ },
39
+ "bugs": {
40
+ "url": "https://github.com/oorabona/unireq/issues"
41
+ },
42
+ "homepage": "https://github.com/oorabona/unireq/tree/main/packages/core",
43
+ "scripts": {
44
+ "build": "tsup",
45
+ "type-check": "tsc --noEmit",
46
+ "test": "vitest run",
47
+ "test:watch": "vitest",
48
+ "clean": "rm -rf dist *.tsbuildinfo"
49
+ }
50
+ }