@nlozgachev/pipelined 0.39.0 → 0.41.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
@@ -33,7 +33,7 @@ not anything is there:
33
33
  ```ts
34
34
  import { pipe } from "@nlozgachev/pipelined/composition";
35
35
  import { Maybe } from "@nlozgachev/pipelined/core";
36
- import { Num, Str } from "@nlozgachev/pipelined/utils";
36
+ import { Num, Str } from "@nlozgachev/pipelined/data";
37
37
 
38
38
  const parseDiscount = (raw: string): string =>
39
39
  pipe(
@@ -120,7 +120,7 @@ steps to compose naturally with the core types:
120
120
  ```ts
121
121
  import { pipe } from "@nlozgachev/pipelined/composition";
122
122
  import { Maybe } from "@nlozgachev/pipelined/core";
123
- import { Arr, Num, Rec, Str } from "@nlozgachev/pipelined/utils";
123
+ import { Arr, Num, Rec, Str } from "@nlozgachev/pipelined/data";
124
124
 
125
125
  type RawItem = { name: string; price: string; category: string };
126
126
  type Item = { name: string; price: number; category: string };
@@ -0,0 +1,127 @@
1
+ declare const _brand: unique symbol;
2
+ /**
3
+ * Brand<K, T> creates a nominal type by tagging T with a phantom brand K.
4
+ * Prevents accidentally mixing up values that share the same underlying type.
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * type UserId = Brand<"UserId", string>;
9
+ * type ProductId = Brand<"ProductId", string>;
10
+ *
11
+ * const toUserId = Brand.wrap<"UserId", string>();
12
+ * const toProductId = Brand.wrap<"ProductId", string>();
13
+ *
14
+ * const userId: UserId = toUserId("user-123");
15
+ * const productId: ProductId = toProductId("prod-456");
16
+ *
17
+ * // Type error: ProductId is not assignable to UserId
18
+ * // const wrong: UserId = productId;
19
+ * ```
20
+ */
21
+ type Brand<K extends string, T> = T & {
22
+ readonly [_brand]: K;
23
+ };
24
+ declare namespace Brand {
25
+ /**
26
+ * Returns a constructor that wraps a value of type T in brand K.
27
+ * The resulting function performs an unchecked cast — only use when the raw
28
+ * value is known to satisfy the brand's invariants.
29
+ *
30
+ * @example
31
+ * ```ts
32
+ * type PositiveNumber = Brand<"PositiveNumber", number>;
33
+ * const toPositiveNumber = Brand.wrap<"PositiveNumber", number>();
34
+ *
35
+ * const n: PositiveNumber = toPositiveNumber(42);
36
+ * ```
37
+ */
38
+ const wrap: <K extends string, T>() => (value: T) => Brand<K, T>;
39
+ /**
40
+ * Strips the brand and returns the underlying value.
41
+ * Since Brand<K, T> extends T this is rarely needed, but can improve readability.
42
+ *
43
+ * @example
44
+ * ```ts
45
+ * const userId: UserId = toUserId("user-123");
46
+ * const raw: string = Brand.unwrap(userId); // "user-123"
47
+ * ```
48
+ */
49
+ const unwrap: <K extends string, T>(branded: Brand<K, T>) => T;
50
+ }
51
+
52
+ /**
53
+ * A branded nominal type representing a duration of time in milliseconds.
54
+ * Use Duration to ensure safe time-based operators and clear unit conversions.
55
+ *
56
+ * @example
57
+ * ```ts
58
+ * const halfSecond = Duration.milliseconds(500);
59
+ * const twoSeconds = Duration.seconds(2);
60
+ * const total = pipe(halfSecond, Duration.add(twoSeconds));
61
+ *
62
+ * Duration.toSeconds(total); // 2.5
63
+ * ```
64
+ */
65
+ type Duration = Brand<"Duration", number>;
66
+ declare namespace Duration {
67
+ /**
68
+ * Creates a Duration from milliseconds.
69
+ */
70
+ const milliseconds: (ms: number) => Duration;
71
+ /**
72
+ * Creates a Duration from seconds.
73
+ */
74
+ const seconds: (s: number) => Duration;
75
+ /**
76
+ * Creates a Duration from minutes.
77
+ */
78
+ const minutes: (m: number) => Duration;
79
+ /**
80
+ * Creates a Duration from hours.
81
+ */
82
+ const hours: (h: number) => Duration;
83
+ /**
84
+ * Creates a Duration from days.
85
+ */
86
+ const days: (d: number) => Duration;
87
+ /**
88
+ * Converts a Duration back to raw milliseconds.
89
+ */
90
+ const toMilliseconds: (d: Duration) => number;
91
+ /**
92
+ * Converts a Duration to seconds.
93
+ */
94
+ const toSeconds: (d: Duration) => number;
95
+ /**
96
+ * Converts a Duration to minutes.
97
+ */
98
+ const toMinutes: (d: Duration) => number;
99
+ /**
100
+ * Converts a Duration to hours.
101
+ */
102
+ const toHours: (d: Duration) => number;
103
+ /**
104
+ * Converts a Duration to days.
105
+ */
106
+ const toDays: (d: Duration) => number;
107
+ /**
108
+ * Adds two Durations together.
109
+ *
110
+ * @example
111
+ * ```ts
112
+ * pipe(Duration.seconds(1), Duration.add(Duration.milliseconds(500))); // 1500ms
113
+ * ```
114
+ */
115
+ const add: (other: Duration) => (self: Duration) => Duration;
116
+ /**
117
+ * Subtracts the other Duration from this one.
118
+ *
119
+ * @example
120
+ * ```ts
121
+ * pipe(Duration.seconds(1), Duration.subtract(Duration.milliseconds(500))); // 500ms
122
+ * ```
123
+ */
124
+ const subtract: (other: Duration) => (self: Duration) => Duration;
125
+ }
126
+
127
+ export { Brand as B, Duration as D };
@@ -0,0 +1,127 @@
1
+ declare const _brand: unique symbol;
2
+ /**
3
+ * Brand<K, T> creates a nominal type by tagging T with a phantom brand K.
4
+ * Prevents accidentally mixing up values that share the same underlying type.
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * type UserId = Brand<"UserId", string>;
9
+ * type ProductId = Brand<"ProductId", string>;
10
+ *
11
+ * const toUserId = Brand.wrap<"UserId", string>();
12
+ * const toProductId = Brand.wrap<"ProductId", string>();
13
+ *
14
+ * const userId: UserId = toUserId("user-123");
15
+ * const productId: ProductId = toProductId("prod-456");
16
+ *
17
+ * // Type error: ProductId is not assignable to UserId
18
+ * // const wrong: UserId = productId;
19
+ * ```
20
+ */
21
+ type Brand<K extends string, T> = T & {
22
+ readonly [_brand]: K;
23
+ };
24
+ declare namespace Brand {
25
+ /**
26
+ * Returns a constructor that wraps a value of type T in brand K.
27
+ * The resulting function performs an unchecked cast — only use when the raw
28
+ * value is known to satisfy the brand's invariants.
29
+ *
30
+ * @example
31
+ * ```ts
32
+ * type PositiveNumber = Brand<"PositiveNumber", number>;
33
+ * const toPositiveNumber = Brand.wrap<"PositiveNumber", number>();
34
+ *
35
+ * const n: PositiveNumber = toPositiveNumber(42);
36
+ * ```
37
+ */
38
+ const wrap: <K extends string, T>() => (value: T) => Brand<K, T>;
39
+ /**
40
+ * Strips the brand and returns the underlying value.
41
+ * Since Brand<K, T> extends T this is rarely needed, but can improve readability.
42
+ *
43
+ * @example
44
+ * ```ts
45
+ * const userId: UserId = toUserId("user-123");
46
+ * const raw: string = Brand.unwrap(userId); // "user-123"
47
+ * ```
48
+ */
49
+ const unwrap: <K extends string, T>(branded: Brand<K, T>) => T;
50
+ }
51
+
52
+ /**
53
+ * A branded nominal type representing a duration of time in milliseconds.
54
+ * Use Duration to ensure safe time-based operators and clear unit conversions.
55
+ *
56
+ * @example
57
+ * ```ts
58
+ * const halfSecond = Duration.milliseconds(500);
59
+ * const twoSeconds = Duration.seconds(2);
60
+ * const total = pipe(halfSecond, Duration.add(twoSeconds));
61
+ *
62
+ * Duration.toSeconds(total); // 2.5
63
+ * ```
64
+ */
65
+ type Duration = Brand<"Duration", number>;
66
+ declare namespace Duration {
67
+ /**
68
+ * Creates a Duration from milliseconds.
69
+ */
70
+ const milliseconds: (ms: number) => Duration;
71
+ /**
72
+ * Creates a Duration from seconds.
73
+ */
74
+ const seconds: (s: number) => Duration;
75
+ /**
76
+ * Creates a Duration from minutes.
77
+ */
78
+ const minutes: (m: number) => Duration;
79
+ /**
80
+ * Creates a Duration from hours.
81
+ */
82
+ const hours: (h: number) => Duration;
83
+ /**
84
+ * Creates a Duration from days.
85
+ */
86
+ const days: (d: number) => Duration;
87
+ /**
88
+ * Converts a Duration back to raw milliseconds.
89
+ */
90
+ const toMilliseconds: (d: Duration) => number;
91
+ /**
92
+ * Converts a Duration to seconds.
93
+ */
94
+ const toSeconds: (d: Duration) => number;
95
+ /**
96
+ * Converts a Duration to minutes.
97
+ */
98
+ const toMinutes: (d: Duration) => number;
99
+ /**
100
+ * Converts a Duration to hours.
101
+ */
102
+ const toHours: (d: Duration) => number;
103
+ /**
104
+ * Converts a Duration to days.
105
+ */
106
+ const toDays: (d: Duration) => number;
107
+ /**
108
+ * Adds two Durations together.
109
+ *
110
+ * @example
111
+ * ```ts
112
+ * pipe(Duration.seconds(1), Duration.add(Duration.milliseconds(500))); // 1500ms
113
+ * ```
114
+ */
115
+ const add: (other: Duration) => (self: Duration) => Duration;
116
+ /**
117
+ * Subtracts the other Duration from this one.
118
+ *
119
+ * @example
120
+ * ```ts
121
+ * pipe(Duration.seconds(1), Duration.subtract(Duration.milliseconds(500))); // 500ms
122
+ * ```
123
+ */
124
+ const subtract: (other: Duration) => (self: Duration) => Duration;
125
+ }
126
+
127
+ export { Brand as B, Duration as D };
@@ -1,4 +1,5 @@
1
- import { NonEmptyList, Duration } from './types.js';
1
+ import { D as Duration } from './Duration-BTeT9D-q.mjs';
2
+ import { NonEmptyList } from './types.mjs';
2
3
 
