@nunofyobiz/effect-extras 1.0.0 → 2.0.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.
@@ -0,0 +1,1265 @@
1
+ /**
2
+ * The `WarnResult` data type — a result that may carry a success value and/or
3
+ * warnings, where both sides are optional but never both absent.
4
+ *
5
+ * @since 0.0.0
6
+ */
7
+ import { Data, Effect, Option, Predicate, Struct, pipe } from "effect";
8
+ import { constUndefined, identity } from "effect/Function";
9
+
10
+ /**
11
+ * A result that may come with a success value and may come with warnings — both
12
+ * are optional, but never both absent.
13
+ *
14
+ * Where `Result<A, E>` models an exclusive choice (success _or_ failure),
15
+ * `WarnResult` is an "inclusive or": the success value and the warnings can each
16
+ * be present independently. It is a tagged enum with three constructors:
17
+ * `SuccessOnly` (only a `success` value), `WarningsOnly` (only `warnings`), and
18
+ * `SuccessWithWarnings` (both). Reach for it when an operation can succeed, warn,
19
+ * or do both at once — e.g. a parse that yields a value _and_ a list of warnings,
20
+ * or that only produces warnings.
21
+ *
22
+ * @example
23
+ * ```ts
24
+ * import { WarnResult } from "@nunofyobiz/effect-extras"
25
+ *
26
+ * const both: WarnResult.WarnResult<string, number> =
27
+ * WarnResult.SuccessWithWarnings({
28
+ * warnings: "rounded down",
29
+ * success: 1
30
+ * })
31
+ *
32
+ * assert.deepStrictEqual(both._tag, "SuccessWithWarnings")
33
+ * ```
34
+ *
35
+ * @category models
36
+ * @since 0.0.0
37
+ */
38
+ export type WarnResult<W, A> = Data.TaggedEnum<{
39
+ WarningsOnly: {
40
+ readonly warnings: W;
41
+ };
42
+
43
+ SuccessOnly: {
44
+ readonly success: A;
45
+ };
46
+
47
+ SuccessWithWarnings: {
48
+ readonly warnings: W;
49
+ readonly success: A;
50
+ };
51
+ }>;
52
+
53
+ /**
54
+ * The `WarningsOnly` member of `WarnResult` — a result that carries only
55
+ * `warnings` and no `success` value.
56
+ *
57
+ * @example
58
+ * ```ts
59
+ * import { WarnResult } from "@nunofyobiz/effect-extras"
60
+ *
61
+ * const value: WarnResult.WarningsOnly<string> = WarnResult.WarningsOnly({
62
+ * warnings: "skipped 2 rows"
63
+ * })
64
+ *
65
+ * assert.deepStrictEqual(value.warnings, "skipped 2 rows")
66
+ * ```
67
+ *
68
+ * @category models
69
+ * @since 0.0.0
70
+ */
71
+ export type WarningsOnly<W> = WarnResult<W, never> & {
72
+ _tag: "WarningsOnly";
73
+ };
74
+
75
+ /**
76
+ * The `SuccessOnly` member of `WarnResult` — a result that carries only a
77
+ * `success` value and no `warnings`.
78
+ *
79
+ * @example
80
+ * ```ts
81
+ * import { WarnResult } from "@nunofyobiz/effect-extras"
82
+ *
83
+ * const value: WarnResult.SuccessOnly<number> = WarnResult.SuccessOnly({
84
+ * success: 1
85
+ * })
86
+ *
87
+ * assert.deepStrictEqual(value.success, 1)
88
+ * ```
89
+ *
90
+ * @category models
91
+ * @since 0.0.0
92
+ */
93
+ export type SuccessOnly<A> = WarnResult<never, A> & {
94
+ _tag: "SuccessOnly";
95
+ };
96
+
97
+ /**
98
+ * The `SuccessWithWarnings` member of `WarnResult` — a result that carries both a
99
+ * `success` value and `warnings`.
100
+ *
101
+ * @example
102
+ * ```ts
103
+ * import { WarnResult } from "@nunofyobiz/effect-extras"
104
+ *
105
+ * const value: WarnResult.SuccessWithWarnings<string, number> =
106
+ * WarnResult.SuccessWithWarnings({
107
+ * warnings: "rounded down",
108
+ * success: 1
109
+ * })
110
+ *
111
+ * assert.deepStrictEqual(value, {
112
+ * _tag: "SuccessWithWarnings",
113
+ * warnings: "rounded down",
114
+ * success: 1
115
+ * })
116
+ * ```
117
+ *
118
+ * @category models
119
+ * @since 0.0.0
120
+ */
121
+ export type SuccessWithWarnings<W, A> = WarnResult<W, A> & {
122
+ _tag: "SuccessWithWarnings";
123
+ };
124
+
125
+ /**
126
+ * Any `WarnResult` that is guaranteed to carry `warnings` — either `WarningsOnly`
127
+ * or `SuccessWithWarnings`.
128
+ *
129
+ * @example
130
+ * ```ts
131
+ * import { WarnResult } from "@nunofyobiz/effect-extras"
132
+ *
133
+ * const value: WarnResult.WithWarnings<string, number> = WarnResult.WarningsOnly({
134
+ * warnings: "skipped 2 rows"
135
+ * })
136
+ *
137
+ * assert.deepStrictEqual(value.warnings, "skipped 2 rows")
138
+ * ```
139
+ *
140
+ * @category models
141
+ * @since 0.0.0
142
+ */
143
+ export type WithWarnings<W, A> = WarningsOnly<W> | SuccessWithWarnings<W, A>;
144
+
145
+ /**
146
+ * Any `WarnResult` that is guaranteed to carry a `success` value — either
147
+ * `SuccessOnly` or `SuccessWithWarnings`.
148
+ *
149
+ * @example
150
+ * ```ts
151
+ * import { WarnResult } from "@nunofyobiz/effect-extras"
152
+ *
153
+ * const value: WarnResult.WithSuccess<string, number> = WarnResult.SuccessOnly({
154
+ * success: 1
155
+ * })
156
+ *
157
+ * assert.deepStrictEqual(value.success, 1)
158
+ * ```
159
+ *
160
+ * @category models
161
+ * @since 0.0.0
162
+ */
163
+ export type WithSuccess<W, A> = SuccessOnly<A> | SuccessWithWarnings<W, A>;
164
+
165
+ interface WarnResultDefinition extends Data.TaggedEnum.WithGenerics<2> {
166
+ readonly taggedEnum: WarnResult<this["A"], this["B"]>;
167
+ }
168
+
169
+ const taggedEnum = Data.taggedEnum<WarnResultDefinition>();
170
+
171
+ /**
172
+ * Constructs a `WarningsOnly` — a `WarnResult` that carries only `warnings`.
173
+ *
174
+ * @example
175
+ * ```ts
176
+ * import { WarnResult } from "@nunofyobiz/effect-extras"
177
+ *
178
+ * const value = WarnResult.WarningsOnly({ warnings: "skipped 2 rows" })
179
+ *
180
+ * assert.deepStrictEqual(value._tag, "WarningsOnly")
181
+ * assert.deepStrictEqual(value.warnings, "skipped 2 rows")
182
+ * ```
183
+ *
184
+ * @category constructors
185
+ * @since 0.0.0
186
+ */
187
+ export const WarningsOnly = taggedEnum.WarningsOnly;
188
+
189
+ /**
190
+ * Constructs a `SuccessOnly` — a `WarnResult` that carries only a `success` value.
191
+ *
192
+ * @example
193
+ * ```ts
194
+ * import { WarnResult } from "@nunofyobiz/effect-extras"
195
+ *
196
+ * const value = WarnResult.SuccessOnly({ success: 1 })
197
+ *
198
+ * assert.deepStrictEqual(value._tag, "SuccessOnly")
199
+ * assert.deepStrictEqual(value.success, 1)
200
+ * ```
201
+ *
202
+ * @category constructors
203
+ * @since 0.0.0
204
+ */
205
+ export const SuccessOnly = taggedEnum.SuccessOnly;
206
+
207
+ /**
208
+ * Constructs a `SuccessWithWarnings` — a `WarnResult` that carries both a
209
+ * `success` value and `warnings`.
210
+ *
211
+ * @example
212
+ * ```ts
213
+ * import { WarnResult } from "@nunofyobiz/effect-extras"
214
+ *
215
+ * const value = WarnResult.SuccessWithWarnings({
216
+ * warnings: "rounded down",
217
+ * success: 1
218
+ * })
219
+ *
220
+ * assert.deepStrictEqual(value._tag, "SuccessWithWarnings")
221
+ * assert.deepStrictEqual(value.warnings, "rounded down")
222
+ * assert.deepStrictEqual(value.success, 1)
223
+ * ```
224
+ *
225
+ * @category constructors
226
+ * @since 0.0.0
227
+ */
228
+ export const SuccessWithWarnings = taggedEnum.SuccessWithWarnings;
229
+
230
+ /**
231
+ * Builds per-tag refinements for `WarnResult`. `is("WarningsOnly")` is a type
232
+ * guard that narrows a `WarnResult` to its `WarningsOnly` member, and likewise for
233
+ * `"SuccessOnly"` and `"SuccessWithWarnings"`.
234
+ *
235
+ * @example
236
+ * ```ts
237
+ * import { WarnResult } from "@nunofyobiz/effect-extras"
238
+ *
239
+ * assert.deepStrictEqual(
240
+ * WarnResult.is("WarningsOnly")(WarnResult.WarningsOnly({ warnings: "w" })),
241
+ * true
242
+ * )
243
+ * assert.deepStrictEqual(
244
+ * WarnResult.is("WarningsOnly")(WarnResult.SuccessOnly({ success: 1 })),
245
+ * false
246
+ * )
247
+ * ```
248
+ *
249
+ * @category guards
250
+ * @since 0.0.0
251
+ */
252
+ export const is = taggedEnum.$is;
253
+
254
+ /**
255
+ * Folds a `WarnResult` over its three tags. Provide a handler for `WarningsOnly`,
256
+ * `SuccessOnly`, and `SuccessWithWarnings` and `match` returns a function from a
257
+ * `WarnResult` to the handlers' common result type.
258
+ *
259
+ * @example
260
+ * ```ts
261
+ * import { WarnResult } from "@nunofyobiz/effect-extras"
262
+ *
263
+ * const describe = WarnResult.match({
264
+ * WarningsOnly: ({ warnings }) => `warnings ${warnings}`,
265
+ * SuccessOnly: ({ success }) => `success ${success}`,
266
+ * SuccessWithWarnings: ({ warnings, success }) => `both ${warnings}/${success}`
267
+ * })
268
+ *
269
+ * assert.deepStrictEqual(
270
+ * describe(WarnResult.SuccessWithWarnings({ warnings: "w", success: 1 })),
271
+ * "both w/1"
272
+ * )
273
+ * ```
274
+ *
275
+ * @category pattern matching
276
+ * @since 0.0.0
277
+ */
278
+ export const match = taggedEnum.$match;
279
+
280
+ /**
281
+ * Builds a `WarnResult` known to carry `warnings`, choosing `SuccessWithWarnings`
282
+ * when a `success` value is present and `WarningsOnly` otherwise.
283
+ *
284
+ * Use it when the `warnings` are mandatory and the `success` value is an optional
285
+ * companion: pass an absent (`null`/`undefined`) `success` to get a
286
+ * `WarningsOnly`, or a present one to get a `SuccessWithWarnings`. The return type
287
+ * `WithWarnings<W, A>` reflects that `warnings` are always present.
288
+ *
289
+ * @example
290
+ * ```ts
291
+ * import { WarnResult } from "@nunofyobiz/effect-extras"
292
+ *
293
+ * assert.deepStrictEqual(
294
+ * WarnResult.WithWarnings({ warnings: "w", success: 1 }),
295
+ * WarnResult.SuccessWithWarnings({ warnings: "w", success: 1 })
296
+ * )
297
+ *
298
+ * assert.deepStrictEqual(
299
+ * WarnResult.WithWarnings({ warnings: "w" }),
300
+ * WarnResult.WarningsOnly({ warnings: "w" })
301
+ * )
302
+ * ```
303
+ *
304
+ * @category constructors
305
+ * @since 0.0.0
306
+ */
307
+ export const WithWarnings = <W, A>({
308
+ warnings,
309
+ success,
310
+ }: {
311
+ warnings: W;
312
+ success?: A | undefined;
313
+ }): WithWarnings<W, A> =>
314
+ Predicate.isNotNullish(success)
315
+ ? SuccessWithWarnings({ warnings, success })
316
+ : WarningsOnly({ warnings });
317
+
318
+ /**
319
+ * Builds a `WarnResult` known to carry a `success` value, choosing
320
+ * `SuccessWithWarnings` when `warnings` are present and `SuccessOnly` otherwise.
321
+ *
322
+ * The mirror of `WithWarnings`: the `success` value is mandatory and the
323
+ * `warnings` are an optional companion. Pass absent (`null`/`undefined`)
324
+ * `warnings` to get a `SuccessOnly`, or present ones to get a
325
+ * `SuccessWithWarnings`. The return type `WithSuccess<W, A>` reflects that a
326
+ * `success` value is always present.
327
+ *
328
+ * @example
329
+ * ```ts
330
+ * import { WarnResult } from "@nunofyobiz/effect-extras"
331
+ *
332
+ * assert.deepStrictEqual(
333
+ * WarnResult.WithSuccess({ warnings: "w", success: 1 }),
334
+ * WarnResult.SuccessWithWarnings({ warnings: "w", success: 1 })
335
+ * )
336
+ *
337
+ * assert.deepStrictEqual(
338
+ * WarnResult.WithSuccess({ success: 1 }),
339
+ * WarnResult.SuccessOnly({ success: 1 })
340
+ * )
341
+ * ```
342
+ *
343
+ * @category constructors
344
+ * @since 0.0.0
345
+ */
346
+ export const WithSuccess = <W, A>({
347
+ warnings,
348
+ success,
349
+ }: {
350
+ warnings?: W | undefined;
351
+ success: A;
352
+ }): WithSuccess<W, A> =>
353
+ Predicate.isNotNullish(warnings)
354
+ ? SuccessWithWarnings({ warnings, success })
355
+ : SuccessOnly({ success });
356
+
357
+ /**
358
+ * Builds a `WarnResult` from a pair of possibly-nullish inputs, wrapping the
359
+ * result in an `Option` so the all-absent case is expressible.
360
+ *
361
+ * Returns `Option.some(SuccessWithWarnings)` when both are present,
362
+ * `Option.some(WarningsOnly)` or `Option.some(SuccessOnly)` when exactly one is
363
+ * present, and `Option.none()` when both are nullish. Use it as the total entry
364
+ * point for turning an optional success value and optional warnings into a
365
+ * `WarnResult`.
366
+ *
367
+ * @example
368
+ * ```ts
369
+ * import { WarnResult } from "@nunofyobiz/effect-extras"
370
+ * import { Option } from "effect"
371
+ *
372
+ * assert.deepStrictEqual(
373
+ * WarnResult.optionFromNullables({ warnings: "w", success: 1 }),
374
+ * Option.some(WarnResult.SuccessWithWarnings({ warnings: "w", success: 1 }))
375
+ * )
376
+ *
377
+ * assert.deepStrictEqual(
378
+ * WarnResult.optionFromNullables({ warnings: "w", success: null }),
379
+ * Option.some(WarnResult.WarningsOnly({ warnings: "w" }))
380
+ * )
381
+ *
382
+ * assert.deepStrictEqual(
383
+ * WarnResult.optionFromNullables({ warnings: null, success: undefined }),
384
+ * Option.none()
385
+ * )
386
+ * ```
387
+ *
388
+ * @category constructors
389
+ * @since 0.0.0
390
+ */
391
+ export const optionFromNullables = <W, A>({
392
+ warnings,
393
+ success,
394
+ }: {
395
+ warnings?: W | null | undefined;
396
+ success?: A | null | undefined;
397
+ }): Option.Option<WarnResult<W, A>> => {
398
+ if (Predicate.isNotNullish(warnings) && Predicate.isNotNullish(success)) {
399
+ return Option.some(SuccessWithWarnings({ warnings, success }));
400
+ }
401
+ if (Predicate.isNotNullish(warnings)) {
402
+ return Option.some(WarningsOnly({ warnings }));
403
+ }
404
+ if (Predicate.isNotNullish(success)) {
405
+ return Option.some(SuccessOnly({ success }));
406
+ }
407
+ return Option.none();
408
+ };
409
+
410
+ /**
411
+ * Builds a `WarnResult` from a pair of possibly-nullish inputs, falling back to
412
+ * the `orElse` thunk when both are absent.
413
+ *
414
+ * The non-optional companion to `optionFromNullables`: it unwraps the same logic
415
+ * but resolves the all-absent case with `orElse` instead of an `Option`. The
416
+ * default `orElse` throws, so omit it only when at least one side is guaranteed
417
+ * present.
418
+ *
419
+ * @example
420
+ * ```ts
421
+ * import { WarnResult } from "@nunofyobiz/effect-extras"
422
+ *
423
+ * assert.deepStrictEqual(
424
+ * WarnResult.fromNullables({ warnings: "w", success: 1 }),
425
+ * WarnResult.SuccessWithWarnings({ warnings: "w", success: 1 })
426
+ * )
427
+ *
428
+ * // Both absent — fall back via orElse instead of throwing
429
+ * assert.deepStrictEqual(
430
+ * WarnResult.fromNullables({
431
+ * warnings: null,
432
+ * success: null,
433
+ * orElse: () => WarnResult.WarningsOnly({ warnings: "none" })
434
+ * }),
435
+ * WarnResult.WarningsOnly({ warnings: "none" })
436
+ * )
437
+ * ```
438
+ *
439
+ * @category constructors
440
+ * @since 0.0.0
441
+ */
442
+ export const fromNullables = <W, A>({
443
+ warnings,
444
+ success,
445
+ orElse = () => {
446
+ throw new Error("Both warnings and success are nullish");
447
+ },
448
+ }: {
449
+ warnings?: W | null | undefined;
450
+ success?: A | null | undefined;
451
+ orElse?: () => WarnResult<W, A>;
452
+ }): WarnResult<W, A> =>
453
+ pipe(optionFromNullables({ warnings, success }), Option.getOrElse(orElse));
454
+
455
+ /**
456
+ * Folds a `WarnResult` from the warnings' perspective, collapsing the three tags
457
+ * into two handlers.
458
+ *
459
+ * Both `WarningsOnly` and `SuccessWithWarnings` carry `warnings`, so they route to
460
+ * the `Warnings` handler; only `SuccessOnly` lacks `warnings` and routes to
461
+ * `SuccessOnly`. Use it when you care about the `warnings` and treat the
462
+ * success-only case as the exception.
463
+ *
464
+ * @example
465
+ * ```ts
466
+ * import { WarnResult } from "@nunofyobiz/effect-extras"
467
+ *
468
+ * const onWarnings = WarnResult.matchWarnings({
469
+ * Warnings: (warnings: string) => `warnings ${warnings}`,
470
+ * SuccessOnly: (success: number) => `success ${success}`
471
+ * })
472
+ *
473
+ * assert.deepStrictEqual(
474
+ * onWarnings(WarnResult.WarningsOnly({ warnings: "w" })),
475
+ * "warnings w"
476
+ * )
477
+ * assert.deepStrictEqual(
478
+ * onWarnings(WarnResult.SuccessWithWarnings({ warnings: "w", success: 1 })),
479
+ * "warnings w"
480
+ * )
481
+ * assert.deepStrictEqual(
482
+ * onWarnings(WarnResult.SuccessOnly({ success: 1 })),
483
+ * "success 1"
484
+ * )
485
+ * ```
486
+ *
487
+ * @category pattern matching
488
+ * @since 0.0.0
489
+ */
490
+ export const matchWarnings =
491
+ <W, A, Z>({
492
+ Warnings,
493
+ SuccessOnly,
494
+ }: {
495
+ Warnings: (warnings: W) => Z;
496
+ SuccessOnly: (success: A) => Z;
497
+ }) =>
498
+ (warnResult: WarnResult<W, A>): Z =>
499
+ pipe(
500
+ warnResult,
501
+
502
+ match({
503
+ WarningsOnly: ({ warnings }) => Warnings(warnings),
504
+ SuccessOnly: ({ success }) => SuccessOnly(success),
505
+ SuccessWithWarnings: ({ warnings }) => Warnings(warnings),
506
+ }),
507
+
508
+ // Make Typescript happy
509
+ (a) => a as Z,
510
+ );
511
+
512
+ /**
513
+ * Folds a `WarnResult` from the success value's perspective, collapsing the three
514
+ * tags into two handlers.
515
+ *
516
+ * The mirror of `matchWarnings`: both `SuccessOnly` and `SuccessWithWarnings`
517
+ * carry a `success` value, so they route to the `Success` handler; only
518
+ * `WarningsOnly` lacks a `success` value and routes to `WarningsOnly`. Use it when
519
+ * you care about the `success` value and treat the warnings-only case as the
520
+ * exception.
521
+ *
522
+ * @example
523
+ * ```ts
524
+ * import { WarnResult } from "@nunofyobiz/effect-extras"
525
+ *
526
+ * const onSuccess = WarnResult.matchSuccess({
527
+ * WarningsOnly: (warnings: string) => `warnings ${warnings}`,
528
+ * Success: (success: number) => `success ${success}`
529
+ * })
530
+ *
531
+ * assert.deepStrictEqual(
532
+ * onSuccess(WarnResult.SuccessOnly({ success: 1 })),
533
+ * "success 1"
534
+ * )
535
+ * assert.deepStrictEqual(
536
+ * onSuccess(WarnResult.SuccessWithWarnings({ warnings: "w", success: 1 })),
537
+ * "success 1"
538
+ * )
539
+ * assert.deepStrictEqual(
540
+ * onSuccess(WarnResult.WarningsOnly({ warnings: "w" })),
541
+ * "warnings w"
542
+ * )
543
+ * ```
544
+ *
545
+ * @category pattern matching
546
+ * @since 0.0.0
547
+ */
548
+ export const matchSuccess =
549
+ <W, A, Z>({
550
+ WarningsOnly,
551
+ Success,
552
+ }: {
553
+ WarningsOnly: (warnings: W) => Z;
554
+ Success: (success: A) => Z;
555
+ }) =>
556
+ (warnResult: WarnResult<W, A>): Z =>
557
+ pipe(
558
+ warnResult,
559
+
560
+ match({
561
+ WarningsOnly: ({ warnings }) => WarningsOnly(warnings),
562
+ SuccessOnly: ({ success }) => Success(success),
563
+ SuccessWithWarnings: ({ success }) => Success(success),
564
+ }),
565
+
566
+ // Make Typescript happy
567
+ (a) => a as Z,
568
+ );
569
+
570
+ /**
571
+ * Completes a `WarnResult` into a guaranteed `SuccessWithWarnings` by filling
572
+ * whichever side is missing from the matching `orElse` thunk.
573
+ *
574
+ * A `SuccessWithWarnings` passes through unchanged; a `WarningsOnly` gains a
575
+ * `success` value from `orElseSuccess`; a `SuccessOnly` gains `warnings` from
576
+ * `orElseWarnings`. Use it to normalise a partial `WarnResult` into the
577
+ * both-present shape before reading both sides.
578
+ *
579
+ * @example
580
+ * ```ts
581
+ * import { WarnResult } from "@nunofyobiz/effect-extras"
582
+ *
583
+ * const fill = WarnResult.orElse({
584
+ * orElseWarnings: () => "no warnings",
585
+ * orElseSuccess: () => 0
586
+ * })
587
+ *
588
+ * assert.deepStrictEqual(
589
+ * fill(WarnResult.WarningsOnly({ warnings: "w" })),
590
+ * WarnResult.SuccessWithWarnings({ warnings: "w", success: 0 })
591
+ * )
592
+ * assert.deepStrictEqual(
593
+ * fill(WarnResult.SuccessOnly({ success: 1 })),
594
+ * WarnResult.SuccessWithWarnings({ warnings: "no warnings", success: 1 })
595
+ * )
596
+ * ```
597
+ *
598
+ * @category getters
599
+ * @since 0.0.0
600
+ */
601
+ export const orElse = <W2, A2>({
602
+ orElseWarnings,
603
+ orElseSuccess,
604
+ }: {
605
+ orElseWarnings: () => W2;
606
+ orElseSuccess: () => A2;
607
+ }): (<W, A>(
608
+ warnResult: WarnResult<W, A>,
609
+ ) => SuccessWithWarnings<W | W2, A | A2>) =>
610
+ match({
611
+ WarningsOnly: ({ warnings }) =>
612
+ SuccessWithWarnings({ warnings, success: orElseSuccess() }),
613
+ SuccessOnly: ({ success }) =>
614
+ SuccessWithWarnings({ warnings: orElseWarnings(), success }),
615
+ SuccessWithWarnings: ({ warnings, success }) =>
616
+ SuccessWithWarnings({ warnings, success }),
617
+ });
618
+
619
+ /**
620
+ * Completes a `WarnResult` into a `SuccessWithWarnings` whose missing side is
621
+ * filled with `undefined`.
622
+ *
623
+ * A specialisation of `orElse` that supplies `undefined` for whichever side is
624
+ * absent, so the result always exposes both `warnings` and `success` keys (each
625
+ * possibly `undefined`). Use it when you want to destructure both sides without
626
+ * branching on the tag.
627
+ *
628
+ * @example
629
+ * ```ts
630
+ * import { WarnResult } from "@nunofyobiz/effect-extras"
631
+ *
632
+ * assert.deepStrictEqual(
633
+ * WarnResult.orUndefined(WarnResult.WarningsOnly({ warnings: "w" })),
634
+ * WarnResult.SuccessWithWarnings({ warnings: "w", success: undefined })
635
+ * )
636
+ * assert.deepStrictEqual(
637
+ * WarnResult.orUndefined(WarnResult.SuccessOnly({ success: 1 })),
638
+ * WarnResult.SuccessWithWarnings({ warnings: undefined, success: 1 })
639
+ * )
640
+ * ```
641
+ *
642
+ * @category getters
643
+ * @since 0.0.0
644
+ */
645
+ export const orUndefined = orElse({
646
+ orElseWarnings: () => undefined,
647
+ orElseSuccess: () => undefined,
648
+ });
649
+
650
+ /**
651
+ * Extracts the `warnings` of a `WarnResult`, falling back to `orElseReturn` when
652
+ * no `warnings` are present.
653
+ *
654
+ * `WarningsOnly` and `SuccessWithWarnings` return their `warnings`; `SuccessOnly`
655
+ * returns the result of `orElseReturn`. Use it to read the warnings with a default
656
+ * in one step.
657
+ *
658
+ * @example
659
+ * ```ts
660
+ * import { WarnResult } from "@nunofyobiz/effect-extras"
661
+ *
662
+ * const warningsOrNone = WarnResult.warningsOrElse(() => "no warnings")
663
+ *
664
+ * assert.deepStrictEqual(
665
+ * warningsOrNone(WarnResult.WarningsOnly({ warnings: "w" })),
666
+ * "w"
667
+ * )
668
+ * assert.deepStrictEqual(
669
+ * warningsOrNone(WarnResult.SuccessWithWarnings({ warnings: "w", success: 1 })),
670
+ * "w"
671
+ * )
672
+ * assert.deepStrictEqual(
673
+ * warningsOrNone(WarnResult.SuccessOnly({ success: 1 })),
674
+ * "no warnings"
675
+ * )
676
+ * ```
677
+ *
678
+ * @category getters
679
+ * @since 0.0.0
680
+ */
681
+ export const warningsOrElse =
682
+ <Z>(orElseReturn: () => Z) =>
683
+ <W, A>(warnResult: WarnResult<W, A>): W | Z =>
684
+ pipe(
685
+ warnResult,
686
+ orElse({
687
+ orElseWarnings: orElseReturn,
688
+ orElseSuccess: constUndefined,
689
+ }),
690
+ Struct.get("warnings"),
691
+ );
692
+
693
+ /**
694
+ * Extracts the `warnings` of a `WarnResult`, returning `undefined` when no
695
+ * `warnings` are present.
696
+ *
697
+ * A specialisation of `warningsOrElse` whose fallback is `undefined`:
698
+ * `WarningsOnly` and `SuccessWithWarnings` yield their `warnings`, while
699
+ * `SuccessOnly` yields `undefined`.
700
+ *
701
+ * @example
702
+ * ```ts
703
+ * import { WarnResult } from "@nunofyobiz/effect-extras"
704
+ *
705
+ * assert.deepStrictEqual(
706
+ * WarnResult.warningsOrUndefined(WarnResult.WarningsOnly({ warnings: "w" })),
707
+ * "w"
708
+ * )
709
+ * assert.deepStrictEqual(
710
+ * WarnResult.warningsOrUndefined(WarnResult.SuccessOnly({ success: 1 })),
711
+ * undefined
712
+ * )
713
+ * ```
714
+ *
715
+ * @category getters
716
+ * @since 0.0.0
717
+ */
718
+ export const warningsOrUndefined = warningsOrElse(() => undefined);
719
+
720
+ /**
721
+ * Extracts the `success` value of a `WarnResult`, falling back to `orElseReturn`
722
+ * when no `success` value is present.
723
+ *
724
+ * The mirror of `warningsOrElse`: `SuccessOnly` and `SuccessWithWarnings` return
725
+ * their `success` value; `WarningsOnly` returns the result of `orElseReturn`.
726
+ *
727
+ * @example
728
+ * ```ts
729
+ * import { WarnResult } from "@nunofyobiz/effect-extras"
730
+ *
731
+ * const successOrZero = WarnResult.successOrElse(() => 0)
732
+ *
733
+ * assert.deepStrictEqual(
734
+ * successOrZero(WarnResult.SuccessOnly({ success: 1 })),
735
+ * 1
736
+ * )
737
+ * assert.deepStrictEqual(
738
+ * successOrZero(WarnResult.WarningsOnly({ warnings: "w" })),
739
+ * 0
740
+ * )
741
+ * ```
742
+ *
743
+ * @category getters
744
+ * @since 0.0.0
745
+ */
746
+ export const successOrElse =
747
+ <Z>(orElseReturn: () => Z) =>
748
+ <W, A>(warnResult: WarnResult<W, A>): A | Z =>
749
+ pipe(
750
+ warnResult,
751
+ orElse({
752
+ orElseWarnings: constUndefined,
753
+ orElseSuccess: orElseReturn,
754
+ }),
755
+ Struct.get("success"),
756
+ );
757
+
758
+ /**
759
+ * Extracts the `success` value of a `WarnResult`, returning `undefined` when no
760
+ * `success` value is present.
761
+ *
762
+ * A specialisation of `successOrElse` whose fallback is `undefined`: `SuccessOnly`
763
+ * and `SuccessWithWarnings` yield their `success` value, while `WarningsOnly`
764
+ * yields `undefined`.
765
+ *
766
+ * @example
767
+ * ```ts
768
+ * import { WarnResult } from "@nunofyobiz/effect-extras"
769
+ *
770
+ * assert.deepStrictEqual(
771
+ * WarnResult.successOrUndefined(WarnResult.SuccessOnly({ success: 1 })),
772
+ * 1
773
+ * )
774
+ * assert.deepStrictEqual(
775
+ * WarnResult.successOrUndefined(WarnResult.WarningsOnly({ warnings: "w" })),
776
+ * undefined
777
+ * )
778
+ * ```
779
+ *
780
+ * @category getters
781
+ * @since 0.0.0
782
+ */
783
+ export const successOrUndefined = successOrElse(() => undefined);
784
+
785
+ /**
786
+ * Extracts the `success` value of a `WarnResult` as an `Option`.
787
+ *
788
+ * `SuccessOnly` and `SuccessWithWarnings` yield `Option.some(success)`;
789
+ * `WarningsOnly` yields `Option.none()`. Use it when you want to chain the success
790
+ * value through `Option` combinators rather than fall back to a default eagerly.
791
+ *
792
+ * @example
793
+ * ```ts
794
+ * import { WarnResult } from "@nunofyobiz/effect-extras"
795
+ * import { Option } from "effect"
796
+ *
797
+ * assert.deepStrictEqual(
798
+ * WarnResult.successOption(WarnResult.SuccessOnly({ success: 1 })),
799
+ * Option.some(1)
800
+ * )
801
+ * assert.deepStrictEqual(
802
+ * WarnResult.successOption(WarnResult.WarningsOnly({ warnings: "w" })),
803
+ * Option.none()
804
+ * )
805
+ * ```
806
+ *
807
+ * @category getters
808
+ * @since 0.0.0
809
+ */
810
+ export const successOption = <W, A>(
811
+ warnResult: WarnResult<W, A>,
812
+ ): Option.Option<A> =>
813
+ pipe(
814
+ warnResult,
815
+ matchSuccess({
816
+ WarningsOnly: () => Option.none(),
817
+ Success: Option.some,
818
+ }),
819
+ );
820
+
821
+ /**
822
+ * Extracts the `warnings` of a `WarnResult` as an `Option`.
823
+ *
824
+ * The mirror of `successOption`: `WarningsOnly` and `SuccessWithWarnings` yield
825
+ * `Option.some(warnings)`, while `SuccessOnly` yields `Option.none()`.
826
+ *
827
+ * @example
828
+ * ```ts
829
+ * import { WarnResult } from "@nunofyobiz/effect-extras"
830
+ * import { Option } from "effect"
831
+ *
832
+ * assert.deepStrictEqual(
833
+ * WarnResult.warningsOption(WarnResult.WarningsOnly({ warnings: "w" })),
834
+ * Option.some("w")
835
+ * )
836
+ * assert.deepStrictEqual(
837
+ * WarnResult.warningsOption(WarnResult.SuccessOnly({ success: 1 })),
838
+ * Option.none()
839
+ * )
840
+ * ```
841
+ *
842
+ * @category getters
843
+ * @since 0.0.0
844
+ */
845
+ export const warningsOption = <W, A>(
846
+ warnResult: WarnResult<W, A>,
847
+ ): Option.Option<W> =>
848
+ pipe(
849
+ warnResult,
850
+ matchWarnings({
851
+ Warnings: Option.some,
852
+ SuccessOnly: () => Option.none(),
853
+ }),
854
+ );
855
+
856
+ /**
857
+ * Transforms both sides of a `WarnResult`, applying `mapWarnings` to any
858
+ * `warnings` and `mapSuccess` to any `success` value.
859
+ *
860
+ * Each constructor is rebuilt with its mapped contents, so `WarningsOnly` maps
861
+ * only the warnings, `SuccessOnly` only the success value, and
862
+ * `SuccessWithWarnings` both. The tag is preserved. Use it as the bifunctor map
863
+ * over `WarnResult`.
864
+ *
865
+ * @example
866
+ * ```ts
867
+ * import { WarnResult } from "@nunofyobiz/effect-extras"
868
+ *
869
+ * const both = WarnResult.mapBoth({
870
+ * mapWarnings: (warnings: string) => warnings.toUpperCase(),
871
+ * mapSuccess: (success: number) => success + 1
872
+ * })
873
+ *
874
+ * assert.deepStrictEqual(
875
+ * both(WarnResult.SuccessWithWarnings({ warnings: "w", success: 1 })),
876
+ * WarnResult.SuccessWithWarnings({ warnings: "W", success: 2 })
877
+ * )
878
+ * assert.deepStrictEqual(
879
+ * both(WarnResult.WarningsOnly({ warnings: "w" })),
880
+ * WarnResult.WarningsOnly({ warnings: "W" })
881
+ * )
882
+ * ```
883
+ *
884
+ * @category mapping
885
+ * @since 0.0.0
886
+ */
887
+ export const mapBoth = <W1, A1, W2, A2>({
888
+ mapWarnings,
889
+ mapSuccess,
890
+ }: {
891
+ mapWarnings: (warnings: W1) => W2;
892
+ mapSuccess: (success: A1) => A2;
893
+ }): ((warnResult: WarnResult<W1, A1>) => WarnResult<W2, A2>) =>
894
+ match({
895
+ WarningsOnly: ({ warnings }) =>
896
+ WarningsOnly({ warnings: mapWarnings(warnings) }),
897
+ SuccessOnly: ({ success }) => SuccessOnly({ success: mapSuccess(success) }),
898
+ SuccessWithWarnings: ({ warnings, success }) =>
899
+ SuccessWithWarnings({
900
+ warnings: mapWarnings(warnings),
901
+ success: mapSuccess(success),
902
+ }),
903
+ });
904
+
905
+ /**
906
+ * Effectful `mapBoth`: transforms each present side through an `Effect`,
907
+ * reassembling the results into a `WarnResult` inside an `Effect`.
908
+ *
909
+ * For `SuccessWithWarnings` both effects run via `Effect.all` and their results
910
+ * are combined; `WarningsOnly`/`SuccessOnly` run only the relevant effect. Errors
911
+ * and requirements from both mappers are unioned into the result type. Use it when
912
+ * mapping a `WarnResult`'s sides requires effects (validation, IO).
913
+ *
914
+ * @example
915
+ * ```ts
916
+ * import { WarnResult } from "@nunofyobiz/effect-extras"
917
+ * import { Effect } from "effect"
918
+ *
919
+ * const both = WarnResult.mapBothEffect({
920
+ * mapWarnings: (warnings: string) => Effect.succeed(warnings.toUpperCase()),
921
+ * mapSuccess: (success: number) => Effect.succeed(success + 1)
922
+ * })
923
+ *
924
+ * assert.deepStrictEqual(
925
+ * Effect.runSync(
926
+ * both(WarnResult.SuccessWithWarnings({ warnings: "w", success: 1 }))
927
+ * ),
928
+ * WarnResult.SuccessWithWarnings({ warnings: "W", success: 2 })
929
+ * )
930
+ * ```
931
+ *
932
+ * @category sequencing
933
+ * @since 0.0.0
934
+ */
935
+ export const mapBothEffect = <W1, A1, W2, A2, EW, EA, RW, RA>({
936
+ mapWarnings,
937
+ mapSuccess,
938
+ }: {
939
+ mapWarnings: (warnings: W1) => Effect.Effect<W2, EW, RW>;
940
+ mapSuccess: (success: A1) => Effect.Effect<A2, EA, RA>;
941
+ }): ((
942
+ warnResult: WarnResult<W1, A1>,
943
+ ) => Effect.Effect<WarnResult<W2, A2>, EW | EA, RW | RA>) =>
944
+ match({
945
+ WarningsOnly: ({ warnings }) =>
946
+ pipe(
947
+ mapWarnings(warnings),
948
+ Effect.map((warnings2) => WarningsOnly({ warnings: warnings2 })),
949
+ ),
950
+
951
+ SuccessOnly: ({ success }) =>
952
+ pipe(
953
+ mapSuccess(success),
954
+ Effect.map((success2) => SuccessOnly({ success: success2 })),
955
+ ),
956
+
957
+ SuccessWithWarnings: ({ warnings, success }) =>
958
+ pipe(
959
+ Effect.all({
960
+ warnings: mapWarnings(warnings),
961
+ success: mapSuccess(success),
962
+ }),
963
+ Effect.map(({ warnings: warnings2, success: success2 }) =>
964
+ SuccessWithWarnings({ warnings: warnings2, success: success2 }),
965
+ ),
966
+ ),
967
+ });
968
+
969
+ /**
970
+ * Transforms the `warnings` of a `WarnResult`, leaving any `success` value
971
+ * untouched.
972
+ *
973
+ * A specialisation of `mapBoth` with the success mapper set to `identity`:
974
+ * `WarningsOnly` and `SuccessWithWarnings` have their `warnings` mapped, while
975
+ * `SuccessOnly` passes through unchanged.
976
+ *
977
+ * @example
978
+ * ```ts
979
+ * import { WarnResult } from "@nunofyobiz/effect-extras"
980
+ *
981
+ * const shout = WarnResult.mapWarnings((warnings: string) =>
982
+ * warnings.toUpperCase()
983
+ * )
984
+ *
985
+ * assert.deepStrictEqual(
986
+ * shout(WarnResult.SuccessWithWarnings({ warnings: "w", success: 1 })),
987
+ * WarnResult.SuccessWithWarnings({ warnings: "W", success: 1 })
988
+ * )
989
+ * assert.deepStrictEqual(
990
+ * shout(WarnResult.SuccessOnly({ success: 1 })),
991
+ * WarnResult.SuccessOnly({ success: 1 })
992
+ * )
993
+ * ```
994
+ *
995
+ * @category mapping
996
+ * @since 0.0.0
997
+ */
998
+ export const mapWarnings = <W1, W2>(
999
+ mapWarnings: (warnings: W1) => W2,
1000
+ ): (<A>(warnResult: WarnResult<W1, A>) => WarnResult<W2, A>) =>
1001
+ mapBoth({ mapWarnings, mapSuccess: identity });
1002
+
1003
+ /**
1004
+ * Chains the `warnings` of a `WarnResult` into a new `WarnResult`, flattening the
1005
+ * result.
1006
+ *
1007
+ * Whenever `warnings` are present (`WarningsOnly` or `SuccessWithWarnings`) they
1008
+ * are passed to `mapWarnings`, whose returned `WarnResult` replaces the original;
1009
+ * `SuccessOnly` passes through unchanged. Use it to sequence warnings-driven
1010
+ * computations that themselves produce a `WarnResult`.
1011
+ *
1012
+ * @example
1013
+ * ```ts
1014
+ * import { WarnResult } from "@nunofyobiz/effect-extras"
1015
+ *
1016
+ * const chain = WarnResult.flatMapWarnings((warnings: string) =>
1017
+ * warnings.length > 0
1018
+ * ? WarnResult.WarningsOnly({ warnings: warnings.toUpperCase() })
1019
+ * : WarnResult.SuccessOnly({ success: 0 })
1020
+ * )
1021
+ *
1022
+ * assert.deepStrictEqual(
1023
+ * chain(WarnResult.WarningsOnly({ warnings: "w" })),
1024
+ * WarnResult.WarningsOnly({ warnings: "W" })
1025
+ * )
1026
+ * assert.deepStrictEqual(
1027
+ * chain(WarnResult.SuccessOnly({ success: 1 })),
1028
+ * WarnResult.SuccessOnly({ success: 1 })
1029
+ * )
1030
+ * ```
1031
+ *
1032
+ * @category sequencing
1033
+ * @since 0.0.0
1034
+ */
1035
+ export const flatMapWarnings = <W1, W2, A2>(
1036
+ mapWarnings: (warnings: W1) => WarnResult<W2, A2>,
1037
+ ): (<A1>(warnResult: WarnResult<W1, A1>) => WarnResult<W2, A1 | A2>) =>
1038
+ match({
1039
+ WarningsOnly: ({ warnings }) => mapWarnings(warnings),
1040
+ SuccessOnly: ({ success }) => SuccessOnly({ success }),
1041
+ SuccessWithWarnings: ({ warnings }) => mapWarnings(warnings),
1042
+ });
1043
+
1044
+ /**
1045
+ * Effectful `mapWarnings`: transforms the `warnings` of a `WarnResult` through an
1046
+ * `Effect`, leaving any `success` value untouched.
1047
+ *
1048
+ * A specialisation of `mapBothEffect` with the success mapper set to
1049
+ * `Effect.succeed`: the `warnings` (when present) are mapped effectfully and the
1050
+ * `success` value is carried through unchanged.
1051
+ *
1052
+ * @example
1053
+ * ```ts
1054
+ * import { WarnResult } from "@nunofyobiz/effect-extras"
1055
+ * import { Effect } from "effect"
1056
+ *
1057
+ * const shout = WarnResult.mapWarningsEffect((warnings: string) =>
1058
+ * Effect.succeed(warnings.toUpperCase())
1059
+ * )
1060
+ *
1061
+ * assert.deepStrictEqual(
1062
+ * Effect.runSync(
1063
+ * shout(WarnResult.SuccessWithWarnings({ warnings: "w", success: 1 }))
1064
+ * ),
1065
+ * WarnResult.SuccessWithWarnings({ warnings: "W", success: 1 })
1066
+ * )
1067
+ * ```
1068
+ *
1069
+ * @category sequencing
1070
+ * @since 0.0.0
1071
+ */
1072
+ export const mapWarningsEffect = <W1, W2, EW, RW>(
1073
+ mapWarnings: (warnings: W1) => Effect.Effect<W2, EW, RW>,
1074
+ ): (<A>(
1075
+ warnResult: WarnResult<W1, A>,
1076
+ ) => Effect.Effect<WarnResult<W2, A>, EW, RW>) =>
1077
+ mapBothEffect({ mapWarnings, mapSuccess: Effect.succeed });
1078
+
1079
+ /**
1080
+ * Effectful `flatMapWarnings`: chains the `warnings` of a `WarnResult` into an
1081
+ * `Effect` that yields a new `WarnResult`, flattening the result.
1082
+ *
1083
+ * When `warnings` are present they are passed to `mapWarnings`, whose effectful
1084
+ * `WarnResult` replaces the original; `SuccessOnly` is lifted unchanged via
1085
+ * `Effect.succeed`. Use it to sequence warnings-driven effectful computations that
1086
+ * produce a `WarnResult`.
1087
+ *
1088
+ * @example
1089
+ * ```ts
1090
+ * import { WarnResult } from "@nunofyobiz/effect-extras"
1091
+ * import { Effect } from "effect"
1092
+ *
1093
+ * const chain = WarnResult.flatMapWarningsEffect((warnings: string) =>
1094
+ * Effect.succeed(WarnResult.WarningsOnly({ warnings: warnings.toUpperCase() }))
1095
+ * )
1096
+ *
1097
+ * assert.deepStrictEqual(
1098
+ * Effect.runSync(chain(WarnResult.WarningsOnly({ warnings: "w" }))),
1099
+ * WarnResult.WarningsOnly({ warnings: "W" })
1100
+ * )
1101
+ * assert.deepStrictEqual(
1102
+ * Effect.runSync(chain(WarnResult.SuccessOnly({ success: 1 }))),
1103
+ * WarnResult.SuccessOnly({ success: 1 })
1104
+ * )
1105
+ * ```
1106
+ *
1107
+ * @category sequencing
1108
+ * @since 0.0.0
1109
+ */
1110
+ export const flatMapWarningsEffect = <W1, W2, A2, EW, RW>(
1111
+ mapWarnings: (warnings: W1) => Effect.Effect<WarnResult<W2, A2>, EW, RW>,
1112
+ ): (<A1>(
1113
+ warnResult: WarnResult<W1, A1>,
1114
+ ) => Effect.Effect<WarnResult<W2, A1 | A2>, EW, RW>) =>
1115
+ match({
1116
+ WarningsOnly: ({ warnings }) => mapWarnings(warnings),
1117
+ SuccessOnly: ({ success }) => Effect.succeed(SuccessOnly({ success })),
1118
+ SuccessWithWarnings: ({ warnings }) => mapWarnings(warnings),
1119
+ });
1120
+
1121
+ /**
1122
+ * Transforms the `success` value of a `WarnResult`, leaving any `warnings`
1123
+ * untouched.
1124
+ *
1125
+ * The mirror of `mapWarnings`: `SuccessOnly` and `SuccessWithWarnings` have their
1126
+ * `success` value mapped, while `WarningsOnly` passes through unchanged.
1127
+ *
1128
+ * @example
1129
+ * ```ts
1130
+ * import { WarnResult } from "@nunofyobiz/effect-extras"
1131
+ *
1132
+ * const inc = WarnResult.mapSuccess((success: number) => success + 1)
1133
+ *
1134
+ * assert.deepStrictEqual(
1135
+ * inc(WarnResult.SuccessWithWarnings({ warnings: "w", success: 1 })),
1136
+ * WarnResult.SuccessWithWarnings({ warnings: "w", success: 2 })
1137
+ * )
1138
+ * assert.deepStrictEqual(
1139
+ * inc(WarnResult.WarningsOnly({ warnings: "w" })),
1140
+ * WarnResult.WarningsOnly({ warnings: "w" })
1141
+ * )
1142
+ * ```
1143
+ *
1144
+ * @category mapping
1145
+ * @since 0.0.0
1146
+ */
1147
+ export const mapSuccess = <A1, A2>(
1148
+ mapSuccess: (success: A1) => A2,
1149
+ ): (<W>(warnResult: WarnResult<W, A1>) => WarnResult<W, A2>) =>
1150
+ mapBoth({ mapWarnings: identity, mapSuccess });
1151
+
1152
+ /**
1153
+ * Chains the `success` value of a `WarnResult` into a new `WarnResult`, flattening
1154
+ * the result.
1155
+ *
1156
+ * The mirror of `flatMapWarnings`: whenever a `success` value is present
1157
+ * (`SuccessOnly` or `SuccessWithWarnings`) it is passed to `mapSuccess`, whose
1158
+ * returned `WarnResult` replaces the original; `WarningsOnly` passes through
1159
+ * unchanged.
1160
+ *
1161
+ * @example
1162
+ * ```ts
1163
+ * import { WarnResult } from "@nunofyobiz/effect-extras"
1164
+ *
1165
+ * const chain = WarnResult.flatMapSuccess((success: number) =>
1166
+ * WarnResult.SuccessOnly({ success: success + 1 })
1167
+ * )
1168
+ *
1169
+ * assert.deepStrictEqual(
1170
+ * chain(WarnResult.SuccessOnly({ success: 1 })),
1171
+ * WarnResult.SuccessOnly({ success: 2 })
1172
+ * )
1173
+ * assert.deepStrictEqual(
1174
+ * chain(WarnResult.WarningsOnly({ warnings: "w" })),
1175
+ * WarnResult.WarningsOnly({ warnings: "w" })
1176
+ * )
1177
+ * ```
1178
+ *
1179
+ * @category sequencing
1180
+ * @since 0.0.0
1181
+ */
1182
+ export const flatMapSuccess = <W2, A1, A2>(
1183
+ mapSuccess: (success: A1) => WarnResult<W2, A2>,
1184
+ ): (<W1>(warnResult: WarnResult<W1, A1>) => WarnResult<W1 | W2, A2>) =>
1185
+ match({
1186
+ WarningsOnly: ({ warnings }) => WarningsOnly({ warnings }),
1187
+ SuccessOnly: ({ success }) => mapSuccess(success),
1188
+ SuccessWithWarnings: ({ success }) => mapSuccess(success),
1189
+ });
1190
+
1191
+ /**
1192
+ * Effectful `mapSuccess`: transforms the `success` value of a `WarnResult` through
1193
+ * an `Effect`, leaving any `warnings` untouched.
1194
+ *
1195
+ * The mirror of `mapWarningsEffect`: the `success` value (when present) is mapped
1196
+ * effectfully and the `warnings` are carried through unchanged via
1197
+ * `Effect.succeed`.
1198
+ *
1199
+ * @example
1200
+ * ```ts
1201
+ * import { WarnResult } from "@nunofyobiz/effect-extras"
1202
+ * import { Effect } from "effect"
1203
+ *
1204
+ * const inc = WarnResult.mapSuccessEffect((success: number) =>
1205
+ * Effect.succeed(success + 1)
1206
+ * )
1207
+ *
1208
+ * assert.deepStrictEqual(
1209
+ * Effect.runSync(
1210
+ * inc(WarnResult.SuccessWithWarnings({ warnings: "w", success: 1 }))
1211
+ * ),
1212
+ * WarnResult.SuccessWithWarnings({ warnings: "w", success: 2 })
1213
+ * )
1214
+ * ```
1215
+ *
1216
+ * @category sequencing
1217
+ * @since 0.0.0
1218
+ */
1219
+ export const mapSuccessEffect = <A1, A2, EA, RA>(
1220
+ mapSuccess: (success: A1) => Effect.Effect<A2, EA, RA>,
1221
+ ): (<W>(
1222
+ warnResult: WarnResult<W, A1>,
1223
+ ) => Effect.Effect<WarnResult<W, A2>, EA, RA>) =>
1224
+ mapBothEffect({ mapWarnings: Effect.succeed, mapSuccess });
1225
+
1226
+ /**
1227
+ * Effectful `flatMapSuccess`: chains the `success` value of a `WarnResult` into an
1228
+ * `Effect` that yields a new `WarnResult`, flattening the result.
1229
+ *
1230
+ * The mirror of `flatMapWarningsEffect`: when a `success` value is present it is
1231
+ * passed to `mapSuccess`, whose effectful `WarnResult` replaces the original;
1232
+ * `WarningsOnly` is lifted unchanged via `Effect.succeed`.
1233
+ *
1234
+ * @example
1235
+ * ```ts
1236
+ * import { WarnResult } from "@nunofyobiz/effect-extras"
1237
+ * import { Effect } from "effect"
1238
+ *
1239
+ * const chain = WarnResult.flatMapSuccessEffect((success: number) =>
1240
+ * Effect.succeed(WarnResult.SuccessOnly({ success: success + 1 }))
1241
+ * )
1242
+ *
1243
+ * assert.deepStrictEqual(
1244
+ * Effect.runSync(chain(WarnResult.SuccessOnly({ success: 1 }))),
1245
+ * WarnResult.SuccessOnly({ success: 2 })
1246
+ * )
1247
+ * assert.deepStrictEqual(
1248
+ * Effect.runSync(chain(WarnResult.WarningsOnly({ warnings: "w" }))),
1249
+ * WarnResult.WarningsOnly({ warnings: "w" })
1250
+ * )
1251
+ * ```
1252
+ *
1253
+ * @category sequencing
1254
+ * @since 0.0.0
1255
+ */
1256
+ export const flatMapSuccessEffect = <W2, A1, A2, EA, RA>(
1257
+ mapSuccess: (success: A1) => Effect.Effect<WarnResult<W2, A2>, EA, RA>,
1258
+ ): (<W1>(
1259
+ warnResult: WarnResult<W1, A1>,
1260
+ ) => Effect.Effect<WarnResult<W1 | W2, A2>, EA, RA>) =>
1261
+ match({
1262
+ WarningsOnly: ({ warnings }) => Effect.succeed(WarningsOnly({ warnings })),
1263
+ SuccessOnly: ({ success }) => mapSuccess(success),
1264
+ SuccessWithWarnings: ({ success }) => mapSuccess(success),
1265
+ });