ripthrow 2.1.1 → 2.2.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 +5 -0
- package/package.json +1 -1
- package/src/factories/ok.ts +3 -3
- package/src/factories/safe-async.ts +1 -1
- package/src/factories/safe.ts +1 -1
- package/src/operators/context.ts +5 -4
- package/src/operators/map.ts +1 -1
- package/src/pattern.ts +65 -19
- package/src/report.ts +13 -7
- package/src/result-builder-async.ts +37 -12
- package/src/result-builder.ts +21 -8
- package/src/utils/all.ts +14 -8
- package/src/utils/pipe.ts +35 -9
package/README.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# ripthrow
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/ripthrow)
|
|
4
|
+
[](https://github.com/MechanicalLabs/ripthrow/actions/workflows/ci.yml)
|
|
5
|
+

|
|
6
|
+
[](https://github.com/MechanicalLabs/ripthrow/blob/main/LICENSE)
|
|
7
|
+
|
|
3
8
|
**Zero-dependency, type-safe error handling for TypeScript.**
|
|
4
9
|
|
|
5
10
|
`ripthrow` is a lightweight library inspired by Rust's `Result` type and the proposed ECMAScript `?=` operator. It allows you to handle success and failure in a structured way, avoiding `try/catch` blocks and making error states explicit in your types.
|
package/package.json
CHANGED
package/src/factories/ok.ts
CHANGED
|
@@ -5,7 +5,7 @@ import type { Result } from "../types";
|
|
|
5
5
|
*
|
|
6
6
|
* @template T The type of the success value.
|
|
7
7
|
* @template E The type of the error value.
|
|
8
|
-
* @param
|
|
8
|
+
* @param args The value to wrap in an Ok result. Required if T is not void.
|
|
9
9
|
* @returns A successful Result object `{ ok: true, value: T }`.
|
|
10
10
|
*
|
|
11
11
|
* @category Factories
|
|
@@ -14,6 +14,6 @@ import type { Result } from "../types";
|
|
|
14
14
|
* const res = Ok(42);
|
|
15
15
|
* if (res.ok) console.log(res.value); // 42
|
|
16
16
|
*/
|
|
17
|
-
export function Ok<T = void, E = unknown>(
|
|
18
|
-
return { ok: true, value:
|
|
17
|
+
export function Ok<T = void, E = unknown>(...args: undefined extends T ? [T?] : [T]): Result<T, E> {
|
|
18
|
+
return { ok: true, value: args[0] as T };
|
|
19
19
|
}
|
package/src/factories/safe.ts
CHANGED
package/src/operators/context.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { mapErr } from "./map-err";
|
|
|
7
7
|
*
|
|
8
8
|
* @template T The type of the success value.
|
|
9
9
|
* @template E The type of the error value.
|
|
10
|
+
* @template C The type of the metadata.
|
|
10
11
|
* @param result The Result to attach context to.
|
|
11
12
|
* @param message The context message.
|
|
12
13
|
* @param help An optional help message.
|
|
@@ -17,12 +18,12 @@ import { mapErr } from "./map-err";
|
|
|
17
18
|
* @example
|
|
18
19
|
* const res = context(safe(() => JSON.parse(data)), "Failed to parse config");
|
|
19
20
|
*/
|
|
20
|
-
export function context<T, E>(
|
|
21
|
+
export function context<T, E, C extends Record<string, unknown> = Record<string, unknown>>(
|
|
21
22
|
result: Result<T, E>,
|
|
22
23
|
message: string,
|
|
23
24
|
help?: string,
|
|
24
|
-
meta?:
|
|
25
|
-
): Result<T, Report
|
|
25
|
+
meta?: C,
|
|
26
|
+
): Result<T, Report<C>> {
|
|
26
27
|
return mapErr(result, (err) => {
|
|
27
28
|
let originalMeta: Record<string, unknown> | undefined;
|
|
28
29
|
if (err && typeof err === "object") {
|
|
@@ -31,7 +32,7 @@ export function context<T, E>(
|
|
|
31
32
|
const merged = { ...(originalMeta || {}), ...(meta || {}) };
|
|
32
33
|
const keys = Object.keys(merged);
|
|
33
34
|
// biome-ignore lint/nursery/noTernary: it's more readable
|
|
34
|
-
const ctx:
|
|
35
|
+
const ctx: C | undefined = keys.length > 0 ? (merged as C) : undefined;
|
|
35
36
|
return Report.from(err, message, {
|
|
36
37
|
help,
|
|
37
38
|
context: ctx,
|
package/src/operators/map.ts
CHANGED
package/src/pattern.ts
CHANGED
|
@@ -4,12 +4,13 @@ import type { Result } from "./types/result";
|
|
|
4
4
|
const $class = Symbol("error-class");
|
|
5
5
|
const noMatch = Symbol("no-match");
|
|
6
6
|
|
|
7
|
-
interface TypedError<A extends unknown[], N extends string
|
|
7
|
+
interface TypedError<A extends unknown[], N extends string, M = Record<string, unknown>>
|
|
8
|
+
extends Error {
|
|
8
9
|
readonly args: A;
|
|
9
10
|
readonly help?: string;
|
|
10
11
|
readonly name: N;
|
|
11
12
|
readonly kind: N;
|
|
12
|
-
readonly _metadata?:
|
|
13
|
+
readonly _metadata?: M;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
interface HandlerEntry {
|
|
@@ -32,37 +33,61 @@ interface ErrDefEntry {
|
|
|
32
33
|
type ErrDefMap = Record<string, ErrDefEntry>;
|
|
33
34
|
|
|
34
35
|
declare const exhaustiveCheck: unique symbol;
|
|
35
|
-
|
|
36
|
+
// biome-ignore lint/suspicious/noExplicitAny: needed to match any metadata type
|
|
37
|
+
type TypedKinds<E> = E extends TypedError<unknown[], infer K, any> ? K : never;
|
|
36
38
|
type AllTypedKindsHandled<E, Handled extends string> = TypedKinds<E> extends Handled ? true : false;
|
|
37
39
|
|
|
38
40
|
type ErrorFactories<T extends ErrDefMap> = {
|
|
39
|
-
[K in keyof T & string]: ErrFactory<
|
|
41
|
+
[K in keyof T & string]: ErrFactory<
|
|
42
|
+
Parameters<T[K]["message"]>,
|
|
43
|
+
K,
|
|
44
|
+
T[K] extends { _metadata: infer M } ? M : Record<string, unknown>
|
|
45
|
+
>;
|
|
40
46
|
} & {
|
|
41
47
|
readonly _type: {
|
|
42
|
-
[K in keyof T & string]: TypedError<
|
|
48
|
+
[K in keyof T & string]: TypedError<
|
|
49
|
+
Parameters<T[K]["message"]>,
|
|
50
|
+
K,
|
|
51
|
+
T[K] extends { _metadata: infer M } ? M : Record<string, unknown>
|
|
52
|
+
>;
|
|
43
53
|
}[keyof T & string];
|
|
44
54
|
};
|
|
45
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Creates a collection of error factories from a definition map.
|
|
58
|
+
*
|
|
59
|
+
* @template T The error definition map.
|
|
60
|
+
* @param defs The definitions for each error kind.
|
|
61
|
+
* @returns An object containing error factories and a combined _type.
|
|
62
|
+
*/
|
|
46
63
|
export function createErrors<T extends ErrDefMap>(defs: T): ErrorFactories<T> {
|
|
47
|
-
|
|
64
|
+
// biome-ignore lint/suspicious/noExplicitAny: internal storage of factories
|
|
65
|
+
const result: Record<string, ErrFactory<unknown[], string, any>> = {};
|
|
48
66
|
for (const [name, def] of Object.entries(defs)) {
|
|
49
67
|
result[name] = createError(name, def.message, def.help, def._metadata);
|
|
50
68
|
}
|
|
51
69
|
return result as unknown as ErrorFactories<T>;
|
|
52
70
|
}
|
|
53
71
|
|
|
54
|
-
|
|
72
|
+
/**
|
|
73
|
+
* Creates a single typed error factory.
|
|
74
|
+
*
|
|
75
|
+
* @template A The type of the arguments.
|
|
76
|
+
* @template N The literal type of the error name.
|
|
77
|
+
* @template M The type of the metadata.
|
|
78
|
+
*/
|
|
79
|
+
export function createError<A extends unknown[], N extends string, M = Record<string, unknown>>(
|
|
55
80
|
name: N,
|
|
56
81
|
message: (...args: A) => string,
|
|
57
82
|
help?: (...args: A) => string,
|
|
58
|
-
_metadata?:
|
|
59
|
-
): ErrFactory<A, N> {
|
|
83
|
+
_metadata?: M,
|
|
84
|
+
): ErrFactory<A, N, M> {
|
|
60
85
|
class _Error extends Error {
|
|
61
86
|
override readonly name: N;
|
|
62
87
|
readonly args: A;
|
|
63
88
|
readonly help: string | undefined;
|
|
64
89
|
readonly kind: N = name;
|
|
65
|
-
readonly _metadata:
|
|
90
|
+
readonly _metadata: M | undefined;
|
|
66
91
|
|
|
67
92
|
constructor(...args: A) {
|
|
68
93
|
super(message(...args));
|
|
@@ -76,10 +101,10 @@ export function createError<A extends unknown[], N extends string>(
|
|
|
76
101
|
}
|
|
77
102
|
Object.defineProperty(_Error, "name", { value: name });
|
|
78
103
|
|
|
79
|
-
const factory = (...args: A): TypedError<A, N> =>
|
|
80
|
-
new _Error(...args) as unknown as TypedError<A, N>;
|
|
104
|
+
const factory = (...args: A): TypedError<A, N, M> =>
|
|
105
|
+
new _Error(...args) as unknown as TypedError<A, N, M>;
|
|
81
106
|
Object.defineProperty(factory, $class, { value: _Error });
|
|
82
|
-
return factory as unknown as ErrFactory<A, N>;
|
|
107
|
+
return factory as unknown as ErrFactory<A, N, M>;
|
|
83
108
|
}
|
|
84
109
|
|
|
85
110
|
export function wrapError<C extends new (...args: never[]) => Error>(
|
|
@@ -90,6 +115,9 @@ export function wrapError<C extends new (...args: never[]) => Error>(
|
|
|
90
115
|
return factory as unknown as ExternalErrFactory<C>;
|
|
91
116
|
}
|
|
92
117
|
|
|
118
|
+
/**
|
|
119
|
+
* Builder for pattern matching on results and errors.
|
|
120
|
+
*/
|
|
93
121
|
export class MatchErrBuilder<T, E, R, Handled extends string = never> {
|
|
94
122
|
private readonly result: Result<T, E>;
|
|
95
123
|
private readonly handlers: HandlerEntry[] = [];
|
|
@@ -98,11 +126,17 @@ export class MatchErrBuilder<T, E, R, Handled extends string = never> {
|
|
|
98
126
|
this.result = result;
|
|
99
127
|
}
|
|
100
128
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
129
|
+
/**
|
|
130
|
+
* Handles a specific error kind.
|
|
131
|
+
*/
|
|
132
|
+
on<A extends unknown[], N extends string, M, HandlerResult>(
|
|
133
|
+
def: ErrFactory<A, N, M>,
|
|
134
|
+
handler: (err: TypedError<A, N, M>) => HandlerResult,
|
|
104
135
|
): MatchErrBuilder<T, E, R | HandlerResult, Handled | N>;
|
|
105
136
|
|
|
137
|
+
/**
|
|
138
|
+
* Handles an external error class.
|
|
139
|
+
*/
|
|
106
140
|
on<C extends new (...args: never[]) => Error, HandlerResult>(
|
|
107
141
|
def: ExternalErrFactory<C>,
|
|
108
142
|
handler: (err: InstanceType<C>) => HandlerResult,
|
|
@@ -121,10 +155,16 @@ export class MatchErrBuilder<T, E, R, Handled extends string = never> {
|
|
|
121
155
|
return this;
|
|
122
156
|
}
|
|
123
157
|
|
|
158
|
+
/**
|
|
159
|
+
* Finalizes the match with a fallback for unhandled errors.
|
|
160
|
+
*/
|
|
124
161
|
otherwise(fallback: (err: E) => R): T | R {
|
|
125
162
|
return this.execute(fallback);
|
|
126
163
|
}
|
|
127
164
|
|
|
165
|
+
/**
|
|
166
|
+
* Ensures all typed errors from a createErrors set are handled.
|
|
167
|
+
*/
|
|
128
168
|
exhaustive(): AllTypedKindsHandled<E, Handled> extends true
|
|
129
169
|
? T | R
|
|
130
170
|
: { readonly [exhaustiveCheck]: typeof exhaustiveCheck } {
|
|
@@ -152,11 +192,17 @@ export class MatchErrBuilder<T, E, R, Handled extends string = never> {
|
|
|
152
192
|
}
|
|
153
193
|
}
|
|
154
194
|
|
|
195
|
+
/**
|
|
196
|
+
* Starts a fluent match operation on a Result's error.
|
|
197
|
+
*/
|
|
155
198
|
export function matchErr<T, E>(result: Result<T, E>): MatchErrBuilder<T, E, never, never> {
|
|
156
199
|
return new MatchErrBuilder(result);
|
|
157
200
|
}
|
|
158
201
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
202
|
+
/**
|
|
203
|
+
* Factory function for creating typed errors.
|
|
204
|
+
*/
|
|
205
|
+
export interface ErrFactory<A extends unknown[], N extends string, M = Record<string, unknown>> {
|
|
206
|
+
(...args: A): TypedError<A, N, M>;
|
|
207
|
+
readonly [$class]: new (...args: A) => TypedError<A, N, M>;
|
|
162
208
|
}
|
package/src/report.ts
CHANGED
|
@@ -2,14 +2,15 @@
|
|
|
2
2
|
* Options for creating a new Report.
|
|
3
3
|
*
|
|
4
4
|
* @category Error Handling
|
|
5
|
+
* @template C The type of the context metadata.
|
|
5
6
|
*/
|
|
6
|
-
export interface ReportOptions {
|
|
7
|
+
export interface ReportOptions<C extends Record<string, unknown> = Record<string, unknown>> {
|
|
7
8
|
/** The underlying cause of the error. */
|
|
8
9
|
cause?: unknown;
|
|
9
10
|
/** A helpful message for the user on how to resolve the error. */
|
|
10
11
|
help?: string | undefined;
|
|
11
12
|
/** Additional key-value pairs to provide more context. */
|
|
12
|
-
context?:
|
|
13
|
+
context?: C | undefined;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
/**
|
|
@@ -17,14 +18,15 @@ export interface ReportOptions {
|
|
|
17
18
|
* Inspired by Rust's `anyhow` or `eyre`.
|
|
18
19
|
*
|
|
19
20
|
* @category Error Handling
|
|
21
|
+
* @template C The type of the context metadata.
|
|
20
22
|
*/
|
|
21
|
-
export class Report extends Error {
|
|
23
|
+
export class Report<C extends Record<string, unknown> = Record<string, unknown>> extends Error {
|
|
22
24
|
/** A helpful message for the user on how to resolve the error. */
|
|
23
25
|
readonly help?: string | undefined;
|
|
24
26
|
/** Additional key-value pairs to provide more context. */
|
|
25
|
-
readonly context?:
|
|
27
|
+
readonly context?: C | undefined;
|
|
26
28
|
|
|
27
|
-
constructor(message: string, options: ReportOptions = {}) {
|
|
29
|
+
constructor(message: string, options: ReportOptions<C> = {}) {
|
|
28
30
|
super(message);
|
|
29
31
|
this.name = "Report";
|
|
30
32
|
this.help = options.help;
|
|
@@ -43,9 +45,13 @@ export class Report extends Error {
|
|
|
43
45
|
* @param options Additional options.
|
|
44
46
|
* @returns A Report instance.
|
|
45
47
|
*/
|
|
46
|
-
static from
|
|
48
|
+
static from<T extends Record<string, unknown> = Record<string, unknown>>(
|
|
49
|
+
err: unknown,
|
|
50
|
+
message?: string,
|
|
51
|
+
options: ReportOptions<T> = {},
|
|
52
|
+
): Report<T> {
|
|
47
53
|
if (err instanceof Report && !message && !options.help && !options.context) {
|
|
48
|
-
return err
|
|
54
|
+
return err as Report<T>;
|
|
49
55
|
}
|
|
50
56
|
|
|
51
57
|
let baseMessage: string;
|
|
@@ -46,8 +46,17 @@ export class AsyncResultBuilder<T, E> {
|
|
|
46
46
|
return this._executed;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Constructs a successful Result wrapped in an AsyncResultBuilder.
|
|
51
|
+
*
|
|
52
|
+
* @template U The type of the success value.
|
|
53
|
+
* @template F The type of the error value.
|
|
54
|
+
* @param args The value to wrap. Required if U is not void.
|
|
55
|
+
*/
|
|
56
|
+
static ok<U = void, F = unknown>(
|
|
57
|
+
...args: undefined extends U ? [U?] : [U]
|
|
58
|
+
): AsyncResultBuilder<U, F> {
|
|
59
|
+
return new AsyncResultBuilder(Promise.resolve(Ok(...args) as Result<U, F>));
|
|
51
60
|
}
|
|
52
61
|
|
|
53
62
|
static err<U, F>(error: F): AsyncResultBuilder<U, F> {
|
|
@@ -58,10 +67,20 @@ export class AsyncResultBuilder<T, E> {
|
|
|
58
67
|
return new AsyncResultBuilder(safeAsync(promise) as AsyncResult<U, F>);
|
|
59
68
|
}
|
|
60
69
|
|
|
61
|
-
|
|
70
|
+
/**
|
|
71
|
+
* Combines multiple Results into a single AsyncResultBuilder.
|
|
72
|
+
* Preserves exact types and positions of the input results.
|
|
73
|
+
*/
|
|
74
|
+
static all<V extends readonly AsyncResult<unknown, unknown>[]>(
|
|
75
|
+
results: [...V],
|
|
76
|
+
): AsyncResultBuilder<
|
|
77
|
+
{ [K in keyof V]: V[K] extends AsyncResult<infer Val, unknown> ? Val : never },
|
|
78
|
+
V[number] extends AsyncResult<unknown, infer ErrV> ? ErrV : never
|
|
79
|
+
> {
|
|
62
80
|
return new AsyncResultBuilder(
|
|
63
81
|
Promise.all(results).then((resolved) => {
|
|
64
|
-
|
|
82
|
+
// biome-ignore lint/suspicious/noExplicitAny: internal storage requires any
|
|
83
|
+
const values: any[] = [];
|
|
65
84
|
for (const res of resolved) {
|
|
66
85
|
if (!res.ok) {
|
|
67
86
|
return Err(res.error);
|
|
@@ -69,7 +88,8 @@ export class AsyncResultBuilder<T, E> {
|
|
|
69
88
|
values.push(res.value);
|
|
70
89
|
}
|
|
71
90
|
return Ok(values);
|
|
72
|
-
|
|
91
|
+
// biome-ignore lint/suspicious/noExplicitAny: complex tuple cast
|
|
92
|
+
}) as any,
|
|
73
93
|
);
|
|
74
94
|
}
|
|
75
95
|
|
|
@@ -160,17 +180,22 @@ export class AsyncResultBuilder<T, E> {
|
|
|
160
180
|
return new AsyncResultBuilder<T, E>(this._promise, [...this._ops, op]);
|
|
161
181
|
}
|
|
162
182
|
|
|
163
|
-
|
|
183
|
+
/**
|
|
184
|
+
* Attaches context to the error if it exists.
|
|
185
|
+
*
|
|
186
|
+
* @template C The type of the metadata.
|
|
187
|
+
*/
|
|
188
|
+
context<C extends Record<string, unknown>>(
|
|
164
189
|
message: string,
|
|
165
190
|
help?: string,
|
|
166
|
-
meta?:
|
|
167
|
-
): AsyncResultBuilder<T, Report
|
|
191
|
+
meta?: C,
|
|
192
|
+
): AsyncResultBuilder<T, Report<C>> {
|
|
168
193
|
const op: Op = (r: Result<unknown, unknown>) =>
|
|
169
194
|
contextOp(r as Result<T, E>, message, help, meta);
|
|
170
|
-
return new AsyncResultBuilder<T, Report
|
|
171
|
-
|
|
172
|
-
op,
|
|
173
|
-
|
|
195
|
+
return new AsyncResultBuilder<T, Report<C>>(
|
|
196
|
+
this._promise as unknown as AsyncResult<T, Report<C>>,
|
|
197
|
+
[...this._ops, op],
|
|
198
|
+
);
|
|
174
199
|
}
|
|
175
200
|
|
|
176
201
|
match<R>(handlers: { ok: (value: T) => R; err: (error: E) => R }): Promise<R> {
|
package/src/result-builder.ts
CHANGED
|
@@ -30,9 +30,13 @@ export class ResultBuilder<T, E> {
|
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
32
|
* Constructs a successful Result wrapped in a ResultBuilder.
|
|
33
|
+
*
|
|
34
|
+
* @template U The type of the success value.
|
|
35
|
+
* @template F The type of the error value.
|
|
36
|
+
* @param args The value to wrap. Required if U is not void.
|
|
33
37
|
*/
|
|
34
|
-
static ok<U = void, F = unknown>(
|
|
35
|
-
return new ResultBuilder({ ok: true, value:
|
|
38
|
+
static ok<U = void, F = unknown>(...args: undefined extends U ? [U?] : [U]): ResultBuilder<U, F> {
|
|
39
|
+
return new ResultBuilder({ ok: true, value: args[0] as U });
|
|
36
40
|
}
|
|
37
41
|
|
|
38
42
|
/**
|
|
@@ -47,7 +51,7 @@ export class ResultBuilder<T, E> {
|
|
|
47
51
|
*/
|
|
48
52
|
static safe<U, F = Error>(fn: () => U): ResultBuilder<U, F> {
|
|
49
53
|
try {
|
|
50
|
-
return ResultBuilder.ok(fn());
|
|
54
|
+
return ResultBuilder.ok<U, F>(fn());
|
|
51
55
|
} catch (e) {
|
|
52
56
|
return ResultBuilder.err(e as F);
|
|
53
57
|
}
|
|
@@ -55,16 +59,25 @@ export class ResultBuilder<T, E> {
|
|
|
55
59
|
|
|
56
60
|
/**
|
|
57
61
|
* Combines multiple Results into a single ResultBuilder.
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
62
|
+
* Preserves exact types and positions of the input results.
|
|
63
|
+
*/
|
|
64
|
+
static all<V extends readonly Result<unknown, unknown>[]>(
|
|
65
|
+
results: [...V],
|
|
66
|
+
): ResultBuilder<
|
|
67
|
+
{ [K in keyof V]: V[K] extends Result<infer Val, unknown> ? Val : never },
|
|
68
|
+
V[number] extends Result<unknown, infer ErrV> ? ErrV : never
|
|
69
|
+
> {
|
|
70
|
+
// biome-ignore lint/suspicious/noExplicitAny: internal storage requires any
|
|
71
|
+
const values: any[] = [];
|
|
61
72
|
for (const res of results) {
|
|
62
73
|
if (!res.ok) {
|
|
63
|
-
|
|
74
|
+
// biome-ignore lint/suspicious/noExplicitAny: complex tuple cast
|
|
75
|
+
return ResultBuilder.err(res.error) as any;
|
|
64
76
|
}
|
|
65
77
|
values.push(res.value);
|
|
66
78
|
}
|
|
67
|
-
|
|
79
|
+
// biome-ignore lint/suspicious/noExplicitAny: complex tuple cast
|
|
80
|
+
return ResultBuilder.ok(values) as any;
|
|
68
81
|
}
|
|
69
82
|
|
|
70
83
|
/**
|
package/src/utils/all.ts
CHANGED
|
@@ -7,24 +7,30 @@ import type { Result } from "../types";
|
|
|
7
7
|
* If all Results are Ok, returns an Ok with an array of all values.
|
|
8
8
|
* If any Result is an Err, returns the first Err encountered.
|
|
9
9
|
*
|
|
10
|
-
* @template T The type of the
|
|
11
|
-
* @template E The type of the error value.
|
|
10
|
+
* @template T The type of the input Results array.
|
|
12
11
|
* @param results An array of Results to combine.
|
|
13
12
|
* @returns A Result with an array of values or the first error.
|
|
14
13
|
*
|
|
15
14
|
* @category Utilities
|
|
16
15
|
* @see any
|
|
17
16
|
* @example
|
|
18
|
-
* const res = all([Ok(1), Ok(
|
|
19
|
-
* const res = all([Ok(1), Err("fail")]); // Err("fail")
|
|
17
|
+
* const res = all([Ok(1), Ok("a")]); // Result<[number, string], any>
|
|
20
18
|
*/
|
|
21
|
-
export function all<T
|
|
22
|
-
|
|
19
|
+
export function all<T extends readonly Result<unknown, unknown>[]>(
|
|
20
|
+
results: [...T],
|
|
21
|
+
): Result<
|
|
22
|
+
{ [K in keyof T]: T[K] extends Result<infer Val, unknown> ? Val : never },
|
|
23
|
+
T[number] extends Result<unknown, infer ErrV> ? ErrV : never
|
|
24
|
+
> {
|
|
25
|
+
// biome-ignore lint/suspicious/noExplicitAny: accumulator type evolves
|
|
26
|
+
const values: any[] = [];
|
|
23
27
|
for (const res of results) {
|
|
24
28
|
if (!res.ok) {
|
|
25
|
-
|
|
29
|
+
// biome-ignore lint/suspicious/noExplicitAny: forced cast for complex tuple inference
|
|
30
|
+
return Err(res.error) as any;
|
|
26
31
|
}
|
|
27
32
|
values.push(res.value);
|
|
28
33
|
}
|
|
29
|
-
|
|
34
|
+
// biome-ignore lint/suspicious/noExplicitAny: forced cast for complex tuple inference
|
|
35
|
+
return Ok(values) as any;
|
|
30
36
|
}
|
package/src/utils/pipe.ts
CHANGED
|
@@ -10,16 +10,42 @@
|
|
|
10
10
|
* (r) => unwrapOr(r, 0),
|
|
11
11
|
* );
|
|
12
12
|
*/
|
|
13
|
-
|
|
14
|
-
export async function pipe<T,
|
|
13
|
+
export async function pipe<T>(value: T | Promise<T>): Promise<Awaited<T>>;
|
|
14
|
+
export async function pipe<T, F1>(value: T | Promise<T>, f1: (arg: T) => F1): Promise<Awaited<F1>>;
|
|
15
|
+
export async function pipe<T, F1, F2>(
|
|
15
16
|
value: T | Promise<T>,
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
): Promise<
|
|
19
|
-
|
|
17
|
+
f1: (arg: T) => F1,
|
|
18
|
+
f2: (arg: Awaited<F1>) => F2,
|
|
19
|
+
): Promise<Awaited<F2>>;
|
|
20
|
+
export async function pipe<T, F1, F2, F3>(
|
|
21
|
+
value: T | Promise<T>,
|
|
22
|
+
f1: (arg: T) => F1,
|
|
23
|
+
f2: (arg: Awaited<F1>) => F2,
|
|
24
|
+
f3: (arg: Awaited<F2>) => F3,
|
|
25
|
+
): Promise<Awaited<F3>>;
|
|
26
|
+
// biome-ignore lint/complexity/useMaxParams: pipe naturally has many parameters for overloads
|
|
27
|
+
export async function pipe<T, F1, F2, F3, F4>(
|
|
28
|
+
value: T | Promise<T>,
|
|
29
|
+
f1: (arg: T) => F1,
|
|
30
|
+
f2: (arg: Awaited<F1>) => F2,
|
|
31
|
+
f3: (arg: Awaited<F2>) => F3,
|
|
32
|
+
f4: (arg: Awaited<F3>) => F4,
|
|
33
|
+
): Promise<Awaited<F4>>;
|
|
34
|
+
// biome-ignore lint/complexity/useMaxParams: pipe naturally has many parameters for overloads
|
|
35
|
+
export async function pipe<T, F1, F2, F3, F4, F5>(
|
|
36
|
+
value: T | Promise<T>,
|
|
37
|
+
f1: (arg: T) => F1,
|
|
38
|
+
f2: (arg: Awaited<F1>) => F2,
|
|
39
|
+
f3: (arg: Awaited<F2>) => F3,
|
|
40
|
+
f4: (arg: Awaited<F3>) => F4,
|
|
41
|
+
f5: (arg: Awaited<F4>) => F5,
|
|
42
|
+
): Promise<Awaited<F5>>;
|
|
43
|
+
// biome-ignore lint/suspicious/noExplicitAny: internal implementation requires any
|
|
44
|
+
export async function pipe<T, Fns extends any[]>(value: T | Promise<T>, ...fns: Fns): Promise<any> {
|
|
45
|
+
// biome-ignore lint/suspicious/noExplicitAny: internal accumulator requires any
|
|
46
|
+
let acc: any = await value;
|
|
20
47
|
for (const fn of fns) {
|
|
21
|
-
|
|
22
|
-
acc = (fn as any)(acc);
|
|
48
|
+
acc = await fn(acc);
|
|
23
49
|
}
|
|
24
|
-
return acc
|
|
50
|
+
return acc;
|
|
25
51
|
}
|