@julr/tenace 1.0.0-next.0

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.
Files changed (41) hide show
  1. package/README.md +1034 -0
  2. package/build/src/adapters/cache/memory.d.ts +23 -0
  3. package/build/src/adapters/cache/memory.js +2 -0
  4. package/build/src/adapters/cache/types.d.ts +56 -0
  5. package/build/src/adapters/cache/types.js +1 -0
  6. package/build/src/adapters/lock/types.d.ts +104 -0
  7. package/build/src/adapters/lock/types.js +1 -0
  8. package/build/src/adapters/rate_limiter/memory.d.ts +14 -0
  9. package/build/src/adapters/rate_limiter/memory.js +2 -0
  10. package/build/src/adapters/rate_limiter/types.d.ts +101 -0
  11. package/build/src/adapters/rate_limiter/types.js +1 -0
  12. package/build/src/backoff.d.ts +79 -0
  13. package/build/src/chaos/manager.d.ts +29 -0
  14. package/build/src/chaos/policies.d.ts +10 -0
  15. package/build/src/chaos/types.d.ts +75 -0
  16. package/build/src/collection.d.ts +81 -0
  17. package/build/src/config.d.ts +38 -0
  18. package/build/src/errors/errors.d.ts +79 -0
  19. package/build/src/errors/main.d.ts +1 -0
  20. package/build/src/errors/main.js +2 -0
  21. package/build/src/errors-BODHnryv.js +67 -0
  22. package/build/src/internal/adapter_policies.d.ts +31 -0
  23. package/build/src/internal/cockatiel_factories.d.ts +18 -0
  24. package/build/src/internal/telemetry.d.ts +50 -0
  25. package/build/src/main.d.ts +176 -0
  26. package/build/src/main.js +1125 -0
  27. package/build/src/memory-DWyezb1O.js +37 -0
  28. package/build/src/memory-DXkg8s6y.js +60 -0
  29. package/build/src/plugin.d.ts +30 -0
  30. package/build/src/policy_configurator.d.ts +108 -0
  31. package/build/src/semaphore.d.ts +71 -0
  32. package/build/src/tenace_builder.d.ts +22 -0
  33. package/build/src/tenace_policy.d.ts +41 -0
  34. package/build/src/types/backoff.d.ts +57 -0
  35. package/build/src/types/collection.d.ts +46 -0
  36. package/build/src/types/main.d.ts +5 -0
  37. package/build/src/types/main.js +1 -0
  38. package/build/src/types/plugin.d.ts +61 -0
  39. package/build/src/types/types.d.ts +241 -0
  40. package/build/src/wait_for.d.ts +23 -0
  41. package/package.json +135 -0
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Base error class for all tenace errors
3
+ */
4
+ export declare class TenaceError extends Error {
5
+ constructor(message: string);
6
+ }
7
+ /**
8
+ * Thrown when an operation times out
9
+ */
10
+ export declare class TimeoutError extends TenaceError {
11
+ constructor(message?: string);
12
+ }
13
+ /**
14
+ * Thrown when an operation is cancelled (e.g., via AbortController)
15
+ */
16
+ export declare class CancelledError extends TenaceError {
17
+ constructor(message?: string);
18
+ }
19
+ /**
20
+ * Thrown when circuit breaker is open and rejecting calls
21
+ */
22
+ export declare class CircuitOpenError extends TenaceError {
23
+ constructor(message?: string);
24
+ }
25
+ /**
26
+ * Thrown when circuit breaker is isolated (manually opened)
27
+ */
28
+ export declare class CircuitIsolatedError extends TenaceError {
29
+ constructor(message?: string);
30
+ }
31
+ /**
32
+ * Thrown when bulkhead rejects a call due to capacity limits
33
+ */
34
+ export declare class BulkheadFullError extends TenaceError {
35
+ constructor(message?: string);
36
+ }
37
+ /**
38
+ * Thrown when an abort signal is triggered
39
+ */
40
+ export declare class AbortError extends TenaceError {
41
+ constructor(message?: string);
42
+ }
43
+ /**
44
+ * Thrown when rate limit is exceeded
45
+ */
46
+ export declare class RateLimitError extends TenaceError {
47
+ /**
48
+ * Time in milliseconds to wait before retrying
49
+ */
50
+ retryAfterMs: number;
51
+ /**
52
+ * Number of remaining calls (0 when rate limited)
53
+ */
54
+ remaining: number;
55
+ constructor(options: {
56
+ message?: string;
57
+ retryAfterMs: number;
58
+ remaining?: number;
59
+ });
60
+ }
61
+ /**
62
+ * Thrown when a distributed lock cannot be acquired
63
+ */
64
+ export declare class LockNotAcquiredError extends TenaceError {
65
+ /**
66
+ * The key that was being locked
67
+ */
68
+ key: string;
69
+ constructor(options: {
70
+ key: string;
71
+ message?: string;
72
+ });
73
+ }
74
+ /**
75
+ * Thrown when waitFor times out before the condition becomes true
76
+ */
77
+ export declare class WaitForTimeoutError extends TenaceError {
78
+ constructor(message?: string);
79
+ }
@@ -0,0 +1 @@
1
+ export { TenaceError, TimeoutError, CancelledError, CircuitOpenError, CircuitIsolatedError, BulkheadFullError, AbortError, RateLimitError, LockNotAcquiredError, WaitForTimeoutError, } from './errors.ts';
@@ -0,0 +1,2 @@
1
+ import { a as CircuitOpenError, c as TenaceError, i as CircuitIsolatedError, l as TimeoutError, n as BulkheadFullError, o as LockNotAcquiredError, r as CancelledError, s as RateLimitError, t as AbortError, u as WaitForTimeoutError } from "../errors-BODHnryv.js";
2
+ export { AbortError, BulkheadFullError, CancelledError, CircuitIsolatedError, CircuitOpenError, LockNotAcquiredError, RateLimitError, TenaceError, TimeoutError, WaitForTimeoutError };
@@ -0,0 +1,67 @@
1
+ var TenaceError = class extends Error {
2
+ constructor(message) {
3
+ super(message);
4
+ this.name = "TenaceError";
5
+ }
6
+ };
7
+ var TimeoutError = class extends TenaceError {
8
+ constructor(message = "Operation timed out") {
9
+ super(message);
10
+ this.name = "TimeoutError";
11
+ }
12
+ };
13
+ var CancelledError = class extends TenaceError {
14
+ constructor(message = "Operation was cancelled") {
15
+ super(message);
16
+ this.name = "CancelledError";
17
+ }
18
+ };
19
+ var CircuitOpenError = class extends TenaceError {
20
+ constructor(message = "Circuit breaker is open") {
21
+ super(message);
22
+ this.name = "CircuitOpenError";
23
+ }
24
+ };
25
+ var CircuitIsolatedError = class extends TenaceError {
26
+ constructor(message = "Circuit breaker is isolated") {
27
+ super(message);
28
+ this.name = "CircuitIsolatedError";
29
+ }
30
+ };
31
+ var BulkheadFullError = class extends TenaceError {
32
+ constructor(message = "Bulkhead capacity exceeded") {
33
+ super(message);
34
+ this.name = "BulkheadFullError";
35
+ }
36
+ };
37
+ var AbortError = class extends TenaceError {
38
+ constructor(message = "Operation aborted") {
39
+ super(message);
40
+ this.name = "AbortError";
41
+ }
42
+ };
43
+ var RateLimitError = class extends TenaceError {
44
+ retryAfterMs;
45
+ remaining;
46
+ constructor(options) {
47
+ super(options.message ?? `Rate limit exceeded. Retry after ${options.retryAfterMs}ms`);
48
+ this.name = "RateLimitError";
49
+ this.retryAfterMs = options.retryAfterMs;
50
+ this.remaining = options.remaining ?? 0;
51
+ }
52
+ };
53
+ var LockNotAcquiredError = class extends TenaceError {
54
+ key;
55
+ constructor(options) {
56
+ super(options.message ?? `Failed to acquire lock for key "${options.key}"`);
57
+ this.name = "LockNotAcquiredError";
58
+ this.key = options.key;
59
+ }
60
+ };
61
+ var WaitForTimeoutError = class extends TenaceError {
62
+ constructor(message = "Condition was not met within the timeout period") {
63
+ super(message);
64
+ this.name = "WaitForTimeoutError";
65
+ }
66
+ };
67
+ export { CircuitOpenError as a, TenaceError as c, CircuitIsolatedError as i, TimeoutError as l, BulkheadFullError as n, LockNotAcquiredError as o, CancelledError as r, RateLimitError as s, AbortError as t, WaitForTimeoutError as u };
@@ -0,0 +1,31 @@
1
+ import type { IPolicy } from 'cockatiel';
2
+ import type { CacheOptions } from '../adapters/cache/types.ts';
3
+ import type { RateLimitOptions } from '../adapters/rate_limiter/types.ts';
4
+ import type { DistributedLockOptions } from '../adapters/lock/types.ts';
5
+ export interface CachePolicyOptions extends CacheOptions {
6
+ optional?: boolean;
7
+ }
8
+ export interface RateLimitPolicyOptions extends RateLimitOptions {
9
+ optional?: boolean;
10
+ }
11
+ export interface LockPolicyOptions extends DistributedLockOptions {
12
+ optional?: boolean;
13
+ }
14
+ /**
15
+ * Creates a cache policy that integrates into the cockatiel pipeline.
16
+ * - On entry: check cache, return if hit
17
+ * - On exit (success): store in cache
18
+ */
19
+ export declare function createCachePolicy<T>(options: CachePolicyOptions): IPolicy;
20
+ /**
21
+ * Creates a rate limit policy that integrates into the cockatiel pipeline.
22
+ * - Check rate limit before execution
23
+ * - Throw RateLimitError if exceeded
24
+ */
25
+ export declare function createRateLimitPolicy<T>(options: RateLimitPolicyOptions): IPolicy;
26
+ /**
27
+ * Creates a distributed lock policy that integrates into the cockatiel pipeline.
28
+ * - Acquire lock before execution
29
+ * - Release lock after execution (success or failure)
30
+ */
31
+ export declare function createLockPolicy<T>(options: LockPolicyOptions): IPolicy;
@@ -0,0 +1,18 @@
1
+ import { ConsecutiveBreaker, CountBreaker, type IPolicy, SamplingBreaker } from 'cockatiel';
2
+ import type { BreakerConfig, CircuitBreakerConfig, RetryConfig } from '../types/types.ts';
3
+ /**
4
+ * Wraps cockatiel errors into tenace errors
5
+ */
6
+ export declare function wrapError(error: unknown): Error;
7
+ /**
8
+ * Creates a breaker instance from config
9
+ */
10
+ export declare function createBreaker(config: BreakerConfig): ConsecutiveBreaker | CountBreaker | SamplingBreaker;
11
+ /**
12
+ * Creates a circuit breaker cockatiel policy with hooks and telemetry
13
+ */
14
+ export declare function createCircuitBreakerPolicy(config: CircuitBreakerConfig): IPolicy;
15
+ /**
16
+ * Creates a retry cockatiel policy with telemetry
17
+ */
18
+ export declare function createRetryPolicy(config: RetryConfig): IPolicy;
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Telemetry utilities for emitting span events.
3
+ * Only emits events if OpenTelemetry is available and a span is active.
4
+ * Zero overhead if OTel is not installed or no span is active.
5
+ */
6
+ /**
7
+ * Try to load OpenTelemetry API. Safe to call multiple times.
8
+ */
9
+ export declare function loadOtelApi(): Promise<typeof import('@opentelemetry/api') | undefined>;
10
+ /**
11
+ * Get the currently active span, if any.
12
+ */
13
+ export declare function getActiveSpan(): import("@opentelemetry/api").Span | undefined;
14
+ /**
15
+ * Check if telemetry is available (OTel loaded and span active).
16
+ */
17
+ export declare function hasTelemetry(): boolean;
18
+ /**
19
+ * Emit a span event with attributes.
20
+ */
21
+ export declare function emitEvent(name: string, attributes?: Record<string, string | number | boolean>): void;
22
+ /**
23
+ * Set span attributes.
24
+ */
25
+ export declare function setAttributes(attributes: Record<string, string | number | boolean>): void;
26
+ type SpanAttributes = Record<string, string | number | boolean>;
27
+ /**
28
+ * Create a child span and execute the callback within it.
29
+ * Handles both sync and async callbacks, sets proper status, and records exceptions.
30
+ * No-op if OTel is not loaded.
31
+ */
32
+ export declare function recordSpan<T>(name: string, fn: () => T | Promise<T>, attributes?: SpanAttributes): T | Promise<T>;
33
+ export declare const TelemetryEvents: {
34
+ readonly RETRY_ATTEMPT: "tenace.retry";
35
+ readonly RETRY_SUCCESS: "tenace.retry.success";
36
+ readonly RETRY_EXHAUSTED: "tenace.retry.exhausted";
37
+ readonly TIMEOUT: "tenace.timeout";
38
+ readonly CIRCUIT_OPEN: "tenace.circuit_breaker.open";
39
+ readonly CIRCUIT_CLOSE: "tenace.circuit_breaker.close";
40
+ readonly CIRCUIT_HALF_OPEN: "tenace.circuit_breaker.half_open";
41
+ readonly BULKHEAD_REJECTED: "tenace.bulkhead.rejected";
42
+ readonly CACHE_HIT: "tenace.cache.hit";
43
+ readonly CACHE_MISS: "tenace.cache.miss";
44
+ readonly RATE_LIMIT_REJECTED: "tenace.rate_limit.rejected";
45
+ readonly LOCK_ACQUIRED: "tenace.lock.acquired";
46
+ readonly LOCK_NOT_ACQUIRED: "tenace.lock.not_acquired";
47
+ readonly LOCK_RELEASED: "tenace.lock.released";
48
+ readonly FALLBACK_TRIGGERED: "tenace.fallback";
49
+ };
50
+ export {};
@@ -0,0 +1,176 @@
1
+ import type { TenaceFunction, WaitForOptions } from './types/types.ts';
2
+ import type { GlobalChaosConfig } from './chaos/types.ts';
3
+ import type { TenacePlugin } from './plugin.ts';
4
+ import { TenacePolicy } from './tenace_policy.ts';
5
+ import { TenaceBuilder } from './tenace_builder.ts';
6
+ import { CollectionBuilder } from './collection.ts';
7
+ export { Semaphore, semaphore } from './semaphore.ts';
8
+ export { CollectionBuilder } from './collection.ts';
9
+ export { configStore } from './config.ts';
10
+ export { backoff } from './backoff.ts';
11
+ export { use, getPlugins, clearPlugins, registerHook } from './plugin.ts';
12
+ /**
13
+ * Main entry point for creating resilient operations
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * // Simple call with chained methods
18
+ * const result = await Tenace
19
+ * .call(() => myService.doThing())
20
+ * .withTimeout(1000)
21
+ * .withRetry(3)
22
+ * .withExponentialBackoff(100, 2000)
23
+ * .withCircuitBreaker(5, 10000)
24
+ * .withFallback(() => defaultValue)
25
+ * .execute()
26
+ *
27
+ * // Reusable policy
28
+ * const policy = Tenace.policy()
29
+ * .withTimeout(1000)
30
+ * .withRetry(3)
31
+ * .withCircuitBreaker(5, 10000)
32
+ *
33
+ * await policy.call(() => fetchUser())
34
+ * await policy.call(() => fetchPosts())
35
+ *
36
+ * // Execute collection of tasks with concurrency
37
+ * const results = await Tenace.all([
38
+ * () => fetchUser(1),
39
+ * () => fetchUser(2),
40
+ * ])
41
+ * .withConcurrency(5)
42
+ * .withRetryPerTask(2)
43
+ * .execute()
44
+ *
45
+ * // Map over items with concurrency
46
+ * const users = await Tenace.map([1, 2, 3], (id) => fetchUser(id))
47
+ * .withConcurrency(5)
48
+ * .execute()
49
+ * ```
50
+ */
51
+ export declare const Tenace: {
52
+ /**
53
+ * Creates a resilience builder for the given function
54
+ */
55
+ call<T>(fn: TenaceFunction<T>): TenaceBuilder<T>;
56
+ /**
57
+ * Creates a reusable resilience policy
58
+ */
59
+ policy(): TenacePolicy;
60
+ /**
61
+ * Convenience method to wrap a function with a policy
62
+ */
63
+ wrap<T>(fn: TenaceFunction<T>, policy: TenacePolicy): () => Promise<T>;
64
+ /**
65
+ * Execute a collection of async tasks with concurrency control.
66
+ *
67
+ * @example
68
+ * ```ts
69
+ * const results = await Tenace.all([
70
+ * () => fetchUser(1),
71
+ * () => fetchUser(2),
72
+ * () => fetchUser(3),
73
+ * ])
74
+ * .withConcurrency(2)
75
+ * .withRetryPerTask(3)
76
+ * .withTimeoutPerTask(5000)
77
+ * .execute()
78
+ * ```
79
+ */
80
+ all<T>(tasks: Array<() => Promise<T>>): CollectionBuilder<T>;
81
+ /**
82
+ * Map over items and execute with concurrency control.
83
+ *
84
+ * @example
85
+ * ```ts
86
+ * const users = await Tenace.map([1, 2, 3], async (id) => {
87
+ * return fetchUser(id)
88
+ * })
89
+ * .withConcurrency(5)
90
+ * .execute()
91
+ * ```
92
+ */
93
+ map<TInput, TOutput>(items: Iterable<TInput>, mapper: (item: TInput, index: number) => Promise<TOutput>): CollectionBuilder<TOutput>;
94
+ /**
95
+ * Wait for a condition to become true by polling at regular intervals.
96
+ *
97
+ * @example
98
+ * ```ts
99
+ * // Wait for a service to be healthy
100
+ * await Tenace.waitFor(() => isServiceHealthy(), {
101
+ * interval: '1s',
102
+ * timeout: '30s',
103
+ * })
104
+ *
105
+ * // Wait for a database to be ready
106
+ * await Tenace.waitFor(async () => {
107
+ * try {
108
+ * await db.ping()
109
+ * return true
110
+ * } catch {
111
+ * return false
112
+ * }
113
+ * }, { timeout: '1m' })
114
+ * ```
115
+ */
116
+ waitFor(condition: () => boolean | Promise<boolean>, options?: WaitForOptions): Promise<void>;
117
+ /**
118
+ * Chaos engineering utilities for testing resilience.
119
+ *
120
+ * @example
121
+ * ```ts
122
+ * // Enable global chaos (affects all Tenace calls)
123
+ * Tenace.chaos.enable({ fault: 1 }) // 100% fault rate
124
+ * Tenace.chaos.enable({ latency: 500 }) // 500ms delay
125
+ * Tenace.chaos.enable({
126
+ * fault: { rate: 0.1, error: new Error('Random') },
127
+ * latency: { rate: 0.2, delay: { min: 100, max: 2000 } }
128
+ * })
129
+ *
130
+ * // Disable chaos
131
+ * Tenace.chaos.disable()
132
+ * ```
133
+ */
134
+ chaos: {
135
+ /**
136
+ * Enable global chaos injection.
137
+ * Supports shorthands:
138
+ * - `{ fault: 1 }` = 100% fault rate with default error
139
+ * - `{ latency: 500 }` = 500ms fixed delay with rate=1
140
+ */
141
+ enable: (config: GlobalChaosConfig) => void;
142
+ /**
143
+ * Disable global chaos injection
144
+ */
145
+ disable: () => void;
146
+ /**
147
+ * Check if global chaos is currently enabled
148
+ */
149
+ isEnabled: () => boolean;
150
+ };
151
+ /**
152
+ * Register global hooks for resilience events.
153
+ * Each method returns an unsubscribe function.
154
+ *
155
+ * @example
156
+ * ```ts
157
+ * const unsubscribe = Tenace.hooks.onRetry((e) => console.log(`Retry ${e.attempt}`))
158
+ * // Later: unsubscribe()
159
+ * ```
160
+ */
161
+ hooks: {
162
+ onRetry: (fn: NonNullable<TenacePlugin["onRetry"]>) => () => void;
163
+ onRetryExhausted: (fn: NonNullable<TenacePlugin["onRetryExhausted"]>) => () => void;
164
+ onTimeout: (fn: NonNullable<TenacePlugin["onTimeout"]>) => () => void;
165
+ onCircuitOpened: (fn: NonNullable<TenacePlugin["onCircuitOpened"]>) => () => void;
166
+ onCircuitClosed: (fn: NonNullable<TenacePlugin["onCircuitClosed"]>) => () => void;
167
+ onCircuitHalfOpened: (fn: NonNullable<TenacePlugin["onCircuitHalfOpened"]>) => () => void;
168
+ onBulkheadRejected: (fn: NonNullable<TenacePlugin["onBulkheadRejected"]>) => () => void;
169
+ onCacheHit: (fn: NonNullable<TenacePlugin["onCacheHit"]>) => () => void;
170
+ onCacheMiss: (fn: NonNullable<TenacePlugin["onCacheMiss"]>) => () => void;
171
+ onRateLimitRejected: (fn: NonNullable<TenacePlugin["onRateLimitRejected"]>) => () => void;
172
+ onFallback: (fn: NonNullable<TenacePlugin["onFallback"]>) => () => void;
173
+ onLockAcquired: (fn: NonNullable<TenacePlugin["onLockAcquired"]>) => () => void;
174
+ onLockRejected: (fn: NonNullable<TenacePlugin["onLockRejected"]>) => () => void;
175
+ };
176
+ };