Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@git-stunts/alfred

Package Overview
Dependencies
Maintainers
1
Versions
14
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@git-stunts/alfred - npm Package Compare versions

Comparing version
0.5.0
to
0.6.0
+24
-0
CHANGELOG.md

@@ -8,2 +8,26 @@ # Changelog

## [0.6.0] - 2026-02-02
### Added
- **Browser Support**: Alfred now officially supports modern browsers (Chrome 85+, Firefox 79+, Safari 14+, Edge 85+).
- **Browser Demo**: Interactive "Flaky Fetch Lab" (`npm run demo:web`) demonstrates resilience policies running in a browser.
- **Playwright Tests**: Browser compatibility tests verify retry, timeout, bulkhead, and circuit breaker work in Chromium.
- **Resolution Timing Documentation**: New README section documenting when dynamic options (functions) are resolved for each policy—per attempt, per admission, per event, or per execute.
- **Resolution Timing Tests**: Comprehensive test suite verifying option resolution timing with call counters.
- **Hedge Safety Guardrails**: Documentation for safe hedge usage—idempotent operations only, AbortSignal handling, bulkhead composition.
- **Hedge Recipes**: `hedgeRead` pattern for database/cache operations, `happyEyeballsFetch` for multi-endpoint racing.
- **Full JSDoc Coverage**: All source files and TypeScript declarations now have complete documentation.
### Fixed
- **JSR Module Docs**: Entrypoints now use `@module` tag with examples for proper JSR documentation.
- **JSR Publish Config**: Fixed exclude list to only publish required files (was including examples, scripts, etc.).
- **TypeScript Declarations**: `testing.d.ts` updated with missing types (`HedgeOptions`, `MetricsSink`, `Resolvable`, `Policy.hedge`).
- **Example in index.d.ts**: Fixed incorrect compose example to use proper Policy fluent API.
### Changed
- **ROADMAP.md**: v0.5 and v0.6 milestones marked complete.
## [0.5.0] - 2026-02-02

@@ -10,0 +34,0 @@

+12
-9
{
"name": "@git-stunts/alfred",
"version": "0.5.0",
"version": "0.6.0",
"description": "Production-grade resilience patterns for async ops: retry/backoff+jitter, circuit breaker, bulkhead, timeout.",

@@ -13,13 +13,16 @@

"exclude": [
"!src/**/*.js",
"!index.d.ts",
"!README.md",
"!LICENSE",
"!CHANGELOG.md",
".github",
".devcontainer",
"docker",
"examples",
"scripts",
"test",
"node_modules",
"test",
"docker",
".devcontainer"
".editorconfig",
"eslint.config.js",
"package-lock.json",
"ROADMAP.md",
"setup-hooks.sh"
]
}
}
{
"name": "@git-stunts/alfred",
"version": "0.5.0",
"version": "0.6.0",
"description": "Production-grade resilience patterns for async ops: retry/backoff+jitter, circuit breaker, bulkhead, timeout.",

@@ -26,4 +26,7 @@ "type": "module",

"test:watch": "vitest",
"test:browser": "npm --prefix examples/web run test",
"lint": "eslint .",
"format": "prettier --write ."
"format": "prettier --write .",
"demo:web": "npm --prefix examples/web run dev",
"demo:web:install": "npm --prefix examples/web install"
},

@@ -57,3 +60,4 @@ "author": "James Ross <james@flyingrobots.dev>",

"fault-tolerance",
"async"
"async",
"browser"
],

