@nlozgachev/pipelined 0.47.0 → 0.49.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
@@ -18,11 +18,12 @@ In mainstream TypeScript, code is often burdened by implicit control flow: unche
18
18
  manual null propagation, and unhandled asynchronous failures. `pipelined` turns these complex
19
19
  runtime states into simple, transparent data structures that compose. By representing optionality as
20
20
  `Maybe`, failures as `Result`, lazy asynchronous pipelines as `Task.Result`, and repeated stateful
21
- interactions as `Op`, the library helps disentangle business logic from control mechanics.
21
+ interactions as `Op` and `Stream`, the library helps disentangle business logic from control
22
+ mechanics.
22
23
 
23
24
  To support these patterns without introducing bloat, the library is designed to be lightweight,
24
- zero-dependency, and fully tree-shakeable. The core module (`/core`) is under 14 KB gzipped, and the
25
- entire toolkit is under 21 KB gzipped, making it equally suitable for client and server
25
+ zero-dependency, and fully tree-shakeable. The core module (`/core`) is under 16 KB gzipped, and the
26
+ entire toolkit is under 25 KB gzipped, making it equally suitable for client and server
26
27
  environments.
27
28
 
28
29
  ## Documentation
@@ -303,6 +304,57 @@ The system supports a variety of built-in strategies — `restartable`, `exclusi
303
304
  `throttled`, `queue`, `buffered`, `concurrent`, `keyed`, and `once` — making the integration of
304
305
  complex async scenarios highly predictable.
305
306
 
307
+ ## Example: typed event streaming and sequence reduction
308
+
309
+ Decoupling event producers from stateful event consumers often leads to untyped event emitters or
310
+ complex ad-hoc state machines. `Stream` models event pipelines with typed message schemas, sequence
311
+ pattern matching, and state reduction:
312
+
313
+ ```ts
314
+ import { Stream } from "@nlozgachev/pipelined/core";
315
+
316
+ type UserFlowMessages = {
317
+ sessionStarted: { sessionId: string };
318
+ stepCompleted: { stepName: string };
319
+ flowFinished: { totalTimeMs: number };
320
+ };
321
+
322
+ const flowStream = Stream.make<UserFlowMessages>();
323
+
324
+ // Match sequence: sessionStarted -> stepCompleted -> flowFinished
325
+ const sub = Stream.listen(
326
+ flowStream,
327
+ ["sessionStarted", "stepCompleted", "flowFinished"],
328
+ { ordered: true },
329
+ ).reduce(
330
+ (msg, state) => {
331
+ if (msg.kind === "flowFinished") {
332
+ return { completedFlows: state.completedFlows + 1 };
333
+ }
334
+ return state;
335
+ },
336
+ { completedFlows: 0 },
337
+ );
338
+
339
+ // Emit typed messages to the stream
340
+ Stream.emit(flowStream, {
341
+ kind: "sessionStarted",
342
+ value: { sessionId: "sess-101" },
343
+ });
344
+
345
+ Stream.emit(flowStream, {
346
+ kind: "stepCompleted",
347
+ value: { stepName: "onboarding" },
348
+ });
349
+
350
+ Stream.emit(flowStream, {
351
+ kind: "flowFinished",
352
+ value: { totalTimeMs: 4200 },
353
+ });
354
+
355
+ sub.getState(); // { completedFlows: 1 }
356
+ ```
357
+
306
358
  ## What is included
307
359
 
308
360
  The library covers the full spectrum of state and control flow scenarios encountered in production
@@ -322,8 +374,9 @@ provides a strongly-typed, immutable two-element pair.
322
374
  handled by `Task.Result`, `Task.Maybe`, and `Task.Validation`. For managing stateful, recurring
323
375
  asynchronous operations with complex scheduling, `Op` implements named concurrency strategies such
324
376
  as `restartable`, `exclusive`, `debounced`, `throttled`, and `queue`, handling retries, timeouts,
325
- and signal propagation automatically. `Deferred` represents a lightweight, infallible asynchronous
326
- value that is guaranteed to always resolve without rejection.
377
+ and signal propagation automatically. `Stream` provides typed event streaming, sequence pattern
378
+ matching, state reduction, and structural forwarding across channels. `Deferred` represents a
379
+ lightweight, infallible asynchronous value that is guaranteed to always resolve without rejection.
327
380
 
328
381
  ### Optics and environment state
329
382
 
@@ -0,0 +1,181 @@
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
+ * type UserId = Brand<"UserId", string>;
46
+ * const toUserId = Brand.wrap<"UserId", string>();
47
+ * const userId: UserId = toUserId("user-123");
48
+ * const raw: string = Brand.unwrap(userId); // "user-123"
49
+ * ```
50
+ */
51
+ const unwrap: <K extends string, T>(branded: Brand<K, T>) => T;
52
+ }
53
+
54
+ /**
55
+ * A branded nominal type representing a duration of time in milliseconds.
56
+ * Use Duration to ensure safe time-based operators and clear unit conversions.
57
+ *
58
+ * @example
59
+ * ```ts
60
+ * const halfSecond = Duration.milliseconds(500);
61
+ * const twoSeconds = Duration.seconds(2);
62
+ * const total = pipe(halfSecond, Duration.add(twoSeconds));
63
+ *
64
+ * Duration.to.seconds(total); // 2.5
65
+ * ```
66
+ */
67
+ type Duration = Brand<"Duration", number>;
68
+ declare namespace Duration {
69
+ /**
70
+ * Creates a Duration from milliseconds.
71
+ *
72
+ * @example
73
+ * ```ts
74
+ * Duration.milliseconds(500); // 500ms Duration
75
+ * ```
76
+ */
77
+ const milliseconds: (ms: number) => Duration;
78
+ /**
79
+ * Creates a Duration from seconds.
80
+ *
81
+ * @example
82
+ * ```ts
83
+ * Duration.seconds(2); // 2000ms Duration
84
+ * ```
85
+ */
86
+ const seconds: (s: number) => Duration;
87
+ /**
88
+ * Creates a Duration from minutes.
89
+ *
90
+ * @example
91
+ * ```ts
92
+ * Duration.minutes(5); // 300000ms Duration
93
+ * ```
94
+ */
95
+ const minutes: (m: number) => Duration;
96
+ /**
97
+ * Creates a Duration from hours.
98
+ *
99
+ * @example
100
+ * ```ts
101
+ * Duration.hours(1); // 3600000ms Duration
102
+ * ```
103
+ */
104
+ const hours: (h: number) => Duration;
105
+ /**
106
+ * Creates a Duration from days.
107
+ *
108
+ * @example
109
+ * ```ts
110
+ * Duration.days(1); // 86400000ms Duration
111
+ * ```
112
+ */
113
+ const days: (d: number) => Duration;
114
+ namespace to {
115
+ /**
116
+ * Converts a Duration back to raw milliseconds.
117
+ *
118
+ * @example
119
+ * ```ts
120
+ * Duration.to.milliseconds(Duration.seconds(2)); // 2000
121
+ * ```
122
+ */
123
+ const milliseconds: (d: Duration) => number;
124
+ /**
125
+ * Converts a Duration to seconds.
126
+ *
127
+ * @example
128
+ * ```ts
129
+ * Duration.to.seconds(Duration.milliseconds(2500)); // 2.5
130
+ * ```
131
+ */
132
+ const seconds: (d: Duration) => number;
133
+ /**
134
+ * Converts a Duration to minutes.
135
+ *
136
+ * @example
137
+ * ```ts
138
+ * Duration.to.minutes(Duration.seconds(120)); // 2
139
+ * ```
140
+ */
141
+ const minutes: (d: Duration) => number;
142
+ /**
143
+ * Converts a Duration to hours.
144
+ *
145
+ * @example
146
+ * ```ts
147
+ * Duration.to.hours(Duration.minutes(90)); // 1.5
148
+ * ```
149
+ */
150
+ const hours: (d: Duration) => number;
151
+ /**
152
+ * Converts a Duration to days.
153
+ *
154
+ * @example
155
+ * ```ts
156
+ * Duration.to.days(Duration.hours(36)); // 1.5
157
+ * ```
158
+ */
159
+ const days: (d: Duration) => number;
160
+ }
161
+ /**
162
+ * Adds two Durations together.
163
+ *
164
+ * @example
165
+ * ```ts
166
+ * pipe(Duration.seconds(1), Duration.add(Duration.milliseconds(500))); // 1500ms
167
+ * ```
168
+ */
169
+ const add: (other: Duration) => (self: Duration) => Duration;
170
+ /**
171
+ * Subtracts the other Duration from this one.
172
+ *
173
+ * @example
174
+ * ```ts
175
+ * pipe(Duration.seconds(1), Duration.subtract(Duration.milliseconds(500))); // 500ms
176
+ * ```
177
+ */
178
+ const subtract: (other: Duration) => (self: Duration) => Duration;
179
+ }
180
+
181
+ export { Brand as B, Duration as D };
@@ -0,0 +1,181 @@
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
+ * type UserId = Brand<"UserId", string>;
46
+ * const toUserId = Brand.wrap<"UserId", string>();
47
+ * const userId: UserId = toUserId("user-123");
48
+ * const raw: string = Brand.unwrap(userId); // "user-123"
49
+ * ```
50
+ */
51
+ const unwrap: <K extends string, T>(branded: Brand<K, T>) => T;
52
+ }
53
+
54
+ /**
55
+ * A branded nominal type representing a duration of time in milliseconds.
56
+ * Use Duration to ensure safe time-based operators and clear unit conversions.
57
+ *
58
+ * @example
59
+ * ```ts
60
+ * const halfSecond = Duration.milliseconds(500);
61
+ * const twoSeconds = Duration.seconds(2);
62
+ * const total = pipe(halfSecond, Duration.add(twoSeconds));
63
+ *
64
+ * Duration.to.seconds(total); // 2.5
65
+ * ```
66
+ */
67
+ type Duration = Brand<"Duration", number>;
68
+ declare namespace Duration {
69
+ /**
70
+ * Creates a Duration from milliseconds.
71
+ *
72
+ * @example
73
+ * ```ts
74
+ * Duration.milliseconds(500); // 500ms Duration
75
+ * ```
76
+ */
77
+ const milliseconds: (ms: number) => Duration;
78
+ /**
79
+ * Creates a Duration from seconds.
80
+ *
81
+ * @example
82
+ * ```ts
83
+ * Duration.seconds(2); // 2000ms Duration
84
+ * ```
85
+ */
86
+ const seconds: (s: number) => Duration;
87
+ /**
88
+ * Creates a Duration from minutes.
89
+ *
90
+ * @example
91
+ * ```ts
92
+ * Duration.minutes(5); // 300000ms Duration
93
+ * ```
94
+ */
95
+ const minutes: (m: number) => Duration;
96
+ /**
97
+ * Creates a Duration from hours.
98
+ *
99
+ * @example
100
+ * ```ts
101
+ * Duration.hours(1); // 3600000ms Duration
102
+ * ```
103
+ */
104
+ const hours: (h: number) => Duration;
105
+ /**
106
+ * Creates a Duration from days.
107
+ *
108
+ * @example
109
+ * ```ts
110
+ * Duration.days(1); // 86400000ms Duration
111
+ * ```
112
+ */
113
+ const days: (d: number) => Duration;
114
+ namespace to {
115
+ /**
116
+ * Converts a Duration back to raw milliseconds.
117
+ *
118
+ * @example
119
+ * ```ts
120
+ * Duration.to.milliseconds(Duration.seconds(2)); // 2000
121
+ * ```
122
+ */
123
+ const milliseconds: (d: Duration) => number;
124
+ /**
125
+ * Converts a Duration to seconds.
126
+ *
127
+ * @example
128
+ * ```ts
129
+ * Duration.to.seconds(Duration.milliseconds(2500)); // 2.5
130
+ * ```
131
+ */
132
+ const seconds: (d: Duration) => number;
133
+ /**
134
+ * Converts a Duration to minutes.
135
+ *
136
+ * @example
137
+ * ```ts
138
+ * Duration.to.minutes(Duration.seconds(120)); // 2
139
+ * ```
140
+ */
141
+ const minutes: (d: Duration) => number;
142
+ /**
143
+ * Converts a Duration to hours.
144
+ *
145
+ * @example
146
+ * ```ts
147
+ * Duration.to.hours(Duration.minutes(90)); // 1.5
148
+ * ```
149
+ */
150
+ const hours: (d: Duration) => number;
151
+ /**
152
+ * Converts a Duration to days.
153
+ *
154
+ * @example
155
+ * ```ts
156
+ * Duration.to.days(Duration.hours(36)); // 1.5
157
+ * ```
158
+ */
159
+ const days: (d: Duration) => number;
160
+ }
161
+ /**
162
+ * Adds two Durations together.
163
+ *
164
+ * @example
165
+ * ```ts
166
+ * pipe(Duration.seconds(1), Duration.add(Duration.milliseconds(500))); // 1500ms
167
+ * ```
168
+ */
169
+ const add: (other: Duration) => (self: Duration) => Duration;
170
+ /**
171
+ * Subtracts the other Duration from this one.
172
+ *
173
+ * @example
174
+ * ```ts
175
+ * pipe(Duration.seconds(1), Duration.subtract(Duration.milliseconds(500))); // 500ms
176
+ * ```
177
+ */
178
+ const subtract: (other: Duration) => (self: Duration) => Duration;
179
+ }
180
+
181
+ export { Brand as B, Duration as D };
@@ -1,4 +1,4 @@
1
- import { Duration } from './types.js';
1
+ import { D as Duration } from './Duration-B8joKzro.mjs';
2
2
 
3
3
  declare const _deferred: unique symbol;
4
4
  /**
@@ -54,6 +54,25 @@ declare namespace Deferred {
54
54
  */
55
55
  const Promise: <A>(d: Deferred<A>) => globalThis.Promise<A>;
56
56
  }
57
+ /**
58
+ * Combines an array or tuple of `Deferred` values into a single `Deferred` of a tuple.
59
+ * Resolves when all input `Deferred`s resolve.
60
+ *
61
+ * @example
62
+ * ```ts
63
+ * const [a, b] = await Deferred.all([d1, d2]);
64
+ * ```
65
+ */
66
+ const all: <T extends readonly Deferred<unknown>[]>(deferreds: T) => Deferred<{ [K in keyof T]: T[K] extends Deferred<infer A> ? A : never; }>;
67
+ /**
68
+ * Races multiple `Deferred` values and resolves with the first one to settle.
69
+ *
70
+ * @example
71
+ * ```ts
72
+ * const winner = await Deferred.race([d1, d2]);
73
+ * ```
74
+ */
75
+ const race: <A>(deferreds: ReadonlyArray<Deferred<A>>) => Deferred<A>;
57
76
  }
58
77
 
59
78
  /**
@@ -1,4 +1,4 @@
1
- import { Duration } from './types.mjs';
1
+ import { D as Duration } from './Duration-B8joKzro.js';
2
2
 
3
3
  declare const _deferred: unique symbol;
4
4
  /**
@@ -54,6 +54,25 @@ declare namespace Deferred {
54
54
  */
55
55
  const Promise: <A>(d: Deferred<A>) => globalThis.Promise<A>;
56
56
  }
57
+ /**
58
+ * Combines an array or tuple of `Deferred` values into a single `Deferred` of a tuple.
59
+ * Resolves when all input `Deferred`s resolve.
60
+ *
61
+ * @example
62
+ * ```ts
63
+ * const [a, b] = await Deferred.all([d1, d2]);
64
+ * ```
65
+ */
66
+ const all: <T extends readonly Deferred<unknown>[]>(deferreds: T) => Deferred<{ [K in keyof T]: T[K] extends Deferred<infer A> ? A : never; }>;
67
+ /**
68
+ * Races multiple `Deferred` values and resolves with the first one to settle.
69
+ *
70
+ * @example
71
+ * ```ts
72
+ * const winner = await Deferred.race([d1, d2]);
73
+ * ```
74
+ */
75
+ const race: <A>(deferreds: ReadonlyArray<Deferred<A>>) => Deferred<A>;
57
76
  }
58
77
 
59
78
  /**