@jeengbe/config 0.0.9 → 1.0.1
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 +18 -3
- package/dist/ast.d.mts +108 -8
- package/dist/ast.d.mts.map +1 -1
- package/dist/ast.mjs +113 -7
- package/dist/ast.mjs.map +1 -1
- package/dist/env.d.mts +203 -5
- package/dist/env.d.mts.map +1 -1
- package/dist/env.mjs +75 -35
- package/dist/env.mjs.map +1 -1
- package/dist/if-enabled.d.mts +13 -2
- package/dist/if-enabled.d.mts.map +1 -1
- package/dist/if-enabled.mjs +11 -0
- package/dist/if-enabled.mjs.map +1 -1
- package/dist/index.d.mts +3 -2
- package/dist/index.mjs +2 -1
- package/dist/validation.d.mts +103 -6
- package/dist/validation.d.mts.map +1 -1
- package/dist/validation.mjs +109 -5
- package/dist/validation.mjs.map +1 -1
- package/package.json +6 -2
- package/src/ast.ts +211 -19
- package/src/env.ts +361 -73
- package/src/if-enabled.ts +15 -7
- package/src/index.ts +8 -1
- package/src/validation.ts +162 -12
package/src/validation.ts
CHANGED
|
@@ -1,21 +1,171 @@
|
|
|
1
|
-
import { Either } from '@jeengbe/prelude';
|
|
1
|
+
import { Either, mapMaybe, Maybe } from '@jeengbe/prelude';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* The result of validating a config value: a Left of accumulated structured errors, or a Right of the
|
|
5
|
+
* validated value together with a log of every default value that was substituted along the way.
|
|
6
|
+
*
|
|
7
|
+
* `map` and `flatMap` behave like a regular value monad, except that `flatMap` also concatenates the
|
|
8
|
+
* defaulted-key log of both sides instead of discarding either - so chaining validations never loses
|
|
9
|
+
* track of which defaults were applied upstream.
|
|
10
|
+
*/
|
|
11
|
+
export class ValidationResult<T> {
|
|
12
|
+
private constructor(private readonly result: Either<ValidationFailure, ValidationSuccess<T>>) {}
|
|
4
13
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Creates a successful ValidationResult with the given value and log of defaulted keys.
|
|
16
|
+
*/
|
|
17
|
+
static success<T>(success: ValidationSuccess<T>): ValidationResult<T> {
|
|
18
|
+
return new ValidationResult(Either.right(success));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Creates a failed ValidationResult with the given errors.
|
|
23
|
+
*/
|
|
24
|
+
static fail(failure: ValidationFailure): ValidationResult<never> {
|
|
25
|
+
return new ValidationResult(Either.left(failure));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Maps the value of this ValidationResult if it succeeded, preserving its defaulted-key log. Performs
|
|
30
|
+
* no operation if this is a failure.
|
|
31
|
+
*/
|
|
32
|
+
map<U>(f: (value: T) => U): ValidationResult<U> {
|
|
33
|
+
return new ValidationResult(
|
|
34
|
+
this.result.map(({ defaulted, value }) => ({ defaulted, value: f(value) })),
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Flat maps the value of this ValidationResult if it succeeded, concatenating the defaulted-key log of
|
|
40
|
+
* this result with that of the one returned by `f`. Performs no operation if this is a failure.
|
|
41
|
+
*/
|
|
42
|
+
flatMap<U>(f: (value: T) => ValidationResult<U>): ValidationResult<U> {
|
|
43
|
+
return new ValidationResult(
|
|
44
|
+
this.result.flatMap(({ defaulted, value }) =>
|
|
45
|
+
f(value).result.map(({ defaulted: newDefaulted, value: newValue }) => ({
|
|
46
|
+
defaulted: [...defaulted, ...newDefaulted],
|
|
47
|
+
value: newValue,
|
|
48
|
+
})),
|
|
49
|
+
),
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Applies the provided functions to this ValidationResult, depending on whether it failed or succeeded,
|
|
55
|
+
* and returns the result.
|
|
56
|
+
*/
|
|
57
|
+
fold<R1, R2>(
|
|
58
|
+
onFailure: (failure: ValidationFailure) => R1,
|
|
59
|
+
onSuccess: (success: ValidationSuccess<T>) => R2,
|
|
60
|
+
): R1 | R2 {
|
|
61
|
+
return this.result.fold(onFailure, onSuccess);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
getLeft(): Maybe<ValidationFailure> {
|
|
65
|
+
return this.result.getLeft();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
get(): Maybe<T> {
|
|
69
|
+
return mapMaybe(this.result.get(), ({ value }) => value);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface ValidationFailure {
|
|
74
|
+
errors: readonly ValidationError[];
|
|
75
|
+
}
|
|
8
76
|
|
|
9
|
-
|
|
77
|
+
/**
|
|
78
|
+
* A single structured validation error, pinpointing the key and path it occurred at.
|
|
79
|
+
*/
|
|
80
|
+
export interface ValidationError {
|
|
81
|
+
path: string;
|
|
82
|
+
key: string;
|
|
83
|
+
message: string;
|
|
84
|
+
formatHint?: string;
|
|
85
|
+
value: unknown;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface ValidationSuccess<T> {
|
|
89
|
+
defaulted: readonly ValidationDefaulted[];
|
|
90
|
+
value: T;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Records that a default value was substituted in place of a missing environment variable.
|
|
95
|
+
*/
|
|
96
|
+
export interface ValidationDefaulted {
|
|
97
|
+
path: string;
|
|
98
|
+
key: string;
|
|
99
|
+
defaultValue: unknown;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Combines multiple ValidationResults into one, preserving the tuple's value types. Succeeds with all
|
|
104
|
+
* values and the concatenation of every branch's defaulted-key log if every result succeeded, or fails
|
|
105
|
+
* with all accumulated errors otherwise.
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
*
|
|
109
|
+
* ```ts
|
|
110
|
+
* const result1: ValidationResult<number> = ValidationResult.fail({
|
|
111
|
+
* errors: [{ path: '$.a', key: 'A', message: 'error1', value: undefined }],
|
|
112
|
+
* });
|
|
113
|
+
* const result2: ValidationResult<string> = ValidationResult.success({ value: 'value2', defaulted: [] });
|
|
114
|
+
* const result3: ValidationResult<boolean> = ValidationResult.fail({
|
|
115
|
+
* errors: [{ path: '$.c', key: 'C', message: 'error3', value: undefined }],
|
|
116
|
+
* });
|
|
117
|
+
*
|
|
118
|
+
* const combinedResult = collectValidationResults(result1, result2, result3);
|
|
119
|
+
*
|
|
120
|
+
* console.log(combinedResult); // Left with both `error1` and `error3`
|
|
121
|
+
* ```
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
*
|
|
125
|
+
* ```ts
|
|
126
|
+
* const result1: ValidationResult<number> = ValidationResult.success({ value: 42, defaulted: [] });
|
|
127
|
+
* const result2: ValidationResult<string> = ValidationResult.success({ value: 'value2', defaulted: [] });
|
|
128
|
+
* const result3: ValidationResult<boolean> = ValidationResult.success({ value: true, defaulted: [] });
|
|
129
|
+
*
|
|
130
|
+
* const combinedResult = collectValidationResults(result1, result2, result3);
|
|
131
|
+
*
|
|
132
|
+
* console.log(combinedResult); // Right([42, 'value2', true])
|
|
133
|
+
* ```
|
|
134
|
+
*/
|
|
135
|
+
export function collectValidationResults<const U extends readonly ValidationResult<unknown>[]>(
|
|
10
136
|
...results: U
|
|
11
|
-
):
|
|
12
|
-
const errors =
|
|
137
|
+
): CollectValidationResult<U> {
|
|
138
|
+
const errors: ValidationError[] = [];
|
|
139
|
+
const defaulted: ValidationDefaulted[] = [];
|
|
140
|
+
const values: unknown[] = [];
|
|
141
|
+
|
|
142
|
+
for (const result of results) {
|
|
143
|
+
result.fold(
|
|
144
|
+
(failure) => errors.push(...failure.errors),
|
|
145
|
+
(success) => {
|
|
146
|
+
defaulted.push(...success.defaulted);
|
|
147
|
+
values.push(success.value);
|
|
148
|
+
},
|
|
149
|
+
);
|
|
150
|
+
}
|
|
13
151
|
|
|
14
|
-
return
|
|
15
|
-
|
|
16
|
-
|
|
152
|
+
return (
|
|
153
|
+
errors.length
|
|
154
|
+
? ValidationResult.fail({ errors })
|
|
155
|
+
: ValidationResult.success({ defaulted, value: values })
|
|
156
|
+
) as CollectValidationResult<U>;
|
|
17
157
|
}
|
|
18
158
|
|
|
19
|
-
|
|
159
|
+
type CollectValidationResult<T extends readonly ValidationResult<unknown>[]> = ValidationResult<{
|
|
160
|
+
[K in keyof T]: T[K] extends ValidationResult<infer U> ? U : never;
|
|
161
|
+
}>;
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Type guard checking whether val is one of the values in arr.
|
|
165
|
+
*/
|
|
166
|
+
export function arrayIncludes<const T extends string | number | boolean | null | undefined>(
|
|
167
|
+
arr: readonly T[],
|
|
168
|
+
val: unknown,
|
|
169
|
+
): val is T {
|
|
20
170
|
return arr.includes(val as T);
|
|
21
171
|
}
|