tryo 0.1.0 → 0.13.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/LICENSE +19 -4
- package/README.md +182 -7
- package/dist/index.cjs +1 -0
- package/dist/index.d.cts +395 -0
- package/dist/index.d.ts +395 -0
- package/dist/index.js +1 -0
- package/package.json +57 -26
package/LICENSE
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
1
|
+
MIT License
|
|
3
2
|
|
|
4
|
-
|
|
3
|
+
Copyright (c) 2026 Sebastian Sala
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,15 +1,190 @@
|
|
|
1
|
-
#
|
|
1
|
+
# tryo
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Run sync/async functions and return a typed Result instead of throwing. `tryo` provides powerful error normalization, retry logic, concurrency control, and circuit breakers with a premium developer experience.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
4
6
|
|
|
5
7
|
```bash
|
|
6
|
-
|
|
8
|
+
npm install tryo
|
|
9
|
+
# or
|
|
10
|
+
bun add tryo
|
|
7
11
|
```
|
|
8
12
|
|
|
9
|
-
|
|
13
|
+
## Basic Usage
|
|
10
14
|
|
|
11
|
-
|
|
12
|
-
|
|
15
|
+
You can use the top-level shortcuts for simple cases, or create a configured instance for complex scenarios.
|
|
16
|
+
|
|
17
|
+
### Using Shortcuts
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import { run } from 'tryo'
|
|
21
|
+
|
|
22
|
+
const result = await run(async () => {
|
|
23
|
+
const res = await fetch('/api/data')
|
|
24
|
+
if (!res.ok) throw new Error('Failed')
|
|
25
|
+
return res.json()
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
if (result.ok) {
|
|
29
|
+
console.log(result.data) // result.data is typed
|
|
30
|
+
} else {
|
|
31
|
+
console.error(result.error.code) // "HTTP", "UNKNOWN", etc.
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Using the Factory (Best for Apps)
|
|
36
|
+
|
|
37
|
+
Creating an instance allows you to shared configuration like retries, circuit breakers, and custom error rules across your app.
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import tryo from 'tryo'
|
|
41
|
+
|
|
42
|
+
const ex = tryo({
|
|
43
|
+
retry: {
|
|
44
|
+
maxRetries: 3,
|
|
45
|
+
strategy: RetryStrategies.exponential(100),
|
|
46
|
+
},
|
|
47
|
+
circuitBreaker: {
|
|
48
|
+
failureThreshold: 5,
|
|
49
|
+
resetTimeout: 10000,
|
|
50
|
+
},
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
const result = await ex.run(fetchData)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Error Handling
|
|
57
|
+
|
|
58
|
+
`tryo` normalizes all errors into a `TypedError` instance with a stable `code`.
|
|
59
|
+
|
|
60
|
+
### The `TypedError` Shape
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
type TypedError<Code extends string = string, Meta = unknown> = {
|
|
64
|
+
code: Code // Stable error code (e.g. "TIMEOUT", "HTTP")
|
|
65
|
+
message: string // Human-readable message
|
|
66
|
+
cause?: unknown // Original error
|
|
67
|
+
meta?: Meta // Extra metadata (optional)
|
|
68
|
+
status?: number // Optional HTTP status (if applicable)
|
|
69
|
+
retryable: boolean // Whether the error is safe to retry
|
|
70
|
+
timestamp: number // When the error occurred (ms)
|
|
71
|
+
stack?: string // Stack trace for debugging
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Default Rules
|
|
76
|
+
|
|
77
|
+
By default, `tryo` detects:
|
|
78
|
+
|
|
79
|
+
- **ABORTED**: Detects `AbortError`.
|
|
80
|
+
- **TIMEOUT**: Detects `TimeoutError`.
|
|
81
|
+
- **HTTP**: Detects status codes in error objects.
|
|
82
|
+
- **UNKNOWN**: Fallback for everything else.
|
|
83
|
+
|
|
84
|
+
### Custom Rules
|
|
85
|
+
|
|
86
|
+
Map specific exceptions to typed error codes using the `rules` option.
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
import tryo, { errorRule } from 'tryo'
|
|
90
|
+
|
|
91
|
+
const ex = tryo({
|
|
92
|
+
rules: [
|
|
93
|
+
errorRule
|
|
94
|
+
.when(e => e === 'unauthorized')
|
|
95
|
+
.toError(() => ({
|
|
96
|
+
code: 'AUTH_ERROR',
|
|
97
|
+
message: 'Please login',
|
|
98
|
+
})),
|
|
99
|
+
] as const,
|
|
100
|
+
})
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## API Reference
|
|
104
|
+
|
|
105
|
+
### `.run(task, options?)`
|
|
106
|
+
|
|
107
|
+
Executes a single task. The `options` can override the instance defaults (except `signal`, which must be passed per call).
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
const result = await ex.run(task, {
|
|
111
|
+
timeout: 5000,
|
|
112
|
+
signal: abortController.signal,
|
|
113
|
+
})
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### `.all(tasks, options?)`
|
|
117
|
+
|
|
118
|
+
Executes multiple tasks with **concurrency control**. Like `Promise.allSettled` but with retries, timeouts, and a worker pool.
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
const tasks = [() => job(1), () => job(2), () => job(3)]
|
|
122
|
+
|
|
123
|
+
// Execute 5-at-a-time
|
|
124
|
+
const results = await ex.all(tasks, { concurrency: 5 })
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### `.partitionAll(results)`
|
|
128
|
+
|
|
129
|
+
Utility to separate successes from failures after an `all()` call.
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
const { ok, failure, aborted, timeout } = ex.partitionAll(results)
|
|
133
|
+
|
|
134
|
+
console.log(`Successes: ${ok.length}`)
|
|
135
|
+
console.log(`Errors: ${failure.length}`)
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### `.runOrThrow(task, options?)`
|
|
139
|
+
|
|
140
|
+
Utility if you prefer exceptions but want the power of `tryo` (retries, breaker, normalization).
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
const data = await ex.runOrThrow(task) // Returns data or throws TypedError
|
|
13
144
|
```
|
|
14
145
|
|
|
15
|
-
|
|
146
|
+
## Advanced Features
|
|
147
|
+
|
|
148
|
+
### Concurrency
|
|
149
|
+
|
|
150
|
+
The `all()` method includes a worker pool that respects your `concurrency` limit and stops launching new tasks if the `signal` is aborted.
|
|
151
|
+
|
|
152
|
+
### Circuit Breaker
|
|
153
|
+
|
|
154
|
+
If your tasks fail repeatedly, the circuit breaker opens and prevents further calls to protect your downstream services.
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
const ex = tryo({
|
|
158
|
+
circuitBreaker: {
|
|
159
|
+
failureThreshold: 5,
|
|
160
|
+
resetTimeout: 30000,
|
|
161
|
+
halfOpenRequests: 2,
|
|
162
|
+
},
|
|
163
|
+
})
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Observability Hooks
|
|
167
|
+
|
|
168
|
+
Add hooks for logging or monitoring:
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
const ex = tryo({
|
|
172
|
+
hooks: {
|
|
173
|
+
onRetry: (attempt, error, delay) => console.log(`Retry ${attempt}...`),
|
|
174
|
+
onCircuitStateChange: (from, to) =>
|
|
175
|
+
console.log(`Breaker moved: ${from} -> ${to}`),
|
|
176
|
+
},
|
|
177
|
+
})
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Why tryo?
|
|
181
|
+
|
|
182
|
+
1. **No More Try/Catch**: Handle results as data.
|
|
183
|
+
2. **Concurrency Control**: Built-in worker pool for batch operations.
|
|
184
|
+
3. **Normalized Errors**: Stable codes instead of unreliable error messages.
|
|
185
|
+
4. **Resiliency**: Sophisticated retry strategies and circuit breakers out of the box.
|
|
186
|
+
5. **Type Safety**: Full TypeScript support with inference for custom error rules.
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
License: MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var D=Object.defineProperty;var ce=Object.getOwnPropertyDescriptor;var le=Object.getOwnPropertyNames;var me=Object.prototype.hasOwnProperty;var de=(e,r,t)=>r in e?D(e,r,{enumerable:!0,configurable:!0,writable:!0,value:t}):e[r]=t;var ye=(e,r)=>{for(var t in r)D(e,t,{get:r[t],enumerable:!0})},pe=(e,r,t,n)=>{if(r&&typeof r=="object"||typeof r=="function")for(let o of le(r))!me.call(e,o)&&o!==t&&D(e,o,{get:()=>r[o],enumerable:!(n=ce(r,o))||n.enumerable});return e};var fe=e=>pe(D({},"__esModule",{value:!0}),e);var m=(e,r,t)=>de(e,typeof r!="symbol"?r+"":r,t);var Ae={};ye(Ae,{RetryStrategies:()=>X,all:()=>ne,allOrThrow:()=>oe,asConcurrencyLimit:()=>B,asMilliseconds:()=>C,asPercentage:()=>J,asRetryCount:()=>R,asStatusCode:()=>K,errorRule:()=>G,orThrow:()=>te,run:()=>ee,runOrThrow:()=>re,tryo:()=>z});module.exports=fe(Ae);var T=class extends Error{constructor(t,n){var o;super(t);m(this,"cause");m(this,"meta");m(this,"status");m(this,"timestamp");m(this,"retryable");this.timestamp=Date.now(),this.retryable=(o=n==null?void 0:n.retryable)!=null?o:!0,this.name=this.constructor.name,this.cause=n==null?void 0:n.cause,this.meta=n==null?void 0:n.meta,this.status=n==null?void 0:n.status,Error.captureStackTrace&&Error.captureStackTrace(this,this.constructor)}is(t){return this.code===t}withMeta(t){return Object.assign(this,{meta:t})}withStatus(t){return Object.assign(this,{status:t})}withCause(t){return Object.assign(this,{cause:t})}withRetryable(t){return this.retryable=t,this}toJSON(){return{name:this.name,code:this.code,message:this.message,timestamp:this.timestamp,retryable:this.retryable,cause:this.cause,stack:this.stack}}},v=class extends T{constructor(t,n){super(`Operation timed out after ${t}ms`,{cause:n,retryable:!0});m(this,"code","TIMEOUT")}};var A=class extends T{constructor(t,n){super(`Circuit breaker is open, reset after ${t}ms`,{cause:n,retryable:!1});m(this,"code","CIRCUIT_OPEN")}},I=class extends T{constructor(t,n,o){super(t,{cause:o,meta:{validationErrors:n},retryable:!1});this.validationErrors=n;m(this,"code","VALIDATION")}};var F=class extends T{constructor(t,n){super(t,{cause:n});m(this,"code","UNKNOWN")}};var C=e=>{if(e<0||!Number.isFinite(e))throw new Error(`Invalid milliseconds: must be a non-negative finite number, got ${e}`);return e},R=e=>{if(e<0||!Number.isInteger(e))throw new Error(`Invalid retry count: must be a non-negative integer, got ${e}`);return e},B=e=>{if(e<1||!Number.isInteger(e))throw new Error(`Invalid concurrency limit: must be a positive integer, got ${e}`);return e},J=e=>{if(e<0||e>100||!Number.isFinite(e))throw new Error(`Invalid percentage: must be between 0 and 100, got ${e}`);return e},K=e=>{if(!Number.isInteger(e)||e<100||e>599)throw new Error(`Invalid status code: must be an integer between 100-599, got ${e}`);return e};var L=class{constructor(r){m(this,"state");m(this,"config");this.config={failureThreshold:R(r.failureThreshold),resetTimeout:C(r.resetTimeout),halfOpenRequests:R(r.halfOpenRequests),shouldCountAsFailure:r.shouldCountAsFailure},this.state={state:"closed",failureCount:0,halfOpenCount:0}}async canExecute(){if(this.updateStateIfNeeded(),this.state.state==="open")return!1;if(this.state.state==="half-open"){if(this.state.halfOpenCount>=this.config.halfOpenRequests)return!1;this.state={...this.state,halfOpenCount:this.state.halfOpenCount+1}}return!0}async recordSuccess(){switch(this.state.state){case"closed":this.state={...this.state,failureCount:0};break;case"half-open":this.state={state:"closed",failureCount:0,halfOpenCount:0};break;case"open":break}}async recordFailure(r){var o,a,i;if(!((i=(a=(o=this.config).shouldCountAsFailure)==null?void 0:a.call(o,r))!=null?i:!0))return;let n=new Date;switch(this.state.state){case"closed":{let s=this.state.failureCount+1;s>=this.config.failureThreshold?this.state={state:"open",failureCount:s,halfOpenCount:0,lastFailureTime:n,nextAttemptTime:new Date(n.getTime()+this.config.resetTimeout)}:this.state={...this.state,failureCount:s,lastFailureTime:n};break}case"half-open":this.state={state:"open",failureCount:this.state.failureCount+1,halfOpenCount:0,lastFailureTime:n,nextAttemptTime:new Date(n.getTime()+this.config.resetTimeout)};break;case"open":this.state={...this.state,lastFailureTime:n,nextAttemptTime:new Date(n.getTime()+this.config.resetTimeout)};break}}getState(){return this.updateStateIfNeeded(),{...this.state,canExecute:this.state.state!=="open"}}createOpenError(){let r=this.state.nextAttemptTime?C(Math.max(0,this.state.nextAttemptTime.getTime()-Date.now())):this.config.resetTimeout;return new A(r)}forceState(r){this.state={state:r,failureCount:0,halfOpenCount:0}}reset(){this.state={state:"closed",failureCount:0,halfOpenCount:0}}updateStateIfNeeded(){this.state.state==="open"&&this.state.nextAttemptTime&&new Date>=this.state.nextAttemptTime&&(this.state={...this.state,state:"half-open",halfOpenCount:0})}};var q=(e,r)=>t=>{for(let n of e){let o=n(t);if(o!==null)return o}return r(t)},Y=e=>r=>r instanceof T?r:r instanceof Error?new e(r.message,r):typeof r=="string"?new e(r):new e("Unknown error occurred",r);var Te=e=>{if(typeof e!="object"||e===null)return!1;let r=e;return typeof r.status=="number"||typeof r.statusCode=="number"},Ee=e=>{if(typeof e!="object"||e===null)return!1;let r=e;return typeof r.code=="string"&&r.code.length>0},we=e=>{var r;if(e instanceof Error){if(e.name==="TypeError")return!0;let t=e.message.toLowerCase();if(t.includes("fetch")&&t.includes("failed")||t.includes("network"))return!0}if(Ee(e)){let t=((r=e.code)!=null?r:"").toUpperCase();return t==="ECONNRESET"||t==="ECONNREFUSED"||t==="ETIMEDOUT"||t==="ENOTFOUND"||t==="EAI_AGAIN"}return!1},U=class{constructor(r){this.predicate=r}toCode(r){return new _(this.predicate,r)}toError(r){return t=>{if(!this.predicate(t))return null;let n=r(t),o=n.code;class a extends T{constructor(){var u;super(n.message,{cause:(u=n.cause)!=null?u:t,meta:n.meta,status:n.status,retryable:n.retryable});m(this,"code",o)}}return new a}}},_=class{constructor(r,t){this.predicate=r;this.errorCode=t}with(r){return t=>{if(!this.predicate(t))return null;let n=r(t),o=this.errorCode;class a extends T{constructor(){super(n.message,{cause:n.cause,meta:n.meta,status:n.status,retryable:n.retryable});m(this,"code",o)}}return new a}}},E={when:e=>new U(e),instance:e=>new U(r=>r instanceof e),code:e=>({for:r=>new _(r,e)}),string:(e,r)=>({when:t=>E.when(t).toCode(r).with(n=>({message:n===e?e:`${r}: ${n}`,cause:n}))}),httpStatus:(e,r)=>({for:t=>E.when(t).toCode(r!=null?r:`HTTP_${e}`).with(n=>({message:`HTTP ${e} error`,cause:n}))})};var w={typed:(e=>e instanceof T?e:null),abort:E.when(e=>e instanceof DOMException&&e.name==="AbortError").toCode("ABORTED").with(e=>({message:e.message||"Operation was aborted",cause:e,retryable:!1})),timeout:E.when(e=>e instanceof Error&&e.name==="TimeoutError").toCode("TIMEOUT").with(e=>({message:e.message||"Operation timed out",cause:e})),network:E.when(e=>we(e)).toCode("NETWORK").with(e=>({message:(e instanceof Error,e.message||"Network error"),cause:e})),http:E.when(e=>{var n;if(!Te(e))return!1;let r=e,t=(n=r.status)!=null?n:r.statusCode;return typeof t=="number"&&t>=400}).toCode("HTTP").with(e=>{var n;let r=(n=e.status)!=null?n:e.statusCode,t=typeof r=="number"&&(r>=500||r===429);return{message:e.message||`HTTP ${r!=null?r:"error"} error`,cause:e,status:typeof r=="number"?r:void 0,retryable:t}}),unknown:E.when(e=>e instanceof Error&&!(e instanceof T)).toCode("UNKNOWN").with(e=>({message:e.message||"Unknown error occurred",cause:e}))};var be=[w.typed,w.abort,w.timeout,w.http,w.network,w.unknown];var G={when:e=>E.when(e),instance:e=>E.instance(e)},Q=be,He=w.timeout,Ve=w.abort,We=w.network,Je=w.http,Ke=E.when(e=>e instanceof A).toCode("CIRCUIT_OPEN").with(e=>({message:e.message,cause:e})),qe=E.when(e=>e instanceof I).toCode("VALIDATION").with(e=>({message:e.message,cause:e}));var X={fixed:e=>({type:"fixed",delay:e}),exponential:(e,r=2,t)=>({type:"exponential",base:e,factor:r,maxDelay:t}),fibonacci:(e,r)=>({type:"fibonacci",base:e,maxDelay:r}),custom:e=>({type:"custom",calculate:e})},Z=(e,r,t)=>{switch(e.type){case"fixed":return e.delay;case"exponential":{let n=e.base*e.factor**(Number(r)-1);return e.maxDelay?Math.min(n,e.maxDelay):n}case"fibonacci":{let n=e.base*he(Number(r));return e.maxDelay?Math.min(n,e.maxDelay):n}case"custom":return e.calculate(r,t);default:return e}},he=e=>{if(e<=1)return 1;let r=1,t=1;for(let n=2;n<=e;n++){let o=r+t;r=t,t=o}return t};var j=(e,r)=>new Promise((t,n)=>{if(r!=null&&r.aborted){n(new DOMException("Aborted","AbortError"));return}let o=s=>{r&&s&&r.removeEventListener("abort",s)},a,i=()=>{a&&clearTimeout(a),o(i),n(new DOMException("Aborted","AbortError"))};a=setTimeout(()=>{o(i),t()},e),r==null||r.addEventListener("abort",i,{once:!0})});var Ce=e=>{var i,s,u;if(e.toError)return e.toError;let r=(i=e.rulesMode)!=null?i:"extend",t=(s=e.rules)!=null?s:[],n=Q,o=(u=e.fallback)!=null?u:(c=>Y(F)(c)),a=r==="replace"?t:[...t,...n];return q(a,o)},H=class e{constructor(r={}){m(this,"circuitBreaker");m(this,"config");m(this,"lastCircuitState");let{rules:t,rulesMode:n,fallback:o,toError:a,mapError:i,...s}=r,u=Ce(r),c={...s,errorHandling:{normalizer:u,mapError:i}};this.config=c,c.circuitBreaker&&(this.circuitBreaker=new L(c.circuitBreaker),this.lastCircuitState=this.circuitBreaker.getState().state)}async run(r,t={}){var a,i,s,u,c,b;let n={...this.config,...t};if(this.circuitBreaker){let l=(a=this.lastCircuitState)!=null?a:this.circuitBreaker.getState().state,y=await this.circuitBreaker.canExecute(),p=this.circuitBreaker.getState().state;if(l!==p)try{(s=(i=n.hooks)==null?void 0:i.onCircuitStateChange)==null||s.call(i,l,p)}catch{}if(this.lastCircuitState=p,!y)return{type:"failure",ok:!1,data:null,error:this.circuitBreaker.createOpenError(),metrics:{totalAttempts:0,totalRetries:0,totalDuration:0,retryHistory:[]}}}let o=await xe(r,n);if(this.circuitBreaker){let l=(u=this.lastCircuitState)!=null?u:this.circuitBreaker.getState().state;o.ok?await this.circuitBreaker.recordSuccess():await this.circuitBreaker.recordFailure(o.error);let y=this.circuitBreaker.getState().state;if(l!==y)try{(b=(c=n.hooks)==null?void 0:c.onCircuitStateChange)==null||b.call(c,l,y)}catch{}this.lastCircuitState=y}return o}async runOrThrow(r,t={}){let n=await this.run(r,t);if(n.ok)return n.data;throw n.error}async orThrow(r,t={}){let n=await this.run(r,t);if(n.ok)return n.data;throw n.error}async all(r,t={}){var b;let n={...this.config,...t},o=(b=n.concurrency)!=null?b:Number.POSITIVE_INFINITY,a=Number.isFinite(o)?Number(B(o)):Number.POSITIVE_INFINITY,i=new Array(r.length),s=0,u=async()=>{var l;for(;s<r.length&&!((l=n.signal)!=null&&l.aborted);){let y=s++;if(y>=r.length)break;let p=r[y];p&&(i[y]=await this.run(p,n))}},c=Array.from({length:Math.min(a,r.length)},()=>u());await Promise.all(c);for(let l=0;l<r.length;l++)if(!(l in i)){let y=r[l];y&&(i[l]=await this.run(y,n))}return i}async allOrThrow(r,t={}){let n=await this.all(r,t);for(let o of n)if(!o.ok)throw o.error;return n.map(o=>{if(!o.ok)throw o.error;return o.data})}partitionAll(r){let t=[],n=[],o=[],a=[],i=[];for(let s of r){if(s.type==="success"){t.push(s);continue}switch(n.push(s),s.type){case"failure":o.push(s);break;case"aborted":a.push(s);break;case"timeout":i.push(s);break}}return{ok:t,errors:n,failure:o,aborted:a,timeout:i}}getCircuitBreakerState(){var r;return(r=this.circuitBreaker)==null?void 0:r.getState()}resetCircuitBreaker(){var r;(r=this.circuitBreaker)==null||r.reset()}getConfig(){return{...this.config}}withConfig(r){var u,c;let{errorHandling:t,...n}=this.config,{errorHandling:o,...a}=r,i=(u=o==null?void 0:o.normalizer)!=null?u:t.normalizer,s=(c=o==null?void 0:o.mapError)!=null?c:t.mapError;return new e({...n,...a,toError:i,mapError:s})}withErrorType(r={}){return new e(r)}};function z(e={}){let r=new H(e);return{run:(t,n)=>r.run(t,n),orThrow:(t,n)=>r.orThrow(t,n),runOrThrow:(t,n)=>r.runOrThrow(t,n),all:(t,n)=>r.all(t,n),allOrThrow:(t,n)=>r.allOrThrow(t,n),partitionAll:t=>r.partitionAll(t),withConfig:t=>r.withConfig(t)}}async function xe(e,r){let{signal:t,ignoreAbort:n=!0,timeout:o,retry:a,errorHandling:i,hooks:s,logger:u}=r,c=(g,...d)=>{try{g==null||g(...d)}catch{}},b,l=0,y=0,p=[],k=Date.now(),{signal:x,cleanup:se}=Re(t);try{if(x.aborted){c(s==null?void 0:s.onAbort,x);let d=i.normalizer(new DOMException("Aborted","AbortError")),f=i.mapError?i.mapError(d):d;return{type:"aborted",ok:!1,data:null,error:f,metrics:{totalAttempts:l,totalRetries:y,totalDuration:Date.now()-k,lastError:f,retryHistory:p}}}let g=async d=>{var f,P,O;l=d;try{let M=e({signal:x}),S=o?await ke(M,o,x):await M;return c(s==null?void 0:s.onSuccess,S),c(u==null?void 0:u.info,`Task succeeded on attempt ${d}`),S}catch(M){let S=i.normalizer(M),h=i.mapError?i.mapError(S):S;b=h,h.code==="ABORTED"&&c(s==null?void 0:s.onAbort,x),n&&h.code==="ABORTED"||(c(s==null?void 0:s.onError,h),c(u==null?void 0:u.error,`Task failed on attempt ${d}`,h));let ae=R((f=a==null?void 0:a.maxRetries)!=null?f:0);if(d<=Number(ae)){let V=a==null?void 0:a.shouldRetry,ie={totalAttempts:Number(l),elapsedTime:Date.now()-k,startTime:new Date(k),lastDelay:(P=p[p.length-1])!=null&&P.delay?Number((O=p[p.length-1])==null?void 0:O.delay):void 0};if(!V||V(d,h,ie)){let ue=a?Z(a.strategy,d,h):0,$=ge(ue,a==null?void 0:a.jitter),W=C($);return p.push({attempt:d,error:h,delay:W,timestamp:new Date}),c(s==null?void 0:s.onRetry,d,h,$),c(u==null?void 0:u.info,`Retrying in ${$}ms (attempt ${d+1})`),await j(W,x),g(d+1)}}throw h}};try{let d=await g(1);y=l>0?Number(l)-1:0;let f={totalAttempts:l,totalRetries:y,totalDuration:Date.now()-k,retryHistory:p};return c(s==null?void 0:s.onFinally,f),{type:"success",ok:!0,data:d,error:null,metrics:f}}catch(d){let f=b!=null?b:i.normalizer(d),P=f.code==="TIMEOUT"?"timeout":f.code==="ABORTED"?"aborted":"failure";y=l>0?Number(l)-1:0;let O={totalAttempts:l,totalRetries:y,totalDuration:Date.now()-k,lastError:f,retryHistory:p};return c(s==null?void 0:s.onFinally,O),{type:P,ok:!1,data:null,error:f,metrics:O}}}finally{se()}}function Re(e){let r=new AbortController;if(!e)return{signal:r.signal,cleanup:()=>{}};let t=()=>r.abort();return e.aborted?(t(),{signal:r.signal,cleanup:()=>{}}):(e.addEventListener("abort",t,{once:!0}),{signal:r.signal,cleanup:()=>e.removeEventListener("abort",t)})}async function ke(e,r,t){return new Promise((n,o)=>{if(t!=null&&t.aborted){o(new DOMException("Aborted","AbortError"));return}let a=!1,i=setTimeout(()=>{a=!0,u(),o(new v(C(r)))},r),s=()=>{a||(a=!0,u(),o(new DOMException("Aborted","AbortError")))},u=()=>{clearTimeout(i),t==null||t.removeEventListener("abort",s)};t==null||t.addEventListener("abort",s,{once:!0}),e.then(c=>{a||(a=!0,u(),n(c))},c=>{a||(a=!0,u(),o(c))})})}function ge(e,r){if(!r||r.type==="none"||e<=0)return e;switch(r.type){case"full":{let t=Number(r.ratio)/100,n=Math.max(0,Number(e)*(1-t)),o=Number(e);return n+Math.random()*(o-n)}case"equal":{let t=Number(r.ratio)/100,n=Number(e)*t/2;return Number(e)-n+Math.random()*n}case"custom":return r.calculate(e);default:return r}}var N=z(),ee=N.run,re=N.runOrThrow,te=N.orThrow,ne=N.all,oe=N.allOrThrow;0&&(module.exports={RetryStrategies,all,allOrThrow,asConcurrencyLimit,asMilliseconds,asPercentage,asRetryCount,asStatusCode,errorRule,orThrow,run,runOrThrow,tryo});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Branded types for enhanced type safety and runtime validation
|
|
3
|
+
* These provide compile-time guarantees while maintaining runtime checks
|
|
4
|
+
*/
|
|
5
|
+
type Milliseconds = number & {
|
|
6
|
+
readonly __brand: 'Milliseconds';
|
|
7
|
+
};
|
|
8
|
+
type RetryCount = number & {
|
|
9
|
+
readonly __brand: 'RetryCount';
|
|
10
|
+
};
|
|
11
|
+
type ConcurrencyLimit = number & {
|
|
12
|
+
readonly __brand: 'ConcurrencyLimit';
|
|
13
|
+
};
|
|
14
|
+
type Percentage = number & {
|
|
15
|
+
readonly __brand: 'Percentage';
|
|
16
|
+
};
|
|
17
|
+
type StatusCode = number & {
|
|
18
|
+
readonly __brand: 'StatusCode';
|
|
19
|
+
};
|
|
20
|
+
declare const asMilliseconds: (n: number) => Milliseconds;
|
|
21
|
+
declare const asRetryCount: (n: number) => RetryCount;
|
|
22
|
+
declare const asConcurrencyLimit: (n: number) => ConcurrencyLimit;
|
|
23
|
+
declare const asPercentage: (n: number) => Percentage;
|
|
24
|
+
declare const asStatusCode: (n: number) => StatusCode;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Modern typed error hierarchy with enhanced capabilities
|
|
28
|
+
* Provides type-safe error handling with fluent API and metadata support
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
declare abstract class TypedError<Code extends string = string, Meta = unknown> extends Error {
|
|
32
|
+
abstract readonly code: Code;
|
|
33
|
+
readonly cause?: unknown;
|
|
34
|
+
readonly meta?: Meta;
|
|
35
|
+
readonly status?: number;
|
|
36
|
+
readonly timestamp: number;
|
|
37
|
+
readonly retryable: boolean;
|
|
38
|
+
constructor(message: string, opts?: {
|
|
39
|
+
cause?: unknown;
|
|
40
|
+
meta?: Meta;
|
|
41
|
+
status?: number;
|
|
42
|
+
retryable?: boolean;
|
|
43
|
+
});
|
|
44
|
+
is<C extends string>(code: C): this is TypedError<C> & {
|
|
45
|
+
code: C;
|
|
46
|
+
};
|
|
47
|
+
withMeta<const M>(meta: M): this & {
|
|
48
|
+
meta: M;
|
|
49
|
+
};
|
|
50
|
+
withStatus(status: number): this & {
|
|
51
|
+
status: number;
|
|
52
|
+
};
|
|
53
|
+
withCause(cause: unknown): this & {
|
|
54
|
+
cause: unknown;
|
|
55
|
+
};
|
|
56
|
+
withRetryable(retryable: boolean): this;
|
|
57
|
+
toJSON(): Record<string, unknown>;
|
|
58
|
+
}
|
|
59
|
+
declare class TimeoutError extends TypedError<'TIMEOUT'> {
|
|
60
|
+
readonly code: "TIMEOUT";
|
|
61
|
+
constructor(timeout: Milliseconds, cause?: unknown);
|
|
62
|
+
}
|
|
63
|
+
declare class AbortedError extends TypedError<'ABORTED'> {
|
|
64
|
+
readonly code: "ABORTED";
|
|
65
|
+
constructor(reason?: string, cause?: unknown);
|
|
66
|
+
}
|
|
67
|
+
declare class CircuitOpenError extends TypedError<'CIRCUIT_OPEN'> {
|
|
68
|
+
readonly code: "CIRCUIT_OPEN";
|
|
69
|
+
constructor(resetAfter: Milliseconds, cause?: unknown);
|
|
70
|
+
}
|
|
71
|
+
type ValidationMeta = {
|
|
72
|
+
validationErrors: unknown[];
|
|
73
|
+
};
|
|
74
|
+
declare class ValidationError extends TypedError<'VALIDATION', ValidationMeta> {
|
|
75
|
+
readonly validationErrors: unknown[];
|
|
76
|
+
readonly code: "VALIDATION";
|
|
77
|
+
constructor(message: string, validationErrors: unknown[], cause?: unknown);
|
|
78
|
+
}
|
|
79
|
+
declare class NetworkError extends TypedError<'NETWORK'> {
|
|
80
|
+
readonly statusCode?: number | undefined;
|
|
81
|
+
readonly code: "NETWORK";
|
|
82
|
+
constructor(message: string, statusCode?: number | undefined, cause?: unknown);
|
|
83
|
+
}
|
|
84
|
+
type HttpMeta = {
|
|
85
|
+
response?: unknown;
|
|
86
|
+
};
|
|
87
|
+
declare class HttpError extends TypedError<'HTTP', HttpMeta> {
|
|
88
|
+
readonly status: number;
|
|
89
|
+
readonly response?: unknown | undefined;
|
|
90
|
+
readonly code: "HTTP";
|
|
91
|
+
constructor(message: string, status: number, response?: unknown | undefined, cause?: unknown);
|
|
92
|
+
}
|
|
93
|
+
declare class UnknownError extends TypedError<'UNKNOWN'> {
|
|
94
|
+
readonly code: "UNKNOWN";
|
|
95
|
+
constructor(message: string, cause?: unknown);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Modern circuit breaker implementation with enhanced state management
|
|
100
|
+
* Provides circuit breaker pattern with type safety and observability
|
|
101
|
+
*/
|
|
102
|
+
|
|
103
|
+
interface CircuitBreakerConfig<E extends TypedError = TypedError> {
|
|
104
|
+
/** Number of consecutive failures before opening circuit */
|
|
105
|
+
readonly failureThreshold: number;
|
|
106
|
+
/** How long to wait before attempting to close circuit */
|
|
107
|
+
readonly resetTimeout: number;
|
|
108
|
+
/** Number of requests allowed in half-open state */
|
|
109
|
+
readonly halfOpenRequests: number;
|
|
110
|
+
/** Optional function to determine if error should count as failure */
|
|
111
|
+
readonly shouldCountAsFailure?: (error: E) => boolean;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Modern error normalization system
|
|
116
|
+
* Provides type-safe error transformation and normalization
|
|
117
|
+
*/
|
|
118
|
+
|
|
119
|
+
type ErrorNormalizer<E extends TypedError = TypedError> = (error: unknown) => E;
|
|
120
|
+
type ErrorRule<E extends TypedError = TypedError> = (error: unknown) => E | null;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Modern result types with enhanced discriminated unions
|
|
124
|
+
* Provides better type safety and more granular result categorization
|
|
125
|
+
*/
|
|
126
|
+
|
|
127
|
+
type TryoResult<T, E extends TypedError = TypedError> = SuccessResult<T, E> | FailureResult<E> | AbortedResult<E> | TimeoutResult<E>;
|
|
128
|
+
interface SuccessResult<T, E extends TypedError> {
|
|
129
|
+
readonly type: 'success';
|
|
130
|
+
readonly ok: true;
|
|
131
|
+
readonly data: T;
|
|
132
|
+
readonly error: null;
|
|
133
|
+
readonly metrics?: TryoMetrics$1<E>;
|
|
134
|
+
}
|
|
135
|
+
interface FailureResult<E extends TypedError> {
|
|
136
|
+
readonly type: 'failure';
|
|
137
|
+
readonly ok: false;
|
|
138
|
+
readonly data: null;
|
|
139
|
+
readonly error: E;
|
|
140
|
+
readonly metrics?: TryoMetrics$1<E>;
|
|
141
|
+
}
|
|
142
|
+
interface AbortedResult<E extends TypedError> {
|
|
143
|
+
readonly type: 'aborted';
|
|
144
|
+
readonly ok: false;
|
|
145
|
+
readonly data: null;
|
|
146
|
+
readonly error: E;
|
|
147
|
+
readonly metrics?: TryoMetrics$1<E>;
|
|
148
|
+
}
|
|
149
|
+
interface TimeoutResult<E extends TypedError> {
|
|
150
|
+
readonly type: 'timeout';
|
|
151
|
+
readonly ok: false;
|
|
152
|
+
readonly data: null;
|
|
153
|
+
readonly error: E;
|
|
154
|
+
readonly metrics?: TryoMetrics$1<E>;
|
|
155
|
+
}
|
|
156
|
+
interface TryoMetrics$1<E extends TypedError> {
|
|
157
|
+
readonly totalAttempts: RetryCount;
|
|
158
|
+
readonly totalRetries: RetryCount;
|
|
159
|
+
readonly totalDuration: Milliseconds;
|
|
160
|
+
readonly lastError?: E;
|
|
161
|
+
readonly retryHistory: Array<{
|
|
162
|
+
readonly attempt: RetryCount;
|
|
163
|
+
readonly error: E;
|
|
164
|
+
readonly delay: Milliseconds;
|
|
165
|
+
readonly timestamp: Date;
|
|
166
|
+
}>;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Modern retry strategies with enhanced capabilities
|
|
171
|
+
* Provides various retry patterns with type safety
|
|
172
|
+
*/
|
|
173
|
+
|
|
174
|
+
type RetryStrategy = FixedDelayStrategy | ExponentialBackoffStrategy | FibonacciBackoffStrategy | CustomDelayStrategy;
|
|
175
|
+
interface FixedDelayStrategy {
|
|
176
|
+
readonly type: 'fixed';
|
|
177
|
+
readonly delay: number;
|
|
178
|
+
}
|
|
179
|
+
interface ExponentialBackoffStrategy {
|
|
180
|
+
readonly type: 'exponential';
|
|
181
|
+
readonly base: number;
|
|
182
|
+
readonly factor: number;
|
|
183
|
+
readonly maxDelay?: number;
|
|
184
|
+
}
|
|
185
|
+
interface FibonacciBackoffStrategy {
|
|
186
|
+
readonly type: 'fibonacci';
|
|
187
|
+
readonly base: number;
|
|
188
|
+
readonly maxDelay?: number;
|
|
189
|
+
}
|
|
190
|
+
interface CustomDelayStrategy {
|
|
191
|
+
readonly type: 'custom';
|
|
192
|
+
readonly calculate: (attempt: RetryCount, error: unknown) => number;
|
|
193
|
+
}
|
|
194
|
+
declare const RetryStrategies: {
|
|
195
|
+
readonly fixed: (delay: number) => FixedDelayStrategy;
|
|
196
|
+
readonly exponential: (base: number, factor?: number, maxDelay?: number) => ExponentialBackoffStrategy;
|
|
197
|
+
readonly fibonacci: (base: number, maxDelay?: number) => FibonacciBackoffStrategy;
|
|
198
|
+
readonly custom: (calculate: (attempt: RetryCount, error: unknown) => number) => CustomDelayStrategy;
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
interface TryoConfig<E extends TypedError = TypedError> {
|
|
202
|
+
/** Abort signal passed to tasks */
|
|
203
|
+
readonly signal?: AbortSignal;
|
|
204
|
+
/** If true, aborts are treated as non-throwing failures */
|
|
205
|
+
readonly ignoreAbort?: boolean;
|
|
206
|
+
/** Timeout configuration */
|
|
207
|
+
readonly timeout?: number;
|
|
208
|
+
/** Retry configuration */
|
|
209
|
+
readonly retry?: RetryConfig<E>;
|
|
210
|
+
/** Circuit breaker configuration */
|
|
211
|
+
readonly circuitBreaker?: CircuitBreakerConfig<E>;
|
|
212
|
+
/** Error handling configuration */
|
|
213
|
+
readonly errorHandling: ErrorHandlingConfig<E>;
|
|
214
|
+
/** Concurrency configuration for batch operations */
|
|
215
|
+
readonly concurrency?: number;
|
|
216
|
+
/** Logging configuration */
|
|
217
|
+
readonly logger?: LoggerConfig<E>;
|
|
218
|
+
/** Callback hooks */
|
|
219
|
+
readonly hooks?: HookConfig<E>;
|
|
220
|
+
}
|
|
221
|
+
interface RetryConfig<E extends TypedError> {
|
|
222
|
+
/** Maximum number of retry attempts */
|
|
223
|
+
readonly maxRetries: number;
|
|
224
|
+
/** Base delay strategy */
|
|
225
|
+
readonly strategy: RetryStrategy;
|
|
226
|
+
/** Jitter configuration to prevent thundering herd */
|
|
227
|
+
readonly jitter?: JitterConfig;
|
|
228
|
+
/** Function to determine if retry should be attempted */
|
|
229
|
+
readonly shouldRetry?: ShouldRetryPredicate<E>;
|
|
230
|
+
}
|
|
231
|
+
type ShouldRetryPredicate<E extends TypedError> = (attempt: number, error: E, context: RetryContext) => boolean;
|
|
232
|
+
interface RetryContext {
|
|
233
|
+
/** Total attempts made so far */
|
|
234
|
+
readonly totalAttempts: number;
|
|
235
|
+
/** Elapsed time since start */
|
|
236
|
+
readonly elapsedTime: number;
|
|
237
|
+
/** Start timestamp */
|
|
238
|
+
readonly startTime: Date;
|
|
239
|
+
/** Last delay applied */
|
|
240
|
+
readonly lastDelay?: number;
|
|
241
|
+
}
|
|
242
|
+
interface ErrorHandlingConfig<E extends TypedError> {
|
|
243
|
+
/** Error normalizer function */
|
|
244
|
+
readonly normalizer: ErrorNormalizer<E>;
|
|
245
|
+
/** Optional error mapping/transformation */
|
|
246
|
+
readonly mapError?: (error: E) => E;
|
|
247
|
+
}
|
|
248
|
+
interface LoggerConfig<E extends TypedError> {
|
|
249
|
+
/** Debug logging function */
|
|
250
|
+
readonly debug?: (message: string, meta?: unknown) => void;
|
|
251
|
+
/** Error logging function */
|
|
252
|
+
readonly error?: (message: string, error: E) => void;
|
|
253
|
+
/** Info logging function */
|
|
254
|
+
readonly info?: (message: string, meta?: unknown) => void;
|
|
255
|
+
/** Warning logging function */
|
|
256
|
+
readonly warn?: (message: string, meta?: unknown) => void;
|
|
257
|
+
}
|
|
258
|
+
interface HookConfig<E extends TypedError> {
|
|
259
|
+
/** Called on successful execution */
|
|
260
|
+
readonly onSuccess?: <T>(data: T, metrics?: TryoMetrics<E>) => void;
|
|
261
|
+
/** Called on failed execution */
|
|
262
|
+
readonly onError?: (error: E, metrics?: TryoMetrics<E>) => void;
|
|
263
|
+
/** Called always, success or failure */
|
|
264
|
+
readonly onFinally?: (metrics?: TryoMetrics<E>) => void;
|
|
265
|
+
/** Called on abort */
|
|
266
|
+
readonly onAbort?: (signal: AbortSignal) => void;
|
|
267
|
+
/** Called before retry attempt */
|
|
268
|
+
readonly onRetry?: (attempt: number, error: E, delay: number) => void;
|
|
269
|
+
/** Called when circuit breaker state changes */
|
|
270
|
+
readonly onCircuitStateChange?: (from: CircuitState, to: CircuitState) => void;
|
|
271
|
+
}
|
|
272
|
+
type CircuitState = 'closed' | 'open' | 'half-open';
|
|
273
|
+
type TryoMetrics<E extends TypedError> = TryoMetrics$1<E>;
|
|
274
|
+
type JitterConfig = {
|
|
275
|
+
type: 'none';
|
|
276
|
+
} | {
|
|
277
|
+
type: 'full';
|
|
278
|
+
ratio: number;
|
|
279
|
+
} | {
|
|
280
|
+
type: 'equal';
|
|
281
|
+
ratio: number;
|
|
282
|
+
} | {
|
|
283
|
+
type: 'custom';
|
|
284
|
+
calculate: (delay: number) => number;
|
|
285
|
+
};
|
|
286
|
+
declare const JitterConfig: {
|
|
287
|
+
readonly none: () => JitterConfig;
|
|
288
|
+
readonly full: (ratio?: number) => JitterConfig;
|
|
289
|
+
readonly equal: (ratio?: number) => JitterConfig;
|
|
290
|
+
readonly custom: (calculate: (delay: number) => number) => JitterConfig;
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
type RulesMode = 'extend' | 'replace';
|
|
294
|
+
type DefaultError = AbortedError | TimeoutError | NetworkError | HttpError | CircuitOpenError | ValidationError | UnknownError;
|
|
295
|
+
type NonNull<T> = T extends null ? never : T;
|
|
296
|
+
type RuleReturn<R> = R extends (err: unknown) => infer Out ? NonNull<Out> : never;
|
|
297
|
+
type InferErrorFromRules<TRules extends readonly ErrorRule<TypedError>[]> = TRules extends readonly [] ? TypedError : RuleReturn<TRules[number]> | UnknownError;
|
|
298
|
+
type TryoOptions<E extends TypedError = TypedError> = Omit<Partial<TryoConfig<E>>, 'errorHandling' | 'signal'> & {
|
|
299
|
+
rules?: Array<ErrorRule<E>>;
|
|
300
|
+
rulesMode?: RulesMode;
|
|
301
|
+
fallback?: (err: unknown) => E;
|
|
302
|
+
toError?: (err: unknown) => E;
|
|
303
|
+
mapError?: (error: E) => E;
|
|
304
|
+
};
|
|
305
|
+
declare function tryo<const TRules extends readonly ErrorRule<TypedError>[]>(options: Omit<TryoOptions<InferErrorFromRules<TRules>>, 'rules'> & {
|
|
306
|
+
rules: TRules;
|
|
307
|
+
}): Tryo<InferErrorFromRules<TRules>>;
|
|
308
|
+
declare function tryo<E extends TypedError = DefaultError>(options?: TryoOptions<E>): Tryo<E>;
|
|
309
|
+
type Tryo<E extends TypedError = TypedError> = {
|
|
310
|
+
run: <T>(task: (ctx: {
|
|
311
|
+
signal: AbortSignal;
|
|
312
|
+
}) => Promise<T>, options?: Partial<TryoConfig<E>>) => Promise<TryoResult<T, E>>;
|
|
313
|
+
runOrThrow: <T>(task: (ctx: {
|
|
314
|
+
signal: AbortSignal;
|
|
315
|
+
}) => Promise<T>, options?: Partial<TryoConfig<E>>) => Promise<T>;
|
|
316
|
+
orThrow: <T>(task: (ctx: {
|
|
317
|
+
signal: AbortSignal;
|
|
318
|
+
}) => Promise<T>, options?: Partial<TryoConfig<E>>) => Promise<T>;
|
|
319
|
+
all: <T>(tasks: Array<(ctx: {
|
|
320
|
+
signal: AbortSignal;
|
|
321
|
+
}) => Promise<T>>, options?: Partial<TryoConfig<E> & {
|
|
322
|
+
concurrency?: number;
|
|
323
|
+
}>) => Promise<Array<TryoResult<T, E>>>;
|
|
324
|
+
allOrThrow: <T>(tasks: Array<(ctx: {
|
|
325
|
+
signal: AbortSignal;
|
|
326
|
+
}) => Promise<T>>, options?: Partial<TryoConfig<E> & {
|
|
327
|
+
concurrency?: number;
|
|
328
|
+
}>) => Promise<T[]>;
|
|
329
|
+
partitionAll: <T>(results: Array<TryoResult<T, E>>) => {
|
|
330
|
+
ok: Array<SuccessResult<T, E>>;
|
|
331
|
+
errors: Array<FailureResult<E> | AbortedResult<E> | TimeoutResult<E>>;
|
|
332
|
+
failure: Array<FailureResult<E>>;
|
|
333
|
+
aborted: Array<AbortedResult<E>>;
|
|
334
|
+
timeout: Array<TimeoutResult<E>>;
|
|
335
|
+
};
|
|
336
|
+
withConfig: (additionalConfig: Omit<Partial<TryoConfig<E>>, 'signal'>) => Tryo<E>;
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
declare const run: <T>(task: (ctx: {
|
|
340
|
+
signal: AbortSignal;
|
|
341
|
+
}) => Promise<T>, options?: Partial<TryoConfig<DefaultError>> | undefined) => Promise<TryoResult<T, DefaultError>>;
|
|
342
|
+
declare const runOrThrow: <T>(task: (ctx: {
|
|
343
|
+
signal: AbortSignal;
|
|
344
|
+
}) => Promise<T>, options?: Partial<TryoConfig<DefaultError>> | undefined) => Promise<T>;
|
|
345
|
+
declare const orThrow: <T>(task: (ctx: {
|
|
346
|
+
signal: AbortSignal;
|
|
347
|
+
}) => Promise<T>, options?: Partial<TryoConfig<DefaultError>> | undefined) => Promise<T>;
|
|
348
|
+
declare const all: <T>(tasks: ((ctx: {
|
|
349
|
+
signal: AbortSignal;
|
|
350
|
+
}) => Promise<T>)[], options?: Partial<TryoConfig<DefaultError> & {
|
|
351
|
+
concurrency?: number;
|
|
352
|
+
}> | undefined) => Promise<TryoResult<T, DefaultError>[]>;
|
|
353
|
+
declare const allOrThrow: <T>(tasks: ((ctx: {
|
|
354
|
+
signal: AbortSignal;
|
|
355
|
+
}) => Promise<T>)[], options?: Partial<TryoConfig<DefaultError> & {
|
|
356
|
+
concurrency?: number;
|
|
357
|
+
}> | undefined) => Promise<T[]>;
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Modern fluent error rule builder
|
|
361
|
+
* Provides type-safe error rule creation with enhanced ergonomics
|
|
362
|
+
*/
|
|
363
|
+
|
|
364
|
+
declare class ErrorRuleBuilder<T> {
|
|
365
|
+
private readonly predicate;
|
|
366
|
+
constructor(predicate: (err: unknown) => err is T);
|
|
367
|
+
toCode<const C extends string>(code: C): ErrorMapper<T, C>;
|
|
368
|
+
toError<const Out extends {
|
|
369
|
+
code: string;
|
|
370
|
+
message: string;
|
|
371
|
+
meta?: unknown;
|
|
372
|
+
status?: number;
|
|
373
|
+
cause?: unknown;
|
|
374
|
+
retryable?: boolean;
|
|
375
|
+
}>(mapper: (err: T) => Out): ErrorRule<TypedError<Out['code'], Out['meta']>>;
|
|
376
|
+
}
|
|
377
|
+
declare class ErrorMapper<T, C extends string> {
|
|
378
|
+
private readonly predicate;
|
|
379
|
+
private readonly errorCode;
|
|
380
|
+
constructor(predicate: (err: unknown) => err is T, errorCode: C);
|
|
381
|
+
with<const M = unknown>(mapper: (err: T) => {
|
|
382
|
+
message: string;
|
|
383
|
+
cause?: unknown;
|
|
384
|
+
meta?: M;
|
|
385
|
+
status?: number;
|
|
386
|
+
retryable?: boolean;
|
|
387
|
+
}): (err: unknown) => TypedError<C, M> | null;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
declare const errorRule: {
|
|
391
|
+
readonly when: <T>(predicate: (err: unknown) => err is T) => ErrorRuleBuilder<T>;
|
|
392
|
+
readonly instance: <T extends new (...args: unknown[]) => unknown>(ErrorClass: T) => ErrorRuleBuilder<InstanceType<T>>;
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
export { type AbortedResult, type ConcurrencyLimit, type FailureResult, type Milliseconds, type Percentage, type RetryCount, RetryStrategies, type RulesMode, type StatusCode, type SuccessResult, type TimeoutResult, type TryoConfig, type TryoMetrics$1 as TryoMetrics, type TryoOptions, type TryoResult, all, allOrThrow, asConcurrencyLimit, asMilliseconds, asPercentage, asRetryCount, asStatusCode, errorRule, orThrow, run, runOrThrow, tryo };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Branded types for enhanced type safety and runtime validation
|
|
3
|
+
* These provide compile-time guarantees while maintaining runtime checks
|
|
4
|
+
*/
|
|
5
|
+
type Milliseconds = number & {
|
|
6
|
+
readonly __brand: 'Milliseconds';
|
|
7
|
+
};
|
|
8
|
+
type RetryCount = number & {
|
|
9
|
+
readonly __brand: 'RetryCount';
|
|
10
|
+
};
|
|
11
|
+
type ConcurrencyLimit = number & {
|
|
12
|
+
readonly __brand: 'ConcurrencyLimit';
|
|
13
|
+
};
|
|
14
|
+
type Percentage = number & {
|
|
15
|
+
readonly __brand: 'Percentage';
|
|
16
|
+
};
|
|
17
|
+
type StatusCode = number & {
|
|
18
|
+
readonly __brand: 'StatusCode';
|
|
19
|
+
};
|
|
20
|
+
declare const asMilliseconds: (n: number) => Milliseconds;
|
|
21
|
+
declare const asRetryCount: (n: number) => RetryCount;
|
|
22
|
+
declare const asConcurrencyLimit: (n: number) => ConcurrencyLimit;
|
|
23
|
+
declare const asPercentage: (n: number) => Percentage;
|
|
24
|
+
declare const asStatusCode: (n: number) => StatusCode;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Modern typed error hierarchy with enhanced capabilities
|
|
28
|
+
* Provides type-safe error handling with fluent API and metadata support
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
declare abstract class TypedError<Code extends string = string, Meta = unknown> extends Error {
|
|
32
|
+
abstract readonly code: Code;
|
|
33
|
+
readonly cause?: unknown;
|
|
34
|
+
readonly meta?: Meta;
|
|
35
|
+
readonly status?: number;
|
|
36
|
+
readonly timestamp: number;
|
|
37
|
+
readonly retryable: boolean;
|
|
38
|
+
constructor(message: string, opts?: {
|
|
39
|
+
cause?: unknown;
|
|
40
|
+
meta?: Meta;
|
|
41
|
+
status?: number;
|
|
42
|
+
retryable?: boolean;
|
|
43
|
+
});
|
|
44
|
+
is<C extends string>(code: C): this is TypedError<C> & {
|
|
45
|
+
code: C;
|
|
46
|
+
};
|
|
47
|
+
withMeta<const M>(meta: M): this & {
|
|
48
|
+
meta: M;
|
|
49
|
+
};
|
|
50
|
+
withStatus(status: number): this & {
|
|
51
|
+
status: number;
|
|
52
|
+
};
|
|
53
|
+
withCause(cause: unknown): this & {
|
|
54
|
+
cause: unknown;
|
|
55
|
+
};
|
|
56
|
+
withRetryable(retryable: boolean): this;
|
|
57
|
+
toJSON(): Record<string, unknown>;
|
|
58
|
+
}
|
|
59
|
+
declare class TimeoutError extends TypedError<'TIMEOUT'> {
|
|
60
|
+
readonly code: "TIMEOUT";
|
|
61
|
+
constructor(timeout: Milliseconds, cause?: unknown);
|
|
62
|
+
}
|
|
63
|
+
declare class AbortedError extends TypedError<'ABORTED'> {
|
|
64
|
+
readonly code: "ABORTED";
|
|
65
|
+
constructor(reason?: string, cause?: unknown);
|
|
66
|
+
}
|
|
67
|
+
declare class CircuitOpenError extends TypedError<'CIRCUIT_OPEN'> {
|
|
68
|
+
readonly code: "CIRCUIT_OPEN";
|
|
69
|
+
constructor(resetAfter: Milliseconds, cause?: unknown);
|
|
70
|
+
}
|
|
71
|
+
type ValidationMeta = {
|
|
72
|
+
validationErrors: unknown[];
|
|
73
|
+
};
|
|
74
|
+
declare class ValidationError extends TypedError<'VALIDATION', ValidationMeta> {
|
|
75
|
+
readonly validationErrors: unknown[];
|
|
76
|
+
readonly code: "VALIDATION";
|
|
77
|
+
constructor(message: string, validationErrors: unknown[], cause?: unknown);
|
|
78
|
+
}
|
|
79
|
+
declare class NetworkError extends TypedError<'NETWORK'> {
|
|
80
|
+
readonly statusCode?: number | undefined;
|
|
81
|
+
readonly code: "NETWORK";
|
|
82
|
+
constructor(message: string, statusCode?: number | undefined, cause?: unknown);
|
|
83
|
+
}
|
|
84
|
+
type HttpMeta = {
|
|
85
|
+
response?: unknown;
|
|
86
|
+
};
|
|
87
|
+
declare class HttpError extends TypedError<'HTTP', HttpMeta> {
|
|
88
|
+
readonly status: number;
|
|
89
|
+
readonly response?: unknown | undefined;
|
|
90
|
+
readonly code: "HTTP";
|
|
91
|
+
constructor(message: string, status: number, response?: unknown | undefined, cause?: unknown);
|
|
92
|
+
}
|
|
93
|
+
declare class UnknownError extends TypedError<'UNKNOWN'> {
|
|
94
|
+
readonly code: "UNKNOWN";
|
|
95
|
+
constructor(message: string, cause?: unknown);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Modern circuit breaker implementation with enhanced state management
|
|
100
|
+
* Provides circuit breaker pattern with type safety and observability
|
|
101
|
+
*/
|
|
102
|
+
|
|
103
|
+
interface CircuitBreakerConfig<E extends TypedError = TypedError> {
|
|
104
|
+
/** Number of consecutive failures before opening circuit */
|
|
105
|
+
readonly failureThreshold: number;
|
|
106
|
+
/** How long to wait before attempting to close circuit */
|
|
107
|
+
readonly resetTimeout: number;
|
|
108
|
+
/** Number of requests allowed in half-open state */
|
|
109
|
+
readonly halfOpenRequests: number;
|
|
110
|
+
/** Optional function to determine if error should count as failure */
|
|
111
|
+
readonly shouldCountAsFailure?: (error: E) => boolean;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Modern error normalization system
|
|
116
|
+
* Provides type-safe error transformation and normalization
|
|
117
|
+
*/
|
|
118
|
+
|
|
119
|
+
type ErrorNormalizer<E extends TypedError = TypedError> = (error: unknown) => E;
|
|
120
|
+
type ErrorRule<E extends TypedError = TypedError> = (error: unknown) => E | null;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Modern result types with enhanced discriminated unions
|
|
124
|
+
* Provides better type safety and more granular result categorization
|
|
125
|
+
*/
|
|
126
|
+
|
|
127
|
+
type TryoResult<T, E extends TypedError = TypedError> = SuccessResult<T, E> | FailureResult<E> | AbortedResult<E> | TimeoutResult<E>;
|
|
128
|
+
interface SuccessResult<T, E extends TypedError> {
|
|
129
|
+
readonly type: 'success';
|
|
130
|
+
readonly ok: true;
|
|
131
|
+
readonly data: T;
|
|
132
|
+
readonly error: null;
|
|
133
|
+
readonly metrics?: TryoMetrics$1<E>;
|
|
134
|
+
}
|
|
135
|
+
interface FailureResult<E extends TypedError> {
|
|
136
|
+
readonly type: 'failure';
|
|
137
|
+
readonly ok: false;
|
|
138
|
+
readonly data: null;
|
|
139
|
+
readonly error: E;
|
|
140
|
+
readonly metrics?: TryoMetrics$1<E>;
|
|
141
|
+
}
|
|
142
|
+
interface AbortedResult<E extends TypedError> {
|
|
143
|
+
readonly type: 'aborted';
|
|
144
|
+
readonly ok: false;
|
|
145
|
+
readonly data: null;
|
|
146
|
+
readonly error: E;
|
|
147
|
+
readonly metrics?: TryoMetrics$1<E>;
|
|
148
|
+
}
|
|
149
|
+
interface TimeoutResult<E extends TypedError> {
|
|
150
|
+
readonly type: 'timeout';
|
|
151
|
+
readonly ok: false;
|
|
152
|
+
readonly data: null;
|
|
153
|
+
readonly error: E;
|
|
154
|
+
readonly metrics?: TryoMetrics$1<E>;
|
|
155
|
+
}
|
|
156
|
+
interface TryoMetrics$1<E extends TypedError> {
|
|
157
|
+
readonly totalAttempts: RetryCount;
|
|
158
|
+
readonly totalRetries: RetryCount;
|
|
159
|
+
readonly totalDuration: Milliseconds;
|
|
160
|
+
readonly lastError?: E;
|
|
161
|
+
readonly retryHistory: Array<{
|
|
162
|
+
readonly attempt: RetryCount;
|
|
163
|
+
readonly error: E;
|
|
164
|
+
readonly delay: Milliseconds;
|
|
165
|
+
readonly timestamp: Date;
|
|
166
|
+
}>;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Modern retry strategies with enhanced capabilities
|
|
171
|
+
* Provides various retry patterns with type safety
|
|
172
|
+
*/
|
|
173
|
+
|
|
174
|
+
type RetryStrategy = FixedDelayStrategy | ExponentialBackoffStrategy | FibonacciBackoffStrategy | CustomDelayStrategy;
|
|
175
|
+
interface FixedDelayStrategy {
|
|
176
|
+
readonly type: 'fixed';
|
|
177
|
+
readonly delay: number;
|
|
178
|
+
}
|
|
179
|
+
interface ExponentialBackoffStrategy {
|
|
180
|
+
readonly type: 'exponential';
|
|
181
|
+
readonly base: number;
|
|
182
|
+
readonly factor: number;
|
|
183
|
+
readonly maxDelay?: number;
|
|
184
|
+
}
|
|
185
|
+
interface FibonacciBackoffStrategy {
|
|
186
|
+
readonly type: 'fibonacci';
|
|
187
|
+
readonly base: number;
|
|
188
|
+
readonly maxDelay?: number;
|
|
189
|
+
}
|
|
190
|
+
interface CustomDelayStrategy {
|
|
191
|
+
readonly type: 'custom';
|
|
192
|
+
readonly calculate: (attempt: RetryCount, error: unknown) => number;
|
|
193
|
+
}
|
|
194
|
+
declare const RetryStrategies: {
|
|
195
|
+
readonly fixed: (delay: number) => FixedDelayStrategy;
|
|
196
|
+
readonly exponential: (base: number, factor?: number, maxDelay?: number) => ExponentialBackoffStrategy;
|
|
197
|
+
readonly fibonacci: (base: number, maxDelay?: number) => FibonacciBackoffStrategy;
|
|
198
|
+
readonly custom: (calculate: (attempt: RetryCount, error: unknown) => number) => CustomDelayStrategy;
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
interface TryoConfig<E extends TypedError = TypedError> {
|
|
202
|
+
/** Abort signal passed to tasks */
|
|
203
|
+
readonly signal?: AbortSignal;
|
|
204
|
+
/** If true, aborts are treated as non-throwing failures */
|
|
205
|
+
readonly ignoreAbort?: boolean;
|
|
206
|
+
/** Timeout configuration */
|
|
207
|
+
readonly timeout?: number;
|
|
208
|
+
/** Retry configuration */
|
|
209
|
+
readonly retry?: RetryConfig<E>;
|
|
210
|
+
/** Circuit breaker configuration */
|
|
211
|
+
readonly circuitBreaker?: CircuitBreakerConfig<E>;
|
|
212
|
+
/** Error handling configuration */
|
|
213
|
+
readonly errorHandling: ErrorHandlingConfig<E>;
|
|
214
|
+
/** Concurrency configuration for batch operations */
|
|
215
|
+
readonly concurrency?: number;
|
|
216
|
+
/** Logging configuration */
|
|
217
|
+
readonly logger?: LoggerConfig<E>;
|
|
218
|
+
/** Callback hooks */
|
|
219
|
+
readonly hooks?: HookConfig<E>;
|
|
220
|
+
}
|
|
221
|
+
interface RetryConfig<E extends TypedError> {
|
|
222
|
+
/** Maximum number of retry attempts */
|
|
223
|
+
readonly maxRetries: number;
|
|
224
|
+
/** Base delay strategy */
|
|
225
|
+
readonly strategy: RetryStrategy;
|
|
226
|
+
/** Jitter configuration to prevent thundering herd */
|
|
227
|
+
readonly jitter?: JitterConfig;
|
|
228
|
+
/** Function to determine if retry should be attempted */
|
|
229
|
+
readonly shouldRetry?: ShouldRetryPredicate<E>;
|
|
230
|
+
}
|
|
231
|
+
type ShouldRetryPredicate<E extends TypedError> = (attempt: number, error: E, context: RetryContext) => boolean;
|
|
232
|
+
interface RetryContext {
|
|
233
|
+
/** Total attempts made so far */
|
|
234
|
+
readonly totalAttempts: number;
|
|
235
|
+
/** Elapsed time since start */
|
|
236
|
+
readonly elapsedTime: number;
|
|
237
|
+
/** Start timestamp */
|
|
238
|
+
readonly startTime: Date;
|
|
239
|
+
/** Last delay applied */
|
|
240
|
+
readonly lastDelay?: number;
|
|
241
|
+
}
|
|
242
|
+
interface ErrorHandlingConfig<E extends TypedError> {
|
|
243
|
+
/** Error normalizer function */
|
|
244
|
+
readonly normalizer: ErrorNormalizer<E>;
|
|
245
|
+
/** Optional error mapping/transformation */
|
|
246
|
+
readonly mapError?: (error: E) => E;
|
|
247
|
+
}
|
|
248
|
+
interface LoggerConfig<E extends TypedError> {
|
|
249
|
+
/** Debug logging function */
|
|
250
|
+
readonly debug?: (message: string, meta?: unknown) => void;
|
|
251
|
+
/** Error logging function */
|
|
252
|
+
readonly error?: (message: string, error: E) => void;
|
|
253
|
+
/** Info logging function */
|
|
254
|
+
readonly info?: (message: string, meta?: unknown) => void;
|
|
255
|
+
/** Warning logging function */
|
|
256
|
+
readonly warn?: (message: string, meta?: unknown) => void;
|
|
257
|
+
}
|
|
258
|
+
interface HookConfig<E extends TypedError> {
|
|
259
|
+
/** Called on successful execution */
|
|
260
|
+
readonly onSuccess?: <T>(data: T, metrics?: TryoMetrics<E>) => void;
|
|
261
|
+
/** Called on failed execution */
|
|
262
|
+
readonly onError?: (error: E, metrics?: TryoMetrics<E>) => void;
|
|
263
|
+
/** Called always, success or failure */
|
|
264
|
+
readonly onFinally?: (metrics?: TryoMetrics<E>) => void;
|
|
265
|
+
/** Called on abort */
|
|
266
|
+
readonly onAbort?: (signal: AbortSignal) => void;
|
|
267
|
+
/** Called before retry attempt */
|
|
268
|
+
readonly onRetry?: (attempt: number, error: E, delay: number) => void;
|
|
269
|
+
/** Called when circuit breaker state changes */
|
|
270
|
+
readonly onCircuitStateChange?: (from: CircuitState, to: CircuitState) => void;
|
|
271
|
+
}
|
|
272
|
+
type CircuitState = 'closed' | 'open' | 'half-open';
|
|
273
|
+
type TryoMetrics<E extends TypedError> = TryoMetrics$1<E>;
|
|
274
|
+
type JitterConfig = {
|
|
275
|
+
type: 'none';
|
|
276
|
+
} | {
|
|
277
|
+
type: 'full';
|
|
278
|
+
ratio: number;
|
|
279
|
+
} | {
|
|
280
|
+
type: 'equal';
|
|
281
|
+
ratio: number;
|
|
282
|
+
} | {
|
|
283
|
+
type: 'custom';
|
|
284
|
+
calculate: (delay: number) => number;
|
|
285
|
+
};
|
|
286
|
+
declare const JitterConfig: {
|
|
287
|
+
readonly none: () => JitterConfig;
|
|
288
|
+
readonly full: (ratio?: number) => JitterConfig;
|
|
289
|
+
readonly equal: (ratio?: number) => JitterConfig;
|
|
290
|
+
readonly custom: (calculate: (delay: number) => number) => JitterConfig;
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
type RulesMode = 'extend' | 'replace';
|
|
294
|
+
type DefaultError = AbortedError | TimeoutError | NetworkError | HttpError | CircuitOpenError | ValidationError | UnknownError;
|
|
295
|
+
type NonNull<T> = T extends null ? never : T;
|
|
296
|
+
type RuleReturn<R> = R extends (err: unknown) => infer Out ? NonNull<Out> : never;
|
|
297
|
+
type InferErrorFromRules<TRules extends readonly ErrorRule<TypedError>[]> = TRules extends readonly [] ? TypedError : RuleReturn<TRules[number]> | UnknownError;
|
|
298
|
+
type TryoOptions<E extends TypedError = TypedError> = Omit<Partial<TryoConfig<E>>, 'errorHandling' | 'signal'> & {
|
|
299
|
+
rules?: Array<ErrorRule<E>>;
|
|
300
|
+
rulesMode?: RulesMode;
|
|
301
|
+
fallback?: (err: unknown) => E;
|
|
302
|
+
toError?: (err: unknown) => E;
|
|
303
|
+
mapError?: (error: E) => E;
|
|
304
|
+
};
|
|
305
|
+
declare function tryo<const TRules extends readonly ErrorRule<TypedError>[]>(options: Omit<TryoOptions<InferErrorFromRules<TRules>>, 'rules'> & {
|
|
306
|
+
rules: TRules;
|
|
307
|
+
}): Tryo<InferErrorFromRules<TRules>>;
|
|
308
|
+
declare function tryo<E extends TypedError = DefaultError>(options?: TryoOptions<E>): Tryo<E>;
|
|
309
|
+
type Tryo<E extends TypedError = TypedError> = {
|
|
310
|
+
run: <T>(task: (ctx: {
|
|
311
|
+
signal: AbortSignal;
|
|
312
|
+
}) => Promise<T>, options?: Partial<TryoConfig<E>>) => Promise<TryoResult<T, E>>;
|
|
313
|
+
runOrThrow: <T>(task: (ctx: {
|
|
314
|
+
signal: AbortSignal;
|
|
315
|
+
}) => Promise<T>, options?: Partial<TryoConfig<E>>) => Promise<T>;
|
|
316
|
+
orThrow: <T>(task: (ctx: {
|
|
317
|
+
signal: AbortSignal;
|
|
318
|
+
}) => Promise<T>, options?: Partial<TryoConfig<E>>) => Promise<T>;
|
|
319
|
+
all: <T>(tasks: Array<(ctx: {
|
|
320
|
+
signal: AbortSignal;
|
|
321
|
+
}) => Promise<T>>, options?: Partial<TryoConfig<E> & {
|
|
322
|
+
concurrency?: number;
|
|
323
|
+
}>) => Promise<Array<TryoResult<T, E>>>;
|
|
324
|
+
allOrThrow: <T>(tasks: Array<(ctx: {
|
|
325
|
+
signal: AbortSignal;
|
|
326
|
+
}) => Promise<T>>, options?: Partial<TryoConfig<E> & {
|
|
327
|
+
concurrency?: number;
|
|
328
|
+
}>) => Promise<T[]>;
|
|
329
|
+
partitionAll: <T>(results: Array<TryoResult<T, E>>) => {
|
|
330
|
+
ok: Array<SuccessResult<T, E>>;
|
|
331
|
+
errors: Array<FailureResult<E> | AbortedResult<E> | TimeoutResult<E>>;
|
|
332
|
+
failure: Array<FailureResult<E>>;
|
|
333
|
+
aborted: Array<AbortedResult<E>>;
|
|
334
|
+
timeout: Array<TimeoutResult<E>>;
|
|
335
|
+
};
|
|
336
|
+
withConfig: (additionalConfig: Omit<Partial<TryoConfig<E>>, 'signal'>) => Tryo<E>;
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
declare const run: <T>(task: (ctx: {
|
|
340
|
+
signal: AbortSignal;
|
|
341
|
+
}) => Promise<T>, options?: Partial<TryoConfig<DefaultError>> | undefined) => Promise<TryoResult<T, DefaultError>>;
|
|
342
|
+
declare const runOrThrow: <T>(task: (ctx: {
|
|
343
|
+
signal: AbortSignal;
|
|
344
|
+
}) => Promise<T>, options?: Partial<TryoConfig<DefaultError>> | undefined) => Promise<T>;
|
|
345
|
+
declare const orThrow: <T>(task: (ctx: {
|
|
346
|
+
signal: AbortSignal;
|
|
347
|
+
}) => Promise<T>, options?: Partial<TryoConfig<DefaultError>> | undefined) => Promise<T>;
|
|
348
|
+
declare const all: <T>(tasks: ((ctx: {
|
|
349
|
+
signal: AbortSignal;
|
|
350
|
+
}) => Promise<T>)[], options?: Partial<TryoConfig<DefaultError> & {
|
|
351
|
+
concurrency?: number;
|
|
352
|
+
}> | undefined) => Promise<TryoResult<T, DefaultError>[]>;
|
|
353
|
+
declare const allOrThrow: <T>(tasks: ((ctx: {
|
|
354
|
+
signal: AbortSignal;
|
|
355
|
+
}) => Promise<T>)[], options?: Partial<TryoConfig<DefaultError> & {
|
|
356
|
+
concurrency?: number;
|
|
357
|
+
}> | undefined) => Promise<T[]>;
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Modern fluent error rule builder
|
|
361
|
+
* Provides type-safe error rule creation with enhanced ergonomics
|
|
362
|
+
*/
|
|
363
|
+
|
|
364
|
+
declare class ErrorRuleBuilder<T> {
|
|
365
|
+
private readonly predicate;
|
|
366
|
+
constructor(predicate: (err: unknown) => err is T);
|
|
367
|
+
toCode<const C extends string>(code: C): ErrorMapper<T, C>;
|
|
368
|
+
toError<const Out extends {
|
|
369
|
+
code: string;
|
|
370
|
+
message: string;
|
|
371
|
+
meta?: unknown;
|
|
372
|
+
status?: number;
|
|
373
|
+
cause?: unknown;
|
|
374
|
+
retryable?: boolean;
|
|
375
|
+
}>(mapper: (err: T) => Out): ErrorRule<TypedError<Out['code'], Out['meta']>>;
|
|
376
|
+
}
|
|
377
|
+
declare class ErrorMapper<T, C extends string> {
|
|
378
|
+
private readonly predicate;
|
|
379
|
+
private readonly errorCode;
|
|
380
|
+
constructor(predicate: (err: unknown) => err is T, errorCode: C);
|
|
381
|
+
with<const M = unknown>(mapper: (err: T) => {
|
|
382
|
+
message: string;
|
|
383
|
+
cause?: unknown;
|
|
384
|
+
meta?: M;
|
|
385
|
+
status?: number;
|
|
386
|
+
retryable?: boolean;
|
|
387
|
+
}): (err: unknown) => TypedError<C, M> | null;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
declare const errorRule: {
|
|
391
|
+
readonly when: <T>(predicate: (err: unknown) => err is T) => ErrorRuleBuilder<T>;
|
|
392
|
+
readonly instance: <T extends new (...args: unknown[]) => unknown>(ErrorClass: T) => ErrorRuleBuilder<InstanceType<T>>;
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
export { type AbortedResult, type ConcurrencyLimit, type FailureResult, type Milliseconds, type Percentage, type RetryCount, RetryStrategies, type RulesMode, type StatusCode, type SuccessResult, type TimeoutResult, type TryoConfig, type TryoMetrics$1 as TryoMetrics, type TryoOptions, type TryoResult, all, allOrThrow, asConcurrencyLimit, asMilliseconds, asPercentage, asRetryCount, asStatusCode, errorRule, orThrow, run, runOrThrow, tryo };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var j=Object.defineProperty;var ee=(e,r,t)=>r in e?j(e,r,{enumerable:!0,configurable:!0,writable:!0,value:t}):e[r]=t;var m=(e,r,t)=>ee(e,typeof r!="symbol"?r+"":r,t);var T=class extends Error{constructor(t,n){var o;super(t);m(this,"cause");m(this,"meta");m(this,"status");m(this,"timestamp");m(this,"retryable");this.timestamp=Date.now(),this.retryable=(o=n==null?void 0:n.retryable)!=null?o:!0,this.name=this.constructor.name,this.cause=n==null?void 0:n.cause,this.meta=n==null?void 0:n.meta,this.status=n==null?void 0:n.status,Error.captureStackTrace&&Error.captureStackTrace(this,this.constructor)}is(t){return this.code===t}withMeta(t){return Object.assign(this,{meta:t})}withStatus(t){return Object.assign(this,{status:t})}withCause(t){return Object.assign(this,{cause:t})}withRetryable(t){return this.retryable=t,this}toJSON(){return{name:this.name,code:this.code,message:this.message,timestamp:this.timestamp,retryable:this.retryable,cause:this.cause,stack:this.stack}}},D=class extends T{constructor(t,n){super(`Operation timed out after ${t}ms`,{cause:n,retryable:!0});m(this,"code","TIMEOUT")}};var g=class extends T{constructor(t,n){super(`Circuit breaker is open, reset after ${t}ms`,{cause:n,retryable:!1});m(this,"code","CIRCUIT_OPEN")}},v=class extends T{constructor(t,n,o){super(t,{cause:o,meta:{validationErrors:n},retryable:!1});this.validationErrors=n;m(this,"code","VALIDATION")}};var I=class extends T{constructor(t,n){super(t,{cause:n});m(this,"code","UNKNOWN")}};var x=e=>{if(e<0||!Number.isFinite(e))throw new Error(`Invalid milliseconds: must be a non-negative finite number, got ${e}`);return e},A=e=>{if(e<0||!Number.isInteger(e))throw new Error(`Invalid retry count: must be a non-negative integer, got ${e}`);return e},_=e=>{if(e<1||!Number.isInteger(e))throw new Error(`Invalid concurrency limit: must be a positive integer, got ${e}`);return e},re=e=>{if(e<0||e>100||!Number.isFinite(e))throw new Error(`Invalid percentage: must be between 0 and 100, got ${e}`);return e},te=e=>{if(!Number.isInteger(e)||e<100||e>599)throw new Error(`Invalid status code: must be an integer between 100-599, got ${e}`);return e};var F=class{constructor(r){m(this,"state");m(this,"config");this.config={failureThreshold:A(r.failureThreshold),resetTimeout:x(r.resetTimeout),halfOpenRequests:A(r.halfOpenRequests),shouldCountAsFailure:r.shouldCountAsFailure},this.state={state:"closed",failureCount:0,halfOpenCount:0}}async canExecute(){if(this.updateStateIfNeeded(),this.state.state==="open")return!1;if(this.state.state==="half-open"){if(this.state.halfOpenCount>=this.config.halfOpenRequests)return!1;this.state={...this.state,halfOpenCount:this.state.halfOpenCount+1}}return!0}async recordSuccess(){switch(this.state.state){case"closed":this.state={...this.state,failureCount:0};break;case"half-open":this.state={state:"closed",failureCount:0,halfOpenCount:0};break;case"open":break}}async recordFailure(r){var o,a,i;if(!((i=(a=(o=this.config).shouldCountAsFailure)==null?void 0:a.call(o,r))!=null?i:!0))return;let n=new Date;switch(this.state.state){case"closed":{let s=this.state.failureCount+1;s>=this.config.failureThreshold?this.state={state:"open",failureCount:s,halfOpenCount:0,lastFailureTime:n,nextAttemptTime:new Date(n.getTime()+this.config.resetTimeout)}:this.state={...this.state,failureCount:s,lastFailureTime:n};break}case"half-open":this.state={state:"open",failureCount:this.state.failureCount+1,halfOpenCount:0,lastFailureTime:n,nextAttemptTime:new Date(n.getTime()+this.config.resetTimeout)};break;case"open":this.state={...this.state,lastFailureTime:n,nextAttemptTime:new Date(n.getTime()+this.config.resetTimeout)};break}}getState(){return this.updateStateIfNeeded(),{...this.state,canExecute:this.state.state!=="open"}}createOpenError(){let r=this.state.nextAttemptTime?x(Math.max(0,this.state.nextAttemptTime.getTime()-Date.now())):this.config.resetTimeout;return new g(r)}forceState(r){this.state={state:r,failureCount:0,halfOpenCount:0}}reset(){this.state={state:"closed",failureCount:0,halfOpenCount:0}}updateStateIfNeeded(){this.state.state==="open"&&this.state.nextAttemptTime&&new Date>=this.state.nextAttemptTime&&(this.state={...this.state,state:"half-open",halfOpenCount:0})}};var W=(e,r)=>t=>{for(let n of e){let o=n(t);if(o!==null)return o}return r(t)},J=e=>r=>r instanceof T?r:r instanceof Error?new e(r.message,r):typeof r=="string"?new e(r):new e("Unknown error occurred",r);var ne=e=>{if(typeof e!="object"||e===null)return!1;let r=e;return typeof r.status=="number"||typeof r.statusCode=="number"},oe=e=>{if(typeof e!="object"||e===null)return!1;let r=e;return typeof r.code=="string"&&r.code.length>0},se=e=>{var r;if(e instanceof Error){if(e.name==="TypeError")return!0;let t=e.message.toLowerCase();if(t.includes("fetch")&&t.includes("failed")||t.includes("network"))return!0}if(oe(e)){let t=((r=e.code)!=null?r:"").toUpperCase();return t==="ECONNRESET"||t==="ECONNREFUSED"||t==="ETIMEDOUT"||t==="ENOTFOUND"||t==="EAI_AGAIN"}return!1},B=class{constructor(r){this.predicate=r}toCode(r){return new L(this.predicate,r)}toError(r){return t=>{if(!this.predicate(t))return null;let n=r(t),o=n.code;class a extends T{constructor(){var u;super(n.message,{cause:(u=n.cause)!=null?u:t,meta:n.meta,status:n.status,retryable:n.retryable});m(this,"code",o)}}return new a}}},L=class{constructor(r,t){this.predicate=r;this.errorCode=t}with(r){return t=>{if(!this.predicate(t))return null;let n=r(t),o=this.errorCode;class a extends T{constructor(){super(n.message,{cause:n.cause,meta:n.meta,status:n.status,retryable:n.retryable});m(this,"code",o)}}return new a}}},E={when:e=>new B(e),instance:e=>new B(r=>r instanceof e),code:e=>({for:r=>new L(r,e)}),string:(e,r)=>({when:t=>E.when(t).toCode(r).with(n=>({message:n===e?e:`${r}: ${n}`,cause:n}))}),httpStatus:(e,r)=>({for:t=>E.when(t).toCode(r!=null?r:`HTTP_${e}`).with(n=>({message:`HTTP ${e} error`,cause:n}))})};var w={typed:(e=>e instanceof T?e:null),abort:E.when(e=>e instanceof DOMException&&e.name==="AbortError").toCode("ABORTED").with(e=>({message:e.message||"Operation was aborted",cause:e,retryable:!1})),timeout:E.when(e=>e instanceof Error&&e.name==="TimeoutError").toCode("TIMEOUT").with(e=>({message:e.message||"Operation timed out",cause:e})),network:E.when(e=>se(e)).toCode("NETWORK").with(e=>({message:(e instanceof Error,e.message||"Network error"),cause:e})),http:E.when(e=>{var n;if(!ne(e))return!1;let r=e,t=(n=r.status)!=null?n:r.statusCode;return typeof t=="number"&&t>=400}).toCode("HTTP").with(e=>{var n;let r=(n=e.status)!=null?n:e.statusCode,t=typeof r=="number"&&(r>=500||r===429);return{message:e.message||`HTTP ${r!=null?r:"error"} error`,cause:e,status:typeof r=="number"?r:void 0,retryable:t}}),unknown:E.when(e=>e instanceof Error&&!(e instanceof T)).toCode("UNKNOWN").with(e=>({message:e.message||"Unknown error occurred",cause:e}))};var ae=[w.typed,w.abort,w.timeout,w.http,w.network,w.unknown];var ie={when:e=>E.when(e),instance:e=>E.instance(e)},K=ae,Fe=w.timeout,Be=w.abort,Le=w.network,Ue=w.http,_e=E.when(e=>e instanceof g).toCode("CIRCUIT_OPEN").with(e=>({message:e.message,cause:e})),ze=E.when(e=>e instanceof v).toCode("VALIDATION").with(e=>({message:e.message,cause:e}));var ue={fixed:e=>({type:"fixed",delay:e}),exponential:(e,r=2,t)=>({type:"exponential",base:e,factor:r,maxDelay:t}),fibonacci:(e,r)=>({type:"fibonacci",base:e,maxDelay:r}),custom:e=>({type:"custom",calculate:e})},q=(e,r,t)=>{switch(e.type){case"fixed":return e.delay;case"exponential":{let n=e.base*e.factor**(Number(r)-1);return e.maxDelay?Math.min(n,e.maxDelay):n}case"fibonacci":{let n=e.base*ce(Number(r));return e.maxDelay?Math.min(n,e.maxDelay):n}case"custom":return e.calculate(r,t);default:return e}},ce=e=>{if(e<=1)return 1;let r=1,t=1;for(let n=2;n<=e;n++){let o=r+t;r=t,t=o}return t};var Y=(e,r)=>new Promise((t,n)=>{if(r!=null&&r.aborted){n(new DOMException("Aborted","AbortError"));return}let o=s=>{r&&s&&r.removeEventListener("abort",s)},a,i=()=>{a&&clearTimeout(a),o(i),n(new DOMException("Aborted","AbortError"))};a=setTimeout(()=>{o(i),t()},e),r==null||r.addEventListener("abort",i,{once:!0})});var le=e=>{var i,s,u;if(e.toError)return e.toError;let r=(i=e.rulesMode)!=null?i:"extend",t=(s=e.rules)!=null?s:[],n=K,o=(u=e.fallback)!=null?u:(c=>J(I)(c)),a=r==="replace"?t:[...t,...n];return W(a,o)},z=class e{constructor(r={}){m(this,"circuitBreaker");m(this,"config");m(this,"lastCircuitState");let{rules:t,rulesMode:n,fallback:o,toError:a,mapError:i,...s}=r,u=le(r),c={...s,errorHandling:{normalizer:u,mapError:i}};this.config=c,c.circuitBreaker&&(this.circuitBreaker=new F(c.circuitBreaker),this.lastCircuitState=this.circuitBreaker.getState().state)}async run(r,t={}){var a,i,s,u,c,b;let n={...this.config,...t};if(this.circuitBreaker){let l=(a=this.lastCircuitState)!=null?a:this.circuitBreaker.getState().state,y=await this.circuitBreaker.canExecute(),p=this.circuitBreaker.getState().state;if(l!==p)try{(s=(i=n.hooks)==null?void 0:i.onCircuitStateChange)==null||s.call(i,l,p)}catch{}if(this.lastCircuitState=p,!y)return{type:"failure",ok:!1,data:null,error:this.circuitBreaker.createOpenError(),metrics:{totalAttempts:0,totalRetries:0,totalDuration:0,retryHistory:[]}}}let o=await me(r,n);if(this.circuitBreaker){let l=(u=this.lastCircuitState)!=null?u:this.circuitBreaker.getState().state;o.ok?await this.circuitBreaker.recordSuccess():await this.circuitBreaker.recordFailure(o.error);let y=this.circuitBreaker.getState().state;if(l!==y)try{(b=(c=n.hooks)==null?void 0:c.onCircuitStateChange)==null||b.call(c,l,y)}catch{}this.lastCircuitState=y}return o}async runOrThrow(r,t={}){let n=await this.run(r,t);if(n.ok)return n.data;throw n.error}async orThrow(r,t={}){let n=await this.run(r,t);if(n.ok)return n.data;throw n.error}async all(r,t={}){var b;let n={...this.config,...t},o=(b=n.concurrency)!=null?b:Number.POSITIVE_INFINITY,a=Number.isFinite(o)?Number(_(o)):Number.POSITIVE_INFINITY,i=new Array(r.length),s=0,u=async()=>{var l;for(;s<r.length&&!((l=n.signal)!=null&&l.aborted);){let y=s++;if(y>=r.length)break;let p=r[y];p&&(i[y]=await this.run(p,n))}},c=Array.from({length:Math.min(a,r.length)},()=>u());await Promise.all(c);for(let l=0;l<r.length;l++)if(!(l in i)){let y=r[l];y&&(i[l]=await this.run(y,n))}return i}async allOrThrow(r,t={}){let n=await this.all(r,t);for(let o of n)if(!o.ok)throw o.error;return n.map(o=>{if(!o.ok)throw o.error;return o.data})}partitionAll(r){let t=[],n=[],o=[],a=[],i=[];for(let s of r){if(s.type==="success"){t.push(s);continue}switch(n.push(s),s.type){case"failure":o.push(s);break;case"aborted":a.push(s);break;case"timeout":i.push(s);break}}return{ok:t,errors:n,failure:o,aborted:a,timeout:i}}getCircuitBreakerState(){var r;return(r=this.circuitBreaker)==null?void 0:r.getState()}resetCircuitBreaker(){var r;(r=this.circuitBreaker)==null||r.reset()}getConfig(){return{...this.config}}withConfig(r){var u,c;let{errorHandling:t,...n}=this.config,{errorHandling:o,...a}=r,i=(u=o==null?void 0:o.normalizer)!=null?u:t.normalizer,s=(c=o==null?void 0:o.mapError)!=null?c:t.mapError;return new e({...n,...a,toError:i,mapError:s})}withErrorType(r={}){return new e(r)}};function $(e={}){let r=new z(e);return{run:(t,n)=>r.run(t,n),orThrow:(t,n)=>r.orThrow(t,n),runOrThrow:(t,n)=>r.runOrThrow(t,n),all:(t,n)=>r.all(t,n),allOrThrow:(t,n)=>r.allOrThrow(t,n),partitionAll:t=>r.partitionAll(t),withConfig:t=>r.withConfig(t)}}async function me(e,r){let{signal:t,ignoreAbort:n=!0,timeout:o,retry:a,errorHandling:i,hooks:s,logger:u}=r,c=(k,...d)=>{try{k==null||k(...d)}catch{}},b,l=0,y=0,p=[],R=Date.now(),{signal:C,cleanup:G}=de(t);try{if(C.aborted){c(s==null?void 0:s.onAbort,C);let d=i.normalizer(new DOMException("Aborted","AbortError")),f=i.mapError?i.mapError(d):d;return{type:"aborted",ok:!1,data:null,error:f,metrics:{totalAttempts:l,totalRetries:y,totalDuration:Date.now()-R,lastError:f,retryHistory:p}}}let k=async d=>{var f,P,O;l=d;try{let M=e({signal:C}),S=o?await ye(M,o,C):await M;return c(s==null?void 0:s.onSuccess,S),c(u==null?void 0:u.info,`Task succeeded on attempt ${d}`),S}catch(M){let S=i.normalizer(M),h=i.mapError?i.mapError(S):S;b=h,h.code==="ABORTED"&&c(s==null?void 0:s.onAbort,C),n&&h.code==="ABORTED"||(c(s==null?void 0:s.onError,h),c(u==null?void 0:u.error,`Task failed on attempt ${d}`,h));let Q=A((f=a==null?void 0:a.maxRetries)!=null?f:0);if(d<=Number(Q)){let H=a==null?void 0:a.shouldRetry,X={totalAttempts:Number(l),elapsedTime:Date.now()-R,startTime:new Date(R),lastDelay:(P=p[p.length-1])!=null&&P.delay?Number((O=p[p.length-1])==null?void 0:O.delay):void 0};if(!H||H(d,h,X)){let Z=a?q(a.strategy,d,h):0,U=pe(Z,a==null?void 0:a.jitter),V=x(U);return p.push({attempt:d,error:h,delay:V,timestamp:new Date}),c(s==null?void 0:s.onRetry,d,h,U),c(u==null?void 0:u.info,`Retrying in ${U}ms (attempt ${d+1})`),await Y(V,C),k(d+1)}}throw h}};try{let d=await k(1);y=l>0?Number(l)-1:0;let f={totalAttempts:l,totalRetries:y,totalDuration:Date.now()-R,retryHistory:p};return c(s==null?void 0:s.onFinally,f),{type:"success",ok:!0,data:d,error:null,metrics:f}}catch(d){let f=b!=null?b:i.normalizer(d),P=f.code==="TIMEOUT"?"timeout":f.code==="ABORTED"?"aborted":"failure";y=l>0?Number(l)-1:0;let O={totalAttempts:l,totalRetries:y,totalDuration:Date.now()-R,lastError:f,retryHistory:p};return c(s==null?void 0:s.onFinally,O),{type:P,ok:!1,data:null,error:f,metrics:O}}}finally{G()}}function de(e){let r=new AbortController;if(!e)return{signal:r.signal,cleanup:()=>{}};let t=()=>r.abort();return e.aborted?(t(),{signal:r.signal,cleanup:()=>{}}):(e.addEventListener("abort",t,{once:!0}),{signal:r.signal,cleanup:()=>e.removeEventListener("abort",t)})}async function ye(e,r,t){return new Promise((n,o)=>{if(t!=null&&t.aborted){o(new DOMException("Aborted","AbortError"));return}let a=!1,i=setTimeout(()=>{a=!0,u(),o(new D(x(r)))},r),s=()=>{a||(a=!0,u(),o(new DOMException("Aborted","AbortError")))},u=()=>{clearTimeout(i),t==null||t.removeEventListener("abort",s)};t==null||t.addEventListener("abort",s,{once:!0}),e.then(c=>{a||(a=!0,u(),n(c))},c=>{a||(a=!0,u(),o(c))})})}function pe(e,r){if(!r||r.type==="none"||e<=0)return e;switch(r.type){case"full":{let t=Number(r.ratio)/100,n=Math.max(0,Number(e)*(1-t)),o=Number(e);return n+Math.random()*(o-n)}case"equal":{let t=Number(r.ratio)/100,n=Number(e)*t/2;return Number(e)-n+Math.random()*n}case"custom":return r.calculate(e);default:return r}}var N=$(),fe=N.run,Te=N.runOrThrow,Ee=N.orThrow,we=N.all,be=N.allOrThrow;export{ue as RetryStrategies,we as all,be as allOrThrow,_ as asConcurrencyLimit,x as asMilliseconds,re as asPercentage,A as asRetryCount,te as asStatusCode,ie as errorRule,Ee as orThrow,fe as run,Te as runOrThrow,$ as tryo};
|
package/package.json
CHANGED
|
@@ -1,28 +1,59 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
2
|
+
"name": "tryo",
|
|
3
|
+
"version": "0.13.0",
|
|
4
|
+
"description": "Run sync/async functions and return a typed Result instead of throwing.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"main": "./dist/index.cjs",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"require": "./dist/index.cjs"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"README.md",
|
|
20
|
+
"LICENSE"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsup",
|
|
24
|
+
"typecheck": "tsc --noEmit",
|
|
25
|
+
"test": "bun test",
|
|
26
|
+
"docs:dev": "cd docs && mintlify dev",
|
|
27
|
+
"docs:dev:es": "cd docs && mintlify dev --config mint.es.json",
|
|
28
|
+
"docs:build": "cd docs && mintlify build",
|
|
29
|
+
"docs:preview": "cd docs && mintlify preview",
|
|
30
|
+
"prepublishOnly": "npm run build",
|
|
31
|
+
"format": "biome format --write",
|
|
32
|
+
"lint": "biome lint",
|
|
33
|
+
"lint:fix": "biome lint --fix",
|
|
34
|
+
"check": "biome check",
|
|
35
|
+
"check:fix": "biome check --write",
|
|
36
|
+
"prepare": "husky"
|
|
37
|
+
},
|
|
38
|
+
"lint-staged": {
|
|
39
|
+
"*.{ts,tsx,js,jsx,json}": [
|
|
40
|
+
"biome check --write"
|
|
41
|
+
]
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@biomejs/biome": "2.3.13",
|
|
45
|
+
"@types/bun": "latest",
|
|
46
|
+
"husky": "^9.1.7",
|
|
47
|
+
"lint-staged": "^16.2.7",
|
|
48
|
+
"mint": "latest",
|
|
49
|
+
"tsup": "^8.5.1",
|
|
50
|
+
"typescript": "^5.0.0"
|
|
51
|
+
},
|
|
52
|
+
"license": "MIT",
|
|
53
|
+
"homepage": "https://tryo-docs.pages.dev/",
|
|
54
|
+
"bugs": "https://github.com/sebasxsala/tryo/issues",
|
|
55
|
+
"author": {
|
|
56
|
+
"name": "sebasxsala",
|
|
57
|
+
"url": "https://github.com/sebasxsala"
|
|
58
|
+
}
|
|
28
59
|
}
|