@mkvlrn/result 2.0.0 → 4.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 CHANGED
@@ -1,43 +1,94 @@
1
- # Result
1
+ # Result Pattern
2
2
 
3
- A lightweight, zero-dependency TypeScript utility for type-safe error handling using the Result pattern.
3
+ Type-safe Result pattern for TypeScript representing success or error. Anything to avoid try/catch hell.
4
4
 
5
5
  ## Installation
6
6
 
7
7
  ```bash
8
- # Using yarn
9
- yarn add @mkvlrn/result
8
+ npm add @mkvlrn/result
10
9
  ```
11
10
 
12
11
  ## Usage
13
12
 
14
- ```ts
15
- import { Result } from "@mkvlrn/result";
13
+ ```typescript
14
+ import { Result, AsyncResult, R } from "@mkvlrn/result";
16
15
 
17
- // Success case
18
- const success = Result.success(42);
19
- if (success.ok) {
20
- console.log(success.value); // 42
16
+ // Success
17
+ const success = R.ok(42);
18
+
19
+ // Error
20
+ const failure = R.error(new Error("Something went wrong"));
21
+
22
+ // Check result
23
+ const result = R.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 R.error(new Error("Division by zero"));
39
+ }
40
+ return R.ok(a / b);
21
41
  }
22
42
 
23
- // Error case
24
- const error = Result.error(new Error("Something went wrong"));
25
- if (!error.ok) {
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
- // Custom error type
30
- type ValidationError = { field: string; message: string };
31
- const validationError = Result.error<ValidationError>({
32
- field: "email",
33
- message: "Invalid email format",
34
- });
49
+ ### Async Operations
50
+
51
+ ```typescript
52
+ async function fetchUser(id: number): AsyncResult<User, Error> {
53
+ try {
54
+ const response = await fetch(`/api/users/${id}`);
55
+ if (!response.ok) {
56
+ return R.error(new Error(`HTTP ${response.status}`));
57
+ }
58
+ const user = await response.json();
59
+ return R.ok(user);
60
+ } catch (error) {
61
+ return R.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 = "ValidationError";
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
- ## Features
92
+ ## License
38
93
 
39
- - Type-safe error handling
40
- - Zero dependencies
41
- - Simple API
42
- - Full TypeScript support
43
- - Tiny bundle size
94
+ MIT
package/build/index.d.ts CHANGED
@@ -1,18 +1,35 @@
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 with an error message.
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 = Error> = {
9
+ export type Result<T, E extends Error> = {
10
10
  readonly error: undefined;
11
11
  readonly value: T;
12
12
  } | {
13
13
  readonly error: E;
14
14
  };
15
- export declare const Result: {
15
+ /**
16
+ * Async version of Result type that wraps a Result in a Promise.
17
+ */
18
+ export type AsyncResult<T, E extends Error> = Promise<Result<T, E>>;
19
+ /**
20
+ * Result utility functions for creating Result objects.
21
+ */
22
+ export declare const R: {
23
+ /**
24
+ * Creates a successful Result with the given value.
25
+ * @param value The success value
26
+ * @returns A Result object representing success
27
+ */
16
28
  ok<T>(value: T): Result<T, never>;
17
- error<E = Error>(error: E): Result<never, E>;
29
+ /**
30
+ * Creates an error Result with the given error.
31
+ * @param error The error value
32
+ * @returns A Result object representing error
33
+ */
34
+ error<E extends Error>(error: E): Result<never, E>;
18
35
  };
package/build/index.js CHANGED
@@ -1,14 +1,7 @@
1
- export const Result = {
2
- /**
3
- * Creates a successful Result with the given value.
4
- * @param value The success value
5
- * @returns A Result object representing success
6
- */
1
+ /**
2
+ * Result utility functions for creating Result objects.
3
+ */
4
+ export const R = {
7
5
  ok: (value) => ({ error: undefined, value }),
8
- /**
9
- * Creates an error Result with the given error.
10
- * @param error The error value
11
- * @returns A Result object representing error
12
- */
13
6
  error: (error) => ({ error }),
14
7
  };
@@ -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 { R } 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 R.error(new Error("cannot divide by zero"));
16
+ }
17
+ return R.ok(a / b);
18
+ }
19
+ async function longRunning(shouldFail) {
20
+ await setTimeout(1);
21
+ return shouldFail ? R.error(new CustomError(42, "wrong")) : R.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": "2.0.0",
4
+ "version": "4.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.0.6",
23
- "typescript": "^5.8.3"
23
+ "@biomejs/biome": "^2.1.2",
24
+ "@types/node": "^24.1.0",
25
+ "typescript": "^5.8.3",
26
+ "vitest": "^3.2.4"
24
27
  }
25
28
  }