-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
2b537f4
commit b4da104
Showing
8 changed files
with
268 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
{ | ||
"name": "functype", | ||
"version": "0.8.30", | ||
"version": "0.8.31", | ||
"description": "A smallish functional library for TypeScript", | ||
"author": "[email protected]", | ||
"license": "MIT", | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { Typeable } from "../../typeable/Typeable" | ||
|
||
/** | ||
* Base Object from which most other objects inherit | ||
* @param type | ||
* @constructor | ||
*/ | ||
export function Base(type: string) { | ||
return { | ||
...Typeable(type), | ||
toString() { | ||
return `${type}()` | ||
}, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { Typeable } from "../../typeable/Typeable" | ||
|
||
const NAME = "Throwable" as const | ||
|
||
export type Throwable = Error & | ||
Typeable<typeof NAME> & { | ||
readonly data?: unknown // Optional readonly data field for extra info | ||
} | ||
|
||
// AppError factory function | ||
export const Throwable = (srcError: unknown, data?: unknown): Throwable => { | ||
const message: string = | ||
srcError instanceof Error ? srcError.message : typeof srcError === "string" ? srcError : "An unknown error occurred" // Fallback message if srcError is neither Error nor string | ||
|
||
const stack: string | undefined = srcError instanceof Error ? srcError.stack : undefined | ||
|
||
// Create a new Error object | ||
const error = new Error(message) | ||
error.name = NAME | ||
|
||
// Return the custom error with readonly properties for message, stack, and data | ||
return { | ||
_tag: NAME, // Use const for the _tag to make it immutable | ||
name: error.name, | ||
message: error.message, | ||
stack: stack ?? error.stack, // Use original stack if available, otherwise fallback to new error's stack | ||
data, // Optional readonly data | ||
} | ||
} | ||
|
||
Throwable.NAME = NAME |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import { Either, Left, Right } from "../../either/Either" | ||
import { Base } from "../base/Base" | ||
import { Throwable } from "../error/Throwable" | ||
|
||
export type AppException<T> = Either<Throwable, T> | ||
|
||
/** | ||
* AppException factory function | ||
* @param error | ||
* @param data | ||
* @constructor | ||
*/ | ||
export const AppException = <T>(error: unknown, data?: unknown): AppException<T> => { | ||
const appError = Throwable(error, data) | ||
return { | ||
...Base("AppException"), | ||
...Left(appError), | ||
} | ||
} | ||
|
||
export type AppResult<T> = Either<Throwable, T> | ||
|
||
export const AppResult = <T>(data: T): AppResult<T> => { | ||
return { | ||
...Base("AppResult"), | ||
...Right(data), | ||
} | ||
} | ||
|
||
export type Task<T> = Either<Throwable, T> | ||
|
||
export function Task<T>(f: () => T, e: (error: unknown) => unknown = (error: unknown) => error): Task<T> { | ||
try { | ||
return AppResult<T>(f()) | ||
} catch (error) { | ||
return AppException<T>(e(error)) | ||
} | ||
} | ||
|
||
Task.success = <T>(data: T) => AppResult<T>(data) | ||
Task.fail = <T>(error: unknown) => AppException<T>(error) | ||
|
||
export type AsyncTask<T> = Promise<Task<T>> | ||
|
||
export async function AsyncTask<T>( | ||
f: () => T, | ||
e: (error: unknown) => unknown = (error: unknown) => error, | ||
): AsyncTask<T> { | ||
try { | ||
const result = await f() | ||
return AppResult<T>(result) | ||
} catch (error) { | ||
const result = await e(error) | ||
return AppException<T>(result) | ||
} | ||
} | ||
|
||
AsyncTask.success = <T>(data: T) => AppResult<T>(data) | ||
AsyncTask.fail = <T>(error: unknown) => AppException<T>(error) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
import { isLeft, isRight } from "../../../src" | ||
import { Throwable } from "../../../src/core/error/Throwable" | ||
import { AppException, AppResult, AsyncTask, Task } from "../../../src/core/task/Task" | ||
|
||
describe("AppException", () => { | ||
test("should create an AppException with error", () => { | ||
const error = new Error("Test error") | ||
const result = AppException<string>(error) | ||
|
||
expect(isLeft(result)).toBe(true) | ||
expect(result._tag).toBe("Left") | ||
expect((result.value as Throwable)._tag).toBe("Throwable") | ||
expect((result.value as Error).message).toBe("Test error") | ||
}) | ||
|
||
test("should create an AppException with error and additional data", () => { | ||
const error = new Error("Test error") | ||
const data = { additionalInfo: "extra data" } | ||
const result = AppException<string>(error, data) | ||
|
||
expect(isLeft(result)).toBe(true) | ||
expect(result._tag).toBe("Left") | ||
expect(result.value instanceof Error).toBe(false) | ||
expect((result.value as unknown as Throwable).data).toEqual(data) | ||
}) | ||
}) | ||
|
||
describe("AppResult", () => { | ||
test("should create a successful AppResult", () => { | ||
const data = "test data" | ||
const result = AppResult(data) | ||
|
||
expect(isRight(result)).toBe(true) | ||
expect(result._tag).toBe("Right") | ||
expect(result.value).toBe(data) | ||
}) | ||
|
||
test("should work with different data types", () => { | ||
const numberResult = AppResult(42) | ||
const objectResult = AppResult({ key: "value" }) | ||
const arrayResult = AppResult([1, 2, 3]) | ||
|
||
expect(isRight(numberResult)).toBe(true) | ||
expect(numberResult.value).toBe(42) | ||
expect(isRight(objectResult)).toBe(true) | ||
expect(objectResult.value).toEqual({ key: "value" }) | ||
expect(isRight(arrayResult)).toBe(true) | ||
expect(arrayResult.value).toEqual([1, 2, 3]) | ||
}) | ||
}) | ||
|
||
describe("Task", () => { | ||
test("should handle successful operations", () => { | ||
const result = Task(() => "success") | ||
|
||
expect(isRight(result)).toBe(true) | ||
expect(result.value).toBe("success") | ||
}) | ||
|
||
test("should handle errors", () => { | ||
const error = new Error("Task failed") | ||
const result = Task(() => { | ||
throw error | ||
}) | ||
|
||
expect(isLeft(result)).toBe(true) | ||
expect(result.value._tag).toBe("Throwable") | ||
expect((result.value as Error).message).toBe("Task failed") | ||
}) | ||
|
||
test("should use custom error handler", () => { | ||
const error = new Error("Original error") | ||
const customError = "Custom error message" | ||
const result = Task( | ||
() => { | ||
throw error | ||
}, | ||
() => customError, | ||
) | ||
|
||
expect(isLeft(result)).toBe(true) | ||
expect(result.value.message).toBe(customError) | ||
}) | ||
|
||
test("Task.success should create successful result", () => { | ||
const result = Task.success("data") | ||
|
||
expect(isRight(result)).toBe(true) | ||
expect(result.value).toBe("data") | ||
}) | ||
|
||
test("Task.fail should create failure result", () => { | ||
const error = new Error("Failed") | ||
const result = Task.fail(error) | ||
|
||
expect(isLeft(result)).toBe(true) | ||
expect((result.value as Throwable)._tag).toBe("Throwable") | ||
expect((result.value as Error).message).toBe("Failed") | ||
}) | ||
}) | ||
|
||
describe("AsyncTask", () => { | ||
test("should handle successful async operations", async () => { | ||
const result = await AsyncTask(async () => "success") | ||
|
||
expect(result).toBe("success") | ||
}) | ||
|
||
test("should handle async errors", async () => { | ||
const error = new Error("Async task failed") | ||
try { | ||
await AsyncTask(async () => { | ||
throw error | ||
}) | ||
} catch (error) { | ||
expect((error as unknown as Throwable).message).toBe("Async task failed") | ||
} | ||
}) | ||
|
||
test("should use custom async error handler", async () => { | ||
const error = new Error("Original error") | ||
const customError = "Custom async error message" | ||
try { | ||
await AsyncTask( | ||
async () => { | ||
throw error | ||
}, | ||
async () => customError, | ||
) | ||
} catch (error) { | ||
expect((error as unknown as Throwable).message).toBe(customError) | ||
} | ||
}) | ||
|
||
test("AsyncTask.success should create successful result", () => { | ||
const result = AsyncTask.success("data") | ||
|
||
expect(isRight(result)).toBe(true) | ||
expect(result.value).toBe("data") | ||
}) | ||
|
||
test("AsyncTask.fail should create failure result", () => { | ||
const error = new Error("Failed") | ||
const result = AsyncTask.fail(error) | ||
|
||
expect(isLeft(result)).toBe(true) | ||
expect((result.value as unknown as Throwable)._tag).toBe("Throwable") | ||
expect((result.value as Error).message).toBe("Failed") | ||
}) | ||
|
||
test("should handle promises", async () => { | ||
const successPromise = Promise.resolve("success") | ||
const result = await AsyncTask(async () => successPromise) | ||
|
||
expect(result).toBe("success") | ||
}) | ||
}) |