3
4
  declare const _deferred: unique symbol;
4
5
  /**
@@ -1093,7 +1094,7 @@ declare namespace Task {
1093
1094
  * );
1094
1095
  * ```
1095
1096
  */
1096
- const run: (signal?: AbortSignal) => <A>(task: Task<A>) => Promise<A>;
1097
+ const run: (signal?: AbortSignal) => <A>(task: Task<A>) => Deferred<A>;
1097
1098
  /**
1098
1099
  * Converts a Task value into an object containing a single property.
1099
1100
  * Initiates the pipeline accumulator record.
@@ -1,4 +1,5 @@
1
- import { NonEmptyList, Duration } from './types.mjs';
1
+ import { D as Duration } from './Duration-BTeT9D-q.js';
2
+ import { NonEmptyList } from './types.js';
2
3
 
3
4
  declare const _deferred: unique symbol;
4
5
  /**
@@ -1093,7 +1094,7 @@ declare namespace Task {
1093
1094
  * );
1094
1095
  * ```
1095
1096
  */
1096
- const run: (signal?: AbortSignal) => <A>(task: Task<A>) => Promise<A>;
1097
+ const run: (signal?: AbortSignal) => <A>(task: Task<A>) => Deferred<A>;
1097
1098
  /**
1098
1099
  * Converts a Task value into an object containing a single property.
1099
1100
  * Initiates the pipeline accumulator record.
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  Duration
3
- } from "./chunk-5AFEEFE4.mjs";
3
+ } from "./chunk-GBB6LVLI.mjs";
4
4
 
5
5
  // src/Core/Combinable.ts
6
6
  var Combinable;
@@ -1800,7 +1800,7 @@ var Task;
1800
1800
  };
1801
1801
  return { task, abort };
1802
1802
  };
1803
- Task2.run = (signal) => (task) => Deferred.toPromise(task(signal));
1803
+ Task2.run = (signal) => (task) => task(signal);
1804
1804
  Task2.bindTo = (key) => (data) => (0, Task2.map)((a) => ({ [key]: a }))(data);
1805
1805
  Task2.bind = (key, f) => (data) => (0, Task2.chain)(
1806
1806
  (a) => (0, Task2.map)((b) => ({ ...a, [key]: b }))(f(a))
@@ -1864,7 +1864,7 @@ var TaskResult3;
1864
1864
  ([of_, oa]) => Result.ap(oa)(of_)
1865
1865
  )
1866
1866
  );
1867
- TaskResult4.run = (signal) => (task) => Deferred.toPromise(task(signal));
1867
+ TaskResult4.run = (signal) => (task) => task(signal);
1868
1868
  TaskResult4.bindTo = (key) => (data) => (0, TaskResult4.map)((a) => ({ [key]: a }))(data);
1869
1869
  TaskResult4.bind = (key, f) => (data) => (0, TaskResult4.chain)(
1870
1870
  (a) => (0, TaskResult4.map)((b) => ({ ...a, [key]: b }))(f(a))
@@ -28,6 +28,6 @@ var isNonEmptyList = (list) => list.length > 0;
28
28
 
29
29
  export {
30
30
  Brand,
31
- Duration,
32
- isNonEmptyList
31
+ isNonEmptyList,
32
+ Duration
33
33
  };
@@ -1,3 +1,7 @@
1
+ import {
2
+ Duration
3
+ } from "./chunk-GBB6LVLI.mjs";
4
+
1
5
  // src/Composition/compose.ts
2
6
  function compose(f0, f1, f2, f3, f4, f5, f6, f7, f8, f9) {
3
7
  const len = arguments.length;
@@ -316,10 +320,90 @@ pipe.try = (f, onError) => (a) => {
316
320
  };
317
321
 
318
322
  // src/Composition/tap.ts
319
- var tap = (f) => (a) => {
320
- f(a);
321
- return a;
322
- };
323
+ import { inspect as nodeInspect } from "util";
324
+ function tap(f) {
325
+ return (a) => {
326
+ f(a);
327
+ return a;
328
+ };
329
+ }
330
+ ((tap2) => {
331
+ tap2.log = (options) => (a) => {
332
+ const logger = options?.logger ?? console.log;
333
+ const formatter = options?.formatter ?? ((val) => {
334
+ try {
335
+ return typeof val === "object" && val !== null ? JSON.stringify(val) : String(val);
336
+ } catch {
337
+ return String(val);
338
+ }
339
+ });
340
+ const formatted = formatter(a);
341
+ if (options?.label !== void 0) {
342
+ logger(`[${options.label}]: ${formatted}`);
343
+ } else {
344
+ logger(formatted);
345
+ }
346
+ return a;
347
+ };
348
+ tap2.inspect = (options) => (a) => {
349
+ const label = options?.label;
350
+ const depth = options?.depth ?? null;
351
+ const colors = options?.colors ?? true;
352
+ let formatted;
353
+ if (typeof nodeInspect === "function") {
354
+ formatted = nodeInspect(a, { depth, colors });
355
+ } else {
356
+ try {
357
+ formatted = JSON.stringify(a, null, 2);
358
+ } catch {
359
+ formatted = String(a);
360
+ }
361
+ }
362
+ if (label !== void 0) {
363
+ console.log(`[${label}]: ${formatted}`);
364
+ } else {
365
+ console.log(formatted);
366
+ }
367
+ return a;
368
+ };
369
+ tap2.async = (fn, options) => (a) => {
370
+ const onError = options?.onError ?? console.error;
371
+ fn(a).catch((err) => {
372
+ onError(err);
373
+ });
374
+ return a;
375
+ };
376
+ tap2.time = (fn, config) => (a) => {
377
+ const start = performance.now();
378
+ const triggerFinish = (duration) => {
379
+ if (config.label !== void 0) {
380
+ console.log(`[${config.label}]: ${Duration.toMilliseconds(duration)}ms`);
381
+ } else if (config.onFinish) {
382
+ config.onFinish(duration);
383
+ }
384
+ };
385
+ try {
386
+ const res = fn(a);
387
+ if (res instanceof Promise) {
388
+ res.then(() => {
389
+ const duration = Duration.milliseconds(performance.now() - start);
390
+ triggerFinish(duration);
391
+ }, () => {
392
+ const duration = Duration.milliseconds(performance.now() - start);
393
+ triggerFinish(duration);
394
+ });
395
+ } else {
396
+ const duration = Duration.milliseconds(performance.now() - start);
397
+ triggerFinish(duration);
398
+ }
399
+ } catch (err) {
400
+ const duration = Duration.milliseconds(performance.now() - start);
401
+ triggerFinish(duration);
402
+ throw err;
403
+ }
404
+ return a;
405
+ };
406
+ })(tap || (tap = {}));
323
407
 
324
408
  // src/Composition/uncurry.ts
325
409
  function uncurry(f) {
@@ -3,12 +3,12 @@ import {
3
3
  Maybe,
4
4
  Result,
5
5
  Task
6
- } from "./chunk-4R4XUP4M.mjs";
6
+ } from "./chunk-COGQKPIP.mjs";
7
7
  import {
8
8
  isNonEmptyList
9
- } from "./chunk-5AFEEFE4.mjs";
9
+ } from "./chunk-GBB6LVLI.mjs";
10
10
 
11
- // src/Utils/Arr.ts
11
+ // src/Data/Arr.ts
12
12
  var Arr;
13
13
  ((Arr2) => {
14
14
  Arr2.head = (data) => data.length > 0 ? Maybe.some(data[0]) : Maybe.none();
@@ -340,7 +340,7 @@ var Arr;
340
340
  };
341
341
  })(Arr || (Arr = {}));
342
342
 
343
- // src/Utils/Dict.ts
343
+ // src/Data/Dict.ts
344
344
  var Dict;
345
345
  ((Dict2) => {
346
346
  Dict2.empty = () => new globalThis.Map();
@@ -478,7 +478,7 @@ var Dict;
478
478
  Dict2.toRecord = (m) => Object.fromEntries(m);
479
479
  })(Dict || (Dict = {}));
480
480
 
481
- // src/Utils/Num.ts
481
+ // src/Data/Num.ts
482
482
  var Num;
483
483
  ((Num2) => {
484
484
  Num2.range = (from, to, step = 1) => {
@@ -494,6 +494,7 @@ var Num;
494
494
  };
495
495
  Num2.clamp = (min2, max2) => (n) => Math.min(Math.max(n, min2), max2);
496
496
  Num2.between = (min2, max2) => (n) => n >= min2 && n <= max2;
497
+ Num2.inRange = (start, end) => (n) => n >= start && n < end;
497
498
  Num2.parse = (s) => {
498
499
  if (s.trim() === "") {
499
500
  return Maybe.none();
@@ -545,7 +546,7 @@ var Num;
545
546
  };
546
547
  })(Num || (Num = {}));
547
548
 
548
- // src/Utils/Rec.ts
549
+ // src/Data/Rec.ts
549
550
  var Rec;
550
551
  ((Rec2) => {
551
552
  Rec2.map = (f) => (data) => {
@@ -673,7 +674,7 @@ var Rec;
673
674
  };
674
675
  })(Rec || (Rec = {}));
675
676
 
676
- // src/Utils/Str.ts
677
+ // src/Data/Str.ts
677
678
  var Str;
678
679
  ((Str2) => {
679
680
  Str2.split = (separator) => (s) => s.split(separator);
@@ -733,7 +734,7 @@ var Str;
733
734
  };
734
735
  })(Str || (Str = {}));
735
736
 
736
- // src/Utils/Uniq.ts
737
+ // src/Data/Uniq.ts
737
738
  var Uniq;
738
739
  ((Uniq2) => {
739
740
  Uniq2.empty = () => new globalThis.Set();
@@ -1,3 +1,5 @@
1
+ import { D as Duration } from './Duration-BTeT9D-q.mjs';
2
+
1
3
  /**
2
4
  * Composes functions from right to left, returning a new function.
3
5
  * This is the traditional mathematical function composition: (f . g)(x) = f(g(x))
@@ -617,7 +619,132 @@ interface pipe {
617
619
  *
618
620
  * @see {@link Maybe.tap} for Maybe-specific tap that only runs on Some
619
621
  */
