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 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
@@ -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
- * `safe` is the core function of the SAFE-AWAIT-LIB package.
160
+ * Executes a synchronous or asynchronous operation safely.
95
161
  *
96
- * It safely executes any synchronous or asynchronous operation and
97
- * always returns a predictable tuple instead of throwing errors.
162
+ * `safe` wraps any function or promise and always returns a predictable tuple
163
+ * instead of throwing errors.
98
164
  *
99
- * ## Basic usage
165
+ * ## Usage
100
166
  * ```ts
101
- * import safe from "safe-await-lib";
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.message, err.code);
107
- * } else {
108
- * console.log(data);
170
+ * console.error(err.code, err.message);
109
171
  * }
110
172
  * ```
111
173
  *
112
- * ## Philosophy
113
- * - No try/catch pollution
114
- * - No unhandled promise rejections
115
- * - Explicit error handling
116
- *
117
- * ## Planned extensions
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
- * `safe` is the core function of the SAFE-AWAIT-LIB package.
160
+ * Executes a synchronous or asynchronous operation safely.
95
161
  *
96
- * It safely executes any synchronous or asynchronous operation and
97
- * always returns a predictable tuple instead of throwing errors.
162
+ * `safe` wraps any function or promise and always returns a predictable tuple
163
+ * instead of throwing errors.
98
164
  *
99
- * ## Basic usage
165
+ * ## Usage
100
166
  * ```ts
101
- * import safe from "safe-await-lib";
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.message, err.code);
107
- * } else {
108
- * console.log(data);
170
+ * console.error(err.code, err.message);
109
171
  * }
110
172
  * ```
111
173
  *
112
- * ## Philosophy
113
- * - No try/catch pollution
114
- * - No unhandled promise rejections
115
- * - Explicit error handling
116
- *
117
- * ## Planned extensions
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
- // 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 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
- // 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 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":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "safe-await-lib",
3
- "version": "0.1.4",
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": {