@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,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
+ }