architex-js 1.4.0 → 1.6.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/package.json +4 -2
- package/src/guards/Guard.js +165 -0
- package/src/guards/index.js +1 -0
- package/src/index.js +3 -1
- package/src/result/Result.js +149 -0
- package/src/result/index.js +1 -0
- package/test/guard.test.js +94 -0
- package/test/result.test.js +76 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "architex-js",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"main": "src/index.js",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./src/index.js",
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
"./mixins": "./src/mixins/index.js",
|
|
9
9
|
"./microkernel": "./src/microkernel/index.js",
|
|
10
10
|
"./exceptions": "./src/exceptions/index.js",
|
|
11
|
-
"./pipeline": "./src/pipeline/index.js"
|
|
11
|
+
"./pipeline": "./src/pipeline/index.js",
|
|
12
|
+
"./result": "./src/result/index.js",
|
|
13
|
+
"./guards": "./src/guards/index.js"
|
|
12
14
|
},
|
|
13
15
|
"description": "Architectural Toolbox for JavaScript - Providing high-level building blocks for robust systems.",
|
|
14
16
|
"author": {
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Defensive programming utilities.
|
|
3
|
+
* Guard methods throw descriptive errors when assertions fail,
|
|
4
|
+
* keeping domain invariants clean and readable.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* Guard.notNull(userId, 'userId');
|
|
8
|
+
* Guard.minLength(username, 3, 'username');
|
|
9
|
+
* Guard.positiveNumber(price, 'price');
|
|
10
|
+
*/
|
|
11
|
+
class Guard {
|
|
12
|
+
/**
|
|
13
|
+
* @param {string} fieldName
|
|
14
|
+
* @param {string} message
|
|
15
|
+
*/
|
|
16
|
+
static #fail(fieldName, message) {
|
|
17
|
+
throw new Error(`[Guard] ${fieldName}: ${message}`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Asserts the value is not null or undefined.
|
|
22
|
+
* @param {*} value
|
|
23
|
+
* @param {string} fieldName
|
|
24
|
+
*/
|
|
25
|
+
static notNull(value, fieldName = 'value') {
|
|
26
|
+
if (value === null || value === undefined) {
|
|
27
|
+
Guard.#fail(fieldName, 'must not be null or undefined');
|
|
28
|
+
}
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Asserts the string is non-empty after trimming.
|
|
34
|
+
* @param {string} value
|
|
35
|
+
* @param {string} fieldName
|
|
36
|
+
*/
|
|
37
|
+
static notEmpty(value, fieldName = 'value') {
|
|
38
|
+
Guard.notNull(value, fieldName);
|
|
39
|
+
if (typeof value !== 'string' || value.trim().length === 0) {
|
|
40
|
+
Guard.#fail(fieldName, 'must not be an empty string');
|
|
41
|
+
}
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Asserts the string has at least `min` characters.
|
|
47
|
+
* @param {string} value
|
|
48
|
+
* @param {number} min
|
|
49
|
+
* @param {string} fieldName
|
|
50
|
+
*/
|
|
51
|
+
static minLength(value, min, fieldName = 'value') {
|
|
52
|
+
Guard.notNull(value, fieldName);
|
|
53
|
+
if (typeof value !== 'string' || value.length < min) {
|
|
54
|
+
Guard.#fail(fieldName, `must have at least ${min} character(s), got ${value?.length ?? 0}`);
|
|
55
|
+
}
|
|
56
|
+
return value;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Asserts the string has at most `max` characters.
|
|
61
|
+
* @param {string} value
|
|
62
|
+
* @param {number} max
|
|
63
|
+
* @param {string} fieldName
|
|
64
|
+
*/
|
|
65
|
+
static maxLength(value, max, fieldName = 'value') {
|
|
66
|
+
Guard.notNull(value, fieldName);
|
|
67
|
+
if (typeof value !== 'string' || value.length > max) {
|
|
68
|
+
Guard.#fail(fieldName, `must have at most ${max} character(s), got ${value?.length ?? 0}`);
|
|
69
|
+
}
|
|
70
|
+
return value;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Asserts the number is greater than zero (no zero allowed).
|
|
75
|
+
* @param {number} value
|
|
76
|
+
* @param {string} fieldName
|
|
77
|
+
*/
|
|
78
|
+
static positiveNumber(value, fieldName = 'value') {
|
|
79
|
+
Guard.notNull(value, fieldName);
|
|
80
|
+
if (typeof value !== 'number' || value <= 0) {
|
|
81
|
+
Guard.#fail(fieldName, `must be a positive number, got ${value}`);
|
|
82
|
+
}
|
|
83
|
+
return value;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Asserts the number is zero or greater.
|
|
88
|
+
* @param {number} value
|
|
89
|
+
* @param {string} fieldName
|
|
90
|
+
*/
|
|
91
|
+
static nonNegativeNumber(value, fieldName = 'value') {
|
|
92
|
+
Guard.notNull(value, fieldName);
|
|
93
|
+
if (typeof value !== 'number' || value < 0) {
|
|
94
|
+
Guard.#fail(fieldName, `must be >= 0, got ${value}`);
|
|
95
|
+
}
|
|
96
|
+
return value;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Asserts the value falls within a numeric range [min, max].
|
|
101
|
+
* @param {number} value
|
|
102
|
+
* @param {number} min
|
|
103
|
+
* @param {number} max
|
|
104
|
+
* @param {string} fieldName
|
|
105
|
+
*/
|
|
106
|
+
static inRange(value, min, max, fieldName = 'value') {
|
|
107
|
+
Guard.notNull(value, fieldName);
|
|
108
|
+
if (typeof value !== 'number' || value < min || value > max) {
|
|
109
|
+
Guard.#fail(fieldName, `must be between ${min} and ${max}, got ${value}`);
|
|
110
|
+
}
|
|
111
|
+
return value;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Asserts the value matches a regular expression.
|
|
116
|
+
* @param {string} value
|
|
117
|
+
* @param {RegExp} pattern
|
|
118
|
+
* @param {string} fieldName
|
|
119
|
+
*/
|
|
120
|
+
static matches(value, pattern, fieldName = 'value') {
|
|
121
|
+
Guard.notNull(value, fieldName);
|
|
122
|
+
if (typeof value !== 'string' || !pattern.test(value)) {
|
|
123
|
+
Guard.#fail(fieldName, `must match pattern ${pattern}`);
|
|
124
|
+
}
|
|
125
|
+
return value;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Asserts the value is a valid email address.
|
|
130
|
+
* @param {string} value
|
|
131
|
+
* @param {string} fieldName
|
|
132
|
+
*/
|
|
133
|
+
static isEmail(value, fieldName = 'value') {
|
|
134
|
+
return Guard.matches(value, /^[^\s@]+@[^\s@]+\.[^\s@]+$/, fieldName);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Asserts the value is one of the allowed options.
|
|
139
|
+
* @param {*} value
|
|
140
|
+
* @param {Array} options
|
|
141
|
+
* @param {string} fieldName
|
|
142
|
+
*/
|
|
143
|
+
static oneOf(value, options, fieldName = 'value') {
|
|
144
|
+
Guard.notNull(value, fieldName);
|
|
145
|
+
if (!options.includes(value)) {
|
|
146
|
+
Guard.#fail(fieldName, `must be one of [${options.join(', ')}], got "${value}"`);
|
|
147
|
+
}
|
|
148
|
+
return value;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Asserts the value is an Array with at least one element.
|
|
153
|
+
* @param {Array} value
|
|
154
|
+
* @param {string} fieldName
|
|
155
|
+
*/
|
|
156
|
+
static notEmptyArray(value, fieldName = 'value') {
|
|
157
|
+
Guard.notNull(value, fieldName);
|
|
158
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
159
|
+
Guard.#fail(fieldName, 'must be a non-empty array');
|
|
160
|
+
}
|
|
161
|
+
return value;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export { Guard };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./Guard.js";
|
package/src/index.js
CHANGED
|
@@ -2,4 +2,6 @@ export * from "./abc/index.js";
|
|
|
2
2
|
export * from "./mixins/index.js";
|
|
3
3
|
export * from "./microkernel/index.js";
|
|
4
4
|
export * from "./exceptions/index.js";
|
|
5
|
-
export * from "./pipeline/index.js";
|
|
5
|
+
export * from "./pipeline/index.js";
|
|
6
|
+
export * from "./result/index.js";
|
|
7
|
+
export * from "./guards/index.js";
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents the successful outcome of an operation.
|
|
3
|
+
* @template T
|
|
4
|
+
*/
|
|
5
|
+
class Ok {
|
|
6
|
+
/** @param {T} value */
|
|
7
|
+
constructor(value) {
|
|
8
|
+
this._value = value;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** @returns {true} */
|
|
12
|
+
isOk() { return true; }
|
|
13
|
+
|
|
14
|
+
/** @returns {false} */
|
|
15
|
+
isFail() { return false; }
|
|
16
|
+
|
|
17
|
+
/** @returns {T} */
|
|
18
|
+
get value() { return this._value; }
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Transforms the inner value with a function.
|
|
22
|
+
* @template U
|
|
23
|
+
* @param {(value: T) => U} fn
|
|
24
|
+
* @returns {Ok<U>}
|
|
25
|
+
*/
|
|
26
|
+
map(fn) { return new Ok(fn(this._value)); }
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Chains another Result-returning function.
|
|
30
|
+
* @template U
|
|
31
|
+
* @param {(value: T) => Result<U>} fn
|
|
32
|
+
* @returns {Result<U>}
|
|
33
|
+
*/
|
|
34
|
+
flatMap(fn) { return fn(this._value); }
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Returns the value or the fallback if this is a Fail.
|
|
38
|
+
* @param {T} _fallback
|
|
39
|
+
* @returns {T}
|
|
40
|
+
*/
|
|
41
|
+
getOrElse(_fallback) { return this._value; }
|
|
42
|
+
|
|
43
|
+
toString() { return `Ok(${this._value})`; }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Represents the failed outcome of an operation.
|
|
48
|
+
* @template E
|
|
49
|
+
*/
|
|
50
|
+
class Fail {
|
|
51
|
+
/** @param {E} error */
|
|
52
|
+
constructor(error) {
|
|
53
|
+
this._error = error;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** @returns {false} */
|
|
57
|
+
isOk() { return false; }
|
|
58
|
+
|
|
59
|
+
/** @returns {true} */
|
|
60
|
+
isFail() { return true; }
|
|
61
|
+
|
|
62
|
+
/** @returns {E} */
|
|
63
|
+
get error() { return this._error; }
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Returns Fail unchanged (map is a no-op on failure).
|
|
67
|
+
* @returns {Fail<E>}
|
|
68
|
+
*/
|
|
69
|
+
map(_fn) { return this; }
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Returns Fail unchanged (flatMap is a no-op on failure).
|
|
73
|
+
* @returns {Fail<E>}
|
|
74
|
+
*/
|
|
75
|
+
flatMap(_fn) { return this; }
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Returns the fallback value since this is a failure.
|
|
79
|
+
* @template T
|
|
80
|
+
* @param {T} fallback
|
|
81
|
+
* @returns {T}
|
|
82
|
+
*/
|
|
83
|
+
getOrElse(fallback) { return fallback; }
|
|
84
|
+
|
|
85
|
+
toString() { return `Fail(${this._error})`; }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Railway-Oriented Programming: wraps the result of an operation
|
|
90
|
+
* as either a success (Ok) or a failure (Fail), eliminating try/catch noise.
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* const r = Result.ok(42);
|
|
94
|
+
* if (r.isOk()) console.log(r.value); // 42
|
|
95
|
+
*
|
|
96
|
+
* const e = Result.fail(new Error("boom"));
|
|
97
|
+
* if (e.isFail()) console.error(e.error);
|
|
98
|
+
*/
|
|
99
|
+
class Result {
|
|
100
|
+
/**
|
|
101
|
+
* Creates a successful result.
|
|
102
|
+
* @template T
|
|
103
|
+
* @param {T} value
|
|
104
|
+
* @returns {Ok<T>}
|
|
105
|
+
*/
|
|
106
|
+
static ok(value) {
|
|
107
|
+
return new Ok(value);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Creates a failed result.
|
|
112
|
+
* @template E
|
|
113
|
+
* @param {E} error
|
|
114
|
+
* @returns {Fail<E>}
|
|
115
|
+
*/
|
|
116
|
+
static fail(error) {
|
|
117
|
+
return new Fail(error);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Wraps a function that may throw and returns a Result.
|
|
122
|
+
* @template T
|
|
123
|
+
* @param {() => T} fn
|
|
124
|
+
* @returns {Ok<T> | Fail<unknown>}
|
|
125
|
+
*/
|
|
126
|
+
static try(fn) {
|
|
127
|
+
try {
|
|
128
|
+
return new Ok(fn());
|
|
129
|
+
} catch (e) {
|
|
130
|
+
return new Fail(e);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Wraps an async function that may throw and returns a Promise<Result>.
|
|
136
|
+
* @template T
|
|
137
|
+
* @param {() => Promise<T>} fn
|
|
138
|
+
* @returns {Promise<Ok<T> | Fail<unknown>>}
|
|
139
|
+
*/
|
|
140
|
+
static async tryAsync(fn) {
|
|
141
|
+
try {
|
|
142
|
+
return new Ok(await fn());
|
|
143
|
+
} catch (e) {
|
|
144
|
+
return new Fail(e);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export { Result, Ok, Fail };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./Result.js";
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { Guard } from '../src/guards/index.js';
|
|
3
|
+
|
|
4
|
+
describe('Guard', () => {
|
|
5
|
+
describe('notNull', () => {
|
|
6
|
+
it('should pass for a valid value', () => {
|
|
7
|
+
expect(() => Guard.notNull('hello', 'field')).not.toThrow();
|
|
8
|
+
});
|
|
9
|
+
it('should throw for null', () => {
|
|
10
|
+
expect(() => Guard.notNull(null, 'field')).toThrow('[Guard] field: must not be null or undefined');
|
|
11
|
+
});
|
|
12
|
+
it('should throw for undefined', () => {
|
|
13
|
+
expect(() => Guard.notNull(undefined, 'field')).toThrow();
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe('notEmpty', () => {
|
|
18
|
+
it('should pass for a non-empty string', () => {
|
|
19
|
+
expect(() => Guard.notEmpty('hello', 'name')).not.toThrow();
|
|
20
|
+
});
|
|
21
|
+
it('should throw for an empty string', () => {
|
|
22
|
+
expect(() => Guard.notEmpty('', 'name')).toThrow();
|
|
23
|
+
});
|
|
24
|
+
it('should throw for a whitespace-only string', () => {
|
|
25
|
+
expect(() => Guard.notEmpty(' ', 'name')).toThrow();
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe('minLength', () => {
|
|
30
|
+
it('should pass when length >= min', () => {
|
|
31
|
+
expect(() => Guard.minLength('abc', 3, 'field')).not.toThrow();
|
|
32
|
+
});
|
|
33
|
+
it('should throw when length < min', () => {
|
|
34
|
+
expect(() => Guard.minLength('ab', 3, 'field')).toThrow();
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe('maxLength', () => {
|
|
39
|
+
it('should pass when length <= max', () => {
|
|
40
|
+
expect(() => Guard.maxLength('abc', 5, 'field')).not.toThrow();
|
|
41
|
+
});
|
|
42
|
+
it('should throw when length > max', () => {
|
|
43
|
+
expect(() => Guard.maxLength('abcdef', 5, 'field')).toThrow();
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe('positiveNumber', () => {
|
|
48
|
+
it('should pass for a positive number', () => {
|
|
49
|
+
expect(() => Guard.positiveNumber(1, 'price')).not.toThrow();
|
|
50
|
+
});
|
|
51
|
+
it('should throw for zero', () => {
|
|
52
|
+
expect(() => Guard.positiveNumber(0, 'price')).toThrow();
|
|
53
|
+
});
|
|
54
|
+
it('should throw for negative', () => {
|
|
55
|
+
expect(() => Guard.positiveNumber(-5, 'price')).toThrow();
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe('inRange', () => {
|
|
60
|
+
it('should pass for value within range', () => {
|
|
61
|
+
expect(() => Guard.inRange(5, 1, 10, 'score')).not.toThrow();
|
|
62
|
+
});
|
|
63
|
+
it('should throw for value outside range', () => {
|
|
64
|
+
expect(() => Guard.inRange(11, 1, 10, 'score')).toThrow();
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe('isEmail', () => {
|
|
69
|
+
it('should pass for a valid email', () => {
|
|
70
|
+
expect(() => Guard.isEmail('user@example.com', 'email')).not.toThrow();
|
|
71
|
+
});
|
|
72
|
+
it('should throw for an invalid email', () => {
|
|
73
|
+
expect(() => Guard.isEmail('not-an-email', 'email')).toThrow();
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe('oneOf', () => {
|
|
78
|
+
it('should pass when value is in options', () => {
|
|
79
|
+
expect(() => Guard.oneOf('active', ['active', 'inactive'], 'status')).not.toThrow();
|
|
80
|
+
});
|
|
81
|
+
it('should throw when value is not in options', () => {
|
|
82
|
+
expect(() => Guard.oneOf('pending', ['active', 'inactive'], 'status')).toThrow();
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe('notEmptyArray', () => {
|
|
87
|
+
it('should pass for a non-empty array', () => {
|
|
88
|
+
expect(() => Guard.notEmptyArray([1, 2], 'items')).not.toThrow();
|
|
89
|
+
});
|
|
90
|
+
it('should throw for an empty array', () => {
|
|
91
|
+
expect(() => Guard.notEmptyArray([], 'items')).toThrow();
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { Result, Ok, Fail } from '../src/result/index.js';
|
|
3
|
+
|
|
4
|
+
describe('Result', () => {
|
|
5
|
+
it('Result.ok() should create an Ok instance', () => {
|
|
6
|
+
const r = Result.ok(42);
|
|
7
|
+
expect(r.isOk()).toBe(true);
|
|
8
|
+
expect(r.isFail()).toBe(false);
|
|
9
|
+
expect(r.value).toBe(42);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('Result.fail() should create a Fail instance', () => {
|
|
13
|
+
const err = new Error('boom');
|
|
14
|
+
const r = Result.fail(err);
|
|
15
|
+
expect(r.isOk()).toBe(false);
|
|
16
|
+
expect(r.isFail()).toBe(true);
|
|
17
|
+
expect(r.error).toBe(err);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('Ok.map() should transform the value', () => {
|
|
21
|
+
const r = Result.ok(5).map(x => x * 2);
|
|
22
|
+
expect(r.isOk()).toBe(true);
|
|
23
|
+
expect(r.value).toBe(10);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('Fail.map() should be a no-op and preserve the error', () => {
|
|
27
|
+
const err = new Error('fail');
|
|
28
|
+
const r = Result.fail(err).map(x => x * 2);
|
|
29
|
+
expect(r.isFail()).toBe(true);
|
|
30
|
+
expect(r.error).toBe(err);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('Ok.flatMap() should chain another Result', () => {
|
|
34
|
+
const r = Result.ok(5).flatMap(x => Result.ok(x + 10));
|
|
35
|
+
expect(r.value).toBe(15);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('Fail.flatMap() should be a no-op', () => {
|
|
39
|
+
const err = new Error('fail');
|
|
40
|
+
const r = Result.fail(err).flatMap(x => Result.ok(x + 10));
|
|
41
|
+
expect(r.isFail()).toBe(true);
|
|
42
|
+
expect(r.error).toBe(err);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('Ok.getOrElse() should return the value', () => {
|
|
46
|
+
expect(Result.ok('hello').getOrElse('fallback')).toBe('hello');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('Fail.getOrElse() should return the fallback', () => {
|
|
50
|
+
expect(Result.fail(new Error()).getOrElse('fallback')).toBe('fallback');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('Result.try() should wrap a throwing function in Fail', () => {
|
|
54
|
+
const r = Result.try(() => { throw new Error('oops'); });
|
|
55
|
+
expect(r.isFail()).toBe(true);
|
|
56
|
+
expect(r.error.message).toBe('oops');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('Result.try() should wrap a successful function in Ok', () => {
|
|
60
|
+
const r = Result.try(() => 99);
|
|
61
|
+
expect(r.isOk()).toBe(true);
|
|
62
|
+
expect(r.value).toBe(99);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('Result.tryAsync() should wrap a rejected promise in Fail', async () => {
|
|
66
|
+
const r = await Result.tryAsync(() => Promise.reject(new Error('async error')));
|
|
67
|
+
expect(r.isFail()).toBe(true);
|
|
68
|
+
expect(r.error.message).toBe('async error');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('Result.tryAsync() should wrap a resolved promise in Ok', async () => {
|
|
72
|
+
const r = await Result.tryAsync(() => Promise.resolve('done'));
|
|
73
|
+
expect(r.isOk()).toBe(true);
|
|
74
|
+
expect(r.value).toBe('done');
|
|
75
|
+
});
|
|
76
|
+
});
|