architex-js 1.5.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 +3 -2
- package/src/guards/Guard.js +165 -0
- package/src/guards/index.js +1 -0
- package/src/index.js +3 -1
- package/test/guard.test.js +94 -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",
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
"./microkernel": "./src/microkernel/index.js",
|
|
10
10
|
"./exceptions": "./src/exceptions/index.js",
|
|
11
11
|
"./pipeline": "./src/pipeline/index.js",
|
|
12
|
-
"./result": "./src/result/index.js"
|
|
12
|
+
"./result": "./src/result/index.js",
|
|
13
|
+
"./guards": "./src/guards/index.js"
|
|
13
14
|
},
|
|
14
15
|
"description": "Architectural Toolbox for JavaScript - Providing high-level building blocks for robust systems.",
|
|
15
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,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
|
+
});
|