@mkvlrn/result 2.0.0 → 3.0.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/README.md +76 -25
- package/build/index.d.ts +4 -4
- package/build/index.test.d.ts +1 -0
- package/build/index.test.js +53 -0
- package/package.json +6 -3
package/README.md
CHANGED
|
@@ -1,43 +1,94 @@
|
|
|
1
|
-
# Result
|
|
1
|
+
# Result Pattern
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Type-safe Result pattern for TypeScript representing success or failure.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
|
|
9
|
-
yarn add @mkvlrn/result
|
|
8
|
+
npm add @mkvlrn/result
|
|
10
9
|
```
|
|
11
10
|
|
|
12
11
|
## Usage
|
|
13
12
|
|
|
14
|
-
```
|
|
13
|
+
```typescript
|
|
15
14
|
import { Result } from "@mkvlrn/result";
|
|
16
15
|
|
|
17
|
-
// Success
|
|
18
|
-
const success = Result.
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
// Success
|
|
17
|
+
const success = Result.ok(42);
|
|
18
|
+
|
|
19
|
+
// Error
|
|
20
|
+
const failure = Result.error(new Error("Something went wrong"));
|
|
21
|
+
|
|
22
|
+
// Check result
|
|
23
|
+
const result = Result.ok(42);
|
|
24
|
+
if (result.error) {
|
|
25
|
+
console.log("Error:", result.error.message);
|
|
26
|
+
} else {
|
|
27
|
+
console.log("Value:", result.value);
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Examples
|
|
32
|
+
|
|
33
|
+
### Basic Function
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
function divide(a: number, b: number): Result<number, Error> {
|
|
37
|
+
if (b === 0) {
|
|
38
|
+
return Result.error(new Error("Division by zero"));
|
|
39
|
+
}
|
|
40
|
+
return Result.ok(a / b);
|
|
21
41
|
}
|
|
22
42
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
console.log(error.error.message); // "Something went wrong"
|
|
43
|
+
const result = divide(10, 2);
|
|
44
|
+
if (!result.error) {
|
|
45
|
+
console.log(result.value); // 5
|
|
27
46
|
}
|
|
47
|
+
```
|
|
28
48
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
});
|
|
49
|
+
### Async Operations
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
async function fetchUser(id: number): Promise<Result<User, Error>> {
|
|
53
|
+
try {
|
|
54
|
+
const response = await fetch(`/api/users/${id}`);
|
|
55
|
+
if (!response.ok) {
|
|
56
|
+
return Result.error(new Error(`HTTP ${response.status}`));
|
|
57
|
+
}
|
|
58
|
+
const user = await response.json();
|
|
59
|
+
return Result.ok(user);
|
|
60
|
+
} catch (error) {
|
|
61
|
+
return Result.error(error instanceof Error ? error : new Error("Unknown error"));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Custom Error Types
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
class ValidationError extends Error {
|
|
70
|
+
readonly customField: number;
|
|
71
|
+
|
|
72
|
+
constructor(customField: number, message: string) {
|
|
73
|
+
super(message);
|
|
74
|
+
this.name = "CustomField";
|
|
75
|
+
this.customField = customField;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function validateEmail(email: string): Result<string, ValidationError> {
|
|
80
|
+
if (!email.includes("@")) {
|
|
81
|
+
return Result.error(new ValidationError(400, "custom"));
|
|
82
|
+
}
|
|
83
|
+
return Result.ok(email);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const result = validateEmail("invalid-email");
|
|
87
|
+
if (result.error) {
|
|
88
|
+
console.log(`${result.error.customField}: ${result.error.message}`);
|
|
89
|
+
}
|
|
35
90
|
```
|
|
36
91
|
|
|
37
|
-
##
|
|
92
|
+
## License
|
|
38
93
|
|
|
39
|
-
|
|
40
|
-
- Zero dependencies
|
|
41
|
-
- Simple API
|
|
42
|
-
- Full TypeScript support
|
|
43
|
-
- Tiny bundle size
|
|
94
|
+
MIT
|
package/build/index.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Result type to represent the outcome of an operation.
|
|
3
|
-
* It can either be a success with a value or an error
|
|
4
|
-
* This is a generic type that can be used with any type of value and error.
|
|
3
|
+
* It can either be a success with a value or an error.
|
|
4
|
+
* This is a generic type that can be used with any type of value and error (should extend Error).
|
|
5
5
|
*
|
|
6
6
|
* It is also an alias object containing the ok and error functions to
|
|
7
7
|
* make it easier to create Result objects.
|
|
8
8
|
*/
|
|
9
|
-
export type Result<T, E
|
|
9
|
+
export type Result<T, E extends Error> = {
|
|
10
10
|
readonly error: undefined;
|
|
11
11
|
readonly value: T;
|
|
12
12
|
} | {
|
|
@@ -14,5 +14,5 @@ export type Result<T, E = Error> = {
|
|
|
14
14
|
};
|
|
15
15
|
export declare const Result: {
|
|
16
16
|
ok<T>(value: T): Result<T, never>;
|
|
17
|
-
error<E
|
|
17
|
+
error<E extends Error>(error: E): Result<never, E>;
|
|
18
18
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// biome-ignore lint/correctness/noNodejsModules: need the timer
|
|
2
|
+
import { setTimeout } from "node:timers/promises";
|
|
3
|
+
import { assert, describe, it } from "vitest";
|
|
4
|
+
import { Result } from "./index.js";
|
|
5
|
+
class CustomError extends Error {
|
|
6
|
+
customField;
|
|
7
|
+
constructor(customField, message) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = "CustomField";
|
|
10
|
+
this.customField = customField;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function division(a, b) {
|
|
14
|
+
if (b === 0) {
|
|
15
|
+
return Result.error(new Error("cannot divide by zero"));
|
|
16
|
+
}
|
|
17
|
+
return Result.ok(a / b);
|
|
18
|
+
}
|
|
19
|
+
async function longRunning(shouldFail) {
|
|
20
|
+
await setTimeout(1);
|
|
21
|
+
return shouldFail ? Result.error(new CustomError(42, "wrong")) : Result.ok(3);
|
|
22
|
+
}
|
|
23
|
+
describe("creates a valid result", () => {
|
|
24
|
+
describe("default Error type", () => {
|
|
25
|
+
it("ok result", () => {
|
|
26
|
+
const result = division(4, 2);
|
|
27
|
+
assert.isUndefined(result.error);
|
|
28
|
+
assert.isDefined(result.value);
|
|
29
|
+
assert.strictEqual(result.value, 2);
|
|
30
|
+
});
|
|
31
|
+
it("error result", () => {
|
|
32
|
+
const result = division(4, 0);
|
|
33
|
+
assert.isDefined(result.error);
|
|
34
|
+
assert.instanceOf(result.error, Error);
|
|
35
|
+
assert.strictEqual(result.error.message, "cannot divide by zero");
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
describe("custom error", () => {
|
|
39
|
+
it("ok result", async () => {
|
|
40
|
+
const result = await longRunning(false);
|
|
41
|
+
assert.isUndefined(result.error);
|
|
42
|
+
assert.isDefined(result.value);
|
|
43
|
+
assert.strictEqual(result.value, 3);
|
|
44
|
+
});
|
|
45
|
+
it("error result", async () => {
|
|
46
|
+
const result = await longRunning(true);
|
|
47
|
+
assert.isDefined(result.error);
|
|
48
|
+
assert.instanceOf(result.error, CustomError);
|
|
49
|
+
assert.strictEqual(result.error.message, "wrong");
|
|
50
|
+
assert.strictEqual(result.error.customField, 42);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mkvlrn/result",
|
|
3
3
|
"description": "Result type for TypeScript",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "3.0.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"publishConfig": {
|
|
@@ -16,10 +16,13 @@
|
|
|
16
16
|
"build"
|
|
17
17
|
],
|
|
18
18
|
"scripts": {
|
|
19
|
+
"test": "vitest",
|
|
19
20
|
"build": "rm -rf build && tsc"
|
|
20
21
|
},
|
|
21
22
|
"devDependencies": {
|
|
22
|
-
"@biomejs/biome": "^2.
|
|
23
|
-
"
|
|
23
|
+
"@biomejs/biome": "^2.1.1",
|
|
24
|
+
"@types/node": "^24.0.13",
|
|
25
|
+
"typescript": "^5.8.3",
|
|
26
|
+
"vitest": "^3.2.4"
|
|
24
27
|
}
|
|
25
28
|
}
|