620
- declare const tap: <A>(f: (a: A) => void) => (a: A) => A;
622
+ declare function tap<A>(f: (a: A) => void): (a: A) => A;
623
+ declare namespace tap {
624
+ /**
625
+ * Configuration options for {@link tap.log}.
626
+ */
627
+ type LogOptions<A> = {
628
+ /**
629
+ * An optional label prefix for the log output (e.g., `[label]: value`).
630
+ */
631
+ readonly label?: string;
632
+ /**
633
+ * The logging destination function. Defaults to `console.log`.
634
+ */
635
+ readonly logger?: (message: string) => void;
636
+ /**
637
+ * A custom formatter function to convert the piped value to a string.
638
+ * Defaults to `JSON.stringify` for objects and `String(value)` for primitives.
639
+ */
640
+ readonly formatter?: (value: A) => string;
641
+ };
642
+ /**
643
+ * Configuration options for {@link tap.inspect}.
644
+ */
645
+ type InspectOptions = {
646
+ /**
647
+ * An optional label prefix for the inspect output (e.g., `[label]: value`).
648
+ */
649
+ readonly label?: string;
650
+ /**
651
+ * The maximum depth to recurse when formatting the object.
652
+ * Defaults to `null` (infinite depth).
653
+ */
654
+ readonly depth?: number;
655
+ /**
656
+ * Whether to colorize the output using ANSI color codes.
657
+ * Defaults to `true`.
658
+ */
659
+ readonly colors?: boolean;
660
+ };
661
+ /**
662
+ * Configuration options for {@link tap.async}.
663
+ */
664
+ type AsyncOptions = {
665
+ /**
666
+ * A callback to handle exceptions thrown by the async side-effect.
667
+ * Defaults to logging via `console.error`.
668
+ */
669
+ readonly onError?: (error: unknown) => void;
670
+ };
671
+ /**
672
+ * Configuration options for {@link tap.time}, enforcing mutual exclusivity
673
+ * between console logging and custom handlers.
674
+ */
675
+ type TimeConfig = {
676
+ label: string;
677
+ onFinish?: never;
678
+ } | {
679
+ onFinish: (duration: Duration) => void;
680
+ label?: never;
681
+ };
682
+ /**
683
+ * Logs the piped value to the console or a custom logger, returning the value unchanged.
684
+ *
685
+ * @example
686
+ * ```ts
687
+ * pipe(
688
+ * 42,
689
+ * tap.log(), // logs: 42
690
+ * tap.log({ label: "Count" }) // logs: [Count]: 42
691
+ * );
692
+ * ```
693
+ */
694
+ const log: <A>(options?: LogOptions<A>) => (a: A) => A;
695
+ /**
696
+ * Performs a deep structured inspect formatting on the piped value, returning it unchanged.
697
+ * In Node.js environments, this utilizes Node's `node:util` `inspect` utility.
698
+ *
699
+ * @example
700
+ * ```ts
701
+ * pipe(
702
+ * { user: { name: "Alice", details: { age: 30 } } },
703
+ * tap.inspect({ label: "User Object", depth: 2 })
704
+ * );
705
+ * ```
706
+ */
707
+ const inspect: <A>(options?: InspectOptions) => (a: A) => A;
708
+ /**
709
+ * Triggers a fire-and-forget asynchronous side effect in the background,
710
+ * returning the piped value immediately and synchronously.
711
+ * Any errors thrown by the async function are caught and forwarded to `onError`.
712
+ *
713
+ * @example
714
+ * ```ts
715
+ * pipe(
716
+ * user,
717
+ * tap.async(async (u) => {
718
+ * await saveToDatabase(u);
719
+ * }, { onError: (err) => logError(err) })
720
+ * );
721
+ * ```
722
+ */
723
+ const async: <A>(fn: (a: A) => Promise<unknown>, options?: AsyncOptions) => (a: A) => A;
724
+ /**
725
+ * Runs a function and measures its execution duration, returning the value unchanged.
726
+ * Supports both synchronous and asynchronous functions. If the timed function returns
727
+ * a Promise, duration measurement resolves asynchronously upon resolution/rejection.
728
+ *
729
+ * @example
730
+ * ```ts
731
+ * // Time a synchronous computation
732
+ * pipe(
733
+ * data,
734
+ * tap.time(processData, { label: "sync-process" })
735
+ * );
736
+ *
737
+ * // Time an asynchronous fetch with custom metrics callback
738
+ * pipe(
739
+ * data,
740
+ * tap.time(fetchData, {
741
+ * onFinish: (dur) => metrics.histogram("api.time", Duration.toMilliseconds(dur))
742
+ * })
743
+ * );
744
+ * ```
745
+ */
746
+ const time: <A>(fn: (a: A) => unknown, config: TimeConfig) => (a: A) => A;
747
+ }
621
748
 
622
749
  /**
623
750
  * Converts a curried function into a multi-argument function.