Reusable Policies with definePolicy
definePolicy groups the same functional resilience operators into an immutable and validated configuration that you can reuse across multiple pipelines or extend into new derived policies.
Import
Section titled “Import”import { definePolicy } from 'annotations-design-patterns/policy';import { pipe } from 'annotations-design-patterns/pipe';Basic syntax
Section titled “Basic syntax”const profilePolicy = definePolicy<Receiver, Args, Return>({ fallback: { value: defaultValue }, circuitBreaker: { failures: 3, resetMs: 5_000 }, retry: { attempts: 3, delayMs: 100, idempotent: true }, timeout: { ms: 1_000 },});Only enable the layers you need. Missing ones or false are disabled.
Default order
Section titled “Default order”Layers execute in this order (outside to inside):
- fallback — outermost, catches and returns value
- circuitBreaker — brakes after failures
- retry — retries
- timeout — innermost (limits time)
This is the most logical order for resilience: if everything fails, the fallback catches it.
Complete example
Section titled “Complete example”const profilePolicy = definePolicy<object, readonly [id: string], string>({ fallback: { value: 'unavailable' }, circuitBreaker: { failures: 3, resetMs: 5_000 }, retry: { attempts: 3, delayMs: 100, idempotent: true }, timeout: { ms: 1_000 },});
const loadProfile = pipe( async (id: string) => fetchProfile(id), profilePolicy.operator(),);
// Use itconst profile = await loadProfile.call({}, '42');Extend a policy
Section titled “Extend a policy”Use .extend() to create a new policy with overridden options:
const stricterPolicy = profilePolicy.extend({ retry: { attempts: 5, delayMs: 50, idempotent: true }, timeout: { ms: 500 },});
const strictLoad = pipe(fetchProfile, stricterPolicy.operator());The original policy does not change — .extend() returns a new one.
Validation
Section titled “Validation”definePolicy validates the configuration before the first invocation:
retryrequiresidempotent: true— retry policies without idempotence guarantee are rejected withInvalidPolicyError.- Dangerous combinations are detected — e.g., a policy that cannot guarantee correctness throws an error when compiled, not when executed.
Available layers
Section titled “Available layers”fallback: { value: T | () => T }— Value or function that returns fallbackcircuitBreaker: { failures: number, resetMs: number }— Opens after N consecutive failuresretry: { attempts: number, delayMs?: number, backoffFactor?: number, idempotent: boolean }— Retries with backofftimeout: { ms: number }— Time limit
All are optional. Pass false to disable a specific layer:
const noRetry = profilePolicy.extend({ retry: false });PolicyDefinition methods
Section titled “PolicyDefinition methods”operator()
Section titled “operator()”Returns a functional operator for use in pipe:
const pipeline = pipe(method, policy.operator());extend(overrides)
Section titled “extend(overrides)”Returns a new policy with changes:
const derived = policy.extend({ retry: { attempts: 5, idempotent: true } });describe() (advanced)
Section titled “describe() (advanced)”Generates a text description of the policy (useful for logs):
console.log(policy.describe());// "Fallback('unavailable') → CircuitBreaker(3/5000ms) → Retry(3x) → Timeout(1000ms)"For types and advanced options
Section titled “For types and advanced options”See the Policy Reference.

