diff --git a/README.md b/README.md
index a221c24..c369823 100644
--- a/README.md
+++ b/README.md
@@ -106,6 +106,21 @@ const posts = result.data
posts.map((post) => console.log(post.title));
```
+### `.map`
+
+```ts
+const get = makeMightFail(axios.get);
+const [ error, result ] = await get("/posts").map(post => post.title);
+
+if (error) {
+ // handle error
+ return;
+}
+
+const titles = result.data
+titles.map((post) => console.log(title));
+```
+
## Sync
diff --git a/docs/content/contents.mdx b/docs/content/contents.mdx
index 79caf18..65c2182 100644
--- a/docs/content/contents.mdx
+++ b/docs/content/contents.mdx
@@ -27,6 +27,10 @@ course:
filePath: /getting-started/makeMightFailSync.mdx
slug: /make-might-fail-sync
type: documentation
+ - title: "`.map` Transformations"
+ filePath: /getting-started/map-transformations.mdx
+ slug: /map-transformations
+ type: documentation
- title: "Either Type"
filePath: /getting-started/either.mdx
slug: /either
diff --git a/docs/content/getting-started/getting-started.mdx b/docs/content/getting-started/getting-started.mdx
index c455540..0a0edb0 100644
--- a/docs/content/getting-started/getting-started.mdx
+++ b/docs/content/getting-started/getting-started.mdx
@@ -2,7 +2,10 @@
* [❌ try, catch, finally](/try-catch-finally-is-bad)
* [`mightFail`](/might-fail)
+* [`makeMightFail`](/make-might-fail)
* [`mightFailSync`](/might-fail-sync)
+* [`makeMightFailSync`](/make-might-fail-sync)
+* [`.map` Transformations](/map-transformations)
* [Static Methods](/static-methods)
* [Either Type](/either)
* [Might & Fail](/might-and-fail)
@@ -41,6 +44,32 @@ posts.map((post) => console.log(post.title));
The success case is now the only code that is **not** nested in another block. It's also at the very bottom of the function making it easy to find.
+## Transform Results with `.map`
+
+You can transform successful results without repeatedly handling errors:
+
+```ts
+// Transform results in a functional style
+const [ error, postTitles ] = await mightFail(fetch("/posts"))
+ .map(response => response.json())
+ .map(posts => posts.filter(post => post.published))
+ .map(published => published.map(post => post.title))
+
+if (error) {
+ // Handle any error from the entire chain
+ return
+}
+
+// postTitles contains the transformed result
+console.log(postTitles) // Array of published post titles
+```
+
+The `.map` method:
+- Only executes if the previous step succeeded
+- Catches transformation errors automatically
+- Can be chained for complex transformations
+- Works with all might-fail functions
+
- The sucess case is always at the bottom of the function. All of your error handling logic is next to where the error might occur.
+ The success case is always at the bottom of the function. All of your error handling logic is next to where the error might occur.
diff --git a/docs/content/getting-started/makeMightFail.mdx b/docs/content/getting-started/makeMightFail.mdx
index db6a1d0..8290f82 100644
--- a/docs/content/getting-started/makeMightFail.mdx
+++ b/docs/content/getting-started/makeMightFail.mdx
@@ -51,4 +51,86 @@ posts.map((post) => console.log(post.title));
+## Transforming Results with `.map`
+
+You can transform results directly on the returned promise:
+
+
+
+```ts
+const get = makeMightFail(axios.get);
+const [error, titles] = await get("/posts")
+ .map(response => response.data)
+ .map(posts => posts.map(post => post.title))
+
+if (error) {
+ // handle error
+ return;
+}
+
+console.log(titles); // Array of post titles
+```
+
+
+```ts
+const get = makeMightFail(axios.get);
+const { error, result: titles } = await get("/posts")
+ .map(response => response.data)
+ .map(posts => posts.map(post => post.title))
+
+if (error) {
+ // handle error
+ return;
+}
+
+console.log(titles); // Array of post titles
+```
+
+
+```ts
+const get = makeMightFail(axios.get);
+const [titles, error] = await get("/posts")
+ .map(response => response.data)
+ .map(posts => posts.map(post => post.title))
+
+if (error) {
+ // handle error
+ return;
+}
+
+console.log(titles); // Array of post titles
+```
+
+
+
+This is particularly useful for API responses where you want to extract and transform specific data:
+
+```ts
+const fetchUser = makeMightFail(async (id: number) => {
+ const response = await fetch(`/users/${id}`)
+ return response.json()
+})
+
+const [error, userSummary] = await fetchUser(123)
+ .map(user => ({
+ name: user.name,
+ email: user.email,
+ isActive: user.lastSeen > Date.now() - 86400000 // 24h
+ }))
+ .map(summary => ({
+ ...summary,
+ displayName: summary.isActive ? `${summary.name} (Online)` : summary.name
+ }))
+
+if (error) {
+ console.error('Failed to fetch user:', error.message)
+ return
+}
+
+console.log(userSummary)
+```
+
+
+Learn more about transformations in the [`.map` Transformations](/map-transformations) guide.
+
diff --git a/docs/content/getting-started/map-transformations.mdx b/docs/content/getting-started/map-transformations.mdx
new file mode 100644
index 0000000..3957e45
--- /dev/null
+++ b/docs/content/getting-started/map-transformations.mdx
@@ -0,0 +1,210 @@
+# `.map` Transformations
+
+The `.map` method allows you to transform successful results without having to handle errors repeatedly. It provides a functional programming style for chaining transformations while preserving error handling.
+
+## Basic Usage
+
+Instead of awaiting, checking for errors, and then transforming:
+
+```ts
+// Without .map - more verbose
+const [error1, userData] = await mightFail(fetchUser(123))
+if (error1) return
+
+const [error2, userName] = await mightFailSync(() => userData.name.toUpperCase())
+if (error2) return
+
+console.log(userName)
+```
+
+You can chain transformations directly:
+
+```ts
+// With .map - concise and readable
+const [error, userName] = await mightFail(fetchUser(123))
+ .map(user => user.name.toUpperCase())
+
+if (error) return
+
+console.log(userName)
+```
+
+## Available on All Functions
+
+The `.map` method is available on all might-fail functions:
+
+
+
+```ts
+const [error, result] = await mightFail(promise)
+ .map(data => data.title.toUpperCase())
+
+if (error) {
+ console.error('Error:', error.message)
+ return
+}
+
+console.log(result) // Transformed data
+```
+
+
+
+```ts
+const safeGetUser = makeMightFail(getUserById)
+
+const [error, result] = await safeGetUser(123)
+ .map(user => user.name)
+
+if (error) {
+ console.error('Error:', error.message)
+ return
+}
+
+console.log(result) // User name
+```
+
+
+
+```ts
+// mightFail.all
+const [error, result] = await mightFail.all([
+ fetchProject(id),
+ fetchProjectPositions(id)
+]).map(([project, positions]) => ({
+ ...project,
+ positionCount: positions.length
+}))
+
+// mightFail.race
+const [error, winner] = await mightFail.race([
+ fetchFromServer1(),
+ fetchFromServer2()
+]).map(data => data.result)
+
+// mightFail.any
+const [error, success] = await mightFail.any([
+ tryMethod1(),
+ tryMethod2(),
+ tryMethod3()
+]).map(result => result.value)
+```
+
+
+
+## Chaining Multiple Transformations
+
+You can chain multiple `.map` calls for complex transformations:
+
+```ts
+const [error, summary] = await mightFail(fetchUserPosts(userId))
+ .map(posts => posts.filter(post => post.published))
+ .map(published => published.map(post => post.title))
+ .map(titles => titles.join(', '))
+ .map(titleList => `Published posts: ${titleList}`)
+
+if (error) {
+ console.error('Failed to get post summary:', error.message)
+ return
+}
+
+console.log(summary) // "Published posts: Post 1, Post 2, Post 3"
+```
+
+## Error Handling
+
+### Preserves Original Errors
+If the original promise rejects, `.map` preserves the error:
+
+```ts
+const [error, result] = await mightFail(Promise.reject(new Error("API failed")))
+ .map(data => data.toUpperCase())
+
+console.log(error?.message) // "API failed"
+console.log(result) // undefined
+```
+
+### Catches Transformation Errors
+If the transformation function throws, `.map` catches it:
+
+```ts
+const [error, result] = await mightFail(Promise.resolve(null))
+ .map(data => data.name.toUpperCase()) // Will throw on null
+
+console.log(error?.message) // "Cannot read properties of null"
+console.log(result) // undefined
+```
+
+### Error Short-Circuiting
+Once an error occurs, subsequent `.map` calls are skipped:
+
+```ts
+const [error, result] = await mightFail(Promise.reject(new Error("API failed")))
+ .map(data => data.step1()) // Skipped
+ .map(data => data.step2()) // Skipped
+ .map(data => data.step3()) // Skipped
+
+console.log(error?.message) // "API failed"
+```
+
+## Real-World Example
+
+Here's a practical example combining API calls and transformations:
+
+```ts
+async function getUserDashboard(userId: string) {
+ const [error, dashboard] = await mightFail.all([
+ fetchUser(userId),
+ fetchUserPosts(userId),
+ fetchUserStats(userId)
+ ]).map(([user, posts, stats]) => ({
+ user: {
+ name: user.name,
+ email: user.email
+ },
+ summary: {
+ totalPosts: posts.length,
+ publishedPosts: posts.filter(p => p.published).length,
+ totalViews: stats.views,
+ lastActive: user.lastSeen
+ }
+ })).map(data => ({
+ ...data,
+ summary: {
+ ...data.summary,
+ description: `${data.user.name} has ${data.summary.publishedPosts} published posts with ${data.summary.totalViews} total views`
+ }
+ }))
+
+ if (error) {
+ console.error('Failed to load dashboard:', error.message)
+ return null
+ }
+
+ return dashboard
+}
+```
+
+## Type Safety
+
+The `.map` method maintains full type safety. TypeScript will infer the correct types through the transformation chain:
+
+```ts
+// TypeScript knows the types at each step
+const [error, result] = await mightFail(Promise.resolve({ count: 5 }))
+ .map(data => data.count) // number
+ .map(count => count * 2) // number
+ .map(doubled => doubled.toString()) // string
+
+// result is inferred as string | undefined
+// error is inferred as Error | undefined
+```
+
+## Destructuring Still Works
+
+The transformed result maintains the iterable `Either` interface:
+
+```ts
+// Both work identically
+const { error, result } = await mightFail(promise).map(transform)
+const [error, result] = await mightFail(promise).map(transform)
+```
\ No newline at end of file
diff --git a/docs/content/getting-started/mightFail.mdx b/docs/content/getting-started/mightFail.mdx
index 7c9c4ff..b553377 100644
--- a/docs/content/getting-started/mightFail.mdx
+++ b/docs/content/getting-started/mightFail.mdx
@@ -117,3 +117,68 @@ posts.map((post) => console.log(post.title));
+## Transforming Results with `.map`
+
+You can transform successful results using the `.map` method without having to handle errors repeatedly:
+
+
+
+
+```ts
+// Transform successful results
+const [error, titles] = await mightFail(fetch("/posts"))
+ .map(response => response.json())
+ .map(posts => posts.map(post => post.title))
+
+if (error) {
+ // handle any error from the chain
+ return
+}
+
+console.log(titles) // Array of post titles
+```
+
+
+
+```ts
+// Transform successful results
+const { error, result: titles } = await mightFail(fetch("/posts"))
+ .map(response => response.json())
+ .map(posts => posts.map(post => post.title))
+
+if (error) {
+ // handle any error from the chain
+ return
+}
+
+console.log(titles) // Array of post titles
+```
+
+
+
+```ts
+// Transform successful results
+const [titles, error] = await mightFail(fetch("/posts"))
+ .map(response => response.json())
+ .map(posts => posts.map(post => post.title))
+
+if (error) {
+ // handle any error from the chain
+ return
+}
+
+console.log(titles) // Array of post titles
+```
+
+
+
+The `.map` method:
+- Only runs if the previous step succeeded
+- Catches any errors thrown during transformation
+- Can be chained multiple times
+- Maintains full type safety
+
+
+Learn more about transformations in the [`.map` Transformations](/map-transformations) guide.
+
+
diff --git a/docs/content/more-things/static-methods.mdx b/docs/content/more-things/static-methods.mdx
index d4334e8..6d58811 100644
--- a/docs/content/more-things/static-methods.mdx
+++ b/docs/content/more-things/static-methods.mdx
@@ -11,9 +11,11 @@
- `await mightFail.race([])`
- `await mightFail.any([])`
-These are identical to the static methods on `Promise` but they return an [`Either`](/either) type.
+These are identical to the static methods on `Promise` but they return an [`Either`](/either) type and support [`.map` transformations](/map-transformations).
-## Example
+## Basic Examples
+
+### `mightFail.all`
```ts
import { mightFail } from 'might-fail'
@@ -31,3 +33,155 @@ if (error) {
// result is an array of the results of the promises
console.log(result.map((r) => r.message))
```
+
+### `mightFail.race`
+
+```ts
+const [error, winner] = await mightFail.race([
+ fetch('/api/server1/data'),
+ fetch('/api/server2/data'),
+ fetch('/api/server3/data')
+]);
+
+if (error) {
+ console.error('All servers failed')
+ return
+}
+
+console.log('Fastest server responded:', winner)
+```
+
+### `mightFail.any`
+
+```ts
+const [error, success] = await mightFail.any([
+ tryDatabase1(),
+ tryDatabase2(),
+ tryDatabase3()
+]);
+
+if (error) {
+ console.error('All databases failed:', error.message)
+ return
+}
+
+console.log('At least one database succeeded:', success)
+```
+
+## With `.map` Transformations
+
+All static methods support `.map` for transforming results:
+
+### Transform `mightFail.all` Results
+
+```ts
+// Combine multiple API calls and transform the result
+const [error, projectSummary] = await mightFail.all([
+ fetchProject(projectId),
+ fetchProjectMembers(projectId),
+ fetchProjectStats(projectId)
+]).map(([project, members, stats]) => ({
+ name: project.name,
+ memberCount: members.length,
+ totalCommits: stats.commits,
+ isActive: stats.lastCommit > Date.now() - 86400000 // 24h
+})).map(summary => ({
+ ...summary,
+ description: `${summary.name} has ${summary.memberCount} members and ${summary.totalCommits} commits`
+}))
+
+if (error) {
+ console.error('Failed to load project data:', error.message)
+ return
+}
+
+console.log(projectSummary)
+```
+
+### Transform Race Winner
+
+```ts
+const [error, fastestData] = await mightFail.race([
+ fetchFromCDN(),
+ fetchFromMainServer(),
+ fetchFromBackupServer()
+]).map(response => response.json())
+ .map(data => ({
+ ...data,
+ source: 'fastest-available',
+ fetchedAt: new Date().toISOString()
+ }))
+
+if (error) {
+ console.error('All sources failed')
+ return
+}
+
+console.log('Data from fastest source:', fastestData)
+```
+
+### Process First Success
+
+```ts
+const [error, processedResult] = await mightFail.any([
+ tryExpensiveOperation(),
+ tryFallbackOperation(),
+ tryLastResortOperation()
+]).map(result => ({
+ value: result.data,
+ processedAt: Date.now(),
+ success: true
+}))
+
+if (error) {
+ console.error('All operations failed')
+ return
+}
+
+console.log('Successfully processed:', processedResult)
+```
+
+## Real-World Example
+
+Here's a practical example that combines multiple data sources:
+
+```ts
+async function loadUserDashboard(userId: string) {
+ // Fetch user data and related information in parallel
+ const [error, dashboard] = await mightFail.all([
+ fetchUser(userId),
+ fetchUserPosts(userId),
+ fetchUserNotifications(userId),
+ fetchUserSettings(userId)
+ ]).map(([user, posts, notifications, settings]) => ({
+ user: {
+ id: user.id,
+ name: user.name,
+ avatar: user.avatar
+ },
+ content: {
+ posts: posts.filter(p => p.published),
+ unreadNotifications: notifications.filter(n => !n.read).length
+ },
+ preferences: settings
+ })).map(data => ({
+ ...data,
+ summary: {
+ hasNewContent: data.content.unreadNotifications > 0,
+ postCount: data.content.posts.length,
+ welcomeMessage: `Welcome back, ${data.user.name}!`
+ }
+ }))
+
+ if (error) {
+ console.error('Failed to load dashboard:', error.message)
+ return null
+ }
+
+ return dashboard
+}
+```
+
+
+All static methods preserve the iterable nature of the `Either` type, so you can use both object destructuring `{ error, result }` and array destructuring `[error, result]`.
+
diff --git a/examples/mapExample.ts b/examples/mapExample.ts
new file mode 100644
index 0000000..f569fb7
--- /dev/null
+++ b/examples/mapExample.ts
@@ -0,0 +1,41 @@
+import { makeMightFail } from "../src/makeMightFail"
+
+async function fetchPosts() {
+ return Promise.resolve({
+ data: [
+ { id: 1, title: "First Post", content: "Hello World" },
+ { id: 2, title: "Second Post", content: "More content" }
+ ]
+ })
+}
+
+async function main() {
+ const get = makeMightFail(fetchPosts)
+
+ // Example from README - extracting titles
+ const { error, result } = await get().map(response =>
+ response.data.map(post => post.title)
+ )
+
+ if (error) {
+ console.error('Error:', error.message)
+ return
+ }
+
+ console.log('Post titles:', result)
+
+ // Chain multiple transformations
+ const { error: error2, result: result2 } = await get()
+ .map(response => response.data)
+ .map(posts => posts.length)
+ .map(count => `Found ${count} posts`)
+
+ if (error2) {
+ console.error('Error:', error2.message)
+ return
+ }
+
+ console.log('Count message:', result2)
+}
+
+main()
\ No newline at end of file
diff --git a/examples/mightFailAllMapExample.ts b/examples/mightFailAllMapExample.ts
new file mode 100644
index 0000000..2fbcef8
--- /dev/null
+++ b/examples/mightFailAllMapExample.ts
@@ -0,0 +1,87 @@
+import { mightFail } from "../src/index"
+
+// Simulate API calls
+function getProject(id: string) {
+ return Promise.resolve({
+ project: { id, name: "My Project", status: "active" }
+ })
+}
+
+function getProjectPositions(_id: string) {
+ return Promise.resolve({
+ positions: [
+ { id: 1, title: "Frontend Developer", department: "Engineering" },
+ { id: 2, title: "Backend Developer", department: "Engineering" },
+ { id: 3, title: "Product Manager", department: "Product" }
+ ]
+ })
+}
+
+async function main() {
+ const projectId = "project-123"
+
+ console.log("=== Using Promise.all with mightFail and .map ===")
+
+ // Original approach - manually wrapping Promise.all
+ const { error: error1, result: result1 } = await mightFail(
+ Promise.all([getProject(projectId), getProjectPositions(projectId)])
+ ).map(([projectData, positionsData]) => ({
+ ...projectData.project,
+ positions: positionsData.positions
+ }))
+
+ if (error1) {
+ console.error('Error:', error1.message)
+ return
+ }
+
+ console.log('Result with Promise.all:', result1)
+
+ console.log("\n=== Using mightFail.all with .map ===")
+
+ // New approach - using mightFail.all directly
+ const { error: error2, result: result2 } = await mightFail.all([
+ getProject(projectId),
+ getProjectPositions(projectId)
+ ]).map(([projectData, positionsData]) => ({
+ ...projectData.project,
+ positions: positionsData.positions
+ }))
+
+ if (error2) {
+ console.error('Error:', error2.message)
+ return
+ }
+
+ console.log('Result with mightFail.all:', result2)
+
+ console.log("\n=== Chaining multiple transformations ===")
+
+ // Chain multiple transformations
+ const { error: error3, result: result3 } = await mightFail.all([
+ getProject(projectId),
+ getProjectPositions(projectId)
+ ])
+ .map(([projectData, positionsData]) => ({
+ project: projectData.project,
+ positions: positionsData.positions
+ }))
+ .map(data => ({
+ ...data,
+ totalPositions: data.positions.length,
+ departments: [...new Set(data.positions.map(p => p.department))]
+ }))
+ .map(data => ({
+ ...data,
+ summary: `${data.project.name} has ${data.totalPositions} positions across ${data.departments.length} departments`
+ }))
+
+ if (error3) {
+ console.error('Error:', error3.message)
+ return
+ }
+
+ console.log('Chained result:', result3)
+}
+
+main()
\ No newline at end of file
diff --git a/examples/mightFailMapExample.ts b/examples/mightFailMapExample.ts
new file mode 100644
index 0000000..e1c9954
--- /dev/null
+++ b/examples/mightFailMapExample.ts
@@ -0,0 +1,73 @@
+import { mightFail, makeMightFail } from "../src/index"
+
+async function fetchUser(id: number) {
+ return Promise.resolve({
+ id,
+ name: "John Doe",
+ email: "john@example.com",
+ posts: [
+ { id: 1, title: "First Post", views: 100 },
+ { id: 2, title: "Second Post", views: 250 }
+ ]
+ })
+}
+
+async function main() {
+ console.log("=== Direct mightFail usage with .map ===")
+
+ // Using mightFail directly with .map
+ const { error: error1, result: titles } = await mightFail(fetchUser(123))
+ .map(user => user.posts.map(post => post.title))
+
+ if (error1) {
+ console.error('Error:', error1.message)
+ return
+ }
+
+ console.log('Post titles:', titles)
+
+ // Chain multiple transformations with mightFail
+ const { error: error2, result: summary } = await mightFail(fetchUser(456))
+ .map(user => user.posts)
+ .map(posts => posts.reduce((total, post) => total + post.views, 0))
+ .map(totalViews => `User has ${totalViews} total views`)
+
+ if (error2) {
+ console.error('Error:', error2.message)
+ return
+ }
+
+ console.log('Summary:', summary)
+
+ console.log("\n=== makeMightFail usage with .map ===")
+
+ // Using makeMightFail with .map
+ const safeGetUser = makeMightFail(fetchUser)
+
+ const { error: error3, result: userEmail } = await safeGetUser(789)
+ .map(user => user.email.toUpperCase())
+
+ if (error3) {
+ console.error('Error:', error3.message)
+ return
+ }
+
+ console.log('User email:', userEmail)
+
+ // Error handling during transformation
+ console.log("\n=== Error handling in .map ===")
+
+ const { error: error4, result: result4 } = await mightFail(fetchUser(999))
+ .map(() => {
+ // Simulate an error during transformation
+ throw new Error("Something went wrong during processing")
+ })
+
+ if (error4) {
+ console.error('Caught transformation error:', error4.message)
+ } else {
+ console.log('Result:', result4)
+ }
+}
+
+main()
\ No newline at end of file
diff --git a/src/makeMightFail.ts b/src/makeMightFail.ts
index 51add4b..a6d0023 100644
--- a/src/makeMightFail.ts
+++ b/src/makeMightFail.ts
@@ -1,5 +1,6 @@
import { type Either } from "./Either.js"
import { mightFail, mightFailSync } from "./mightFail.js"
+import { MightFailPromise, createMightFailPromise } from "./utils/utils.type.js"
/**
* Utility type that unwraps a Promise type. If T is a Promise, it extracts the type the Promise resolves to,
@@ -16,7 +17,7 @@ type UnwrapPromise = T extends Promise ? U : T
*
* @template T The function type that returns a Promise.
- * @param {T} func - The async function to be wrapped. This function should return a Promise.
+ * @param func - The async function to be wrapped. This function should return a Promise.
* @return {Function} A new function that, when called, returns a Promise that resolves with an Either object.
* The Either object contains either a 'result' with the resolved value of the original Promise, or an 'error' if the Promise was rejected.
*
@@ -39,12 +40,16 @@ type UnwrapPromise = T extends Promise ? U : T
* }
* console.log('Fetched data:', result);
*/
+
+
export function makeMightFail Promise, E extends Error = Error>(
func: T
-): (...funcArgs: Parameters) => Promise>, E>> {
- return async (...args: Parameters) => {
+): (...funcArgs: Parameters) => MightFailPromise>, E> {
+ return (...args: Parameters) => {
const promise = func(...args)
- return mightFail(promise)
+ const mightFailPromise = mightFail(promise) as Promise>, E>>
+
+ return createMightFailPromise>, E>(mightFailPromise)
}
}
diff --git a/src/utils/createEither.ts b/src/utils/createEither.ts
index 4ac05c1..e90c7d1 100644
--- a/src/utils/createEither.ts
+++ b/src/utils/createEither.ts
@@ -1,6 +1,6 @@
-import { AnyEither, EitherMode } from "./utils.type.js"
-import type { Either as StandardEither } from "../Either.js"
-import type { Either as GoEither } from "../go/Either.js"
+import { AnyEither, EitherMode } from "./utils.type"
+import type { Either as StandardEither } from "../Either"
+import type { Either as GoEither } from "../go/Either"
// This is not how we intended the tuple feature to work but this is the only way we could currently get TypeScript to play nice
// this really should just be an interator on the either object, but it's much more complicated because of TS.
diff --git a/src/utils/mightFailFunction.ts b/src/utils/mightFailFunction.ts
index 8a65795..7a4131f 100644
--- a/src/utils/mightFailFunction.ts
+++ b/src/utils/mightFailFunction.ts
@@ -1,16 +1,19 @@
-import { type Either } from "../Either.js"
import { handleError } from "./errors.js"
import { createEither } from "./createEither.js"
-import { MightFailFunction } from "./utils.type.js"
+import { MightFailFunction, createMightFailPromise } from "./utils.type.js"
-export const mightFailFunction: MightFailFunction<"standard"> = async function (
+export const mightFailFunction: MightFailFunction<"standard"> = function (
promise: T
-): Promise, E>> {
- try {
- const result = await promise
- return createEither, E>({ result, error: undefined })
- } catch (err) {
- const error = handleError(err)
- return createEither, E>({ error, result: undefined })
- }
+) {
+ const basePromise = (async () => {
+ try {
+ const result = await promise
+ return createEither, E>({ result, error: undefined })
+ } catch (err) {
+ const error = handleError(err)
+ return createEither, E>({ error, result: undefined })
+ }
+ })()
+
+ return createMightFailPromise(basePromise)
}
diff --git a/src/utils/utils.type.ts b/src/utils/utils.type.ts
index a3c5c23..b1c92fe 100644
--- a/src/utils/utils.type.ts
+++ b/src/utils/utils.type.ts
@@ -1,5 +1,33 @@
import type { Either as StandardEither } from "../Either.js"
import type { Either as GoEither } from "../go/Either.js"
+import { createEither } from "./createEither.js"
+
+export interface MightFailPromise extends Promise> {
+ map(fn: (value: T) => U): MightFailPromise
+}
+
+export function createMightFailPromise(
+ basePromise: Promise>
+): MightFailPromise {
+ return Object.assign(basePromise, {
+ map(fn: (value: T) => U): MightFailPromise {
+ const mappedPromise = basePromise.then(either => {
+ if (either.error) {
+ return createEither({ error: either.error, result: undefined }) as StandardEither
+ }
+ try {
+ const mappedValue = fn(either.result!)
+ return createEither({ error: undefined, result: mappedValue }) as StandardEither
+ } catch (err) {
+ const error = err instanceof Error ? err as E : new Error(String(err)) as E
+ return createEither({ error, result: undefined }) as StandardEither
+ }
+ })
+
+ return createMightFailPromise(mappedPromise)
+ }
+ }) as MightFailPromise
+}
export type EitherMode = "standard" | "go" | "any"
@@ -7,13 +35,11 @@ export type AnyEither = StandardEither | GoEit
export type MightFailFunction = (
promise: T
-) => Promise<
- TEitherMode extends "standard"
- ? StandardEither, E>
+) => TEitherMode extends "standard"
+ ? MightFailPromise, E>
: TEitherMode extends "go"
- ? GoEither, E>
- : AnyEither, E>
->
+ ? Promise, E>>
+ : MightFailPromise, E> | Promise, E>>
export type PromiseFulfilledResult = {
status: "fulfilled"
@@ -39,13 +65,11 @@ export interface PromiseStaticMethods {
*/
all(
values: T
- ): Promise<
- TEitherMode extends "standard"
- ? Awaited }>>
+ ): TEitherMode extends "standard"
+ ? MightFailPromise<{ -readonly [P in keyof T]: Awaited }>
: TEitherMode extends "go"
- ? Awaited }>>
- : Awaited }>>
- >
+ ? Promise }>>
+ : MightFailPromise<{ -readonly [P in keyof T]: Awaited }> | Promise }>>
/**
* (From lib.es2025.iterable.d.ts)
@@ -56,13 +80,11 @@ export interface PromiseStaticMethods {
*/
all(
values: Iterable>
- ): Promise<
- TEitherMode extends "standard"
- ? Awaited>
+ ): TEitherMode extends "standard"
+ ? MightFailPromise
: TEitherMode extends "go"
- ? Awaited>
- : Awaited>
- >
+ ? Promise>
+ : MightFailPromise | Promise>
/**
* Wraps a Promise.race call in a mightFail function.
@@ -73,13 +95,11 @@ export interface PromiseStaticMethods {
*/
race(
values: Iterable>
- ): Promise<
- TEitherMode extends "standard"
- ? Awaited>
+ ): TEitherMode extends "standard"
+ ? MightFailPromise
: TEitherMode extends "go"
- ? Awaited>
- : Awaited>
- >
+ ? Promise>
+ : MightFailPromise | Promise>
/**
* Wraps a Promise.race call in a mightFail function.
@@ -89,13 +109,11 @@ export interface PromiseStaticMethods {
*/
race(
values: T
- ): Promise<
- TEitherMode extends "standard"
- ? Awaited>
+ ): TEitherMode extends "standard"
+ ? MightFailPromise
: TEitherMode extends "go"
- ? Awaited>
- : Awaited>
- >
+ ? Promise>
+ : MightFailPromise | Promise>
/**
* Wraps a Promise.any call in a mightFail function.
@@ -106,13 +124,11 @@ export interface PromiseStaticMethods {
*/
any(
values: T
- ): Promise<
- TEitherMode extends "standard"
- ? StandardEither, AggregateError>
+ ): TEitherMode extends "standard"
+ ? MightFailPromise
: TEitherMode extends "go"
- ? GoEither, AggregateError>
- : AnyEither, AggregateError>
- >
+ ? Promise>
+ : MightFailPromise | Promise>
/**
* Wraps a Promise.any call in a mightFail function.
@@ -123,11 +139,9 @@ export interface PromiseStaticMethods {
*/
any(
values: Iterable>
- ): Promise<
- TEitherMode extends "standard"
- ? StandardEither, AggregateError>
+ ): TEitherMode extends "standard"
+ ? MightFailPromise
: TEitherMode extends "go"
- ? GoEither, AggregateError>
- : AnyEither, AggregateError>
- >
+ ? Promise>
+ : MightFailPromise | Promise>
}
diff --git a/test/eitherIterability.test.ts b/test/eitherIterability.test.ts
new file mode 100644
index 0000000..ec5bf8d
--- /dev/null
+++ b/test/eitherIterability.test.ts
@@ -0,0 +1,82 @@
+import { expect, test, describe } from "vitest"
+import { mightFail, makeMightFail } from "../src/index"
+
+describe("Either iterability with .map", () => {
+ test("mightFail result is destructurable", async () => {
+ const [error, result] = await mightFail(Promise.resolve("test"))
+ expect(error).toBe(undefined)
+ expect(result).toBe("test")
+ })
+
+ test("mightFail with .map result is destructurable", async () => {
+ const [error, result] = await mightFail(Promise.resolve("test")).map(x => x.toUpperCase())
+ expect(error).toBe(undefined)
+ expect(result).toBe("TEST")
+ })
+
+ test("makeMightFail result is destructurable", async () => {
+ const safeResolve = makeMightFail((value: string) => Promise.resolve(value))
+ const [error, result] = await safeResolve("test")
+ expect(error).toBe(undefined)
+ expect(result).toBe("test")
+ })
+
+ test("makeMightFail with .map result is destructurable", async () => {
+ const safeResolve = makeMightFail((value: string) => Promise.resolve(value))
+ const [error, result] = await safeResolve("test").map(x => x.toUpperCase())
+ expect(error).toBe(undefined)
+ expect(result).toBe("TEST")
+ })
+
+ test("mightFail.all result is destructurable", async () => {
+ const [error, result] = await mightFail.all([Promise.resolve("a"), Promise.resolve("b")])
+ expect(error).toBe(undefined)
+ expect(result).toEqual(["a", "b"])
+ })
+
+ test("mightFail.all with .map result is destructurable", async () => {
+ const [error, result] = await mightFail.all([Promise.resolve("a"), Promise.resolve("b")])
+ .map(([first, second]) => first + second)
+ expect(error).toBe(undefined)
+ expect(result).toBe("ab")
+ })
+
+ test("error cases are destructurable", async () => {
+ const [error, result] = await mightFail(Promise.reject(new Error("test error")))
+ expect(result).toBe(undefined)
+ expect(error?.message).toBe("test error")
+ })
+
+ test("error cases with .map are destructurable", async () => {
+ const [error, result] = await mightFail(Promise.reject(new Error("test error")))
+ .map((x: any) => x.toUpperCase())
+ expect(result).toBe(undefined)
+ expect(error?.message).toBe("test error")
+ })
+
+ test("Either supports for...of iteration", async () => {
+ const either = await mightFail(Promise.resolve("test")).map(x => x.toUpperCase())
+ const items: any[] = []
+ for (const item of either) {
+ items.push(item)
+ }
+ expect(items).toEqual([undefined, "TEST"])
+ })
+
+ test("Either supports array methods like forEach", async () => {
+ const either = await mightFail(Promise.resolve("test")).map(x => x.toUpperCase())
+ const items: any[] = []
+ either.forEach((item: any) => items.push(item))
+ expect(items).toEqual([undefined, "TEST"])
+ })
+
+ test("chained .map preserves iterability", async () => {
+ const [error, result] = await mightFail(Promise.resolve({ count: 5 }))
+ .map(data => data.count * 2)
+ .map(doubled => doubled + 1)
+ .map(final => final.toString())
+
+ expect(error).toBe(undefined)
+ expect(result).toBe("11")
+ })
+})
\ No newline at end of file
diff --git a/test/makeMightFail.test.ts b/test/makeMightFail.test.ts
index 5089fca..24fec14 100644
--- a/test/makeMightFail.test.ts
+++ b/test/makeMightFail.test.ts
@@ -24,3 +24,40 @@ test("fail without error returns an error", async () => {
expect(result).toBe(undefined)
expect(error?.message).toBeTruthy()
})
+
+test("map transforms successful result", async () => {
+ const resolve = (value: { count: number }) => Promise.resolve(value)
+ const func = makeMightFail(resolve)
+ const { error, result } = await func({ count: 5 }).map(data => data.count * 2)
+ expect(error).toBe(undefined)
+ expect(result).toBe(10)
+})
+
+test("map preserves error on failed result", async () => {
+ const reject = (error: Error) => Promise.reject(error)
+ const func = makeMightFail(reject)
+ const { error, result } = await func(new Error("original error")).map(data => data * 2)
+ expect(result).toBe(undefined)
+ expect(error?.message).toBe("original error")
+})
+
+test("map catches errors during transformation", async () => {
+ const resolve = (value: { count: number }) => Promise.resolve(value)
+ const func = makeMightFail(resolve)
+ const { error, result } = await func({ count: 5 }).map(() => {
+ throw new Error("transformation error")
+ })
+ expect(result).toBe(undefined)
+ expect(error?.message).toBe("transformation error")
+})
+
+test("map can be chained multiple times", async () => {
+ const resolve = (value: { count: number }) => Promise.resolve(value)
+ const func = makeMightFail(resolve)
+ const { error, result } = await func({ count: 5 })
+ .map(data => data.count * 2)
+ .map(doubled => doubled + 1)
+ .map(result => result.toString())
+ expect(error).toBe(undefined)
+ expect(result).toBe("11")
+})
diff --git a/test/mightFail.test.ts b/test/mightFail.test.ts
index b61a16e..d0c3152 100644
--- a/test/mightFail.test.ts
+++ b/test/mightFail.test.ts
@@ -136,4 +136,35 @@ describe("Either factories (Might & Fail)", () => {
})
})
})
+
+ describe("map functionality", () => {
+ test("map transforms successful result", async () => {
+ const { error, result } = await mightFail(Promise.resolve({ count: 5 })).map(data => data.count * 2)
+ expect(error).toBe(undefined)
+ expect(result).toBe(10)
+ })
+
+ test("map preserves error on failed result", async () => {
+ const { error, result } = await mightFail(Promise.reject(new Error("original error"))).map(data => data * 2)
+ expect(result).toBe(undefined)
+ expect(error?.message).toBe("original error")
+ })
+
+ test("map catches errors during transformation", async () => {
+ const { error, result } = await mightFail(Promise.resolve({ count: 5 })).map(() => {
+ throw new Error("transformation error")
+ })
+ expect(result).toBe(undefined)
+ expect(error?.message).toBe("transformation error")
+ })
+
+ test("map can be chained multiple times", async () => {
+ const { error, result } = await mightFail(Promise.resolve({ count: 5 }))
+ .map(data => data.count * 2)
+ .map(doubled => doubled + 1)
+ .map(result => result.toString())
+ expect(error).toBe(undefined)
+ expect(result).toBe("11")
+ })
+ })
})
diff --git a/test/mightFailStaticMethodsMap.test.ts b/test/mightFailStaticMethodsMap.test.ts
new file mode 100644
index 0000000..ce4a1a1
--- /dev/null
+++ b/test/mightFailStaticMethodsMap.test.ts
@@ -0,0 +1,60 @@
+import { expect, test, describe } from "vitest"
+import { mightFail } from "../src/index"
+
+describe("mightFail static methods with .map", () => {
+ test("mightFail.all supports .map", async () => {
+ const promises = [
+ Promise.resolve({ id: 1, name: "John" }),
+ Promise.resolve({ id: 2, name: "Jane" })
+ ]
+
+ const { error, result } = await mightFail.all(promises)
+ .map(users => users.map(user => user.name))
+
+ expect(error).toBe(undefined)
+ expect(result).toEqual(["John", "Jane"])
+ })
+
+ test("mightFail.all preserves error with .map", async () => {
+ const promises = [
+ Promise.resolve({ id: 1, name: "John" }),
+ Promise.reject(new Error("API error"))
+ ]
+
+ const { error, result } = await mightFail.all(promises)
+ .map(users => users.map(user => user.name))
+
+ expect(result).toBe(undefined)
+ expect(error?.message).toBe("API error")
+ })
+
+ test("mightFail.all catches transformation errors", async () => {
+ const promises = [
+ Promise.resolve({ id: 1, name: "John" }),
+ Promise.resolve({ id: 2, name: "Jane" })
+ ]
+
+ const { error, result } = await mightFail.all(promises)
+ .map(() => {
+ throw new Error("transformation error")
+ })
+
+ expect(result).toBe(undefined)
+ expect(error?.message).toBe("transformation error")
+ })
+
+ test("mightFail.all supports chained .map", async () => {
+ const promises = [
+ Promise.resolve({ id: 1, score: 85 }),
+ Promise.resolve({ id: 2, score: 92 })
+ ]
+
+ const { error, result } = await mightFail.all(promises)
+ .map(users => users.map(user => user.score))
+ .map(scores => scores.reduce((sum, score) => sum + score, 0))
+ .map(total => `Total score: ${total}`)
+
+ expect(error).toBe(undefined)
+ expect(result).toBe("Total score: 177")
+ })
+})
\ No newline at end of file