safe-await-lib 0.1.3 → 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 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
  [![npm version](https://img.shields.io/npm/v/safe-await-lib.svg)](https://www.npmjs.com/package/safe-await-lib)
5
5
  [![License](https://img.shields.io/npm/l/safe-await-lib.svg)](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 patterns require `try/catch` blocks, which can clutter code and make error handling inconsistent.
14
+ Traditional `async / await` requires repetitive `try/catch` blocks, which:
15
15
 
16
- **SAFE-AWAIT-LIB solves this problem by:**
16
+ - clutter business logic
17
+ - encourage inconsistent error handling
18
+ - hide error intent
17
19
 
18
- - Returning a consistent `[SafeError | null, T | null]` tuple for every async operation
19
- - Normalizing errors into a predictable format
20
- - Eliminating boilerplate `try/catch` blocks
21
- - Making code cleaner, safer, and easier to maintain
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
- - Standardized error handling with `SafeError` objects
29
- - Fully TypeScript-typed for modern development
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
- - Supports dual module output (ESM + CJS)
32
- - Tree-shakable for modern bundlers
33
- - Future-ready with planned modules: `withTimeout`, `retry`, `all`, `allSettled`, `once`, `strict`, `map`, `unwrap`, `mockSuccess`, `mockError`, `debug`
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 Promise.resolve('Hello World!');
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); // 'Hello World!'
72
+ console.log(data); // "Hello World"
69
73
  }
74
+ ```
75
+
76
+ ### Using a direct promise
70
77
 
71
- // Direct promise usage
72
- const [err2, result] = await safe(Promise.resolve(42));
78
+ ```ts
79
+ const [err, result] = await safe(Promise.resolve(42));
73
80
  ```
74
81
 
75
82
  ---
76
83
 
77
- ## 🛠️ API Overview
84
+ ## 🛠️ API
85
+
86
+ ### `safe(input)`
78
87
 
79
- ### `safe(input: SafeInput<T>): SafeResult<T>`
88
+ ```ts
89
+ safe<T>(input: Promise<T> | (() => T | Promise<T>))
90
+ → Promise<[SafeError | null, T | null]>
91
+ ```
80
92
 
81
- - **input**: `Promise<T>` or `() => T | Promise<T>`
82
- - **returns**: `[SafeError | null, T | null]`
93
+ ---
83
94
 
84
- ### `SafeError` Structure
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 additional context
101
+ cause?: unknown; // Original error or context
91
102
  }
92
103
  ```
93
104
 
94
105
  ---
95
106
 
96
- ## 🌱 Advanced Usage Examples
107
+ ## 🌱 Advanced Usage (v0.2.0)
97
108
 
98
- ### Handling multiple async operations
109
+ ### ⏱️ Timeout handling — `safe.withTimeout`
99
110
 
100
111
  ```ts
101
- // Future module: safe.all
102
- const [err, results] = await safe.all([fetchData(), Promise.resolve(10)]);
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
- ### Using retry (planned in v0.2.0)
119
+ ---
120
+
121
+ ### 🔁 Retry logic — `safe.retry`
106
122
 
107
123
  ```ts
108
- const [err, data] = await safe.retry(() => fetchData(), { attempts: 3, delay: 500 });
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
- ## 📊 Roadmap
142
+ ## Available Today (v0.2.0)
143
+
144
+ SAFE-AWAIT-LIB currently provides:
114
145
 
115
- | Version | Features Planned |
116
- | ------- | ---------------------- |
117
- | v0.2.0 | withTimeout, retry |
118
- | v0.3.0 | all, allSettled |
119
- | v0.4.0 | withContext |
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 dev dependencies
157
+ # Install dependencies
132
158
  npm install
133
159
 
134
- # Run development build with watch
160
+ # Development build (watch mode)
135
161
  npm run dev
136
162
 
137
- # Type check
163
+ # Type checking
138
164
  npm run typecheck
139
165
 
140
166
  # Run tests
141
167
  npm test
142
168
 
143
- # Build for distribution
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
- SAFE-AWAIT-LIB welcomes contributions!
192
+ Contributions are welcome.
152
193
 
153
194
  1. Fork the repository
154
- 2. Create a feature branch (`git checkout -b feature/your-feature`)
155
- 3. Run tests locally (`npm test`)
156
- 4. Submit a pull request
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 adhere to the existing **TypeScript typings** and **code style**.
200
+ Please respect the existing **TypeScript typings** and **error model**.
159
201
 
160
202
  ---
161
203
 
162
- ## ❓ FAQ / Tips
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
- - **Why use `[SafeError | null, T | null]` instead of try/catch?**
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
- - **Can I use this in React Native / Expo?**
168
- Yes. SAFE-AWAIT-LIB is fully compatible.
214
+ ### Is this production-ready?
169
215
 
170
- - **How do I handle retries or timeouts?**
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
@@ -49,44 +49,140 @@ type SafeInput<T> = Promise<T> | (() => T | Promise<T>);
49
49
  declare function coreSafe<T>(input: SafeInput<T>): SafeResult<T>;
50
50
 
51
51
  /**
52
- * `safe` is the core function of the SAFE-AWAIT-LIB package.
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.
53
94
  *
54
- * It safely executes any synchronous or asynchronous operation and
55
- * always returns a predictable tuple instead of throwing errors.
95
+ * If the operation does not resolve within the given duration,
96
+ * it fails with a `TIMEOUT_ERROR`.
56
97
  *
57
- * ## Basic usage
98
+ * ## Usage
58
99
  * ```ts
59
- * import safe from "safe-await-lib";
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
+
117
+ /**
118
+ * Standardized error codes used across the SAFE-AWAIT-LIB package.
119
+ *
120
+ * These codes allow consumers to reliably identify the nature
121
+ * of an error without relying on string comparison of messages.
122
+ *
123
+ * Each module of the package should use one of these codes
124
+ * when returning or normalizing an error.
125
+ */
126
+ declare const ERROR_CODES: {
127
+ /** Fallback error for unknown or unhandled failures */
128
+ readonly UNKNOWN: "UNKNOWN_ERROR";
129
+ /** Thrown when an operation exceeds a configured timeout */
130
+ readonly TIMEOUT: "TIMEOUT_ERROR";
131
+ /** Used when all retry attempts have failed */
132
+ readonly RETRY_FAILED: "RETRY_FAILED";
133
+ /** Used when an operation is explicitly aborted or cancelled */
134
+ readonly ABORTED: "ABORT_ERROR";
135
+ /** Used when input validation fails */
136
+ readonly VALIDATION: "VALIDATION_ERROR";
137
+ /** Used when a function guarded by `once()` is called more than once */
138
+ readonly EXECUTION_ONCE: "ALREADY_EXECUTED";
139
+ };
140
+ /**
141
+ * Union type of all supported error codes.
142
+ *
143
+ * This type ensures strong typing and prevents the use
144
+ * of unsupported or custom error codes across the package.
145
+ */
146
+ type ErrorCode = typeof ERROR_CODES[keyof typeof ERROR_CODES];
147
+ /**
148
+ * Internal helper interface to support the `cause` property
149
+ * on Error objects in environments where it is not
150
+ * yet fully supported or typed.
60
151
  *
61
- * const [err, data] = await safe(async () => fetchData());
152
+ * This allows SAFE-AWAIT-LIB to preserve the original error
153
+ * while still returning a normalized SafeError object.
154
+ */
155
+ interface ErrorWithCause extends Error {
156
+ cause?: unknown;
157
+ }
158
+
159
+ /**
160
+ * Executes a synchronous or asynchronous operation safely.
161
+ *
162
+ * `safe` wraps any function or promise and always returns a predictable tuple
163
+ * instead of throwing errors.
164
+ *
165
+ * ## Usage
166
+ * ```ts
167
+ * const [err, data] = await safe(() => doSomething());
62
168
  *
63
169
  * if (err) {
64
- * console.error(err.message, err.code);
65
- * } else {
66
- * console.log(data);
170
+ * console.error(err.code, err.message);
67
171
  * }
68
172
  * ```
69
173
  *
70
- * ## Philosophy
71
- * - No try/catch pollution
72
- * - No unhandled promise rejections
73
- * - Explicit error handling
74
- *
75
- * ## Planned extensions
76
- * - v0.2.0: withTimeout, retry
77
- * - v0.3.0: all, allSettled
78
- * - v0.4.0: withContext
79
- * - v0.5.0: once
80
- * - v0.6.0: strict
81
- * - v0.7.0: map, unwrap
82
- * - v0.8.0: mockSuccess, mockError
83
- * - v0.9.0: debug
84
- *
85
- * ## Return value
86
- * Always returns a tuple:
174
+ * ## Behavior
175
+ * - Never throws
176
+ * - Always resolves
177
+ * - Normalizes all errors into `SafeError`
178
+ *
179
+ * ## Return
87
180
  * - `[null, result]` on success
88
181
  * - `[SafeError, null]` on failure
89
182
  */
90
- declare const safe: typeof coreSafe;
183
+ declare const safe: typeof coreSafe & {
184
+ withTimeout: typeof withTimeout;
185
+ retry: typeof retry;
186
+ };
91
187
 
92
- export { 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
@@ -49,44 +49,140 @@ type SafeInput<T> = Promise<T> | (() => T | Promise<T>);
49
49
  declare function coreSafe<T>(input: SafeInput<T>): SafeResult<T>;
50
50
 
51
51
  /**
52
- * `safe` is the core function of the SAFE-AWAIT-LIB package.
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.
53
94
  *
54
- * It safely executes any synchronous or asynchronous operation and
55
- * always returns a predictable tuple instead of throwing errors.
95
+ * If the operation does not resolve within the given duration,
96
+ * it fails with a `TIMEOUT_ERROR`.
56
97
  *
57
- * ## Basic usage
98
+ * ## Usage
58
99
  * ```ts
59
- * import safe from "safe-await-lib";
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
+
117
+ /**
118
+ * Standardized error codes used across the SAFE-AWAIT-LIB package.
119
+ *
120
+ * These codes allow consumers to reliably identify the nature
121
+ * of an error without relying on string comparison of messages.
122
+ *
123
+ * Each module of the package should use one of these codes
124
+ * when returning or normalizing an error.
125
+ */
126
+ declare const ERROR_CODES: {
127
+ /** Fallback error for unknown or unhandled failures */
128
+ readonly UNKNOWN: "UNKNOWN_ERROR";
129
+ /** Thrown when an operation exceeds a configured timeout */
130
+ readonly TIMEOUT: "TIMEOUT_ERROR";
131
+ /** Used when all retry attempts have failed */
132
+ readonly RETRY_FAILED: "RETRY_FAILED";
133
+ /** Used when an operation is explicitly aborted or cancelled */
134
+ readonly ABORTED: "ABORT_ERROR";
135
+ /** Used when input validation fails */
136
+ readonly VALIDATION: "VALIDATION_ERROR";
137
+ /** Used when a function guarded by `once()` is called more than once */
138
+ readonly EXECUTION_ONCE: "ALREADY_EXECUTED";
139
+ };
140
+ /**
141
+ * Union type of all supported error codes.
142
+ *
143
+ * This type ensures strong typing and prevents the use
144
+ * of unsupported or custom error codes across the package.
145
+ */
146
+ type ErrorCode = typeof ERROR_CODES[keyof typeof ERROR_CODES];
147
+ /**
148
+ * Internal helper interface to support the `cause` property
149
+ * on Error objects in environments where it is not
150
+ * yet fully supported or typed.
60
151
  *
61
- * const [err, data] = await safe(async () => fetchData());
152
+ * This allows SAFE-AWAIT-LIB to preserve the original error
153
+ * while still returning a normalized SafeError object.
154
+ */
155
+ interface ErrorWithCause extends Error {
156
+ cause?: unknown;
157
+ }
158
+
159
+ /**
160
+ * Executes a synchronous or asynchronous operation safely.
161
+ *
162
+ * `safe` wraps any function or promise and always returns a predictable tuple
163
+ * instead of throwing errors.
164
+ *
165
+ * ## Usage
166
+ * ```ts
167
+ * const [err, data] = await safe(() => doSomething());
62
168
  *
63
169
  * if (err) {
64
- * console.error(err.message, err.code);
65
- * } else {
66
- * console.log(data);
170
+ * console.error(err.code, err.message);
67
171
  * }
68
172
  * ```
69
173
  *
70
- * ## Philosophy
71
- * - No try/catch pollution
72
- * - No unhandled promise rejections
73
- * - Explicit error handling
74
- *
75
- * ## Planned extensions
76
- * - v0.2.0: withTimeout, retry
77
- * - v0.3.0: all, allSettled
78
- * - v0.4.0: withContext
79
- * - v0.5.0: once
80
- * - v0.6.0: strict
81
- * - v0.7.0: map, unwrap
82
- * - v0.8.0: mockSuccess, mockError
83
- * - v0.9.0: debug
84
- *
85
- * ## Return value
86
- * Always returns a tuple:
174
+ * ## Behavior
175
+ * - Never throws
176
+ * - Always resolves
177
+ * - Normalizes all errors into `SafeError`
178
+ *
179
+ * ## Return
87
180
  * - `[null, result]` on success
88
181
  * - `[SafeError, null]` on failure
89
182
  */
90
- declare const safe: typeof coreSafe;
183
+ declare const safe: typeof coreSafe & {
184
+ withTimeout: typeof withTimeout;
185
+ retry: typeof retry;
186
+ };
91
187
 
92
- export { 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
- // v0.2.0
82
- // withTimeout,
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 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;AAED,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
- // v0.2.0
55
- // withTimeout,
56
- // retry,
97
+ withTimeout,
98
+ retry
57
99
  // v0.3.0
58
100
  // all,
59
101
  // allSettled,
@@ -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 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;AAED,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":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "safe-await-lib",
3
- "version": "0.1.3",
3
+ "version": "0.2.0",
4
4
  "description": "Safe async/await utility for handling promises without try/catch.",
5
5
  "author": "Chelohub Inc. <npm@borislukrece.com>",
6
6
  "scripts": {