@@ -60,0 +64,0 @@ "devDependencies": {

+159
-1

@@ -86,5 +86,20 @@ # @git-stunts/alfred

- **Deno** (>= 1.35)
- **Browsers** (Chrome 85+, Firefox 79+, Safari 14+, Edge 85+)
Uses standard Web APIs (AbortController, AbortSignal) and runtime-aware clock management to ensure clean process exits (e.g. timer unref where applicable).
Uses standard Web APIs (AbortController, AbortSignal, Promise.any) with no Node-specific dependencies. Runtime-aware clock management ensures clean process exits in server environments.
### Browser Demo
Run the interactive "Flaky Fetch Lab" to see resilience policies in action:
```bash
npm run demo:web:install && npm run demo:web
```
Or run the Playwright browser tests:
```bash
npm run demo:web:install && npm run test:browser
```
---

@@ -234,2 +249,3 @@

- [Error Types](#error-types)
- [Resolution Timing](#resolution-timing-dynamic-options)

@@ -411,2 +427,86 @@ ---

### Safety Guardrails
> **Warning:** Hedging spawns parallel requests. Use responsibly to avoid overloading backends.
1. **Only hedge idempotent operations.** Reads, lookups, and GET requests are safe. Writes, payments, and state mutations are not — you may end up with duplicate side effects.
2. **Always use AbortSignal.** Your operation receives an `AbortSignal` that fires when a faster hedge wins. Honor it to cancel in-flight work (fetch, database queries, etc.).
3. **Combine with bulkhead + circuit breaker.** Prevent self-DDOS and cascading failures:
```javascript
import { Policy } from '@git-stunts/alfred';
// Safe hedging: bulkhead limits total concurrency, circuit breaker fails fast
const safeHedge = Policy.hedge({ delay: 100, maxHedges: 2 })
.wrap(Policy.bulkhead({ limit: 10 }))
.wrap(Policy.circuitBreaker({ threshold: 5, duration: 30_000 }));
await safeHedge.execute((signal) => fetch(url, { signal }));
```
4. **Set reasonable delays.** The `delay` should be based on your P50/P90 latency. Too short = excessive load. Too long = no benefit.
### Recipe: hedgeRead (Read-Only Operations)
A reusable pattern for hedging database reads or cache lookups:
```javascript
import { Policy } from '@git-stunts/alfred';
function createHedgedReader(options = {}) {
const { delay = 50, maxHedges = 1, concurrencyLimit = 5 } = options;
return Policy.hedge({ delay, maxHedges }).wrap(Policy.bulkhead({ limit: concurrencyLimit }));
}
const hedgedRead = createHedgedReader({ delay: 50, maxHedges: 1 });
// Use for any read-only operation
const user = await hedgedRead.execute((signal) => db.users.findById(id, { signal }));
const cached = await hedgedRead.execute((signal) => cache.get(key, { signal }));
```
### Recipe: Happy Eyeballs (Parallel Endpoints)
Race requests to multiple endpoints (e.g., IPv4 vs IPv6, primary vs replica):
```javascript
import { Policy } from '@git-stunts/alfred';
async function happyEyeballsFetch(urls, options = {}) {
const { delay = 50 } = options;
// Create a hedge policy that spawns one hedge per additional URL
const racer = Policy.hedge({ delay, maxHedges: urls.length - 1 });
let urlIndex = 0;
return racer.execute((signal) => {
const url = urls[urlIndex++ % urls.length];
return fetch(url, { signal }).then((res) => {
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res;
});
});
}
// First successful response wins
const response = await happyEyeballsFetch([
'https://api-primary.example.com/data',
'https://api-replica.example.com/data',
]);
```
### Runtime Requirements
Hedge uses `Promise.any()` internally. This is available in:
- Node.js >= 15.0.0
- Deno >= 1.2
- Bun >= 1.0
- Modern browsers (Chrome 85+, Firefox 79+, Safari 14+)
For older runtimes, use a polyfill like [core-js](https://github.com/zloirock/core-js#promiseany) or [promise.any](https://www.npmjs.com/package/promise.any).
---

@@ -667,2 +767,60 @@

## Resolution Timing (Dynamic Options)
All policy options can be passed as **functions** for dynamic/live-tunable behavior. This table documents **when** each option is resolved:
| Policy | Option | Resolution Timing | Description |
| ------------------ | ------------------ | ----------------- | --------------------------------------------- |
| **retry** | `retries` | per attempt | Checked after each failure |
| **retry** | `delay` | per attempt | Calculated before each backoff sleep |
| **retry** | `maxDelay` | per attempt | Applied when calculating delay |
| **retry** | `backoff` | per attempt | Strategy resolved per delay calculation |
| **retry** | `jitter` | per attempt | Jitter type resolved per delay calculation |
| **bulkhead** | `limit` | per admission | Checked when request tries to execute |
| **bulkhead** | `queueLimit` | per admission | Checked when request tries to queue |
| **circuitBreaker** | `threshold` | per event | Checked on each failure |
| **circuitBreaker** | `duration` | per event | Checked when testing for half-open transition |
| **circuitBreaker** | `successThreshold` | per event | Checked on each success in half-open state |
| **timeout** | `ms` | per execute | Resolved once at start of timeout |
| **hedge** | `delay` | per execute | Resolved once at start of execute |
| **hedge** | `maxHedges` | per execute | Resolved once at start of execute |
### Resolution Timing Semantics
- **per execute**: Option is resolved once when `execute()` is called. Changes during execution have no effect.
- **per attempt**: Option is resolved each time an attempt/retry occurs. Allows mid-execution tuning.
- **per admission**: Option is resolved each time a request attempts to enter the bulkhead.
- **per event**: Option is resolved when the relevant event (failure, success, state check) occurs.
### Example: Dynamic Retry Limit
```javascript
let maxRetries = 2;
// Pass a function to make it dynamic
await retry(operation, {
retries: () => maxRetries, // Resolved per attempt
delay: 100,
});
// In another part of your code, you can adjust:
maxRetries = 5; // Future failures will see the new limit
```
### Example: Dynamic Bulkhead Limit
```javascript
let concurrencyLimit = 10;
const bh = bulkhead({
limit: () => concurrencyLimit, // Resolved per admission
queueLimit: 20,
});
// Later, reduce concurrency (takes effect on next admission)
concurrencyLimit = 5;
```
---
## License

@@ -669,0 +827,0 @@

@@ -29,4 +29,4 @@ <!-- SPDX-License-Identifier: Apache-2.0 -->

- [ ] v0.5 — Correctness & Coherence
- [ ] v0.6 — Typed Knobs & Stable Semantics
- [x] v0.5 — Correctness & Coherence
- [x] v0.6 — Typed Knobs & Stable Semantics
- [ ] v0.7 — Rate Limiting (Throughput) Policy

@@ -33,0 +33,0 @@ - [ ] v0.8 — Control Plane Core (In-Memory)

/**
* @fileoverview Custom error types for resilience policy failures.
*
* Each error includes contextual metadata to aid debugging and
* error handling in application code.
*
* @module @git-stunts/alfred/errors
*/
/**
* Error thrown when all retry attempts are exhausted.

@@ -3,0 +12,0 @@ */

/**
* @module @git-stunts/alfred
* @description Production-grade resilience patterns for async operations.
* Includes Retry, Circuit Breaker, Timeout, and Bulkhead policies.
* Includes Retry, Circuit Breaker, Timeout, Bulkhead, and Hedge policies.
*
* @example
* ```ts
* import { compose, retry, circuitBreaker, timeout } from "@git-stunts/alfred";
* import { Policy } from "@git-stunts/alfred";
*
* const policy = compose(
* retry({ retries: 3 }),
* circuitBreaker({ threshold: 5, duration: 60000 }),
* timeout(5000)
* );
* const resilient = Policy.timeout(5_000)
* .wrap(Policy.retry({ retries: 3, backoff: "exponential" }))
* .wrap(Policy.circuitBreaker({ threshold: 5, duration: 60_000 }))
* .wrap(Policy.bulkhead({ limit: 10 }));
*
* await policy.execute(() => fetch("https://api.example.com"));
* const data = await resilient.execute(() => fetch("https://api.example.com"));
* ```

@@ -167,3 +166,3 @@ */

export class MultiSink implements TelemetrySink {
constructor(sinks: TelemetrySink[]);
constructor(sinks?: TelemetrySink[]);
emit(event: TelemetryEvent): void;

@@ -369,12 +368,32 @@ }

/**
* System clock using real time.
* Uses runtime-aware timer management for clean process exits.
*/
export class SystemClock {
/** Returns current time in milliseconds since Unix epoch. */
now(): number;
/** Sleeps for the specified duration. */
sleep(ms: number): Promise<void>;
}
/**
* Test clock for deterministic tests.
* Allows manual control of time progression without real delays.
*/
export class TestClock {
/** Returns current virtual time in milliseconds. */
now(): number;
/** Creates a sleep promise that resolves when time is advanced. */
sleep(ms: number): Promise<void>;
/** Process any timers ready at current time. */
tick(ms?: number): Promise<void>;
/** Advances time and resolves any pending timers. */
advance(ms: number): Promise<void>;
/** Sets absolute time. */
setTime(time: number): void;
/** Returns number of pending timers. */
readonly pendingCount: number;
/** Clears all pending timers and resets time to 0. */
reset(): void;
}
/**
* @fileoverview Main entry point for @git-stunts/alfred resilience library.
* Exports all public APIs for building resilient applications.
* Production-grade resilience patterns for async operations.
*
* Alfred provides composable policies for retry, circuit breaker, bulkhead,
* timeout, and hedge patterns with telemetry support and TestClock for
* deterministic testing.
*
* @example
* ```ts
* import { Policy } from "@git-stunts/alfred";
*
* const resilient = Policy.timeout(5_000)
* .wrap(Policy.retry({ retries: 3, backoff: "exponential" }))
* .wrap(Policy.circuitBreaker({ threshold: 5, duration: 60_000 }))
* .wrap(Policy.bulkhead({ limit: 10 }));
*
* const data = await resilient.execute(() => fetch("https://api.example.com"));
* ```
*
* @module
*/

@@ -5,0 +22,0 @@

@@ -0,1 +1,11 @@

/**
* @fileoverview Circuit breaker policy for fail-fast behavior.
*
* Prevents cascading failures by tracking error rates and "opening"
* the circuit when a threshold is exceeded. Supports half-open state
* for automatic recovery testing.
*
* @module @git-stunts/alfred/policies/circuit-breaker
*/
import { CircuitOpenError } from '../errors.js';

@@ -2,0 +12,0 @@ import { SystemClock } from '../utils/clock.js';

/**
* @fileoverview Fluent Policy API for composing resilience strategies.
*
* The Policy class provides a chainable interface for building complex
* resilience stacks using wrap (sequential), or (fallback), and race
* (concurrent) composition operators.
*
* @module @git-stunts/alfred/policy
*/
/**
* @typedef {(fn: () => Promise<T>) => Promise<T>} Executor

@@ -3,0 +13,0 @@ * @template T

/**
* @fileoverview Telemetry system for observing resilience policy behavior.
* Provides composable sinks for capturing events.
* Telemetry system for observing resilience policy behavior.
*
* Provides composable sinks for capturing, logging, and aggregating
* events emitted by resilience policies.
*
* @module
*/

@@ -25,5 +29,10 @@

constructor() {
/** @type {TelemetryEvent[]} */
this.events = [];
}
/**
* Records an event to the in-memory array.
* @param {TelemetryEvent} event
*/
emit(event) {

@@ -33,2 +42,5 @@ this.events.push(event);

/**
* Clears all recorded events.
*/
clear() {

@@ -44,2 +56,6 @@ this.events = [];

export class ConsoleSink {
/**
* Logs an event to the console.
* @param {TelemetryEvent} event
*/
emit(event) {

@@ -53,6 +69,10 @@ const { type, timestamp = Date.now(), ...rest } = event;

/**
* Sink that does nothing. Default.
* Sink that does nothing. Default when telemetry is not needed.
* @implements {TelemetrySink}
*/
export class NoopSink {
/**
* Discards the event (no-op).
* @param {TelemetryEvent} _event
*/
emit(_event) {

@@ -69,3 +89,4 @@ // No-op

/**
* @param {TelemetrySink[]} sinks
* Creates a MultiSink that broadcasts to all provided sinks.
* @param {TelemetrySink[]} sinks - Array of sinks to broadcast to.
*/

@@ -76,2 +97,6 @@ constructor(sinks = []) {

/**
* Broadcasts an event to all child sinks.
* @param {TelemetryEvent} event
*/
emit(event) {

@@ -78,0 +103,0 @@ for (const sink of this.sinks) {

@@ -0,70 +1,225 @@

/**
* @module @git-stunts/alfred/testing
* @description Testing utilities for Alfred resilience policies.
*
* Provides TestClock for deterministic time control and re-exports
* telemetry sinks useful for test assertions.
*
* @example
* ```ts
* import { TestClock, InMemorySink } from "@git-stunts/alfred/testing";
* import { retry } from "@git-stunts/alfred";
*
* const clock = new TestClock();
* const sink = new InMemorySink();
*
* const promise = retry(() => mightFail(), {
* retries: 3,
* delay: 1000,
* clock,
* telemetry: sink,
* });
*
* await clock.advance(1000); // Advance virtual time
* expect(sink.events).toContainEqual(expect.objectContaining({ type: "retry.scheduled" }));
* ```
*/
/**
* A value that can be either static or resolved dynamically via a function.
* Enables live-tunable policy options.
*/
export type Resolvable<T> = T | (() => T);
/**
* Options for the Retry policy.
*/
export interface RetryOptions {
retries?: number;
delay?: number;
maxDelay?: number;
backoff?: 'constant' | 'linear' | 'exponential';
jitter?: 'none' | 'full' | 'equal' | 'decorrelated';
/** Maximum number of retry attempts. Default: 3 */
retries?: Resolvable<number>;
/** Base delay in milliseconds. Default: 1000 */
delay?: Resolvable<number>;
/** Maximum delay cap in milliseconds. Default: 30000 */
maxDelay?: Resolvable<number>;
/** Backoff strategy. Default: 'constant' */
backoff?: Resolvable<'constant' | 'linear' | 'exponential'>;
/** Jitter strategy to prevent thundering herd. Default: 'none' */
jitter?: Resolvable<'none' | 'full' | 'equal' | 'decorrelated'>;
/** Predicate to determine if an error is retryable. Default: always true */
shouldRetry?: (error: Error) => boolean;
/** Callback invoked before each retry. */
onRetry?: (error: Error, attempt: number, delay: number) => void;
/** Telemetry sink for observability. */
telemetry?: TelemetrySink;
clock?: any;
/** Clock implementation for testing. */
clock?: TestClock | SystemClock;
/** Abort signal to cancel retries. */
signal?: AbortSignal;
}
/**
* Options for the Circuit Breaker policy.
*/
export interface CircuitBreakerOptions {
threshold: number;
duration: number;
successThreshold?: number;
/** Number of failures before opening the circuit. */
threshold: Resolvable<number>;
/** Milliseconds to stay open before transitioning to half-open. */
duration: Resolvable<number>;
/** Consecutive successes required to close the circuit from half-open. Default: 1 */
successThreshold?: Resolvable<number>;
/** Predicate to determine if an error counts as a failure. Default: always true */
shouldTrip?: (error: Error) => boolean;
/** Callback when circuit opens. */
onOpen?: () => void;
/** Callback when circuit closes. */
onClose?: () => void;
/** Callback when circuit transitions to half-open. */
onHalfOpen?: () => void;
/** Telemetry sink for observability. */
telemetry?: TelemetrySink;
clock?: any;
/** Clock implementation for testing. */
clock?: TestClock | SystemClock;
}
/**
* Options for the Timeout policy.
*/
export interface TimeoutOptions {
/** Callback invoked when timeout occurs. */
onTimeout?: (elapsed: number) => void;
/** Telemetry sink for observability. */
telemetry?: TelemetrySink;
/** Clock implementation for testing. */
clock?: TestClock | SystemClock;
}
/**
* Options for the Bulkhead policy.
*/
export interface BulkheadOptions {
limit: number;
queueLimit?: number;
/** Maximum concurrent executions. */
limit: Resolvable<number>;
/** Maximum pending requests in queue. Default: 0 */
queueLimit?: Resolvable<number>;
/** Telemetry sink for observability. */
telemetry?: TelemetrySink;
clock?: any;
/** Clock implementation for testing. */
clock?: TestClock | SystemClock;
}
/**
* Options for the Hedge policy.
*/
export interface HedgeOptions {
/** Milliseconds to wait before spawning a hedge. */
delay: Resolvable<number>;
/** Maximum number of hedged attempts to spawn. Default: 1 */
maxHedges?: Resolvable<number>;
/** Telemetry sink for observability. */
telemetry?: TelemetrySink;
/** Clock implementation for testing. */
clock?: TestClock | SystemClock;
}
/**
* A structured event emitted by the telemetry system.
*/
export interface TelemetryEvent {
/** The type of event (e.g., 'retry.failure', 'circuit.open'). */
type: string;
/** Unix timestamp of the event. */
timestamp: number;
/** Metric increments (counters) to be aggregated by MetricsSink. */
metrics?: Record<string, number>;
/** Additional metadata (error, duration, attempts, etc.). */
[key: string]: any;
}
/**
* Interface for receiving telemetry events.
*/
export interface TelemetrySink {
/**
* Records a telemetry event.
* @param event The structured event.
*/
emit(event: TelemetryEvent): void;
}
/**
* Stores telemetry events in an in-memory array. Useful for testing.
*/
export class InMemorySink implements TelemetrySink {
/** Array of recorded events. */
events: TelemetryEvent[];
/** Records a telemetry event. */
emit(event: TelemetryEvent): void;
/** Clears all recorded events. */
clear(): void;
}
/**
* Logs telemetry events to the console (stdout).
*/
export class ConsoleSink implements TelemetrySink {
/** Records a telemetry event by logging to console. */
emit(event: TelemetryEvent): void;
}
/**
* Discards all telemetry events. Useful for disabling telemetry.
*/
export class NoopSink implements TelemetrySink {
/** Discards the event (no-op). */
emit(event: TelemetryEvent): void;
}
/**
* Broadcasts telemetry events to multiple other sinks.
*/
export class MultiSink implements TelemetrySink {
constructor(sinks: TelemetrySink[]);
/**
* @param sinks Array of sinks to broadcast to.
*/
constructor(sinks?: TelemetrySink[]);
/** Broadcasts event to all child sinks. */
emit(event: TelemetryEvent): void;
}
/**
* Sink that aggregates metrics in memory. Useful for testing and monitoring.
*/
export class MetricsSink implements TelemetrySink {
/** Records a telemetry event and updates metrics. */
emit(event: TelemetryEvent): void;
/** Returns a snapshot of the current metrics. */
get stats(): {
retries: number;
failures: number;
successes: number;
circuitBreaks: number;
circuitRejections: number;
bulkheadRejections: number;
timeouts: number;
hedges: number;
latency: {
count: number;
sum: number;
min: number;
max: number;
avg: number;
};
[key: string]: number | { count: number; sum: number; min: number; max: number; avg: number };
};
/** Resets all metrics to zero. */
clear(): void;
}
/**
* Error thrown when all retry attempts are exhausted.
*/
export class RetryExhaustedError extends Error {
/** Total number of attempts made. */
attempts: number;
/** The last error that caused the failure. */
cause: Error;

@@ -74,4 +229,9 @@ constructor(attempts: number, cause: Error);

/**
* Error thrown when the circuit breaker is open (OPEN state).
*/
export class CircuitOpenError extends Error {
/** When the circuit was opened. */
openedAt: Date;
/** Number of failures that triggered the open state. */
failureCount: number;

@@ -81,4 +241,9 @@ constructor(openedAt: Date, failureCount: number);

/**
* Error thrown when an operation exceeds its time limit.
*/
export class TimeoutError extends Error {
/** The configured timeout in milliseconds. */
timeout: number;
/** Actual elapsed time in milliseconds. */
elapsed: number;

@@ -88,4 +253,9 @@ constructor(timeout: number, elapsed: number);

/**
* Error thrown when the bulkhead limit and queue are both full.
*/
export class BulkheadRejectedError extends Error {
/** The configured concurrency limit. */
limit: number;
/** The configured queue limit. */
queueLimit: number;

@@ -95,2 +265,5 @@ constructor(limit: number, queueLimit: number);

/**
* Executes an async function with configurable retry logic.
*/
export function retry<T>(

@@ -101,11 +274,22 @@ fn: (signal?: AbortSignal) => Promise<T>,

/**
* Represents a Circuit Breaker instance.
*/
export interface CircuitBreaker {
/** Executes a function with circuit breaker protection. */
execute<T>(fn: () => Promise<T>): Promise<T>;
/** Current state of the circuit. */
readonly state: 'CLOSED' | 'OPEN' | 'HALF_OPEN';
}
/**
* Creates a Circuit Breaker policy.
*/
export function circuitBreaker(options: CircuitBreakerOptions): CircuitBreaker;
/**
* Executes a function with a time limit.
*/
export function timeout<T>(
ms: number,
ms: Resolvable<number>,
fn: ((signal: AbortSignal) => Promise<T>) | (() => Promise<T>),

@@ -115,10 +299,38 @@ options?: TimeoutOptions

/**
* Represents a Bulkhead instance.
*/
export interface Bulkhead {
/** Executes a function with concurrency limiting. */
execute<T>(fn: () => Promise<T>): Promise<T>;
/** Current load statistics. */
readonly stats: { active: number; pending: number; available: number };
}
/**
* Creates a Bulkhead policy for concurrency limiting.
*/
export function bulkhead(options: BulkheadOptions): Bulkhead;
/**
* Represents a Hedge policy instance.
*/
export interface Hedge {
/** Executes with speculative hedging. */
execute<T>(fn: (signal?: AbortSignal) => Promise<T>): Promise<T>;
}
/**
* Creates a Hedge policy for speculative execution.
*/
export function hedge(options: HedgeOptions): Hedge;
/**
* Composes multiple policies into a single executable policy.
*/
export function compose(...policies: any[]): { execute<T>(fn: () => Promise<T>): Promise<T> };
/**
* Creates a fallback policy. If the primary policy fails, the secondary is executed.
*/
export function fallback(

@@ -128,2 +340,6 @@ primary: any,

): { execute<T>(fn: () => Promise<T>): Promise<T> };
/**
* Creates a race policy. Executes both policies concurrently; the first to succeed wins.
*/
export function race(

@@ -134,26 +350,81 @@ primary: any,

/**
* Fluent API for building resilience policies.
*/
export class Policy {
constructor(executor: (fn: () => Promise<any>) => Promise<any>);
/** Creates a Retry policy wrapper. */
static retry(options?: RetryOptions): Policy;
/** Creates a Circuit Breaker policy wrapper. */
static circuitBreaker(options: CircuitBreakerOptions): Policy;
static timeout(ms: number, options?: TimeoutOptions): Policy;
/** Creates a Timeout policy wrapper. */
static timeout(ms: Resolvable<number>, options?: TimeoutOptions): Policy;
/** Creates a Bulkhead policy wrapper. */
static bulkhead(options: BulkheadOptions): Policy;
/** Creates a Hedge policy wrapper. */
static hedge(options: HedgeOptions): Policy;
/** Creates a pass-through (no-op) policy. */
static noop(): Policy;
/** Wraps this policy with another (sequential composition). */
wrap(otherPolicy: Policy): Policy;
/** Falls back to another policy if this one fails. */
or(otherPolicy: Policy): Policy;
/** Races this policy against another. */
race(otherPolicy: Policy): Policy;
/** Executes the policy chain. */
execute<T>(fn: () => Promise<T>): Promise<T>;
}
/**
* System clock using real time.
* Uses runtime-aware timer management for clean process exits.
*/
export class SystemClock {
/** Returns current time in milliseconds since Unix epoch. */
now(): number;
/** Sleeps for the specified duration. */
sleep(ms: number): Promise<void>;
}
/**
* Test clock for deterministic tests.
* Allows manual control of time progression without real delays.
*
* @example
* ```ts
* const clock = new TestClock();
* const promise = retry(fn, { delay: 1000, clock });
*
* await clock.advance(1000); // Triggers first retry
* await clock.advance(1000); // Triggers second retry
* ```
*/
export class TestClock {
/** Returns current virtual time in milliseconds. */
now(): number;
/**
* Creates a sleep promise that resolves when time is advanced.
* @param ms Milliseconds to sleep.
*/
sleep(ms: number): Promise<void>;
/**
* Process any timers ready at current time.
* @param ms Optional additional time to add.
*/
tick(ms?: number): Promise<void>;
/**
* Advances time and resolves any pending timers.
* @param ms Milliseconds to advance.
*/
advance(ms: number): Promise<void>;
/**
* Sets absolute time.
* @param time Time in milliseconds.
*/
setTime(time: number): void;
/** Returns number of pending timers. */
readonly pendingCount: number;
/** Clears all pending timers and resets time to 0. */
reset(): void;
}
/**
* @fileoverview Testing utilities for @git-stunts/alfred.
* Provides tools for deterministic testing of resilience policies.
* Testing utilities for deterministic resilience policy tests.
*
* Provides TestClock for controlling time progression without real delays,
* enabling fast and reliable tests for retry, timeout, and hedge policies.
*
* @example
* ```ts
* import { TestClock } from "@git-stunts/alfred/testing";
* import { retry } from "@git-stunts/alfred";
*
* const clock = new TestClock();
* const promise = retry(() => mightFail(), { retries: 3, delay: 1000, clock });
*
* await clock.advance(1000); // Triggers first retry instantly
* await clock.advance(1000); // Triggers second retry
* ```
*
* @module
*/

@@ -5,0 +21,0 @@

/**
* @fileoverview Clock abstractions for time-based operations.
*
* Provides SystemClock for production use and TestClock for deterministic testing.
* All time-based policies accept a clock option for testability.
*
* @module @git-stunts/alfred/utils/clock
*/
/**
* System clock using real time.
* Uses runtime-aware timer management (unref) to allow clean process exits.
*/
export class SystemClock {
/**
* Returns the current time in milliseconds since Unix epoch.
* @returns {number}
*/
now() {

@@ -9,2 +23,8 @@ return Date.now();

/**
* Sleeps for the specified duration.
* Timer is unref'd to prevent blocking process exit.
* @param {number} ms - Milliseconds to sleep
* @returns {Promise<void>}
*/
async sleep(ms) {

@@ -25,10 +45,16 @@ return new Promise((resolve) => {

* Test clock for deterministic tests.
* Allows manual control of time progression.
* Allows manual control of time progression without real delays.
*/
export class TestClock {
constructor() {
/** @type {number} */
this._time = 0;
/** @type {Array<{triggerAt: number, resolve: () => void}>} */
this._pendingTimers = [];
}
/**
* Returns the current virtual time in milliseconds.
* @returns {number}
*/
now() {

@@ -35,0 +61,0 @@ return this._time;

/**
* @fileoverview Utility for resolving static or dynamic configuration values.
*
* Enables live-tunable policy options by accepting either a value or a
* function that returns a value. See "Resolution Timing" in README.
*
* @module @git-stunts/alfred/utils/resolvable
*/
/**
* Resolves a value that might be dynamic.

@@ -3,0 +12,0 @@ *