safe-await-lib 0.1.4 → 0.2.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/LICENCE +15 -0
- package/README.md +104 -59
- package/dist/index.d.mts +83 -29
- package/dist/index.d.ts +83 -29
- package/dist/index.js +45 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +45 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/LICENCE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Chelohub Inc.
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
10
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
11
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
12
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
13
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
14
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
15
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
CHANGED
|
@@ -4,33 +4,39 @@
|
|
|
4
4
|
[](https://www.npmjs.com/package/safe-await-lib)
|
|
5
5
|
[](https://github.com/chelohubinc/safe-await-lib/blob/main/LICENSE)
|
|
6
6
|
|
|
7
|
-
Safe async/await utility for handling promises without try/catch
|
|
7
|
+
Safe async/await utility for handling promises **without try/catch**.
|
|
8
8
|
Designed for **Node.js**, **TypeScript**, **React**, **React Native**, and **Expo**.
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
12
12
|
## 📌 Why SAFE-AWAIT-LIB?
|
|
13
13
|
|
|
14
|
-
Traditional async/await
|
|
14
|
+
Traditional `async / await` requires repetitive `try/catch` blocks, which:
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
- clutter business logic
|
|
17
|
+
- encourage inconsistent error handling
|
|
18
|
+
- hide error intent
|
|
17
19
|
|
|
18
|
-
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
**SAFE-AWAIT-LIB** solves this by enforcing a predictable, explicit error-handling pattern.
|
|
21
|
+
|
|
22
|
+
### Core idea
|
|
23
|
+
|
|
24
|
+
> **Async code should never throw — it should return.**
|
|
22
25
|
|
|
23
26
|
---
|
|
24
27
|
|
|
25
|
-
## 🚀 Features
|
|
28
|
+
## 🚀 Features (v0.2.0)
|
|
26
29
|
|
|
27
30
|
- Safe execution of async functions and promises
|
|
28
|
-
-
|
|
29
|
-
-
|
|
31
|
+
- Always returns a predictable tuple: `[SafeError | null, T | null]`
|
|
32
|
+
- Standardized error normalization
|
|
33
|
+
- Fully typed with TypeScript
|
|
30
34
|
- Compatible with Node.js, React, React Native, and Expo
|
|
31
|
-
-
|
|
32
|
-
- Tree-shakable
|
|
33
|
-
-
|
|
35
|
+
- Dual output: **ESM + CJS**
|
|
36
|
+
- Tree-shakable
|
|
37
|
+
- Built-in utilities:
|
|
38
|
+
- `safe.withTimeout` — timeout control for async operations
|
|
39
|
+
- `safe.retry` — retry logic for unstable operations
|
|
34
40
|
|
|
35
41
|
---
|
|
36
42
|
|
|
@@ -54,121 +60,160 @@ pnpm add safe-await-lib
|
|
|
54
60
|
```ts
|
|
55
61
|
import safe from 'safe-await-lib';
|
|
56
62
|
|
|
57
|
-
// Async function
|
|
58
63
|
async function fetchData() {
|
|
59
|
-
return
|
|
64
|
+
return 'Hello World';
|
|
60
65
|
}
|
|
61
66
|
|
|
62
|
-
// Using safe
|
|
63
67
|
const [err, data] = await safe(fetchData);
|
|
64
68
|
|
|
65
69
|
if (err) {
|
|
66
|
-
console.error(err.message);
|
|
70
|
+
console.error(err.message, err.code);
|
|
67
71
|
} else {
|
|
68
|
-
console.log(data); //
|
|
72
|
+
console.log(data); // "Hello World"
|
|
69
73
|
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Using a direct promise
|
|
70
77
|
|
|
71
|
-
|
|
72
|
-
const [
|
|
78
|
+
```ts
|
|
79
|
+
const [err, result] = await safe(Promise.resolve(42));
|
|
73
80
|
```
|
|
74
81
|
|
|
75
82
|
---
|
|
76
83
|
|
|
77
|
-
## 🛠️ API
|
|
84
|
+
## 🛠️ API
|
|
85
|
+
|
|
86
|
+
### `safe(input)`
|
|
78
87
|
|
|
79
|
-
|
|
88
|
+
```ts
|
|
89
|
+
safe<T>(input: Promise<T> | (() => T | Promise<T>))
|
|
90
|
+
→ Promise<[SafeError | null, T | null]>
|
|
91
|
+
```
|
|
80
92
|
|
|
81
|
-
|
|
82
|
-
- **returns**: `[SafeError | null, T | null]`
|
|
93
|
+
---
|
|
83
94
|
|
|
84
|
-
### `SafeError`
|
|
95
|
+
### `SafeError` structure
|
|
85
96
|
|
|
86
97
|
```ts
|
|
87
98
|
interface SafeError {
|
|
88
99
|
message: string; // Human-readable message
|
|
89
100
|
code: string; // Standardized error code
|
|
90
|
-
cause?: unknown; // Original error or
|
|
101
|
+
cause?: unknown; // Original error or context
|
|
91
102
|
}
|
|
92
103
|
```
|
|
93
104
|
|
|
94
105
|
---
|
|
95
106
|
|
|
96
|
-
## 🌱 Advanced Usage
|
|
107
|
+
## 🌱 Advanced Usage (v0.2.0)
|
|
97
108
|
|
|
98
|
-
###
|
|
109
|
+
### ⏱️ Timeout handling — `safe.withTimeout`
|
|
99
110
|
|
|
100
111
|
```ts
|
|
101
|
-
|
|
102
|
-
|
|
112
|
+
const [err, data] = await safe.withTimeout(fetchData(), 1000);
|
|
113
|
+
|
|
114
|
+
if (err?.code === 'TIMEOUT_ERROR') {
|
|
115
|
+
console.error('Operation timed out');
|
|
116
|
+
}
|
|
103
117
|
```
|
|
104
118
|
|
|
105
|
-
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
### 🔁 Retry logic — `safe.retry`
|
|
106
122
|
|
|
107
123
|
```ts
|
|
108
|
-
const [err, data] = await safe.retry(
|
|
124
|
+
const [err, data] = await safe.retry(
|
|
125
|
+
() => fetchData(),
|
|
126
|
+
{
|
|
127
|
+
retries: 3,
|
|
128
|
+
delayMs: 500,
|
|
129
|
+
onRetry: (error, attempt) => {
|
|
130
|
+
console.log(`Retry ${attempt} failed`, error);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
if (err) {
|
|
136
|
+
console.error(err.code); // RETRY_FAILED
|
|
137
|
+
}
|
|
109
138
|
```
|
|
110
139
|
|
|
111
140
|
---
|
|
112
141
|
|
|
113
|
-
##
|
|
142
|
+
## ✅ Available Today (v0.2.0)
|
|
143
|
+
|
|
144
|
+
SAFE-AWAIT-LIB currently provides:
|
|
114
145
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
| v0.5.0 | once |
|
|
121
|
-
| v0.6.0 | strict |
|
|
122
|
-
| v0.7.0 | map, unwrap |
|
|
123
|
-
| v0.8.0 | mockSuccess, mockError |
|
|
124
|
-
| v0.9.0 | debug |
|
|
146
|
+
- `safe()` — core safe async execution
|
|
147
|
+
- `safe.withTimeout()` — timeout control
|
|
148
|
+
- `safe.retry()` — retry mechanism
|
|
149
|
+
|
|
150
|
+
More utilities will be added incrementally.
|
|
125
151
|
|
|
126
152
|
---
|
|
127
153
|
|
|
128
154
|
## 🧪 Development
|
|
129
155
|
|
|
130
156
|
```bash
|
|
131
|
-
# Install
|
|
157
|
+
# Install dependencies
|
|
132
158
|
npm install
|
|
133
159
|
|
|
134
|
-
#
|
|
160
|
+
# Development build (watch mode)
|
|
135
161
|
npm run dev
|
|
136
162
|
|
|
137
|
-
# Type
|
|
163
|
+
# Type checking
|
|
138
164
|
npm run typecheck
|
|
139
165
|
|
|
140
166
|
# Run tests
|
|
141
167
|
npm test
|
|
142
168
|
|
|
143
|
-
# Build for
|
|
169
|
+
# Build for production
|
|
144
170
|
npm run build
|
|
145
171
|
```
|
|
146
172
|
|
|
147
173
|
---
|
|
148
174
|
|
|
175
|
+
## 📊 Roadmap
|
|
176
|
+
|
|
177
|
+
| Version | Features |
|
|
178
|
+
| ------: | ---------------------- |
|
|
179
|
+
| v0.2.0 | withTimeout, retry ✅ |
|
|
180
|
+
| v0.3.0 | all, allSettled |
|
|
181
|
+
| v0.4.0 | withContext |
|
|
182
|
+
| v0.5.0 | once |
|
|
183
|
+
| v0.6.0 | strict |
|
|
184
|
+
| v0.7.0 | map, unwrap |
|
|
185
|
+
| v0.8.0 | mockSuccess, mockError |
|
|
186
|
+
| v0.9.0 | debug |
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
149
190
|
## 📝 Contributing
|
|
150
191
|
|
|
151
|
-
|
|
192
|
+
Contributions are welcome.
|
|
152
193
|
|
|
153
194
|
1. Fork the repository
|
|
154
|
-
2. Create a feature branch
|
|
155
|
-
3.
|
|
156
|
-
4.
|
|
195
|
+
2. Create a feature branch
|
|
196
|
+
3. Add tests for your changes
|
|
197
|
+
4. Ensure `npm test` passes
|
|
198
|
+
5. Submit a pull request
|
|
157
199
|
|
|
158
|
-
Please
|
|
200
|
+
Please respect the existing **TypeScript typings** and **error model**.
|
|
159
201
|
|
|
160
202
|
---
|
|
161
203
|
|
|
162
|
-
## ❓ FAQ
|
|
204
|
+
## ❓ FAQ
|
|
205
|
+
|
|
206
|
+
### Why return a tuple instead of throwing?
|
|
207
|
+
|
|
208
|
+
It enforces explicit error handling and removes runtime surprises.
|
|
209
|
+
|
|
210
|
+
### Can I use this in React Native or Expo?
|
|
163
211
|
|
|
164
|
-
-
|
|
165
|
-
It ensures consistent error handling and makes async code more readable.
|
|
212
|
+
Yes. SAFE-AWAIT-LIB is runtime-agnostic and fully compatible.
|
|
166
213
|
|
|
167
|
-
|
|
168
|
-
Yes. SAFE-AWAIT-LIB is fully compatible.
|
|
214
|
+
### Is this production-ready?
|
|
169
215
|
|
|
170
|
-
|
|
171
|
-
Future modules like `retry` and `withTimeout` will handle these cases cleanly.
|
|
216
|
+
Yes. The core API is stable and versioned.
|
|
172
217
|
|
|
173
218
|
---
|
|
174
219
|
|
package/dist/index.d.mts
CHANGED
|
@@ -48,6 +48,72 @@ type SafeInput<T> = Promise<T> | (() => T | Promise<T>);
|
|
|
48
48
|
*/
|
|
49
49
|
declare function coreSafe<T>(input: SafeInput<T>): SafeResult<T>;
|
|
50
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Configuration options for the `retry` function.
|
|
53
|
+
*/
|
|
54
|
+
interface RetryOptions {
|
|
55
|
+
/**
|
|
56
|
+
* Number of retry attempts (default: 3)
|
|
57
|
+
*/
|
|
58
|
+
retries?: number;
|
|
59
|
+
/**
|
|
60
|
+
* Delay in milliseconds between attempts (default: 0)
|
|
61
|
+
*/
|
|
62
|
+
delayMs?: number;
|
|
63
|
+
/**
|
|
64
|
+
* Callback invoked after each failed attempt
|
|
65
|
+
*/
|
|
66
|
+
onRetry?: (error: unknown, attempt: number) => void;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Retries a failing operation multiple times before giving up.
|
|
70
|
+
*
|
|
71
|
+
* Useful for unstable or flaky operations such as network requests.
|
|
72
|
+
*
|
|
73
|
+
* ## Usage
|
|
74
|
+
* ```ts
|
|
75
|
+
* const [err, data] = await safe.retry(
|
|
76
|
+
* () => fetchData(),
|
|
77
|
+
* { retries: 3, delayMs: 500 }
|
|
78
|
+
* );
|
|
79
|
+
* ```
|
|
80
|
+
*
|
|
81
|
+
* ## Behavior
|
|
82
|
+
* - Retries the operation up to `retries` times
|
|
83
|
+
* - Optional delay between attempts
|
|
84
|
+
* - Calls `onRetry` after each failure
|
|
85
|
+
* - Returns `RETRY_FAILED` if all attempts fail
|
|
86
|
+
*
|
|
87
|
+
* @param input A function or promise to retry
|
|
88
|
+
* @param options Retry configuration
|
|
89
|
+
*/
|
|
90
|
+
declare function retry<T>(input: SafeInput<T>, options?: RetryOptions): SafeResult<T>;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Executes an operation with a time limit.
|
|
94
|
+
*
|
|
95
|
+
* If the operation does not resolve within the given duration,
|
|
96
|
+
* it fails with a `TIMEOUT_ERROR`.
|
|
97
|
+
*
|
|
98
|
+
* ## Usage
|
|
99
|
+
* ```ts
|
|
100
|
+
* const [err, data] = await safe.withTimeout(fetchData(), 1000);
|
|
101
|
+
*
|
|
102
|
+
* if (err?.code === ERROR_CODES.TIMEOUT) {
|
|
103
|
+
* console.error("Operation timed out");
|
|
104
|
+
* }
|
|
105
|
+
* ```
|
|
106
|
+
*
|
|
107
|
+
* ## Behavior
|
|
108
|
+
* - Resolves with `[null, result]` if completed in time
|
|
109
|
+
* - Resolves with `[SafeError, null]` on timeout or failure
|
|
110
|
+
* - Never throws
|
|
111
|
+
*
|
|
112
|
+
* @param input A promise or synchronous value
|
|
113
|
+
* @param ms Timeout duration in milliseconds
|
|
114
|
+
*/
|
|
115
|
+
declare function withTimeout<T>(input: T | Promise<T>, ms: number): SafeResult<T>;
|
|
116
|
+
|
|
51
117
|
/**
|
|
52
118
|
* Standardized error codes used across the SAFE-AWAIT-LIB package.
|
|
53
119
|
*
|
|
@@ -91,44 +157,32 @@ interface ErrorWithCause extends Error {
|
|
|
91
157
|
}
|
|
92
158
|
|
|
93
159
|
/**
|
|
94
|
-
*
|
|
160
|
+
* Executes a synchronous or asynchronous operation safely.
|
|
95
161
|
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
162
|
+
* `safe` wraps any function or promise and always returns a predictable tuple
|
|
163
|
+
* instead of throwing errors.
|
|
98
164
|
*
|
|
99
|
-
* ##
|
|
165
|
+
* ## Usage
|
|
100
166
|
* ```ts
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
* const [err, data] = await safe(async () => fetchData());
|
|
167
|
+
* const [err, data] = await safe(() => doSomething());
|
|
104
168
|
*
|
|
105
169
|
* if (err) {
|
|
106
|
-
* console.error(err.
|
|
107
|
-
* } else {
|
|
108
|
-
* console.log(data);
|
|
170
|
+
* console.error(err.code, err.message);
|
|
109
171
|
* }
|
|
110
172
|
* ```
|
|
111
173
|
*
|
|
112
|
-
* ##
|
|
113
|
-
* -
|
|
114
|
-
* -
|
|
115
|
-
* -
|
|
116
|
-
*
|
|
117
|
-
* ##
|
|
118
|
-
* - v0.2.0: withTimeout, retry
|
|
119
|
-
* - v0.3.0: all, allSettled
|
|
120
|
-
* - v0.4.0: withContext
|
|
121
|
-
* - v0.5.0: once
|
|
122
|
-
* - v0.6.0: strict
|
|
123
|
-
* - v0.7.0: map, unwrap
|
|
124
|
-
* - v0.8.0: mockSuccess, mockError
|
|
125
|
-
* - v0.9.0: debug
|
|
126
|
-
*
|
|
127
|
-
* ## Return value
|
|
128
|
-
* Always returns a tuple:
|
|
174
|
+
* ## Behavior
|
|
175
|
+
* - Never throws
|
|
176
|
+
* - Always resolves
|
|
177
|
+
* - Normalizes all errors into `SafeError`
|
|
178
|
+
*
|
|
179
|
+
* ## Return
|
|
129
180
|
* - `[null, result]` on success
|
|
130
181
|
* - `[SafeError, null]` on failure
|
|
131
182
|
*/
|
|
132
|
-
declare const safe: typeof coreSafe
|
|
183
|
+
declare const safe: typeof coreSafe & {
|
|
184
|
+
withTimeout: typeof withTimeout;
|
|
185
|
+
retry: typeof retry;
|
|
186
|
+
};
|
|
133
187
|
|
|
134
|
-
export { ERROR_CODES, type ErrorCode, type ErrorWithCause, type SafeError, type SafeInput, type SafeResult, safe as default, safe };
|
|
188
|
+
export { ERROR_CODES, type ErrorCode, type ErrorWithCause, type RetryOptions, type SafeError, type SafeInput, type SafeResult, safe as default, safe };
|
package/dist/index.d.ts
CHANGED
|
@@ -48,6 +48,72 @@ type SafeInput<T> = Promise<T> | (() => T | Promise<T>);
|
|
|
48
48
|
*/
|
|
49
49
|
declare function coreSafe<T>(input: SafeInput<T>): SafeResult<T>;
|
|
50
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Configuration options for the `retry` function.
|
|
53
|
+
*/
|
|
54
|
+
interface RetryOptions {
|
|
55
|
+
/**
|
|
56
|
+
* Number of retry attempts (default: 3)
|
|
57
|
+
*/
|
|
58
|
+
retries?: number;
|
|
59
|
+
/**
|
|
60
|
+
* Delay in milliseconds between attempts (default: 0)
|
|
61
|
+
*/
|
|
62
|
+
delayMs?: number;
|
|
63
|
+
/**
|
|
64
|
+
* Callback invoked after each failed attempt
|
|
65
|
+
*/
|
|
66
|
+
onRetry?: (error: unknown, attempt: number) => void;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Retries a failing operation multiple times before giving up.
|
|
70
|
+
*
|
|
71
|
+
* Useful for unstable or flaky operations such as network requests.
|
|
72
|
+
*
|
|
73
|
+
* ## Usage
|
|
74
|
+
* ```ts
|
|
75
|
+
* const [err, data] = await safe.retry(
|
|
76
|
+
* () => fetchData(),
|
|
77
|
+
* { retries: 3, delayMs: 500 }
|
|
78
|
+
* );
|
|
79
|
+
* ```
|
|
80
|
+
*
|
|
81
|
+
* ## Behavior
|
|
82
|
+
* - Retries the operation up to `retries` times
|
|
83
|
+
* - Optional delay between attempts
|
|
84
|
+
* - Calls `onRetry` after each failure
|
|
85
|
+
* - Returns `RETRY_FAILED` if all attempts fail
|
|
86
|
+
*
|
|
87
|
+
* @param input A function or promise to retry
|
|
88
|
+
* @param options Retry configuration
|
|
89
|
+
*/
|
|
90
|
+
declare function retry<T>(input: SafeInput<T>, options?: RetryOptions): SafeResult<T>;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Executes an operation with a time limit.
|
|
94
|
+
*
|
|
95
|
+
* If the operation does not resolve within the given duration,
|
|
96
|
+
* it fails with a `TIMEOUT_ERROR`.
|
|
97
|
+
*
|
|
98
|
+
* ## Usage
|
|
99
|
+
* ```ts
|
|
100
|
+
* const [err, data] = await safe.withTimeout(fetchData(), 1000);
|
|
101
|
+
*
|
|
102
|
+
* if (err?.code === ERROR_CODES.TIMEOUT) {
|
|
103
|
+
* console.error("Operation timed out");
|
|
104
|
+
* }
|
|
105
|
+
* ```
|
|
106
|
+
*
|
|
107
|
+
* ## Behavior
|
|
108
|
+
* - Resolves with `[null, result]` if completed in time
|
|
109
|
+
* - Resolves with `[SafeError, null]` on timeout or failure
|
|
110
|
+
* - Never throws
|
|
111
|
+
*
|
|
112
|
+
* @param input A promise or synchronous value
|
|
113
|
+
* @param ms Timeout duration in milliseconds
|
|
114
|
+
*/
|
|
115
|
+
declare function withTimeout<T>(input: T | Promise<T>, ms: number): SafeResult<T>;
|
|
116
|
+
|
|
51
117
|
/**
|
|
52
118
|
* Standardized error codes used across the SAFE-AWAIT-LIB package.
|
|
53
119
|
*
|
|
@@ -91,44 +157,32 @@ interface ErrorWithCause extends Error {
|
|
|
91
157
|
}
|
|
92
158
|
|
|
93
159
|
/**
|
|
94
|
-
*
|
|
160
|
+
* Executes a synchronous or asynchronous operation safely.
|
|
95
161
|
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
162
|
+
* `safe` wraps any function or promise and always returns a predictable tuple
|
|
163
|
+
* instead of throwing errors.
|
|
98
164
|
*
|
|
99
|
-
* ##
|
|
165
|
+
* ## Usage
|
|
100
166
|
* ```ts
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
* const [err, data] = await safe(async () => fetchData());
|
|
167
|
+
* const [err, data] = await safe(() => doSomething());
|
|
104
168
|
*
|
|
105
169
|
* if (err) {
|
|
106
|
-
* console.error(err.
|
|
107
|
-
* } else {
|
|
108
|
-
* console.log(data);
|
|
170
|
+
* console.error(err.code, err.message);
|
|
109
171
|
* }
|
|
110
172
|
* ```
|
|
111
173
|
*
|
|
112
|
-
* ##
|
|
113
|
-
* -
|
|
114
|
-
* -
|
|
115
|
-
* -
|
|
116
|
-
*
|
|
117
|
-
* ##
|
|
118
|
-
* - v0.2.0: withTimeout, retry
|
|
119
|
-
* - v0.3.0: all, allSettled
|
|
120
|
-
* - v0.4.0: withContext
|
|
121
|
-
* - v0.5.0: once
|
|
122
|
-
* - v0.6.0: strict
|
|
123
|
-
* - v0.7.0: map, unwrap
|
|
124
|
-
* - v0.8.0: mockSuccess, mockError
|
|
125
|
-
* - v0.9.0: debug
|
|
126
|
-
*
|
|
127
|
-
* ## Return value
|
|
128
|
-
* Always returns a tuple:
|
|
174
|
+
* ## Behavior
|
|
175
|
+
* - Never throws
|
|
176
|
+
* - Always resolves
|
|
177
|
+
* - Normalizes all errors into `SafeError`
|
|
178
|
+
*
|
|
179
|
+
* ## Return
|
|
129
180
|
* - `[null, result]` on success
|
|
130
181
|
* - `[SafeError, null]` on failure
|
|
131
182
|
*/
|
|
132
|
-
declare const safe: typeof coreSafe
|
|
183
|
+
declare const safe: typeof coreSafe & {
|
|
184
|
+
withTimeout: typeof withTimeout;
|
|
185
|
+
retry: typeof retry;
|
|
186
|
+
};
|
|
133
187
|
|
|
134
|
-
export { ERROR_CODES, type ErrorCode, type ErrorWithCause, type SafeError, type SafeInput, type SafeResult, safe as default, safe };
|
|
188
|
+
export { ERROR_CODES, type ErrorCode, type ErrorWithCause, type RetryOptions, type SafeError, type SafeInput, type SafeResult, safe as default, safe };
|
package/dist/index.js
CHANGED
|
@@ -76,11 +76,53 @@ async function coreSafe(input) {
|
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
// src/modules/retry.ts
|
|
80
|
+
function sleep(ms) {
|
|
81
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
82
|
+
}
|
|
83
|
+
async function retry(input, options = {}) {
|
|
84
|
+
const {
|
|
85
|
+
retries = 3,
|
|
86
|
+
delayMs = 0,
|
|
87
|
+
onRetry
|
|
88
|
+
} = options;
|
|
89
|
+
let lastError;
|
|
90
|
+
for (let attempt = 1; attempt <= retries; attempt++) {
|
|
91
|
+
try {
|
|
92
|
+
const data = typeof input === "function" ? await input() : await input;
|
|
93
|
+
return [null, data];
|
|
94
|
+
} catch (err) {
|
|
95
|
+
lastError = err;
|
|
96
|
+
onRetry == null ? void 0 : onRetry(err, attempt);
|
|
97
|
+
if (attempt < retries && delayMs > 0) {
|
|
98
|
+
await sleep(delayMs);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return [formatError(lastError, "RETRY_FAILED"), null];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// src/modules/timeout.ts
|
|
106
|
+
async function withTimeout(input, ms) {
|
|
107
|
+
let timer;
|
|
108
|
+
try {
|
|
109
|
+
const promise = typeof input === "function" ? input() : input;
|
|
110
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
111
|
+
timer = setTimeout(() => reject(formatError(new Error(`Timeout after ${ms}ms`), "TIMEOUT_ERROR")), ms);
|
|
112
|
+
});
|
|
113
|
+
const result = await Promise.race([promise, timeoutPromise]);
|
|
114
|
+
return [null, result];
|
|
115
|
+
} catch (error) {
|
|
116
|
+
return [error, null];
|
|
117
|
+
} finally {
|
|
118
|
+
clearTimeout(timer);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
79
122
|
// src/index.ts
|
|
80
123
|
var safe = Object.assign(coreSafe, {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
// retry,
|
|
124
|
+
withTimeout,
|
|
125
|
+
retry
|
|
84
126
|
// v0.3.0
|
|
85
127
|
// all,
|
|
86
128
|
// allSettled,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/errors/codes.ts","../src/errors/formatter.ts","../src/core/safe.ts"],"sourcesContent":["import { coreSafe } from \"./core/safe\";\n\n/**\n * `safe` is the core function of the SAFE-AWAIT-LIB package.\n *\n * It safely executes any synchronous or asynchronous operation and\n * always returns a predictable tuple instead of throwing errors.\n *\n * ## Basic usage\n * ```ts\n * import safe from \"safe-await-lib\";\n *\n * const [err, data] = await safe(async () => fetchData());\n *\n * if (err) {\n * console.error(err.message, err.code);\n * } else {\n * console.log(data);\n * }\n * ```\n *\n * ## Philosophy\n * - No try/catch pollution\n * - No unhandled promise rejections\n * - Explicit error handling\n *\n * ## Planned extensions\n * - v0.2.0: withTimeout, retry\n * - v0.3.0: all, allSettled\n * - v0.4.0: withContext\n * - v0.5.0: once\n * - v0.6.0: strict\n * - v0.7.0: map, unwrap\n * - v0.8.0: mockSuccess, mockError\n * - v0.9.0: debug\n *\n * ## Return value\n * Always returns a tuple:\n * - `[null, result]` on success\n * - `[SafeError, null]` on failure\n */\nexport const safe = Object.assign(coreSafe, {\n // v0.2.0\n // withTimeout,\n // retry,\n\n // v0.3.0\n // all,\n // allSettled,\n\n // v0.4.0\n // withContext,\n\n // v0.5.0\n // once,\n\n // v0.6.0\n // strict,\n\n // v0.7.0\n // map,\n // unwrap,\n\n // v0.8.0\n // mockSuccess,\n // mockError,\n\n // v0.9.0\n // debug,\n});\n\nexport type { SafeError, SafeResult, SafeInput } from \"./core/type\";\n\nexport type { ERROR_CODES, ErrorCode, ErrorWithCause } from \"./errors/codes\";\n\nexport default safe;","/**\n * Standardized error codes used across the SAFE-AWAIT-LIB package.\n *\n * These codes allow consumers to reliably identify the nature\n * of an error without relying on string comparison of messages.\n *\n * Each module of the package should use one of these codes\n * when returning or normalizing an error.\n */\nexport const ERROR_CODES = {\n /** Fallback error for unknown or unhandled failures */\n UNKNOWN: 'UNKNOWN_ERROR',\n\n /** Thrown when an operation exceeds a configured timeout */\n TIMEOUT: 'TIMEOUT_ERROR',\n\n /** Used when all retry attempts have failed */\n RETRY_FAILED: 'RETRY_FAILED',\n\n /** Used when an operation is explicitly aborted or cancelled */\n ABORTED: 'ABORT_ERROR',\n\n /** Used when input validation fails */\n VALIDATION: 'VALIDATION_ERROR',\n\n /** Used when a function guarded by `once()` is called more than once */\n EXECUTION_ONCE: 'ALREADY_EXECUTED'\n} as const;\n\n/**\n * Union type of all supported error codes.\n *\n * This type ensures strong typing and prevents the use\n * of unsupported or custom error codes across the package.\n */\nexport type ErrorCode = typeof ERROR_CODES[keyof typeof ERROR_CODES];\n\n/**\n * Internal helper interface to support the `cause` property\n * on Error objects in environments where it is not\n * yet fully supported or typed.\n *\n * This allows SAFE-AWAIT-LIB to preserve the original error\n * while still returning a normalized SafeError object.\n */\nexport interface ErrorWithCause extends Error {\n cause?: unknown;\n}\n","import { SafeError } from '../core/type';\nimport { ERROR_CODES, ErrorCode, ErrorWithCause } from './codes';\n\n/**\n * Normalizes any thrown value into a `SafeError`.\n *\n * This function is the foundation of SAFE-AWAIT-LIB's error-handling strategy.\n * It guarantees that all errors returned by the library follow the same\n * predictable structure, regardless of what was originally thrown.\n *\n * Supported inputs:\n * - `Error` instances (native or custom)\n * - string errors\n * - unknown or non-error values\n *\n * ## Normalization rules\n * - Preserves the original error message when possible\n * - Uses a standardized error code\n * - Keeps the original error in the `cause` field when available\n *\n * @param err - Any value thrown or rejected by an operation\n * @param defaultCode - Fallback error code when none is provided\n *\n * @returns A normalized `SafeError` object\n */\nexport function formatError(\n err: unknown,\n defaultCode: ErrorCode = ERROR_CODES.UNKNOWN\n): SafeError {\n if (err instanceof Error) {\n const errorWithCause = err as ErrorWithCause;\n\n return {\n message: err.message,\n code: (err as any).code || defaultCode,\n cause: errorWithCause.cause ?? err\n };\n }\n\n if (typeof err === 'string') {\n return {\n message: err,\n code: defaultCode,\n };\n }\n\n return {\n message: 'An unexpected error occurred',\n code: defaultCode,\n cause: err\n };\n}\n","import { formatError } from \"../errors/formatter\";\nimport { SafeInput, SafeResult } from \"./type\";\n\n/**\n * Internal execution engine for SAFE-AWAIT-LIB.\n *\n * This function executes a promise or a function safely and converts\n * any thrown or rejected value into a standardized `SafeResult` tuple.\n *\n * It is intentionally minimal and side-effect free, serving as the\n * foundation for all higher-level modules (retry, timeout, etc.).\n *\n * @param input - A promise or a function returning a value or a promise\n *\n * @returns A Promise resolving to a `[SafeError | null, T | null]` tuple\n */\nexport async function coreSafe<T>(input: SafeInput<T>): SafeResult<T> {\n try {\n const promise = typeof input === 'function' ? input() : input;\n const data = await promise;\n\n return [null, data];\n } catch (error) {\n return [formatError(error), null];\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACSO,IAAM,cAAc;AAAA;AAAA,EAEvB,SAAS;AAAA;AAAA,EAGT,SAAS;AAAA;AAAA,EAGT,cAAc;AAAA;AAAA,EAGd,SAAS;AAAA;AAAA,EAGT,YAAY;AAAA;AAAA,EAGZ,gBAAgB;AACpB;;;ACFO,SAAS,YACZ,KACA,cAAyB,YAAY,SAC5B;AA5Bb;AA6BI,MAAI,eAAe,OAAO;AACtB,UAAM,iBAAiB;AAEvB,WAAO;AAAA,MACH,SAAS,IAAI;AAAA,MACb,MAAO,IAAY,QAAQ;AAAA,MAC3B,QAAO,oBAAe,UAAf,YAAwB;AAAA,IACnC;AAAA,EACJ;AAEA,MAAI,OAAO,QAAQ,UAAU;AACzB,WAAO;AAAA,MACH,SAAS;AAAA,MACT,MAAM;AAAA,IACV;AAAA,EACJ;AAEA,SAAO;AAAA,IACH,SAAS;AAAA,IACT,MAAM;AAAA,IACN,OAAO;AAAA,EACX;AACJ;;;ACnCA,eAAsB,SAAY,OAAoC;AAClE,MAAI;AACA,UAAM,UAAU,OAAO,UAAU,aAAa,MAAM,IAAI;AACxD,UAAM,OAAO,MAAM;AAEnB,WAAO,CAAC,MAAM,IAAI;AAAA,EACtB,SAAS,OAAO;AACZ,WAAO,CAAC,YAAY,KAAK,GAAG,IAAI;AAAA,EACpC;AACJ;;;AHgBO,IAAM,OAAO,OAAO,OAAO,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA4B5C,CAAC;AAMD,IAAO,gBAAQ;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/errors/codes.ts","../src/errors/formatter.ts","../src/core/safe.ts","../src/modules/retry.ts","../src/modules/timeout.ts"],"sourcesContent":["import { coreSafe } from \"./core/safe\";\nimport { retry } from \"./modules/retry\";\nimport { withTimeout } from \"./modules/timeout\";\n\n/**\n * Executes a synchronous or asynchronous operation safely.\n *\n * `safe` wraps any function or promise and always returns a predictable tuple\n * instead of throwing errors.\n *\n * ## Usage\n * ```ts\n * const [err, data] = await safe(() => doSomething());\n *\n * if (err) {\n * console.error(err.code, err.message);\n * }\n * ```\n *\n * ## Behavior\n * - Never throws\n * - Always resolves\n * - Normalizes all errors into `SafeError`\n *\n * ## Return\n * - `[null, result]` on success\n * - `[SafeError, null]` on failure\n */\nexport const safe = Object.assign(coreSafe, {\n withTimeout,\n retry,\n\n // v0.3.0\n // all,\n // allSettled,\n\n // v0.4.0\n // withContext,\n\n // v0.5.0\n // once,\n\n // v0.6.0\n // strict,\n\n // v0.7.0\n // map,\n // unwrap,\n\n // v0.8.0\n // mockSuccess,\n // mockError,\n\n // v0.9.0\n // debug,\n});\n\nexport type {\n SafeError,\n SafeResult,\n SafeInput\n} from \"./core/type\";\n\nexport type {\n ERROR_CODES,\n ErrorCode,\n ErrorWithCause\n} from \"./errors/codes\";\n\nexport type {\n RetryOptions\n} from \"./modules/retry\";\n\nexport default safe;","/**\n * Standardized error codes used across the SAFE-AWAIT-LIB package.\n *\n * These codes allow consumers to reliably identify the nature\n * of an error without relying on string comparison of messages.\n *\n * Each module of the package should use one of these codes\n * when returning or normalizing an error.\n */\nexport const ERROR_CODES = {\n /** Fallback error for unknown or unhandled failures */\n UNKNOWN: 'UNKNOWN_ERROR',\n\n /** Thrown when an operation exceeds a configured timeout */\n TIMEOUT: 'TIMEOUT_ERROR',\n\n /** Used when all retry attempts have failed */\n RETRY_FAILED: 'RETRY_FAILED',\n\n /** Used when an operation is explicitly aborted or cancelled */\n ABORTED: 'ABORT_ERROR',\n\n /** Used when input validation fails */\n VALIDATION: 'VALIDATION_ERROR',\n\n /** Used when a function guarded by `once()` is called more than once */\n EXECUTION_ONCE: 'ALREADY_EXECUTED'\n} as const;\n\n/**\n * Union type of all supported error codes.\n *\n * This type ensures strong typing and prevents the use\n * of unsupported or custom error codes across the package.\n */\nexport type ErrorCode = typeof ERROR_CODES[keyof typeof ERROR_CODES];\n\n/**\n * Internal helper interface to support the `cause` property\n * on Error objects in environments where it is not\n * yet fully supported or typed.\n *\n * This allows SAFE-AWAIT-LIB to preserve the original error\n * while still returning a normalized SafeError object.\n */\nexport interface ErrorWithCause extends Error {\n cause?: unknown;\n}\n","import { SafeError } from '../core/type';\nimport { ERROR_CODES, ErrorCode, ErrorWithCause } from './codes';\n\n/**\n * Normalizes any thrown value into a `SafeError`.\n *\n * This function is the foundation of SAFE-AWAIT-LIB's error-handling strategy.\n * It guarantees that all errors returned by the library follow the same\n * predictable structure, regardless of what was originally thrown.\n *\n * Supported inputs:\n * - `Error` instances (native or custom)\n * - string errors\n * - unknown or non-error values\n *\n * ## Normalization rules\n * - Preserves the original error message when possible\n * - Uses a standardized error code\n * - Keeps the original error in the `cause` field when available\n *\n * @param err - Any value thrown or rejected by an operation\n * @param defaultCode - Fallback error code when none is provided\n *\n * @returns A normalized `SafeError` object\n */\nexport function formatError(\n err: unknown,\n defaultCode: ErrorCode = ERROR_CODES.UNKNOWN\n): SafeError {\n if (err instanceof Error) {\n const errorWithCause = err as ErrorWithCause;\n\n return {\n message: err.message,\n code: (err as any).code || defaultCode,\n cause: errorWithCause.cause ?? err\n };\n }\n\n if (typeof err === 'string') {\n return {\n message: err,\n code: defaultCode,\n };\n }\n\n return {\n message: 'An unexpected error occurred',\n code: defaultCode,\n cause: err\n };\n}\n","import { formatError } from \"../errors/formatter\";\nimport { SafeInput, SafeResult } from \"./type\";\n\n/**\n * Internal execution engine for SAFE-AWAIT-LIB.\n *\n * This function executes a promise or a function safely and converts\n * any thrown or rejected value into a standardized `SafeResult` tuple.\n *\n * It is intentionally minimal and side-effect free, serving as the\n * foundation for all higher-level modules (retry, timeout, etc.).\n *\n * @param input - A promise or a function returning a value or a promise\n *\n * @returns A Promise resolving to a `[SafeError | null, T | null]` tuple\n */\nexport async function coreSafe<T>(input: SafeInput<T>): SafeResult<T> {\n try {\n const promise = typeof input === 'function' ? input() : input;\n const data = await promise;\n return [null, data];\n } catch (error) {\n return [formatError(error), null];\n }\n}\n","import { SafeInput, SafeResult } from \"../core/type\";\nimport { formatError } from \"../errors/formatter\";\n\n/**\n * Configuration options for the `retry` function.\n */\nexport interface RetryOptions {\n /**\n * Number of retry attempts (default: 3)\n */\n retries?: number;\n\n /**\n * Delay in milliseconds between attempts (default: 0)\n */\n delayMs?: number;\n\n /**\n * Callback invoked after each failed attempt\n */\n onRetry?: (error: unknown, attempt: number) => void;\n}\n\n\nfunction sleep(ms: number) {\n return new Promise(resolve => setTimeout(resolve, ms));\n}\n\n/**\n * Retries a failing operation multiple times before giving up.\n *\n * Useful for unstable or flaky operations such as network requests.\n *\n * ## Usage\n * ```ts\n * const [err, data] = await safe.retry(\n * () => fetchData(),\n * { retries: 3, delayMs: 500 }\n * );\n * ```\n *\n * ## Behavior\n * - Retries the operation up to `retries` times\n * - Optional delay between attempts\n * - Calls `onRetry` after each failure\n * - Returns `RETRY_FAILED` if all attempts fail\n *\n * @param input A function or promise to retry\n * @param options Retry configuration\n */\nexport async function retry<T>(input: SafeInput<T>, options: RetryOptions = {}): SafeResult<T> {\n const {\n retries = 3,\n delayMs = 0,\n onRetry\n } = options;\n\n let lastError: unknown;\n\n for (let attempt = 1; attempt <= retries; attempt++) {\n try {\n const data = typeof input === 'function' ? (await input()) : (await input);\n return [null, data];\n } catch (err) {\n lastError = err;\n onRetry?.(err, attempt);\n if (attempt < retries && delayMs > 0) {\n await sleep(delayMs);\n }\n }\n }\n\n return [formatError(lastError, 'RETRY_FAILED'), null];\n}","import { ERROR_CODES } from '../errors/codes';\nimport { formatError } from '../errors/formatter';\nimport { SafeResult } from './../core/type';\n\n/**\n * Executes an operation with a time limit.\n *\n * If the operation does not resolve within the given duration,\n * it fails with a `TIMEOUT_ERROR`.\n *\n * ## Usage\n * ```ts\n * const [err, data] = await safe.withTimeout(fetchData(), 1000);\n *\n * if (err?.code === ERROR_CODES.TIMEOUT) {\n * console.error(\"Operation timed out\");\n * }\n * ```\n *\n * ## Behavior\n * - Resolves with `[null, result]` if completed in time\n * - Resolves with `[SafeError, null]` on timeout or failure\n * - Never throws\n *\n * @param input A promise or synchronous value\n * @param ms Timeout duration in milliseconds\n */\nexport async function withTimeout<T>(input: T | Promise<T>, ms: number): SafeResult<T> {\n let timer: ReturnType<typeof setTimeout>;\n\n try {\n const promise = typeof input === 'function' ? input() : input;\n\n const timeoutPromise = new Promise<never>((_, reject) => {\n timer = setTimeout(() => reject(formatError(new Error(`Timeout after ${ms}ms`), 'TIMEOUT_ERROR')), ms);\n });\n\n const result = await Promise.race([promise, timeoutPromise]);\n return [null, result];\n } catch (error: any) {\n return [error, null];\n } finally {\n clearTimeout(timer!);\n }\n}"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACSO,IAAM,cAAc;AAAA;AAAA,EAEvB,SAAS;AAAA;AAAA,EAGT,SAAS;AAAA;AAAA,EAGT,cAAc;AAAA;AAAA,EAGd,SAAS;AAAA;AAAA,EAGT,YAAY;AAAA;AAAA,EAGZ,gBAAgB;AACpB;;;ACFO,SAAS,YACZ,KACA,cAAyB,YAAY,SAC5B;AA5Bb;AA6BI,MAAI,eAAe,OAAO;AACtB,UAAM,iBAAiB;AAEvB,WAAO;AAAA,MACH,SAAS,IAAI;AAAA,MACb,MAAO,IAAY,QAAQ;AAAA,MAC3B,QAAO,oBAAe,UAAf,YAAwB;AAAA,IACnC;AAAA,EACJ;AAEA,MAAI,OAAO,QAAQ,UAAU;AACzB,WAAO;AAAA,MACH,SAAS;AAAA,MACT,MAAM;AAAA,IACV;AAAA,EACJ;AAEA,SAAO;AAAA,IACH,SAAS;AAAA,IACT,MAAM;AAAA,IACN,OAAO;AAAA,EACX;AACJ;;;ACnCA,eAAsB,SAAY,OAAoC;AAClE,MAAI;AACA,UAAM,UAAU,OAAO,UAAU,aAAa,MAAM,IAAI;AACxD,UAAM,OAAO,MAAM;AACnB,WAAO,CAAC,MAAM,IAAI;AAAA,EACtB,SAAS,OAAO;AACZ,WAAO,CAAC,YAAY,KAAK,GAAG,IAAI;AAAA,EACpC;AACJ;;;ACAA,SAAS,MAAM,IAAY;AACvB,SAAO,IAAI,QAAQ,aAAW,WAAW,SAAS,EAAE,CAAC;AACzD;AAwBA,eAAsB,MAAS,OAAqB,UAAwB,CAAC,GAAkB;AAC3F,QAAM;AAAA,IACF,UAAU;AAAA,IACV,UAAU;AAAA,IACV;AAAA,EACJ,IAAI;AAEJ,MAAI;AAEJ,WAAS,UAAU,GAAG,WAAW,SAAS,WAAW;AACjD,QAAI;AACA,YAAM,OAAO,OAAO,UAAU,aAAc,MAAM,MAAM,IAAM,MAAM;AACpE,aAAO,CAAC,MAAM,IAAI;AAAA,IACtB,SAAS,KAAK;AACV,kBAAY;AACZ,yCAAU,KAAK;AACf,UAAI,UAAU,WAAW,UAAU,GAAG;AAClC,cAAM,MAAM,OAAO;AAAA,MACvB;AAAA,IACJ;AAAA,EACJ;AAEA,SAAO,CAAC,YAAY,WAAW,cAAc,GAAG,IAAI;AACxD;;;AC9CA,eAAsB,YAAe,OAAuB,IAA2B;AACnF,MAAI;AAEJ,MAAI;AACA,UAAM,UAAU,OAAO,UAAU,aAAa,MAAM,IAAI;AAExD,UAAM,iBAAiB,IAAI,QAAe,CAAC,GAAG,WAAW;AACrD,cAAQ,WAAW,MAAM,OAAO,YAAY,IAAI,MAAM,iBAAiB,EAAE,IAAI,GAAG,eAAe,CAAC,GAAG,EAAE;AAAA,IACzG,CAAC;AAED,UAAM,SAAS,MAAM,QAAQ,KAAK,CAAC,SAAS,cAAc,CAAC;AAC3D,WAAO,CAAC,MAAM,MAAM;AAAA,EACxB,SAAS,OAAY;AACjB,WAAO,CAAC,OAAO,IAAI;AAAA,EACvB,UAAE;AACE,iBAAa,KAAM;AAAA,EACvB;AACJ;;;ALhBO,IAAM,OAAO,OAAO,OAAO,UAAU;AAAA,EACxC;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAyBJ,CAAC;AAkBD,IAAO,gBAAQ;","names":[]}
|
package/dist/index.mjs
CHANGED
|
@@ -49,11 +49,53 @@ async function coreSafe(input) {
|
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
// src/modules/retry.ts
|
|
53
|
+
function sleep(ms) {
|
|
54
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
55
|
+
}
|
|
56
|
+
async function retry(input, options = {}) {
|
|
57
|
+
const {
|
|
58
|
+
retries = 3,
|
|
59
|
+
delayMs = 0,
|
|
60
|
+
onRetry
|
|
61
|
+
} = options;
|
|
62
|
+
let lastError;
|
|
63
|
+
for (let attempt = 1; attempt <= retries; attempt++) {
|
|
64
|
+
try {
|
|
65
|
+
const data = typeof input === "function" ? await input() : await input;
|
|
66
|
+
return [null, data];
|
|
67
|
+
} catch (err) {
|
|
68
|
+
lastError = err;
|
|
69
|
+
onRetry == null ? void 0 : onRetry(err, attempt);
|
|
70
|
+
if (attempt < retries && delayMs > 0) {
|
|
71
|
+
await sleep(delayMs);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return [formatError(lastError, "RETRY_FAILED"), null];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// src/modules/timeout.ts
|
|
79
|
+
async function withTimeout(input, ms) {
|
|
80
|
+
let timer;
|
|
81
|
+
try {
|
|
82
|
+
const promise = typeof input === "function" ? input() : input;
|
|
83
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
84
|
+
timer = setTimeout(() => reject(formatError(new Error(`Timeout after ${ms}ms`), "TIMEOUT_ERROR")), ms);
|
|
85
|
+
});
|
|
86
|
+
const result = await Promise.race([promise, timeoutPromise]);
|
|
87
|
+
return [null, result];
|
|
88
|
+
} catch (error) {
|
|
89
|
+
return [error, null];
|
|
90
|
+
} finally {
|
|
91
|
+
clearTimeout(timer);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
52
95
|
// src/index.ts
|
|
53
96
|
var safe = Object.assign(coreSafe, {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
// retry,
|
|
97
|
+
withTimeout,
|
|
98
|
+
retry
|
|
57
99
|
// v0.3.0
|
|
58
100
|
// all,
|
|
59
101
|
// allSettled,
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/errors/codes.ts","../src/errors/formatter.ts","../src/core/safe.ts","../src/index.ts"],"sourcesContent":["/**\n * Standardized error codes used across the SAFE-AWAIT-LIB package.\n *\n * These codes allow consumers to reliably identify the nature\n * of an error without relying on string comparison of messages.\n *\n * Each module of the package should use one of these codes\n * when returning or normalizing an error.\n */\nexport const ERROR_CODES = {\n /** Fallback error for unknown or unhandled failures */\n UNKNOWN: 'UNKNOWN_ERROR',\n\n /** Thrown when an operation exceeds a configured timeout */\n TIMEOUT: 'TIMEOUT_ERROR',\n\n /** Used when all retry attempts have failed */\n RETRY_FAILED: 'RETRY_FAILED',\n\n /** Used when an operation is explicitly aborted or cancelled */\n ABORTED: 'ABORT_ERROR',\n\n /** Used when input validation fails */\n VALIDATION: 'VALIDATION_ERROR',\n\n /** Used when a function guarded by `once()` is called more than once */\n EXECUTION_ONCE: 'ALREADY_EXECUTED'\n} as const;\n\n/**\n * Union type of all supported error codes.\n *\n * This type ensures strong typing and prevents the use\n * of unsupported or custom error codes across the package.\n */\nexport type ErrorCode = typeof ERROR_CODES[keyof typeof ERROR_CODES];\n\n/**\n * Internal helper interface to support the `cause` property\n * on Error objects in environments where it is not\n * yet fully supported or typed.\n *\n * This allows SAFE-AWAIT-LIB to preserve the original error\n * while still returning a normalized SafeError object.\n */\nexport interface ErrorWithCause extends Error {\n cause?: unknown;\n}\n","import { SafeError } from '../core/type';\nimport { ERROR_CODES, ErrorCode, ErrorWithCause } from './codes';\n\n/**\n * Normalizes any thrown value into a `SafeError`.\n *\n * This function is the foundation of SAFE-AWAIT-LIB's error-handling strategy.\n * It guarantees that all errors returned by the library follow the same\n * predictable structure, regardless of what was originally thrown.\n *\n * Supported inputs:\n * - `Error` instances (native or custom)\n * - string errors\n * - unknown or non-error values\n *\n * ## Normalization rules\n * - Preserves the original error message when possible\n * - Uses a standardized error code\n * - Keeps the original error in the `cause` field when available\n *\n * @param err - Any value thrown or rejected by an operation\n * @param defaultCode - Fallback error code when none is provided\n *\n * @returns A normalized `SafeError` object\n */\nexport function formatError(\n err: unknown,\n defaultCode: ErrorCode = ERROR_CODES.UNKNOWN\n): SafeError {\n if (err instanceof Error) {\n const errorWithCause = err as ErrorWithCause;\n\n return {\n message: err.message,\n code: (err as any).code || defaultCode,\n cause: errorWithCause.cause ?? err\n };\n }\n\n if (typeof err === 'string') {\n return {\n message: err,\n code: defaultCode,\n };\n }\n\n return {\n message: 'An unexpected error occurred',\n code: defaultCode,\n cause: err\n };\n}\n","import { formatError } from \"../errors/formatter\";\nimport { SafeInput, SafeResult } from \"./type\";\n\n/**\n * Internal execution engine for SAFE-AWAIT-LIB.\n *\n * This function executes a promise or a function safely and converts\n * any thrown or rejected value into a standardized `SafeResult` tuple.\n *\n * It is intentionally minimal and side-effect free, serving as the\n * foundation for all higher-level modules (retry, timeout, etc.).\n *\n * @param input - A promise or a function returning a value or a promise\n *\n * @returns A Promise resolving to a `[SafeError | null, T | null]` tuple\n */\nexport async function coreSafe<T>(input: SafeInput<T>): SafeResult<T> {\n try {\n const promise = typeof input === 'function' ? input() : input;\n const data = await promise;\n\n return [null, data];\n } catch (error) {\n return [formatError(error), null];\n }\n}\n","import { coreSafe } from \"./core/safe\";\n\n/**\n * `safe` is the core function of the SAFE-AWAIT-LIB package.\n *\n * It safely executes any synchronous or asynchronous operation and\n * always returns a predictable tuple instead of throwing errors.\n *\n * ## Basic usage\n * ```ts\n * import safe from \"safe-await-lib\";\n *\n * const [err, data] = await safe(async () => fetchData());\n *\n * if (err) {\n * console.error(err.message, err.code);\n * } else {\n * console.log(data);\n * }\n * ```\n *\n * ## Philosophy\n * - No try/catch pollution\n * - No unhandled promise rejections\n * - Explicit error handling\n *\n * ## Planned extensions\n * - v0.2.0: withTimeout, retry\n * - v0.3.0: all, allSettled\n * - v0.4.0: withContext\n * - v0.5.0: once\n * - v0.6.0: strict\n * - v0.7.0: map, unwrap\n * - v0.8.0: mockSuccess, mockError\n * - v0.9.0: debug\n *\n * ## Return value\n * Always returns a tuple:\n * - `[null, result]` on success\n * - `[SafeError, null]` on failure\n */\nexport const safe = Object.assign(coreSafe, {\n // v0.2.0\n // withTimeout,\n // retry,\n\n // v0.3.0\n // all,\n // allSettled,\n\n // v0.4.0\n // withContext,\n\n // v0.5.0\n // once,\n\n // v0.6.0\n // strict,\n\n // v0.7.0\n // map,\n // unwrap,\n\n // v0.8.0\n // mockSuccess,\n // mockError,\n\n // v0.9.0\n // debug,\n});\n\nexport type { SafeError, SafeResult, SafeInput } from \"./core/type\";\n\nexport type { ERROR_CODES, ErrorCode, ErrorWithCause } from \"./errors/codes\";\n\nexport default safe;"],"mappings":";AASO,IAAM,cAAc;AAAA;AAAA,EAEvB,SAAS;AAAA;AAAA,EAGT,SAAS;AAAA;AAAA,EAGT,cAAc;AAAA;AAAA,EAGd,SAAS;AAAA;AAAA,EAGT,YAAY;AAAA;AAAA,EAGZ,gBAAgB;AACpB;;;ACFO,SAAS,YACZ,KACA,cAAyB,YAAY,SAC5B;AA5Bb;AA6BI,MAAI,eAAe,OAAO;AACtB,UAAM,iBAAiB;AAEvB,WAAO;AAAA,MACH,SAAS,IAAI;AAAA,MACb,MAAO,IAAY,QAAQ;AAAA,MAC3B,QAAO,oBAAe,UAAf,YAAwB;AAAA,IACnC;AAAA,EACJ;AAEA,MAAI,OAAO,QAAQ,UAAU;AACzB,WAAO;AAAA,MACH,SAAS;AAAA,MACT,MAAM;AAAA,IACV;AAAA,EACJ;AAEA,SAAO;AAAA,IACH,SAAS;AAAA,IACT,MAAM;AAAA,IACN,OAAO;AAAA,EACX;AACJ;;;ACnCA,eAAsB,SAAY,OAAoC;AAClE,MAAI;AACA,UAAM,UAAU,OAAO,UAAU,aAAa,MAAM,IAAI;AACxD,UAAM,OAAO,MAAM;AAEnB,WAAO,CAAC,MAAM,IAAI;AAAA,EACtB,SAAS,OAAO;AACZ,WAAO,CAAC,YAAY,KAAK,GAAG,IAAI;AAAA,EACpC;AACJ;;;ACgBO,IAAM,OAAO,OAAO,OAAO,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA4B5C,CAAC;AAMD,IAAO,gBAAQ;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/errors/codes.ts","../src/errors/formatter.ts","../src/core/safe.ts","../src/modules/retry.ts","../src/modules/timeout.ts","../src/index.ts"],"sourcesContent":["/**\n * Standardized error codes used across the SAFE-AWAIT-LIB package.\n *\n * These codes allow consumers to reliably identify the nature\n * of an error without relying on string comparison of messages.\n *\n * Each module of the package should use one of these codes\n * when returning or normalizing an error.\n */\nexport const ERROR_CODES = {\n /** Fallback error for unknown or unhandled failures */\n UNKNOWN: 'UNKNOWN_ERROR',\n\n /** Thrown when an operation exceeds a configured timeout */\n TIMEOUT: 'TIMEOUT_ERROR',\n\n /** Used when all retry attempts have failed */\n RETRY_FAILED: 'RETRY_FAILED',\n\n /** Used when an operation is explicitly aborted or cancelled */\n ABORTED: 'ABORT_ERROR',\n\n /** Used when input validation fails */\n VALIDATION: 'VALIDATION_ERROR',\n\n /** Used when a function guarded by `once()` is called more than once */\n EXECUTION_ONCE: 'ALREADY_EXECUTED'\n} as const;\n\n/**\n * Union type of all supported error codes.\n *\n * This type ensures strong typing and prevents the use\n * of unsupported or custom error codes across the package.\n */\nexport type ErrorCode = typeof ERROR_CODES[keyof typeof ERROR_CODES];\n\n/**\n * Internal helper interface to support the `cause` property\n * on Error objects in environments where it is not\n * yet fully supported or typed.\n *\n * This allows SAFE-AWAIT-LIB to preserve the original error\n * while still returning a normalized SafeError object.\n */\nexport interface ErrorWithCause extends Error {\n cause?: unknown;\n}\n","import { SafeError } from '../core/type';\nimport { ERROR_CODES, ErrorCode, ErrorWithCause } from './codes';\n\n/**\n * Normalizes any thrown value into a `SafeError`.\n *\n * This function is the foundation of SAFE-AWAIT-LIB's error-handling strategy.\n * It guarantees that all errors returned by the library follow the same\n * predictable structure, regardless of what was originally thrown.\n *\n * Supported inputs:\n * - `Error` instances (native or custom)\n * - string errors\n * - unknown or non-error values\n *\n * ## Normalization rules\n * - Preserves the original error message when possible\n * - Uses a standardized error code\n * - Keeps the original error in the `cause` field when available\n *\n * @param err - Any value thrown or rejected by an operation\n * @param defaultCode - Fallback error code when none is provided\n *\n * @returns A normalized `SafeError` object\n */\nexport function formatError(\n err: unknown,\n defaultCode: ErrorCode = ERROR_CODES.UNKNOWN\n): SafeError {\n if (err instanceof Error) {\n const errorWithCause = err as ErrorWithCause;\n\n return {\n message: err.message,\n code: (err as any).code || defaultCode,\n cause: errorWithCause.cause ?? err\n };\n }\n\n if (typeof err === 'string') {\n return {\n message: err,\n code: defaultCode,\n };\n }\n\n return {\n message: 'An unexpected error occurred',\n code: defaultCode,\n cause: err\n };\n}\n","import { formatError } from \"../errors/formatter\";\nimport { SafeInput, SafeResult } from \"./type\";\n\n/**\n * Internal execution engine for SAFE-AWAIT-LIB.\n *\n * This function executes a promise or a function safely and converts\n * any thrown or rejected value into a standardized `SafeResult` tuple.\n *\n * It is intentionally minimal and side-effect free, serving as the\n * foundation for all higher-level modules (retry, timeout, etc.).\n *\n * @param input - A promise or a function returning a value or a promise\n *\n * @returns A Promise resolving to a `[SafeError | null, T | null]` tuple\n */\nexport async function coreSafe<T>(input: SafeInput<T>): SafeResult<T> {\n try {\n const promise = typeof input === 'function' ? input() : input;\n const data = await promise;\n return [null, data];\n } catch (error) {\n return [formatError(error), null];\n }\n}\n","import { SafeInput, SafeResult } from \"../core/type\";\nimport { formatError } from \"../errors/formatter\";\n\n/**\n * Configuration options for the `retry` function.\n */\nexport interface RetryOptions {\n /**\n * Number of retry attempts (default: 3)\n */\n retries?: number;\n\n /**\n * Delay in milliseconds between attempts (default: 0)\n */\n delayMs?: number;\n\n /**\n * Callback invoked after each failed attempt\n */\n onRetry?: (error: unknown, attempt: number) => void;\n}\n\n\nfunction sleep(ms: number) {\n return new Promise(resolve => setTimeout(resolve, ms));\n}\n\n/**\n * Retries a failing operation multiple times before giving up.\n *\n * Useful for unstable or flaky operations such as network requests.\n *\n * ## Usage\n * ```ts\n * const [err, data] = await safe.retry(\n * () => fetchData(),\n * { retries: 3, delayMs: 500 }\n * );\n * ```\n *\n * ## Behavior\n * - Retries the operation up to `retries` times\n * - Optional delay between attempts\n * - Calls `onRetry` after each failure\n * - Returns `RETRY_FAILED` if all attempts fail\n *\n * @param input A function or promise to retry\n * @param options Retry configuration\n */\nexport async function retry<T>(input: SafeInput<T>, options: RetryOptions = {}): SafeResult<T> {\n const {\n retries = 3,\n delayMs = 0,\n onRetry\n } = options;\n\n let lastError: unknown;\n\n for (let attempt = 1; attempt <= retries; attempt++) {\n try {\n const data = typeof input === 'function' ? (await input()) : (await input);\n return [null, data];\n } catch (err) {\n lastError = err;\n onRetry?.(err, attempt);\n if (attempt < retries && delayMs > 0) {\n await sleep(delayMs);\n }\n }\n }\n\n return [formatError(lastError, 'RETRY_FAILED'), null];\n}","import { ERROR_CODES } from '../errors/codes';\nimport { formatError } from '../errors/formatter';\nimport { SafeResult } from './../core/type';\n\n/**\n * Executes an operation with a time limit.\n *\n * If the operation does not resolve within the given duration,\n * it fails with a `TIMEOUT_ERROR`.\n *\n * ## Usage\n * ```ts\n * const [err, data] = await safe.withTimeout(fetchData(), 1000);\n *\n * if (err?.code === ERROR_CODES.TIMEOUT) {\n * console.error(\"Operation timed out\");\n * }\n * ```\n *\n * ## Behavior\n * - Resolves with `[null, result]` if completed in time\n * - Resolves with `[SafeError, null]` on timeout or failure\n * - Never throws\n *\n * @param input A promise or synchronous value\n * @param ms Timeout duration in milliseconds\n */\nexport async function withTimeout<T>(input: T | Promise<T>, ms: number): SafeResult<T> {\n let timer: ReturnType<typeof setTimeout>;\n\n try {\n const promise = typeof input === 'function' ? input() : input;\n\n const timeoutPromise = new Promise<never>((_, reject) => {\n timer = setTimeout(() => reject(formatError(new Error(`Timeout after ${ms}ms`), 'TIMEOUT_ERROR')), ms);\n });\n\n const result = await Promise.race([promise, timeoutPromise]);\n return [null, result];\n } catch (error: any) {\n return [error, null];\n } finally {\n clearTimeout(timer!);\n }\n}","import { coreSafe } from \"./core/safe\";\nimport { retry } from \"./modules/retry\";\nimport { withTimeout } from \"./modules/timeout\";\n\n/**\n * Executes a synchronous or asynchronous operation safely.\n *\n * `safe` wraps any function or promise and always returns a predictable tuple\n * instead of throwing errors.\n *\n * ## Usage\n * ```ts\n * const [err, data] = await safe(() => doSomething());\n *\n * if (err) {\n * console.error(err.code, err.message);\n * }\n * ```\n *\n * ## Behavior\n * - Never throws\n * - Always resolves\n * - Normalizes all errors into `SafeError`\n *\n * ## Return\n * - `[null, result]` on success\n * - `[SafeError, null]` on failure\n */\nexport const safe = Object.assign(coreSafe, {\n withTimeout,\n retry,\n\n // v0.3.0\n // all,\n // allSettled,\n\n // v0.4.0\n // withContext,\n\n // v0.5.0\n // once,\n\n // v0.6.0\n // strict,\n\n // v0.7.0\n // map,\n // unwrap,\n\n // v0.8.0\n // mockSuccess,\n // mockError,\n\n // v0.9.0\n // debug,\n});\n\nexport type {\n SafeError,\n SafeResult,\n SafeInput\n} from \"./core/type\";\n\nexport type {\n ERROR_CODES,\n ErrorCode,\n ErrorWithCause\n} from \"./errors/codes\";\n\nexport type {\n RetryOptions\n} from \"./modules/retry\";\n\nexport default safe;"],"mappings":";AASO,IAAM,cAAc;AAAA;AAAA,EAEvB,SAAS;AAAA;AAAA,EAGT,SAAS;AAAA;AAAA,EAGT,cAAc;AAAA;AAAA,EAGd,SAAS;AAAA;AAAA,EAGT,YAAY;AAAA;AAAA,EAGZ,gBAAgB;AACpB;;;ACFO,SAAS,YACZ,KACA,cAAyB,YAAY,SAC5B;AA5Bb;AA6BI,MAAI,eAAe,OAAO;AACtB,UAAM,iBAAiB;AAEvB,WAAO;AAAA,MACH,SAAS,IAAI;AAAA,MACb,MAAO,IAAY,QAAQ;AAAA,MAC3B,QAAO,oBAAe,UAAf,YAAwB;AAAA,IACnC;AAAA,EACJ;AAEA,MAAI,OAAO,QAAQ,UAAU;AACzB,WAAO;AAAA,MACH,SAAS;AAAA,MACT,MAAM;AAAA,IACV;AAAA,EACJ;AAEA,SAAO;AAAA,IACH,SAAS;AAAA,IACT,MAAM;AAAA,IACN,OAAO;AAAA,EACX;AACJ;;;ACnCA,eAAsB,SAAY,OAAoC;AAClE,MAAI;AACA,UAAM,UAAU,OAAO,UAAU,aAAa,MAAM,IAAI;AACxD,UAAM,OAAO,MAAM;AACnB,WAAO,CAAC,MAAM,IAAI;AAAA,EACtB,SAAS,OAAO;AACZ,WAAO,CAAC,YAAY,KAAK,GAAG,IAAI;AAAA,EACpC;AACJ;;;ACAA,SAAS,MAAM,IAAY;AACvB,SAAO,IAAI,QAAQ,aAAW,WAAW,SAAS,EAAE,CAAC;AACzD;AAwBA,eAAsB,MAAS,OAAqB,UAAwB,CAAC,GAAkB;AAC3F,QAAM;AAAA,IACF,UAAU;AAAA,IACV,UAAU;AAAA,IACV;AAAA,EACJ,IAAI;AAEJ,MAAI;AAEJ,WAAS,UAAU,GAAG,WAAW,SAAS,WAAW;AACjD,QAAI;AACA,YAAM,OAAO,OAAO,UAAU,aAAc,MAAM,MAAM,IAAM,MAAM;AACpE,aAAO,CAAC,MAAM,IAAI;AAAA,IACtB,SAAS,KAAK;AACV,kBAAY;AACZ,yCAAU,KAAK;AACf,UAAI,UAAU,WAAW,UAAU,GAAG;AAClC,cAAM,MAAM,OAAO;AAAA,MACvB;AAAA,IACJ;AAAA,EACJ;AAEA,SAAO,CAAC,YAAY,WAAW,cAAc,GAAG,IAAI;AACxD;;;AC9CA,eAAsB,YAAe,OAAuB,IAA2B;AACnF,MAAI;AAEJ,MAAI;AACA,UAAM,UAAU,OAAO,UAAU,aAAa,MAAM,IAAI;AAExD,UAAM,iBAAiB,IAAI,QAAe,CAAC,GAAG,WAAW;AACrD,cAAQ,WAAW,MAAM,OAAO,YAAY,IAAI,MAAM,iBAAiB,EAAE,IAAI,GAAG,eAAe,CAAC,GAAG,EAAE;AAAA,IACzG,CAAC;AAED,UAAM,SAAS,MAAM,QAAQ,KAAK,CAAC,SAAS,cAAc,CAAC;AAC3D,WAAO,CAAC,MAAM,MAAM;AAAA,EACxB,SAAS,OAAY;AACjB,WAAO,CAAC,OAAO,IAAI;AAAA,EACvB,UAAE;AACE,iBAAa,KAAM;AAAA,EACvB;AACJ;;;AChBO,IAAM,OAAO,OAAO,OAAO,UAAU;AAAA,EACxC;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAyBJ,CAAC;AAkBD,IAAO,gBAAQ;","names":[]}
|