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 CHANGED
@@ -1,5 +1,10 @@
1
1
  # ripthrow
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/ripthrow.svg?style=flat-square)](https://www.npmjs.com/package/ripthrow)
4
+ [![CI status](https://img.shields.io/github/actions/workflow/status/MechanicalLabs/ripthrow/ci.yml?branch=main&style=flat-square)](https://github.com/MechanicalLabs/ripthrow/actions/workflows/ci.yml)
5
+ ![Bundle Size](https://img.shields.io/badge/bundle_size-~5.8_KB-blue?style=flat-square)
6
+ [![License](https://img.shields.io/github/license/MechanicalLabs/ripthrow?style=flat-square)](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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ripthrow",
3
3
  "module": "src/index.ts",
4
- "version": "2.1.1",
4
+ "version": "2.2.0",
5
5
  "description": "Zero-overhead, type-safe error handling for TypeScript.",
6
6
  "keywords": [
7
7
  "result",
@@ -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 value The value to wrap in an Ok result.
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>(value?: T): Result<T, E> {
18
- return { ok: true, value: value as T };
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
  }
@@ -21,7 +21,7 @@ import { Ok } from "./ok";
21
21
  export async function safeAsync<T, E = Error>(promise: Promise<T>): Promise<Result<T, E>> {
22
22
  try {
23
23
  const data = await promise;
24
- return Ok(data);
24
+ return Ok<T, E>(data as T);
25
25
  } catch (e) {
26
26
  return Err(e as E);
27
27
  }
@@ -19,7 +19,7 @@ import { Ok } from "./ok";
19
19
  */
20
20
  export function safe<T, E = Error>(fn: () => T): Result<T, E> {
21
21
  try {
22
- return Ok(fn());
22
+ return Ok<T, E>(fn());
23
23
  } catch (e) {
24
24
  return Err(e as E);
25
25
  }
@@ -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?: Record<string, unknown>,
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: Record<string, unknown> | undefined = keys.length > 0 ? merged : undefined;
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,
@@ -21,7 +21,7 @@ import type { Result } from "../types";
21
21
  */
22
22
  export function map<T, E, R>(result: Result<T, E>, fn: (value: T) => R): Result<R, E> {
23
23
  if (result.ok) {
24
- return Ok(fn(result.value));
24
+ return Ok<R, E>(fn(result.value));
25
25
  }
26
26
 
27
27
  return result;
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> extends Error {
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?: Record<string, unknown>;
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
- type TypedKinds<E> = E extends TypedError<unknown[], infer K> ? K : never;
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<Parameters<T[K]["message"]>, K>;
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<Parameters<T[K]["message"]>, K>;
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
- const result: Record<string, ErrFactory<unknown[], string>> = {};
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
- export function createError<A extends unknown[], N extends string>(
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?: Record<string, unknown>,
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: Record<string, unknown> | undefined;
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
- on<A extends unknown[], N extends string, HandlerResult>(
102
- def: ErrFactory<A, N>,
103
- handler: (err: TypedError<A, N>) => HandlerResult,
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
- export interface ErrFactory<A extends unknown[], N extends string> {
160
- (...args: A): TypedError<A, N>;
161
- readonly [$class]: new (...args: A) => TypedError<A, N>;
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?: Record<string, unknown> | undefined;
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?: Record<string, unknown> | undefined;
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(err: unknown, message?: string, options: ReportOptions = {}): Report {
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
- static ok<U = void, F = unknown>(value?: U): AsyncResultBuilder<U, F> {
50
- return new AsyncResultBuilder(Promise.resolve(Ok(value) as Result<U, F>));
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
- static all<U, F>(results: AsyncResult<U, F>[]): AsyncResultBuilder<U[], F> {
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
- const values: U[] = [];
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
- context(
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?: Record<string, unknown>,
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>(this._promise as unknown as AsyncResult<T, Report>, [
171
- ...this._ops,
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> {
@@ -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>(value?: U): ResultBuilder<U, F> {
35
- return new ResultBuilder({ ok: true, value: value as U });
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
- static all<U, F>(results: Result<U, F>[]): ResultBuilder<U[], F> {
60
- const values: U[] = [];
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
- return ResultBuilder.err(res.error);
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
- return ResultBuilder.ok(values);
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 success values.
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(2)]); // Ok([1, 2])
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, E>(results: Result<T, E>[]): Result<T[], E> {
22
- const values: T[] = [];
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
- return Err(res.error);
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
- return Ok(values);
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
- // biome-ignore lint/suspicious/noExplicitAny: variadic pipe needs flexibility
14
- export async function pipe<T, Fns extends Array<(arg: any) => any>>(
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
- ...fns: Fns
17
- // biome-ignore lint/suspicious/noExplicitAny: conditional type inference
18
- ): Promise<Fns extends [...any[], infer Last] ? (Last extends (arg: any) => infer R ? R : T) : T> {
19
- let acc: unknown = await value;
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
- // biome-ignore lint/suspicious/noExplicitAny: variadic pipe
22
- acc = (fn as any)(acc);
48
+ acc = await fn(acc);
23
49
  }
24
- return acc as never;
50
+ return acc;
25
51
  }