@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.
- package/README.md +1034 -0
- package/build/src/adapters/cache/memory.d.ts +23 -0
- package/build/src/adapters/cache/memory.js +2 -0
- package/build/src/adapters/cache/types.d.ts +56 -0
- package/build/src/adapters/cache/types.js +1 -0
- package/build/src/adapters/lock/types.d.ts +104 -0
- package/build/src/adapters/lock/types.js +1 -0
- package/build/src/adapters/rate_limiter/memory.d.ts +14 -0
- package/build/src/adapters/rate_limiter/memory.js +2 -0
- package/build/src/adapters/rate_limiter/types.d.ts +101 -0
- package/build/src/adapters/rate_limiter/types.js +1 -0
- package/build/src/backoff.d.ts +79 -0
- package/build/src/chaos/manager.d.ts +29 -0
- package/build/src/chaos/policies.d.ts +10 -0
- package/build/src/chaos/types.d.ts +75 -0
- package/build/src/collection.d.ts +81 -0
- package/build/src/config.d.ts +38 -0
- package/build/src/errors/errors.d.ts +79 -0
- package/build/src/errors/main.d.ts +1 -0
- package/build/src/errors/main.js +2 -0
- package/build/src/errors-BODHnryv.js +67 -0
- package/build/src/internal/adapter_policies.d.ts +31 -0
- package/build/src/internal/cockatiel_factories.d.ts +18 -0
- package/build/src/internal/telemetry.d.ts +50 -0
- package/build/src/main.d.ts +176 -0
- package/build/src/main.js +1125 -0
- package/build/src/memory-DWyezb1O.js +37 -0
- package/build/src/memory-DXkg8s6y.js +60 -0
- package/build/src/plugin.d.ts +30 -0
- package/build/src/policy_configurator.d.ts +108 -0
- package/build/src/semaphore.d.ts +71 -0
- package/build/src/tenace_builder.d.ts +22 -0
- package/build/src/tenace_policy.d.ts +41 -0
- package/build/src/types/backoff.d.ts +57 -0
- package/build/src/types/collection.d.ts +46 -0
- package/build/src/types/main.d.ts +5 -0
- package/build/src/types/main.js +1 -0
- package/build/src/types/plugin.d.ts +61 -0
- package/build/src/types/types.d.ts +241 -0
- package/build/src/wait_for.d.ts +23 -0
- package/package.json +135 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Duration can be specified as milliseconds (number) or as a string like '5s', '1m', '500ms'
|
|
3
|
+
*/
|
|
4
|
+
export type Duration = string | number;
|
|
5
|
+
/**
|
|
6
|
+
* Parse a duration value to milliseconds
|
|
7
|
+
*/
|
|
8
|
+
export declare function parseDuration(duration: Duration): number;
|
|
9
|
+
/**
|
|
10
|
+
* Context passed to the function being executed
|
|
11
|
+
*/
|
|
12
|
+
export interface TenaceContext {
|
|
13
|
+
signal: AbortSignal;
|
|
14
|
+
/**
|
|
15
|
+
* Current attempt number (starts at 0, increments on each retry)
|
|
16
|
+
*/
|
|
17
|
+
attempt: number;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Function that can be executed with resilience patterns
|
|
21
|
+
*/
|
|
22
|
+
export type TenaceFunction<T> = (context: TenaceContext) => T | Promise<T>;
|
|
23
|
+
/**
|
|
24
|
+
* Delay function for retry - receives attempt number (starting at 1) and the error, returns delay in ms
|
|
25
|
+
*/
|
|
26
|
+
export type RetryDelayFunction = (attempt: number, error: Error) => number;
|
|
27
|
+
/**
|
|
28
|
+
* Event passed to retry hooks
|
|
29
|
+
*/
|
|
30
|
+
export interface RetryEvent {
|
|
31
|
+
attempt: number;
|
|
32
|
+
delay: number;
|
|
33
|
+
error: Error;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Configuration for retry behavior
|
|
37
|
+
*/
|
|
38
|
+
export interface RetryConfig {
|
|
39
|
+
/**
|
|
40
|
+
* Maximum number of retry attempts (default: 3)
|
|
41
|
+
*/
|
|
42
|
+
times?: number;
|
|
43
|
+
/**
|
|
44
|
+
* Delay between retries in ms, or a function that returns the delay.
|
|
45
|
+
* Can be a number, a string duration ('1s', '500ms'), or a function.
|
|
46
|
+
* The function receives the attempt number (starting at 1) and the error.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* // Fixed delay (number)
|
|
50
|
+
* delay: 1000
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* // Fixed delay (string)
|
|
54
|
+
* delay: '1s'
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* // Exponential backoff
|
|
58
|
+
* delay: (attempt) => Math.min(1000 * 2 ** attempt, 30000)
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* // Dynamic delay based on error (e.g., retry-after header)
|
|
62
|
+
* delay: (attempt, error) => error instanceof RateLimitError ? error.retryAfter : 1000
|
|
63
|
+
*/
|
|
64
|
+
delay?: Duration | RetryDelayFunction;
|
|
65
|
+
/**
|
|
66
|
+
* Only retry if this function returns true
|
|
67
|
+
*/
|
|
68
|
+
retryIf?: (error: Error) => boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Abort retrying if this function returns true
|
|
71
|
+
*/
|
|
72
|
+
abortIf?: (error: Error) => boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Called when a retry is triggered
|
|
75
|
+
*/
|
|
76
|
+
onRetry?: (event: RetryEvent) => void;
|
|
77
|
+
/**
|
|
78
|
+
* Called when all retries are exhausted
|
|
79
|
+
*/
|
|
80
|
+
onRetryExhausted?: (event: {
|
|
81
|
+
error: Error;
|
|
82
|
+
}) => void;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Hooks for circuit breaker events
|
|
86
|
+
*/
|
|
87
|
+
export interface CircuitBreakerHooks {
|
|
88
|
+
onOpen?: () => void;
|
|
89
|
+
onClose?: () => void;
|
|
90
|
+
onHalfOpen?: () => void;
|
|
91
|
+
onStateChange?: (state: 'open' | 'closed' | 'half-open') => void;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Configuration for circuit breaker
|
|
95
|
+
*/
|
|
96
|
+
export interface CircuitBreakerConfig {
|
|
97
|
+
halfOpenAfterMs: number;
|
|
98
|
+
breaker: BreakerConfig;
|
|
99
|
+
hooks?: CircuitBreakerHooks;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Breaker strategy configuration
|
|
103
|
+
*/
|
|
104
|
+
export type BreakerConfig = {
|
|
105
|
+
kind: 'consecutive';
|
|
106
|
+
threshold: number;
|
|
107
|
+
} | {
|
|
108
|
+
kind: 'count';
|
|
109
|
+
threshold: number;
|
|
110
|
+
size: number;
|
|
111
|
+
minimumNumberOfCalls?: number;
|
|
112
|
+
} | {
|
|
113
|
+
kind: 'sampling';
|
|
114
|
+
threshold: number;
|
|
115
|
+
durationMs: number;
|
|
116
|
+
minimumRps?: number;
|
|
117
|
+
};
|
|
118
|
+
/**
|
|
119
|
+
* Configuration for OpenTelemetry span
|
|
120
|
+
*/
|
|
121
|
+
export interface SpanConfig {
|
|
122
|
+
name: string;
|
|
123
|
+
attributes?: Record<string, string | number | boolean>;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Timeout strategy
|
|
127
|
+
*/
|
|
128
|
+
export type TimeoutStrategy = 'aggressive' | 'cooperative';
|
|
129
|
+
/**
|
|
130
|
+
* Circuit breaker state
|
|
131
|
+
*/
|
|
132
|
+
export type CircuitState = 'open' | 'closed' | 'half-open';
|
|
133
|
+
/**
|
|
134
|
+
* Handle returned by circuit breaker isolation.
|
|
135
|
+
* Call dispose() to release the isolation.
|
|
136
|
+
*/
|
|
137
|
+
export interface IsolationHandle {
|
|
138
|
+
dispose(): void;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Accessor for circuit breaker state and control.
|
|
142
|
+
* Allows querying state, manual isolation, and reset.
|
|
143
|
+
*/
|
|
144
|
+
export interface CircuitBreakerAccessor {
|
|
145
|
+
/**
|
|
146
|
+
* Current state of the circuit breaker
|
|
147
|
+
*/
|
|
148
|
+
readonly state: CircuitState;
|
|
149
|
+
/**
|
|
150
|
+
* Whether the circuit is currently open (failing fast)
|
|
151
|
+
*/
|
|
152
|
+
readonly isOpen: boolean;
|
|
153
|
+
/**
|
|
154
|
+
* Whether the circuit is currently closed (allowing requests)
|
|
155
|
+
*/
|
|
156
|
+
readonly isClosed: boolean;
|
|
157
|
+
/**
|
|
158
|
+
* Whether the circuit is currently half-open (testing recovery)
|
|
159
|
+
*/
|
|
160
|
+
readonly isHalfOpen: boolean;
|
|
161
|
+
/**
|
|
162
|
+
* Manually isolate (force open) the circuit breaker.
|
|
163
|
+
* All calls will fail with CircuitIsolatedError until disposed.
|
|
164
|
+
* Returns a handle that must be disposed to release isolation.
|
|
165
|
+
*
|
|
166
|
+
* @example
|
|
167
|
+
* ```ts
|
|
168
|
+
* const handle = policy.circuitBreaker.isolate()
|
|
169
|
+
* // ... circuit is now forced open
|
|
170
|
+
* handle.dispose() // release isolation
|
|
171
|
+
* ```
|
|
172
|
+
*/
|
|
173
|
+
isolate(): IsolationHandle;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Types of policy layers that can be added to the resilience pipeline
|
|
177
|
+
*/
|
|
178
|
+
export type PolicyLayerType = 'timeout' | 'retry' | 'circuitBreaker' | 'bulkhead' | 'fallback' | 'span' | 'cache' | 'rateLimit' | 'distributedLock' | 'chaosFault' | 'chaosLatency';
|
|
179
|
+
/**
|
|
180
|
+
* Configuration for timeout layer
|
|
181
|
+
*/
|
|
182
|
+
export interface TimeoutLayerConfig {
|
|
183
|
+
durationMs: number;
|
|
184
|
+
strategy: 'aggressive' | 'cooperative';
|
|
185
|
+
onTimeout?: () => void;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Configuration for bulkhead layer
|
|
189
|
+
*/
|
|
190
|
+
export interface BulkheadLayerConfig {
|
|
191
|
+
limit: number;
|
|
192
|
+
queue?: number;
|
|
193
|
+
onRejected?: () => void;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Configuration for fallback layer
|
|
197
|
+
*/
|
|
198
|
+
export interface FallbackLayerConfig<T> {
|
|
199
|
+
fn: () => T | Promise<T>;
|
|
200
|
+
onFallback?: () => void;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* A layer in the resilience policy stack.
|
|
204
|
+
* Layers are executed in order (first added = outermost).
|
|
205
|
+
*/
|
|
206
|
+
export interface PolicyLayer {
|
|
207
|
+
type: PolicyLayerType;
|
|
208
|
+
config: unknown;
|
|
209
|
+
/**
|
|
210
|
+
* Pre-created cockatiel policy instance (used for shared CB/Bulkhead in ResiliencePolicy)
|
|
211
|
+
*/
|
|
212
|
+
instance?: unknown;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Options for waitFor polling
|
|
216
|
+
*/
|
|
217
|
+
export interface WaitForOptions {
|
|
218
|
+
/**
|
|
219
|
+
* Interval between checks (default: 100ms)
|
|
220
|
+
*/
|
|
221
|
+
interval?: Duration;
|
|
222
|
+
/**
|
|
223
|
+
* Maximum time to wait before throwing WaitForTimeoutError (default: 10s)
|
|
224
|
+
*/
|
|
225
|
+
timeout?: Duration;
|
|
226
|
+
/**
|
|
227
|
+
* Custom message for timeout error
|
|
228
|
+
*/
|
|
229
|
+
message?: string;
|
|
230
|
+
/**
|
|
231
|
+
* Whether to run the check immediately rather than starting by waiting `interval` milliseconds.
|
|
232
|
+
* Useful for when the check, if run immediately, would likely return `false`.
|
|
233
|
+
* In this scenario, set `before` to `false`.
|
|
234
|
+
* (default: true)
|
|
235
|
+
*/
|
|
236
|
+
before?: boolean;
|
|
237
|
+
/**
|
|
238
|
+
* An AbortSignal to cancel the wait operation
|
|
239
|
+
*/
|
|
240
|
+
signal?: AbortSignal;
|
|
241
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type WaitForOptions } from './types/types.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Wait for a condition to become true by polling at regular intervals.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```ts
|
|
7
|
+
* // Wait for a service to be healthy
|
|
8
|
+
* await waitFor(() => isServiceHealthy(), {
|
|
9
|
+
* interval: '1s',
|
|
10
|
+
* timeout: '30s',
|
|
11
|
+
* })
|
|
12
|
+
*
|
|
13
|
+
* // Wait for a resource with custom message
|
|
14
|
+
* await waitFor(
|
|
15
|
+
* async () => {
|
|
16
|
+
* const status = await checkDatabase()
|
|
17
|
+
* return status === 'ready'
|
|
18
|
+
* },
|
|
19
|
+
* { timeout: '1m', message: 'Database did not become ready' }
|
|
20
|
+
* )
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export declare function waitFor(condition: () => boolean | Promise<boolean>, options?: WaitForOptions): Promise<void>;
|
package/package.json
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@julr/tenace",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "1.0.0-next.0",
|
|
5
|
+
"packageManager": "pnpm@10.24.0",
|
|
6
|
+
"description": "A Node.js library to make any call resilient with a fluent and simple API",
|
|
7
|
+
"author": "Julien Ripouteau <julien@ripouteau.com>",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"keywords": [
|
|
10
|
+
"resilience",
|
|
11
|
+
"tenace",
|
|
12
|
+
"retry",
|
|
13
|
+
"timeout",
|
|
14
|
+
"circuit-breaker",
|
|
15
|
+
"backoff",
|
|
16
|
+
"fault-tolerance"
|
|
17
|
+
],
|
|
18
|
+
"exports": {
|
|
19
|
+
".": "./build/src/main.js",
|
|
20
|
+
"./types": "./build/src/types/main.js",
|
|
21
|
+
"./errors": "./build/src/errors/main.js",
|
|
22
|
+
"./adapters/cache/types": "./build/src/adapters/cache/types.js",
|
|
23
|
+
"./adapters/cache/memory": "./build/src/adapters/cache/memory.js",
|
|
24
|
+
"./adapters/rate-limiter/types": "./build/src/adapters/rate_limiter/types.js",
|
|
25
|
+
"./adapters/rate-limiter/memory": "./build/src/adapters/rate_limiter/memory.js",
|
|
26
|
+
"./adapters/lock/types": "./build/src/adapters/lock/types.js"
|
|
27
|
+
},
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=24"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"build",
|
|
33
|
+
"!build/bin",
|
|
34
|
+
"!build/tests",
|
|
35
|
+
"!build/examples"
|
|
36
|
+
],
|
|
37
|
+
"scripts": {
|
|
38
|
+
"clean": "del-cli build",
|
|
39
|
+
"typecheck": "tsc --noEmit",
|
|
40
|
+
"precompile": "pnpm lint && pnpm clean",
|
|
41
|
+
"compile": "tsdown && tsc --emitDeclarationOnly --declaration",
|
|
42
|
+
"build": "pnpm compile",
|
|
43
|
+
"test": "node --import=@poppinss/ts-exec --enable-source-maps bin/test.ts",
|
|
44
|
+
"format": "oxfmt",
|
|
45
|
+
"lint": "oxlint",
|
|
46
|
+
"lint:fix": "oxlint --fix",
|
|
47
|
+
"checks": "pnpm typecheck && pnpm test && pnpm lint",
|
|
48
|
+
"release": "release-it",
|
|
49
|
+
"version": "pnpm build",
|
|
50
|
+
"prepublishOnly": "pnpm build"
|
|
51
|
+
},
|
|
52
|
+
"peerDependencies": {
|
|
53
|
+
"@opentelemetry/api": "^1.0.0"
|
|
54
|
+
},
|
|
55
|
+
"peerDependenciesMeta": {
|
|
56
|
+
"@opentelemetry/api": {
|
|
57
|
+
"optional": true
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"prettier": "@julr/tooling-configs/prettier",
|
|
61
|
+
"dependencies": {
|
|
62
|
+
"@julr/utils": "^1.9.0",
|
|
63
|
+
"bentocache": "^1.5.0",
|
|
64
|
+
"cockatiel": "^3.2.1",
|
|
65
|
+
"p-wait-for": "^6.0.0",
|
|
66
|
+
"rate-limiter-flexible": "^9.0.0"
|
|
67
|
+
},
|
|
68
|
+
"devDependencies": {
|
|
69
|
+
"@japa/assert": "^4.1.1",
|
|
70
|
+
"@japa/runner": "^4.4.0",
|
|
71
|
+
"@julr/tooling-configs": "^3.3.0",
|
|
72
|
+
"@opentelemetry/api": "^1.9.0",
|
|
73
|
+
"@poppinss/ts-exec": "^1.4.1",
|
|
74
|
+
"@release-it/conventional-changelog": "^10.0.3",
|
|
75
|
+
"@types/node": "^24.10.1",
|
|
76
|
+
"@verrou/core": "^0.5.2",
|
|
77
|
+
"del-cli": "^7.0.0",
|
|
78
|
+
"neverthrow": "^8.2.0",
|
|
79
|
+
"oxfmt": "^0.17.0",
|
|
80
|
+
"oxlint": "^1.32.0",
|
|
81
|
+
"oxlint-tsgolint": "^0.9.0",
|
|
82
|
+
"release-it": "^19.1.0",
|
|
83
|
+
"tsdown": "^0.17.4",
|
|
84
|
+
"typescript": "^5.9.3"
|
|
85
|
+
},
|
|
86
|
+
"tsdown": {
|
|
87
|
+
"entry": [
|
|
88
|
+
"./src/main.ts",
|
|
89
|
+
"./src/types/main.ts",
|
|
90
|
+
"./src/errors/main.ts",
|
|
91
|
+
"./src/adapters/cache/types.ts",
|
|
92
|
+
"./src/adapters/cache/memory.ts",
|
|
93
|
+
"./src/adapters/rate_limiter/types.ts",
|
|
94
|
+
"./src/adapters/rate_limiter/memory.ts",
|
|
95
|
+
"./src/adapters/lock/types.ts"
|
|
96
|
+
],
|
|
97
|
+
"outDir": "./build/src",
|
|
98
|
+
"clean": true,
|
|
99
|
+
"format": "esm",
|
|
100
|
+
"minify": "dce-only",
|
|
101
|
+
"fixedExtension": false,
|
|
102
|
+
"dts": false,
|
|
103
|
+
"treeshake": false,
|
|
104
|
+
"sourcemaps": false,
|
|
105
|
+
"target": "esnext"
|
|
106
|
+
},
|
|
107
|
+
"release-it": {
|
|
108
|
+
"git": {
|
|
109
|
+
"requireCleanWorkingDir": true,
|
|
110
|
+
"requireUpstream": true,
|
|
111
|
+
"commitMessage": "chore(release): ${version}",
|
|
112
|
+
"tagAnnotation": "v${version}",
|
|
113
|
+
"push": true,
|
|
114
|
+
"tagName": "v${version}"
|
|
115
|
+
},
|
|
116
|
+
"github": {
|
|
117
|
+
"release": true
|
|
118
|
+
},
|
|
119
|
+
"npm": {
|
|
120
|
+
"publish": true,
|
|
121
|
+
"skipChecks": true,
|
|
122
|
+
"tag": "beta"
|
|
123
|
+
},
|
|
124
|
+
"plugins": {
|
|
125
|
+
"@release-it/conventional-changelog": {
|
|
126
|
+
"preset": {
|
|
127
|
+
"name": "angular"
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
"publishConfig": {
|
|
133
|
+
"access": "public"
|
|
134
|
+
}
|
|
135
|
+
}
|