@nunofyobiz/effect-extras 0.0.1

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