@mkvlrn/result 4.0.3 → 4.0.4
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 +94 -54
- package/package.json +4 -1
- package/.turbo/turbo-build.log +0 -10
- package/CHANGELOG.md +0 -13
- package/biome.jsonc +0 -9
- package/src/index.test.ts +0 -66
- package/src/index.ts +0 -37
- package/tsconfig.json +0 -43
- package/vite.config.ts +0 -55
package/README.md
CHANGED
|
@@ -1,54 +1,94 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
1
|
+
# @mkvlrn/result
|
|
2
|
+
|
|
3
|
+
Type-safe Result pattern for TypeScript representing success or error. Anything to avoid try/catch hell.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm add @mkvlrn/result
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { Result, AsyncResult, R } from "@mkvlrn/result";
|
|
15
|
+
|
|
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);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const result = divide(10, 2);
|
|
44
|
+
if (!result.error) {
|
|
45
|
+
console.log(result.value); // 5
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
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
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## License
|
|
93
|
+
|
|
94
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mkvlrn/result",
|
|
3
3
|
"description": "Simple Result type/pattern for TypeScript",
|
|
4
|
-
"version": "4.0.
|
|
4
|
+
"version": "4.0.4",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"author": "Mike Valeriano <mkvlrn@proton.me>",
|
|
@@ -17,6 +17,9 @@
|
|
|
17
17
|
"engines": {
|
|
18
18
|
"node": "24.x"
|
|
19
19
|
},
|
|
20
|
+
"files": [
|
|
21
|
+
"build"
|
|
22
|
+
],
|
|
20
23
|
"scripts": {
|
|
21
24
|
"build": "vite build",
|
|
22
25
|
"dev": "tsx --watch src/main.ts",
|
package/.turbo/turbo-build.log
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
> @mkvlrn/result@4.0.3 build
|
|
4
|
-
> vite build
|
|
5
|
-
|
|
6
|
-
[1G[0K[36mvite v7.0.6 [32mbuilding for production...[36m[39m
|
|
7
|
-
[2K[1Gtransforming (1) [2msrc/index.ts[22m[2K[1G[32m✓[39m 1 modules transformed.
|
|
8
|
-
[2K[1Grendering chunks (1)...[2K[1G[2K[1Gcomputing gzip size (0)...[2K[1Gcomputing gzip size (1)...[2K[1G[2mbuild/[22m[36mindex.js [39m[1m[2m0.15 kB[22m[1m[22m[2m │ gzip: 0.13 kB[22m[2m │ map: 1.52 kB[22m
|
|
9
|
-
[32m✓ built in 409ms[39m
|
|
10
|
-
[1G[0K⠙[1G[0K
|
package/CHANGELOG.md
DELETED
package/biome.jsonc
DELETED
package/src/index.test.ts
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { setTimeout } from "node:timers/promises";
|
|
2
|
-
import { assert, describe, it } from "vitest";
|
|
3
|
-
import { type AsyncResult, R, type Result } from "./index.js";
|
|
4
|
-
|
|
5
|
-
class CustomError extends Error {
|
|
6
|
-
readonly customField: number;
|
|
7
|
-
|
|
8
|
-
constructor(customField: number, message: string) {
|
|
9
|
-
super(message);
|
|
10
|
-
this.name = "CustomField";
|
|
11
|
-
this.customField = customField;
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function division(a: number, b: number): Result<number, Error> {
|
|
16
|
-
if (b === 0) {
|
|
17
|
-
return R.error(new Error("cannot divide by zero"));
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
return R.ok(a / b);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
async function longRunning(shouldFail: boolean): AsyncResult<number, CustomError> {
|
|
24
|
-
await setTimeout(1);
|
|
25
|
-
|
|
26
|
-
return shouldFail ? R.error(new CustomError(42, "wrong")) : R.ok(3);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
describe("creates a valid result", () => {
|
|
30
|
-
describe("default Error type", () => {
|
|
31
|
-
it("ok result", () => {
|
|
32
|
-
const result = division(4, 2);
|
|
33
|
-
|
|
34
|
-
assert.isUndefined(result.error);
|
|
35
|
-
assert.isDefined(result.value);
|
|
36
|
-
assert.strictEqual(result.value, 2);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it("error result", () => {
|
|
40
|
-
const result = division(4, 0);
|
|
41
|
-
|
|
42
|
-
assert.isDefined(result.error);
|
|
43
|
-
assert.instanceOf(result.error, Error);
|
|
44
|
-
assert.strictEqual(result.error.message, "cannot divide by zero");
|
|
45
|
-
});
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
describe("custom error", () => {
|
|
49
|
-
it("ok result", async () => {
|
|
50
|
-
const result = await longRunning(false);
|
|
51
|
-
|
|
52
|
-
assert.isUndefined(result.error);
|
|
53
|
-
assert.isDefined(result.value);
|
|
54
|
-
assert.strictEqual(result.value, 3);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it("error result", async () => {
|
|
58
|
-
const result = await longRunning(true);
|
|
59
|
-
|
|
60
|
-
assert.isDefined(result.error);
|
|
61
|
-
assert.instanceOf(result.error, CustomError);
|
|
62
|
-
assert.strictEqual(result.error.message, "wrong");
|
|
63
|
-
assert.strictEqual(result.error.customField, 42);
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
|
-
});
|
package/src/index.ts
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
/**
|
|
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 (should extend Error).
|
|
5
|
-
*
|
|
6
|
-
* It is also an alias object containing the ok and error functions to
|
|
7
|
-
* make it easier to create Result objects.
|
|
8
|
-
*/
|
|
9
|
-
export type Result<T, E extends Error> =
|
|
10
|
-
| { readonly error: undefined; readonly value: T }
|
|
11
|
-
| { readonly error: E };
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Async version of Result type that wraps a Result in a Promise.
|
|
15
|
-
*/
|
|
16
|
-
export type AsyncResult<T, E extends Error> = Promise<Result<T, E>>;
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Result utility functions for creating Result objects.
|
|
20
|
-
*/
|
|
21
|
-
export const R: {
|
|
22
|
-
/**
|
|
23
|
-
* Creates a successful Result with the given value.
|
|
24
|
-
* @param value The success value
|
|
25
|
-
* @returns A Result object representing success
|
|
26
|
-
*/
|
|
27
|
-
ok<T>(value: T): Result<T, never>;
|
|
28
|
-
/**
|
|
29
|
-
* Creates an error Result with the given error.
|
|
30
|
-
* @param error The error value
|
|
31
|
-
* @returns A Result object representing error
|
|
32
|
-
*/
|
|
33
|
-
error<E extends Error>(error: E): Result<never, E>;
|
|
34
|
-
} = {
|
|
35
|
-
ok: <T>(value: T): Result<T, never> => ({ error: undefined, value }),
|
|
36
|
-
error: <E extends Error>(error: E): Result<never, E> => ({ error }),
|
|
37
|
-
};
|
package/tsconfig.json
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
// no transpiling
|
|
4
|
-
"noEmit": true,
|
|
5
|
-
|
|
6
|
-
// no funny business
|
|
7
|
-
"erasableSyntaxOnly": true,
|
|
8
|
-
"verbatimModuleSyntax": true,
|
|
9
|
-
|
|
10
|
-
// very strict
|
|
11
|
-
// https://whatislove.dev/articles/the-strictest-typescript-config/
|
|
12
|
-
"allowJs": false,
|
|
13
|
-
"exactOptionalPropertyTypes": true,
|
|
14
|
-
// "noPropertyAccessFromIndexSignature": true,
|
|
15
|
-
"noUncheckedIndexedAccess": true,
|
|
16
|
-
"strict": true,
|
|
17
|
-
"strictNullChecks": true,
|
|
18
|
-
"noUncheckedSideEffectImports": true,
|
|
19
|
-
|
|
20
|
-
// esm
|
|
21
|
-
"esModuleInterop": true,
|
|
22
|
-
"isolatedModules": true,
|
|
23
|
-
"lib": ["ESNext"],
|
|
24
|
-
"module": "esnext",
|
|
25
|
-
"moduleResolution": "bundler",
|
|
26
|
-
"target": "esnext",
|
|
27
|
-
|
|
28
|
-
// pnpm compatibility
|
|
29
|
-
"preserveSymlinks": true,
|
|
30
|
-
|
|
31
|
-
// self explanatory
|
|
32
|
-
"resolveJsonModule": true,
|
|
33
|
-
|
|
34
|
-
// don't try to check for errors on imported libs
|
|
35
|
-
"skipLibCheck": true,
|
|
36
|
-
|
|
37
|
-
// paths
|
|
38
|
-
"baseUrl": ".",
|
|
39
|
-
"paths": {
|
|
40
|
-
"#/*": ["./src/*"]
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
package/vite.config.ts
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { existsSync, globSync } from "node:fs";
|
|
2
|
-
import nodeExternals from "rollup-plugin-node-externals";
|
|
3
|
-
import dts from "vite-plugin-dts";
|
|
4
|
-
import tsconfigPaths from "vite-tsconfig-paths";
|
|
5
|
-
import { defineConfig } from "vitest/config";
|
|
6
|
-
|
|
7
|
-
// application entry point
|
|
8
|
-
const entry = globSync("./src/**/*.ts").filter((f) => !f.endsWith("test.ts"));
|
|
9
|
-
const entryRoot = "src";
|
|
10
|
-
|
|
11
|
-
// emits declarations only if there is no src/main.ts file
|
|
12
|
-
const dtsPlugin = existsSync("./src/main.ts")
|
|
13
|
-
? null
|
|
14
|
-
: dts({ include: entry, logLevel: "error", entryRoot: entryRoot });
|
|
15
|
-
|
|
16
|
-
export default defineConfig({
|
|
17
|
-
plugins: [
|
|
18
|
-
// externalize node built-ins only
|
|
19
|
-
nodeExternals(),
|
|
20
|
-
// resolve tsconfig path aliases
|
|
21
|
-
tsconfigPaths(),
|
|
22
|
-
// declarations (if lib)
|
|
23
|
-
dtsPlugin,
|
|
24
|
-
].filter(Boolean),
|
|
25
|
-
|
|
26
|
-
build: {
|
|
27
|
-
target: "esnext",
|
|
28
|
-
lib: {
|
|
29
|
-
entry,
|
|
30
|
-
formats: ["es"],
|
|
31
|
-
},
|
|
32
|
-
sourcemap: true,
|
|
33
|
-
outDir: "./build",
|
|
34
|
-
emptyOutDir: true,
|
|
35
|
-
rollupOptions: { output: { preserveModules: true, preserveModulesRoot: entryRoot } },
|
|
36
|
-
},
|
|
37
|
-
|
|
38
|
-
test: {
|
|
39
|
-
include: ["./src/**/*.test.{ts,tsx}"],
|
|
40
|
-
reporters: ["verbose"],
|
|
41
|
-
watch: false,
|
|
42
|
-
coverage: {
|
|
43
|
-
all: true,
|
|
44
|
-
clean: true,
|
|
45
|
-
cleanOnRerun: true,
|
|
46
|
-
include: ["src"],
|
|
47
|
-
exclude: ["**/*.test.{ts,tsx}", "**/*main.ts"],
|
|
48
|
-
},
|
|
49
|
-
// biome-ignore lint/style/useNamingConvention: needed for vitest
|
|
50
|
-
env: { NODE_ENV: "test" },
|
|
51
|
-
environment: "node",
|
|
52
|
-
passWithNoTests: true,
|
|
53
|
-
setupFiles: [],
|
|
54
|
-
},
|
|
55
|
-
});
|