@nunofyobiz/effect-extras 2.0.0 → 3.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.
Files changed (133) hide show
  1. package/README.md +38 -3
  2. package/dist/ArrayX.d.ts +381 -0
  3. package/dist/ArrayX.d.ts.map +1 -0
  4. package/dist/ArrayX.js +493 -0
  5. package/dist/ArrayX.js.map +1 -0
  6. package/dist/BigIntX.d.ts +24 -0
  7. package/dist/BigIntX.d.ts.map +1 -0
  8. package/dist/BigIntX.js +30 -0
  9. package/dist/BigIntX.js.map +1 -0
  10. package/dist/BooleanX.d.ts +25 -0
  11. package/dist/BooleanX.d.ts.map +1 -0
  12. package/dist/BooleanX.js +25 -0
  13. package/dist/BooleanX.js.map +1 -0
  14. package/dist/DurationX.d.ts +73 -0
  15. package/dist/DurationX.d.ts.map +1 -0
  16. package/dist/DurationX.js +91 -0
  17. package/dist/DurationX.js.map +1 -0
  18. package/dist/EffectX.d.ts +120 -0
  19. package/dist/EffectX.d.ts.map +1 -0
  20. package/dist/EffectX.js +140 -0
  21. package/dist/EffectX.js.map +1 -0
  22. package/dist/FormDataX.d.ts +49 -0
  23. package/dist/FormDataX.d.ts.map +1 -0
  24. package/dist/FormDataX.js +42 -0
  25. package/dist/FormDataX.js.map +1 -0
  26. package/dist/InclusiveOr.d.ts +1123 -0
  27. package/dist/InclusiveOr.d.ts.map +1 -0
  28. package/dist/InclusiveOr.js +1074 -0
  29. package/dist/InclusiveOr.js.map +1 -0
  30. package/dist/MapX.d.ts +32 -0
  31. package/dist/MapX.d.ts.map +1 -0
  32. package/dist/MapX.js +49 -0
  33. package/dist/MapX.js.map +1 -0
  34. package/dist/NonNullableX.d.ts +174 -0
  35. package/dist/NonNullableX.d.ts.map +1 -0
  36. package/dist/NonNullableX.js +217 -0
  37. package/dist/NonNullableX.js.map +1 -0
  38. package/dist/NumberX.d.ts +178 -0
  39. package/dist/NumberX.d.ts.map +1 -0
  40. package/dist/NumberX.js +214 -0
  41. package/dist/NumberX.js.map +1 -0
  42. package/dist/OptionX.d.ts +187 -0
  43. package/dist/OptionX.d.ts.map +1 -0
  44. package/dist/OptionX.js +201 -0
  45. package/dist/OptionX.js.map +1 -0
  46. package/dist/OrderX.d.ts +32 -0
  47. package/dist/OrderX.d.ts.map +1 -0
  48. package/dist/OrderX.js +32 -0
  49. package/dist/OrderX.js.map +1 -0
  50. package/dist/PredicateX.d.ts +108 -0
  51. package/dist/PredicateX.d.ts.map +1 -0
  52. package/dist/PredicateX.js +111 -0
  53. package/dist/PredicateX.js.map +1 -0
  54. package/dist/PromiseX.d.ts +32 -0
  55. package/dist/PromiseX.d.ts.map +1 -0
  56. package/dist/PromiseX.js +32 -0
  57. package/dist/PromiseX.js.map +1 -0
  58. package/dist/RecordX.d.ts +450 -0
  59. package/dist/RecordX.d.ts.map +1 -0
  60. package/dist/RecordX.js +487 -0
  61. package/dist/RecordX.js.map +1 -0
  62. package/dist/ResultX.d.ts +50 -0
  63. package/dist/ResultX.d.ts.map +1 -0
  64. package/dist/ResultX.js +50 -0
  65. package/dist/ResultX.js.map +1 -0
  66. package/dist/SchemaX.d.ts +249 -0
  67. package/dist/SchemaX.d.ts.map +1 -0
  68. package/dist/SchemaX.js +243 -0
  69. package/dist/SchemaX.js.map +1 -0
  70. package/dist/SetX.d.ts +121 -0
  71. package/dist/SetX.d.ts.map +1 -0
  72. package/dist/SetX.js +137 -0
  73. package/dist/SetX.js.map +1 -0
  74. package/dist/StringX.d.ts +131 -0
  75. package/dist/StringX.d.ts.map +1 -0
  76. package/dist/StringX.js +149 -0
  77. package/dist/StringX.js.map +1 -0
  78. package/dist/StructX.d.ts +219 -0
  79. package/dist/StructX.d.ts.map +1 -0
  80. package/dist/StructX.js +173 -0
  81. package/dist/StructX.js.map +1 -0
  82. package/dist/WarnResult.d.ts +1191 -0
  83. package/dist/WarnResult.d.ts.map +1 -0
  84. package/dist/WarnResult.js +991 -0
  85. package/dist/WarnResult.js.map +1 -0
  86. package/dist/index.d.ts +23 -3772
  87. package/dist/index.d.ts.map +1 -0
  88. package/dist/index.js +22 -1011
  89. package/dist/index.js.map +1 -1
  90. package/package.json +18 -5
  91. package/src/{ArrayX/ArrayX.ts → ArrayX.ts} +6 -88
  92. package/src/{DurationX/DurationX.ts → DurationX.ts} +1 -1
  93. package/src/InclusiveOr.ts +1255 -0
  94. package/src/{NonNullableX/NonNullableX.ts → NonNullableX.ts} +5 -0
  95. package/src/{OptionX/OptionX.ts → OptionX.ts} +8 -2
  96. package/src/{PredicateX/PredicateX.ts → PredicateX.ts} +41 -0
  97. package/src/{RecordX/RecordX.ts → RecordX.ts} +184 -2
  98. package/src/StringX.ts +210 -0
  99. package/src/{WarnResult/WarnResult.ts → WarnResult.ts} +297 -227
  100. package/src/index.ts +22 -20
  101. package/src/ArrayX/index.ts +0 -1
  102. package/src/BigIntX/index.ts +0 -1
  103. package/src/BooleanX/index.ts +0 -1
  104. package/src/DurationX/index.ts +0 -1
  105. package/src/EffectX/index.ts +0 -1
  106. package/src/FormDataX/index.ts +0 -1
  107. package/src/MapX/index.ts +0 -1
  108. package/src/NonNullableX/index.ts +0 -2
  109. package/src/NumberX/index.ts +0 -1
  110. package/src/OptionX/index.ts +0 -1
  111. package/src/OrderX/index.ts +0 -1
  112. package/src/PredicateX/index.ts +0 -1
  113. package/src/PromiseX/index.ts +0 -1
  114. package/src/RecordX/index.ts +0 -1
  115. package/src/ResultX/index.ts +0 -1
  116. package/src/SchemaX/index.ts +0 -1
  117. package/src/SetX/index.ts +0 -1
  118. package/src/StringX/StringX.ts +0 -97
  119. package/src/StringX/index.ts +0 -1
  120. package/src/StructX/index.ts +0 -1
  121. package/src/WarnResult/index.ts +0 -1
  122. /package/src/{BigIntX/BigIntX.ts → BigIntX.ts} +0 -0
  123. /package/src/{BooleanX/BooleanX.ts → BooleanX.ts} +0 -0
  124. /package/src/{EffectX/EffectX.ts → EffectX.ts} +0 -0
  125. /package/src/{FormDataX/FormDataX.ts → FormDataX.ts} +0 -0
  126. /package/src/{MapX/MapX.ts → MapX.ts} +0 -0
  127. /package/src/{NumberX/NumberX.ts → NumberX.ts} +0 -0
  128. /package/src/{OrderX/OrderX.ts → OrderX.ts} +0 -0
  129. /package/src/{PromiseX/PromiseX.ts → PromiseX.ts} +0 -0
  130. /package/src/{ResultX/ResultX.ts → ResultX.ts} +0 -0
  131. /package/src/{SchemaX/SchemaX.ts → SchemaX.ts} +0 -0
  132. /package/src/{SetX/SetX.ts → SetX.ts} +0 -0
  133. /package/src/{StructX/StructX.ts → StructX.ts} +0 -0
@@ -0,0 +1,1255 @@
1
+ /**
2
+ * The `InclusiveOr` data type — an inclusive-or carrying a `left`, a `right`, or both at once.
3
+ *
4
+ * @since 0.0.0
5
+ */
6
+ import { Array, Data, Effect, Option, Predicate, Struct, pipe } from "effect";
7
+ import { constUndefined, dual, 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
+ * `InclusiveOr` 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 { InclusiveOr } from "@nunofyobiz/effect-extras"
22
+ *
23
+ * const both: InclusiveOr.InclusiveOr<number, string> = InclusiveOr.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 InclusiveOr<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 `InclusiveOr` — a value that carries only a `left` and no
51
+ * `right`.
52
+ *
53
+ * @example
54
+ * ```ts
55
+ * import { InclusiveOr } from "@nunofyobiz/effect-extras"
56
+ *
57
+ * const value: InclusiveOr.LeftOnly<number> = InclusiveOr.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> = InclusiveOr<L, never> & {
66
+ _tag: "LeftOnly";
67
+ };
68
+
69
+ /**
70
+ * The `RightOnly` member of `InclusiveOr` — a value that carries only a `right` and no
71
+ * `left`.
72
+ *
73
+ * @example
74
+ * ```ts
75
+ * import { InclusiveOr } from "@nunofyobiz/effect-extras"
76
+ *
77
+ * const value: InclusiveOr.RightOnly<string> = InclusiveOr.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> = InclusiveOr<never, R> & {
86
+ _tag: "RightOnly";
87
+ };
88
+
89
+ /**
90
+ * The `LeftAndRight` member of `InclusiveOr` — a value that carries both a `left` and a
91
+ * `right`.
92
+ *
93
+ * @example
94
+ * ```ts
95
+ * import { InclusiveOr } from "@nunofyobiz/effect-extras"
96
+ *
97
+ * const value: InclusiveOr.LeftAndRight<number, string> = InclusiveOr.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> = InclusiveOr<L, R> & {
109
+ _tag: "LeftAndRight";
110
+ };
111
+
112
+ /**
113
+ * Any `InclusiveOr` that is guaranteed to carry a `left` — either `LeftOnly` or
114
+ * `LeftAndRight`.
115
+ *
116
+ * @example
117
+ * ```ts
118
+ * import { InclusiveOr } from "@nunofyobiz/effect-extras"
119
+ *
120
+ * const value: InclusiveOr.WithLeft<number, string> = InclusiveOr.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 `InclusiveOr` that is guaranteed to carry a `right` — either `RightOnly` or
132
+ * `LeftAndRight`.
133
+ *
134
+ * @example
135
+ * ```ts
136
+ * import { InclusiveOr } from "@nunofyobiz/effect-extras"
137
+ *
138
+ * const value: InclusiveOr.WithRight<number, string> = InclusiveOr.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 InclusiveOrDefinition extends Data.TaggedEnum.WithGenerics<2> {
149
+ readonly taggedEnum: InclusiveOr<this["A"], this["B"]>;
150
+ }
151
+
152
+ const taggedEnum = Data.taggedEnum<InclusiveOrDefinition>();
153
+
154
+ /**
155
+ * Constructs a `LeftOnly` — an `InclusiveOr` that carries only a `left`.
156
+ *
157
+ * @example
158
+ * ```ts
159
+ * import { InclusiveOr } from "@nunofyobiz/effect-extras"
160
+ *
161
+ * const value = InclusiveOr.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` — an `InclusiveOr` that carries only a `right`.
174
+ *
175
+ * @example
176
+ * ```ts
177
+ * import { InclusiveOr } from "@nunofyobiz/effect-extras"
178
+ *
179
+ * const value = InclusiveOr.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` — an `InclusiveOr` that carries both a `left` and a
192
+ * `right`.
193
+ *
194
+ * @example
195
+ * ```ts
196
+ * import { InclusiveOr } from "@nunofyobiz/effect-extras"
197
+ *
198
+ * const value = InclusiveOr.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 `InclusiveOr`. `is("LeftOnly")` is a type guard that
212
+ * narrows an `InclusiveOr` to its `LeftOnly` member, and likewise for `"RightOnly"` and
213
+ * `"LeftAndRight"`.
214
+ *
215
+ * @example
216
+ * ```ts
217
+ * import { InclusiveOr } from "@nunofyobiz/effect-extras"
218
+ *
219
+ * assert.deepStrictEqual(InclusiveOr.is("LeftOnly")(InclusiveOr.LeftOnly({ left: 1 })), true)
220
+ * assert.deepStrictEqual(
221
+ * InclusiveOr.is("LeftOnly")(InclusiveOr.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 an `InclusiveOr` over its three tags. Provide a handler for `LeftOnly`,
233
+ * `RightOnly`, and `LeftAndRight` and `match` returns a function from an `InclusiveOr`
234
+ * to the handlers' common result type.
235
+ *
236
+ * @example
237
+ * ```ts
238
+ * import { InclusiveOr } from "@nunofyobiz/effect-extras"
239
+ *
240
+ * const describe = InclusiveOr.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(InclusiveOr.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 an `InclusiveOr` 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 { InclusiveOr } from "@nunofyobiz/effect-extras"
269
+ *
270
+ * assert.deepStrictEqual(
271
+ * InclusiveOr.WithLeft({ left: 1, right: "a" }),
272
+ * InclusiveOr.LeftAndRight({ left: 1, right: "a" })
273
+ * )
274
+ *
275
+ * assert.deepStrictEqual(
276
+ * InclusiveOr.WithLeft({ left: 1 }),
277
+ * InclusiveOr.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 an `InclusiveOr` 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 { InclusiveOr } from "@nunofyobiz/effect-extras"
307
+ *
308
+ * assert.deepStrictEqual(
309
+ * InclusiveOr.WithRight({ left: 1, right: "a" }),
310
+ * InclusiveOr.LeftAndRight({ left: 1, right: "a" })
311
+ * )
312
+ *
313
+ * assert.deepStrictEqual(
314
+ * InclusiveOr.WithRight({ right: "a" }),
315
+ * InclusiveOr.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 an `InclusiveOr` 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 an `InclusiveOr`.
341
+ *
342
+ * @example
343
+ * ```ts
344
+ * import { InclusiveOr } from "@nunofyobiz/effect-extras"
345
+ * import { Option } from "effect"
346
+ *
347
+ * assert.deepStrictEqual(
348
+ * InclusiveOr.optionFromNullables({ left: 1, right: "a" }),
349
+ * Option.some(InclusiveOr.LeftAndRight({ left: 1, right: "a" }))
350
+ * )
351
+ *
352
+ * assert.deepStrictEqual(
353
+ * InclusiveOr.optionFromNullables({ left: 1, right: null }),
354
+ * Option.some(InclusiveOr.LeftOnly({ left: 1 }))
355
+ * )
356
+ *
357
+ * assert.deepStrictEqual(
358
+ * InclusiveOr.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<InclusiveOr<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 an `InclusiveOr` 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 { InclusiveOr } from "@nunofyobiz/effect-extras"
397
+ *
398
+ * assert.deepStrictEqual(
399
+ * InclusiveOr.fromNullables({ left: 1, right: "a" }),
400
+ * InclusiveOr.LeftAndRight({ left: 1, right: "a" })
401
+ * )
402
+ *
403
+ * // Both absent — fall back via orElse instead of throwing
404
+ * assert.deepStrictEqual(
405
+ * InclusiveOr.fromNullables({
406
+ * left: null,
407
+ * right: null,
408
+ * orElse: () => InclusiveOr.LeftOnly({ left: 0 })
409
+ * }),
410
+ * InclusiveOr.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?: () => InclusiveOr<L, R>;
427
+ }): InclusiveOr<L, R> =>
428
+ pipe(optionFromNullables({ left, right }), Option.getOrElse(orElse));
429
+
430
+ /**
431
+ * Folds an `InclusiveOr` 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 { InclusiveOr } from "@nunofyobiz/effect-extras"
441
+ *
442
+ * const onLeft = InclusiveOr.matchLeft({
443
+ * Left: (left: number) => `left ${left}`,
444
+ * RightOnly: (right: string) => `right ${right}`
445
+ * })
446
+ *
447
+ * assert.deepStrictEqual(onLeft(InclusiveOr.LeftOnly({ left: 1 })), "left 1")
448
+ * assert.deepStrictEqual(
449
+ * onLeft(InclusiveOr.LeftAndRight({ left: 1, right: "a" })),
450
+ * "left 1"
451
+ * )
452
+ * assert.deepStrictEqual(onLeft(InclusiveOr.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
+ (inclusiveOr: InclusiveOr<L, R>): A =>
467
+ // `$match` widens the common return to `Unify<A>`; narrow it back to `A`.
468
+ match(inclusiveOr, {
469
+ LeftOnly: ({ left }) => Left(left),
470
+ RightOnly: ({ right }) => RightOnly(right),
471
+ LeftAndRight: ({ left }) => Left(left),
472
+ }) as A;
473
+
474
+ /**
475
+ * Folds an `InclusiveOr` from the right's perspective, collapsing the three tags into two
476
+ * handlers.
477
+ *
478
+ * The mirror of `matchLeft`: both `RightOnly` and `LeftAndRight` carry a `right`,
479
+ * so they route to the `Right` handler; only `LeftOnly` lacks a `right` and routes
480
+ * to `LeftOnly`. Use it when you care about the `right` value and treat the
481
+ * left-only case as the exception.
482
+ *
483
+ * @example
484
+ * ```ts
485
+ * import { InclusiveOr } from "@nunofyobiz/effect-extras"
486
+ *
487
+ * const onRight = InclusiveOr.matchRight({
488
+ * LeftOnly: (left: number) => `left ${left}`,
489
+ * Right: (right: string) => `right ${right}`
490
+ * })
491
+ *
492
+ * assert.deepStrictEqual(onRight(InclusiveOr.RightOnly({ right: "a" })), "right a")
493
+ * assert.deepStrictEqual(
494
+ * onRight(InclusiveOr.LeftAndRight({ left: 1, right: "a" })),
495
+ * "right a"
496
+ * )
497
+ * assert.deepStrictEqual(onRight(InclusiveOr.LeftOnly({ left: 1 })), "left 1")
498
+ * ```
499
+ *
500
+ * @category pattern matching
501
+ * @since 0.0.0
502
+ */
503
+ export const matchRight =
504
+ <L, R, A>({
505
+ LeftOnly,
506
+ Right,
507
+ }: {
508
+ LeftOnly: (left: L) => A;
509
+ Right: (right: R) => A;
510
+ }) =>
511
+ (inclusiveOr: InclusiveOr<L, R>): A =>
512
+ // `$match` widens the common return to `Unify<A>`; narrow it back to `A`.
513
+ match(inclusiveOr, {
514
+ LeftOnly: ({ left }) => LeftOnly(left),
515
+ RightOnly: ({ right }) => Right(right),
516
+ LeftAndRight: ({ right }) => Right(right),
517
+ }) as A;
518
+
519
+ /**
520
+ * Completes an `InclusiveOr` into a guaranteed `LeftAndRight` by filling whichever side
521
+ * is missing from the matching `orElse` thunk.
522
+ *
523
+ * A `LeftAndRight` passes through unchanged; a `LeftOnly` gains a `right` from
524
+ * `orElseRight`; a `RightOnly` gains a `left` from `orElseLeft`. Use it to
525
+ * normalise a partial `InclusiveOr` into the both-present shape before reading both
526
+ * sides.
527
+ *
528
+ * @example
529
+ * ```ts
530
+ * import { InclusiveOr } from "@nunofyobiz/effect-extras"
531
+ *
532
+ * const fill = InclusiveOr.orElse({
533
+ * orElseLeft: () => 0,
534
+ * orElseRight: () => "default"
535
+ * })
536
+ *
537
+ * assert.deepStrictEqual(
538
+ * fill(InclusiveOr.LeftOnly({ left: 1 })),
539
+ * InclusiveOr.LeftAndRight({ left: 1, right: "default" })
540
+ * )
541
+ * assert.deepStrictEqual(
542
+ * fill(InclusiveOr.RightOnly({ right: "a" })),
543
+ * InclusiveOr.LeftAndRight({ left: 0, right: "a" })
544
+ * )
545
+ * ```
546
+ *
547
+ * @category getters
548
+ * @since 0.0.0
549
+ */
550
+ export const orElse = <L2, R2>({
551
+ orElseLeft,
552
+ orElseRight,
553
+ }: {
554
+ orElseLeft: () => L2;
555
+ orElseRight: () => R2;
556
+ }): (<L, R>(inclusiveOr: InclusiveOr<L, R>) => LeftAndRight<L | L2, R | R2>) =>
557
+ match({
558
+ LeftOnly: ({ left }) => LeftAndRight({ left, right: orElseRight() }),
559
+ RightOnly: ({ right }) => LeftAndRight({ left: orElseLeft(), right }),
560
+ LeftAndRight: ({ left, right }) => LeftAndRight({ left, right }),
561
+ });
562
+
563
+ /**
564
+ * Completes an `InclusiveOr` into a `LeftAndRight` whose missing side is filled with
565
+ * `undefined`.
566
+ *
567
+ * A specialisation of `orElse` that supplies `undefined` for whichever side is
568
+ * absent, so the result always exposes both `left` and `right` keys (each
569
+ * possibly `undefined`). Use it when you want to destructure both sides without
570
+ * branching on the tag.
571
+ *
572
+ * @example
573
+ * ```ts
574
+ * import { InclusiveOr } from "@nunofyobiz/effect-extras"
575
+ *
576
+ * assert.deepStrictEqual(
577
+ * InclusiveOr.orUndefined(InclusiveOr.LeftOnly({ left: 1 })),
578
+ * InclusiveOr.LeftAndRight({ left: 1, right: undefined })
579
+ * )
580
+ * assert.deepStrictEqual(
581
+ * InclusiveOr.orUndefined(InclusiveOr.RightOnly({ right: "a" })),
582
+ * InclusiveOr.LeftAndRight({ left: undefined, right: "a" })
583
+ * )
584
+ * ```
585
+ *
586
+ * @category getters
587
+ * @since 0.0.0
588
+ */
589
+ export const orUndefined = orElse({
590
+ orElseLeft: () => undefined,
591
+ orElseRight: () => undefined,
592
+ });
593
+
594
+ /**
595
+ * Extracts the `left` of an `InclusiveOr`, falling back to `orElseReturn` when no `left`
596
+ * is present.
597
+ *
598
+ * `LeftOnly` and `LeftAndRight` return their `left`; `RightOnly` returns the
599
+ * result of `orElseReturn`. Use it to read the left side with a default in one
600
+ * step.
601
+ *
602
+ * @example
603
+ * ```ts
604
+ * import { InclusiveOr } from "@nunofyobiz/effect-extras"
605
+ *
606
+ * const leftOrZero = InclusiveOr.leftOrElse(() => 0)
607
+ *
608
+ * assert.deepStrictEqual(leftOrZero(InclusiveOr.LeftOnly({ left: 1 })), 1)
609
+ * assert.deepStrictEqual(
610
+ * leftOrZero(InclusiveOr.LeftAndRight({ left: 1, right: "a" })),
611
+ * 1
612
+ * )
613
+ * assert.deepStrictEqual(leftOrZero(InclusiveOr.RightOnly({ right: "a" })), 0)
614
+ * ```
615
+ *
616
+ * @category getters
617
+ * @since 0.0.0
618
+ */
619
+ export const leftOrElse =
620
+ <A>(orElseReturn: () => A) =>
621
+ <L, R>(inclusiveOr: InclusiveOr<L, R>): L | A =>
622
+ pipe(
623
+ inclusiveOr,
624
+ orElse({
625
+ orElseLeft: orElseReturn,
626
+ orElseRight: constUndefined,
627
+ }),
628
+ Struct.get("left"),
629
+ );
630
+
631
+ /**
632
+ * Extracts the `left` of an `InclusiveOr`, returning `undefined` when no `left` is
633
+ * present.
634
+ *
635
+ * A specialisation of `leftOrElse` whose fallback is `undefined`: `LeftOnly` and
636
+ * `LeftAndRight` yield their `left`, while `RightOnly` yields `undefined`.
637
+ *
638
+ * @example
639
+ * ```ts
640
+ * import { InclusiveOr } from "@nunofyobiz/effect-extras"
641
+ *
642
+ * assert.deepStrictEqual(InclusiveOr.leftOrUndefined(InclusiveOr.LeftOnly({ left: 1 })), 1)
643
+ * assert.deepStrictEqual(
644
+ * InclusiveOr.leftOrUndefined(InclusiveOr.RightOnly({ right: "a" })),
645
+ * undefined
646
+ * )
647
+ * ```
648
+ *
649
+ * @category getters
650
+ * @since 0.0.0
651
+ */
652
+ export const leftOrUndefined = leftOrElse(() => undefined);
653
+
654
+ /**
655
+ * Extracts the `right` of an `InclusiveOr`, falling back to `orElseReturn` when no
656
+ * `right` is present.
657
+ *
658
+ * The mirror of `leftOrElse`: `RightOnly` and `LeftAndRight` return their
659
+ * `right`; `LeftOnly` returns the result of `orElseReturn`.
660
+ *
661
+ * @example
662
+ * ```ts
663
+ * import { InclusiveOr } from "@nunofyobiz/effect-extras"
664
+ *
665
+ * const rightOrDefault = InclusiveOr.rightOrElse(() => "default")
666
+ *
667
+ * assert.deepStrictEqual(
668
+ * rightOrDefault(InclusiveOr.RightOnly({ right: "a" })),
669
+ * "a"
670
+ * )
671
+ * assert.deepStrictEqual(
672
+ * rightOrDefault(InclusiveOr.LeftOnly({ left: 1 })),
673
+ * "default"
674
+ * )
675
+ * ```
676
+ *
677
+ * @category getters
678
+ * @since 0.0.0
679
+ */
680
+ export const rightOrElse =
681
+ <A>(orElseReturn: () => A) =>
682
+ <L, R>(inclusiveOr: InclusiveOr<L, R>): R | A =>
683
+ pipe(
684
+ inclusiveOr,
685
+ orElse({
686
+ orElseLeft: constUndefined,
687
+ orElseRight: orElseReturn,
688
+ }),
689
+ Struct.get("right"),
690
+ );
691
+
692
+ /**
693
+ * Extracts the `right` of an `InclusiveOr`, returning `undefined` when no `right` is
694
+ * present.
695
+ *
696
+ * A specialisation of `rightOrElse` whose fallback is `undefined`: `RightOnly` and
697
+ * `LeftAndRight` yield their `right`, while `LeftOnly` yields `undefined`.
698
+ *
699
+ * @example
700
+ * ```ts
701
+ * import { InclusiveOr } from "@nunofyobiz/effect-extras"
702
+ *
703
+ * assert.deepStrictEqual(
704
+ * InclusiveOr.rightOrUndefined(InclusiveOr.RightOnly({ right: "a" })),
705
+ * "a"
706
+ * )
707
+ * assert.deepStrictEqual(
708
+ * InclusiveOr.rightOrUndefined(InclusiveOr.LeftOnly({ left: 1 })),
709
+ * undefined
710
+ * )
711
+ * ```
712
+ *
713
+ * @category getters
714
+ * @since 0.0.0
715
+ */
716
+ export const rightOrUndefined = rightOrElse(() => undefined);
717
+
718
+ /**
719
+ * Extracts the `right` of an `InclusiveOr` as an `Option`.
720
+ *
721
+ * `RightOnly` and `LeftAndRight` yield `Option.some(right)`; `LeftOnly` yields
722
+ * `Option.none()`. Use it when you want to chain the right side through `Option`
723
+ * combinators rather than fall back to a default eagerly.
724
+ *
725
+ * @example
726
+ * ```ts
727
+ * import { InclusiveOr } from "@nunofyobiz/effect-extras"
728
+ * import { Option } from "effect"
729
+ *
730
+ * assert.deepStrictEqual(
731
+ * InclusiveOr.rightOption(InclusiveOr.RightOnly({ right: "a" })),
732
+ * Option.some("a")
733
+ * )
734
+ * assert.deepStrictEqual(
735
+ * InclusiveOr.rightOption(InclusiveOr.LeftOnly({ left: 1 })),
736
+ * Option.none()
737
+ * )
738
+ * ```
739
+ *
740
+ * @category getters
741
+ * @since 0.0.0
742
+ */
743
+ export const rightOption = <L, R>(
744
+ inclusiveOr: InclusiveOr<L, R>,
745
+ ): Option.Option<R> =>
746
+ pipe(
747
+ inclusiveOr,
748
+ matchRight({
749
+ LeftOnly: () => Option.none(),
750
+ Right: Option.some,
751
+ }),
752
+ );
753
+
754
+ /**
755
+ * Extracts the `left` of an `InclusiveOr` as an `Option`.
756
+ *
757
+ * The mirror of `rightOption`: `LeftOnly` and `LeftAndRight` yield
758
+ * `Option.some(left)`, while `RightOnly` yields `Option.none()`.
759
+ *
760
+ * @example
761
+ * ```ts
762
+ * import { InclusiveOr } from "@nunofyobiz/effect-extras"
763
+ * import { Option } from "effect"
764
+ *
765
+ * assert.deepStrictEqual(
766
+ * InclusiveOr.leftOption(InclusiveOr.LeftOnly({ left: 1 })),
767
+ * Option.some(1)
768
+ * )
769
+ * assert.deepStrictEqual(
770
+ * InclusiveOr.leftOption(InclusiveOr.RightOnly({ right: "a" })),
771
+ * Option.none()
772
+ * )
773
+ * ```
774
+ *
775
+ * @category getters
776
+ * @since 0.0.0
777
+ */
778
+ export const leftOption = <L, R>(
779
+ inclusiveOr: InclusiveOr<L, R>,
780
+ ): Option.Option<L> =>
781
+ pipe(
782
+ inclusiveOr,
783
+ matchLeft({
784
+ Left: Option.some,
785
+ RightOnly: () => Option.none(),
786
+ }),
787
+ );
788
+
789
+ /**
790
+ * Transforms both sides of an `InclusiveOr`, applying `mapLeft` to any `left` and
791
+ * `mapRight` to any `right`.
792
+ *
793
+ * Each constructor is rebuilt with its mapped contents, so `LeftOnly` maps only
794
+ * the left, `RightOnly` only the right, and `LeftAndRight` both. The tag is
795
+ * preserved. Use it as the bifunctor map over `InclusiveOr`.
796
+ *
797
+ * @example
798
+ * ```ts
799
+ * import { InclusiveOr } from "@nunofyobiz/effect-extras"
800
+ *
801
+ * const both = InclusiveOr.mapBoth({
802
+ * mapLeft: (left: number) => left + 1,
803
+ * mapRight: (right: string) => right.toUpperCase()
804
+ * })
805
+ *
806
+ * assert.deepStrictEqual(
807
+ * both(InclusiveOr.LeftAndRight({ left: 1, right: "a" })),
808
+ * InclusiveOr.LeftAndRight({ left: 2, right: "A" })
809
+ * )
810
+ * assert.deepStrictEqual(
811
+ * both(InclusiveOr.LeftOnly({ left: 1 })),
812
+ * InclusiveOr.LeftOnly({ left: 2 })
813
+ * )
814
+ * ```
815
+ *
816
+ * @category mapping
817
+ * @since 0.0.0
818
+ */
819
+ export const mapBoth = <L1, R1, L2, R2>({
820
+ mapLeft,
821
+ mapRight,
822
+ }: {
823
+ mapLeft: (left: L1) => L2;
824
+ mapRight: (right: R1) => R2;
825
+ }): ((inclusiveOr: InclusiveOr<L1, R1>) => InclusiveOr<L2, R2>) =>
826
+ match({
827
+ LeftOnly: ({ left }) => LeftOnly({ left: mapLeft(left) }),
828
+ RightOnly: ({ right }) => RightOnly({ right: mapRight(right) }),
829
+ LeftAndRight: ({ left, right }) =>
830
+ LeftAndRight({ left: mapLeft(left), right: mapRight(right) }),
831
+ });
832
+
833
+ /**
834
+ * Effectful `mapBoth`: transforms each present side through an `Effect`,
835
+ * reassembling the results into an `InclusiveOr` inside an `Effect`.
836
+ *
837
+ * For `LeftAndRight` both effects run via `Effect.all` and their results are
838
+ * combined; `LeftOnly`/`RightOnly` run only the relevant effect. Errors and
839
+ * requirements from both mappers are unioned into the result type. Use it when
840
+ * mapping an `InclusiveOr`'s sides requires effects (validation, IO).
841
+ *
842
+ * @example
843
+ * ```ts
844
+ * import { InclusiveOr } from "@nunofyobiz/effect-extras"
845
+ * import { Effect } from "effect"
846
+ *
847
+ * const both = InclusiveOr.mapBothEffect({
848
+ * mapLeft: (left: number) => Effect.succeed(left + 1),
849
+ * mapRight: (right: string) => Effect.succeed(right.toUpperCase())
850
+ * })
851
+ *
852
+ * assert.deepStrictEqual(
853
+ * Effect.runSync(both(InclusiveOr.LeftAndRight({ left: 1, right: "a" }))),
854
+ * InclusiveOr.LeftAndRight({ left: 2, right: "A" })
855
+ * )
856
+ * ```
857
+ *
858
+ * @category sequencing
859
+ * @since 0.0.0
860
+ */
861
+ export const mapBothEffect = <L1, R1, L2, R2, EL, ER, RL, RR>({
862
+ mapLeft,
863
+ mapRight,
864
+ }: {
865
+ mapLeft: (left: L1) => Effect.Effect<L2, EL, RL>;
866
+ mapRight: (right: R1) => Effect.Effect<R2, ER, RR>;
867
+ }): ((
868
+ inclusiveOr: InclusiveOr<L1, R1>,
869
+ ) => Effect.Effect<InclusiveOr<L2, R2>, EL | ER, RL | RR>) =>
870
+ match({
871
+ LeftOnly: ({ left }) =>
872
+ pipe(
873
+ mapLeft(left),
874
+ Effect.map((left2) => LeftOnly({ left: left2 })),
875
+ ),
876
+
877
+ RightOnly: ({ right }) =>
878
+ pipe(
879
+ mapRight(right),
880
+ Effect.map((right2) => RightOnly({ right: right2 })),
881
+ ),
882
+
883
+ LeftAndRight: ({ left, right }) =>
884
+ pipe(
885
+ Effect.all({ left: mapLeft(left), right: mapRight(right) }),
886
+ Effect.map(({ left: left2, right: right2 }) =>
887
+ LeftAndRight({ left: left2, right: right2 }),
888
+ ),
889
+ ),
890
+ });
891
+
892
+ /**
893
+ * Transforms the `left` of an `InclusiveOr`, leaving any `right` untouched.
894
+ *
895
+ * A specialisation of `mapBoth` with the right mapper set to `identity`:
896
+ * `LeftOnly` and `LeftAndRight` have their `left` mapped, while `RightOnly` passes
897
+ * through unchanged.
898
+ *
899
+ * @example
900
+ * ```ts
901
+ * import { InclusiveOr } from "@nunofyobiz/effect-extras"
902
+ *
903
+ * const inc = InclusiveOr.mapLeft((left: number) => left + 1)
904
+ *
905
+ * assert.deepStrictEqual(
906
+ * inc(InclusiveOr.LeftAndRight({ left: 1, right: "a" })),
907
+ * InclusiveOr.LeftAndRight({ left: 2, right: "a" })
908
+ * )
909
+ * assert.deepStrictEqual(
910
+ * inc(InclusiveOr.RightOnly({ right: "a" })),
911
+ * InclusiveOr.RightOnly({ right: "a" })
912
+ * )
913
+ * ```
914
+ *
915
+ * @category mapping
916
+ * @since 0.0.0
917
+ */
918
+ export const mapLeft = <L1, L2>(
919
+ mapLeft: (left: L1) => L2,
920
+ ): (<R>(inclusiveOr: InclusiveOr<L1, R>) => InclusiveOr<L2, R>) =>
921
+ mapBoth({ mapLeft, mapRight: identity });
922
+
923
+ /**
924
+ * Chains the `left` of an `InclusiveOr` into a new `InclusiveOr`, flattening the result.
925
+ *
926
+ * Whenever a `left` is present (`LeftOnly` or `LeftAndRight`) it is passed to
927
+ * `mapLeft`, whose returned `InclusiveOr` replaces the original; `RightOnly` passes
928
+ * through unchanged. Use it to sequence left-driven computations that themselves
929
+ * produce an `InclusiveOr`.
930
+ *
931
+ * @example
932
+ * ```ts
933
+ * import { InclusiveOr } from "@nunofyobiz/effect-extras"
934
+ *
935
+ * const chain = InclusiveOr.flatMapLeft((left: number) =>
936
+ * left > 0
937
+ * ? InclusiveOr.LeftOnly({ left: left * 10 })
938
+ * : InclusiveOr.RightOnly({ right: "non-positive" })
939
+ * )
940
+ *
941
+ * assert.deepStrictEqual(
942
+ * chain(InclusiveOr.LeftOnly({ left: 2 })),
943
+ * InclusiveOr.LeftOnly({ left: 20 })
944
+ * )
945
+ * assert.deepStrictEqual(
946
+ * chain(InclusiveOr.RightOnly({ right: "a" })),
947
+ * InclusiveOr.RightOnly({ right: "a" })
948
+ * )
949
+ * ```
950
+ *
951
+ * @category sequencing
952
+ * @since 0.0.0
953
+ */
954
+ export const flatMapLeft = <L1, L2, R2>(
955
+ mapLeft: (left: L1) => InclusiveOr<L2, R2>,
956
+ ): (<R1>(inclusiveOr: InclusiveOr<L1, R1>) => InclusiveOr<L2, R1 | R2>) =>
957
+ match({
958
+ LeftOnly: ({ left }) => mapLeft(left),
959
+ RightOnly: ({ right }) => RightOnly({ right }),
960
+ LeftAndRight: ({ left }) => mapLeft(left),
961
+ });
962
+
963
+ /**
964
+ * Effectful `mapLeft`: transforms the `left` of an `InclusiveOr` through an `Effect`,
965
+ * leaving any `right` untouched.
966
+ *
967
+ * A specialisation of `mapBothEffect` with the right mapper set to
968
+ * `Effect.succeed`: the `left` (when present) is mapped effectfully and the
969
+ * `right` is carried through unchanged.
970
+ *
971
+ * @example
972
+ * ```ts
973
+ * import { InclusiveOr } from "@nunofyobiz/effect-extras"
974
+ * import { Effect } from "effect"
975
+ *
976
+ * const inc = InclusiveOr.mapLeftEffect((left: number) => Effect.succeed(left + 1))
977
+ *
978
+ * assert.deepStrictEqual(
979
+ * Effect.runSync(inc(InclusiveOr.LeftAndRight({ left: 1, right: "a" }))),
980
+ * InclusiveOr.LeftAndRight({ left: 2, right: "a" })
981
+ * )
982
+ * ```
983
+ *
984
+ * @category sequencing
985
+ * @since 0.0.0
986
+ */
987
+ export const mapLeftEffect = <L1, L2, EL, RL>(
988
+ mapLeft: (left: L1) => Effect.Effect<L2, EL, RL>,
989
+ ): (<R>(
990
+ inclusiveOr: InclusiveOr<L1, R>,
991
+ ) => Effect.Effect<InclusiveOr<L2, R>, EL, RL>) =>
992
+ mapBothEffect({ mapLeft, mapRight: Effect.succeed });
993
+
994
+ /**
995
+ * Effectful `flatMapLeft`: chains the `left` of an `InclusiveOr` into an `Effect` that
996
+ * yields a new `InclusiveOr`, flattening the result.
997
+ *
998
+ * When a `left` is present it is passed to `mapLeft`, whose effectful `InclusiveOr`
999
+ * replaces the original; `RightOnly` is lifted unchanged via `Effect.succeed`. Use
1000
+ * it to sequence left-driven effectful computations that produce an `InclusiveOr`.
1001
+ *
1002
+ * @example
1003
+ * ```ts
1004
+ * import { InclusiveOr } from "@nunofyobiz/effect-extras"
1005
+ * import { Effect } from "effect"
1006
+ *
1007
+ * const chain = InclusiveOr.flatMapLeftEffect((left: number) =>
1008
+ * Effect.succeed(InclusiveOr.LeftOnly({ left: left * 10 }))
1009
+ * )
1010
+ *
1011
+ * assert.deepStrictEqual(
1012
+ * Effect.runSync(chain(InclusiveOr.LeftOnly({ left: 2 }))),
1013
+ * InclusiveOr.LeftOnly({ left: 20 })
1014
+ * )
1015
+ * assert.deepStrictEqual(
1016
+ * Effect.runSync(chain(InclusiveOr.RightOnly({ right: "a" }))),
1017
+ * InclusiveOr.RightOnly({ right: "a" })
1018
+ * )
1019
+ * ```
1020
+ *
1021
+ * @category sequencing
1022
+ * @since 0.0.0
1023
+ */
1024
+ export const flatMapLeftEffect = <L1, L2, R2, EL, RL>(
1025
+ mapLeft: (left: L1) => Effect.Effect<InclusiveOr<L2, R2>, EL, RL>,
1026
+ ): (<R1>(
1027
+ inclusiveOr: InclusiveOr<L1, R1>,
1028
+ ) => Effect.Effect<InclusiveOr<L2, R1 | R2>, EL, RL>) =>
1029
+ match({
1030
+ LeftOnly: ({ left }) => mapLeft(left),
1031
+ RightOnly: ({ right }) => Effect.succeed(RightOnly({ right })),
1032
+ LeftAndRight: ({ left }) => mapLeft(left),
1033
+ });
1034
+
1035
+ /**
1036
+ * Transforms the `right` of an `InclusiveOr`, leaving any `left` untouched.
1037
+ *
1038
+ * The mirror of `mapLeft`: `RightOnly` and `LeftAndRight` have their `right`
1039
+ * mapped, while `LeftOnly` passes through unchanged.
1040
+ *
1041
+ * @example
1042
+ * ```ts
1043
+ * import { InclusiveOr } from "@nunofyobiz/effect-extras"
1044
+ *
1045
+ * const upper = InclusiveOr.mapRight((right: string) => right.toUpperCase())
1046
+ *
1047
+ * assert.deepStrictEqual(
1048
+ * upper(InclusiveOr.LeftAndRight({ left: 1, right: "a" })),
1049
+ * InclusiveOr.LeftAndRight({ left: 1, right: "A" })
1050
+ * )
1051
+ * assert.deepStrictEqual(
1052
+ * upper(InclusiveOr.LeftOnly({ left: 1 })),
1053
+ * InclusiveOr.LeftOnly({ left: 1 })
1054
+ * )
1055
+ * ```
1056
+ *
1057
+ * @category mapping
1058
+ * @since 0.0.0
1059
+ */
1060
+ export const mapRight = <R1, R2>(
1061
+ mapRight: (right: R1) => R2,
1062
+ ): (<L>(inclusiveOr: InclusiveOr<L, R1>) => InclusiveOr<L, R2>) =>
1063
+ mapBoth({ mapLeft: identity, mapRight });
1064
+
1065
+ /**
1066
+ * Chains the `right` of an `InclusiveOr` into a new `InclusiveOr`, flattening the result.
1067
+ *
1068
+ * The mirror of `flatMapLeft`: whenever a `right` is present (`RightOnly` or
1069
+ * `LeftAndRight`) it is passed to `mapRight`, whose returned `InclusiveOr` replaces the
1070
+ * original; `LeftOnly` passes through unchanged.
1071
+ *
1072
+ * @example
1073
+ * ```ts
1074
+ * import { InclusiveOr } from "@nunofyobiz/effect-extras"
1075
+ *
1076
+ * const chain = InclusiveOr.flatMapRight((right: string) =>
1077
+ * InclusiveOr.RightOnly({ right: right.toUpperCase() })
1078
+ * )
1079
+ *
1080
+ * assert.deepStrictEqual(
1081
+ * chain(InclusiveOr.RightOnly({ right: "a" })),
1082
+ * InclusiveOr.RightOnly({ right: "A" })
1083
+ * )
1084
+ * assert.deepStrictEqual(
1085
+ * chain(InclusiveOr.LeftOnly({ left: 1 })),
1086
+ * InclusiveOr.LeftOnly({ left: 1 })
1087
+ * )
1088
+ * ```
1089
+ *
1090
+ * @category sequencing
1091
+ * @since 0.0.0
1092
+ */
1093
+ export const flatMapRight = <L2, R1, R2>(
1094
+ mapRight: (right: R1) => InclusiveOr<L2, R2>,
1095
+ ): (<L1>(inclusiveOr: InclusiveOr<L1, R1>) => InclusiveOr<L1 | L2, R2>) =>
1096
+ match({
1097
+ LeftOnly: ({ left }) => LeftOnly({ left }),
1098
+ RightOnly: ({ right }) => mapRight(right),
1099
+ LeftAndRight: ({ right }) => mapRight(right),
1100
+ });
1101
+
1102
+ /**
1103
+ * Effectful `mapRight`: transforms the `right` of an `InclusiveOr` through an `Effect`,
1104
+ * leaving any `left` untouched.
1105
+ *
1106
+ * The mirror of `mapLeftEffect`: the `right` (when present) is mapped effectfully
1107
+ * and the `left` is carried through unchanged via `Effect.succeed`.
1108
+ *
1109
+ * @example
1110
+ * ```ts
1111
+ * import { InclusiveOr } from "@nunofyobiz/effect-extras"
1112
+ * import { Effect } from "effect"
1113
+ *
1114
+ * const upper = InclusiveOr.mapRightEffect((right: string) =>
1115
+ * Effect.succeed(right.toUpperCase())
1116
+ * )
1117
+ *
1118
+ * assert.deepStrictEqual(
1119
+ * Effect.runSync(upper(InclusiveOr.LeftAndRight({ left: 1, right: "a" }))),
1120
+ * InclusiveOr.LeftAndRight({ left: 1, right: "A" })
1121
+ * )
1122
+ * ```
1123
+ *
1124
+ * @category sequencing
1125
+ * @since 0.0.0
1126
+ */
1127
+ export const mapRightEffect = <R1, R2, ER, RR>(
1128
+ mapRight: (right: R1) => Effect.Effect<R2, ER, RR>,
1129
+ ): (<L>(
1130
+ inclusiveOr: InclusiveOr<L, R1>,
1131
+ ) => Effect.Effect<InclusiveOr<L, R2>, ER, RR>) =>
1132
+ mapBothEffect({ mapLeft: Effect.succeed, mapRight });
1133
+
1134
+ /**
1135
+ * Effectful `flatMapRight`: chains the `right` of an `InclusiveOr` into an `Effect` that
1136
+ * yields a new `InclusiveOr`, flattening the result.
1137
+ *
1138
+ * The mirror of `flatMapLeftEffect`: when a `right` is present it is passed to
1139
+ * `mapRight`, whose effectful `InclusiveOr` replaces the original; `LeftOnly` is lifted
1140
+ * unchanged via `Effect.succeed`.
1141
+ *
1142
+ * @example
1143
+ * ```ts
1144
+ * import { InclusiveOr } from "@nunofyobiz/effect-extras"
1145
+ * import { Effect } from "effect"
1146
+ *
1147
+ * const chain = InclusiveOr.flatMapRightEffect((right: string) =>
1148
+ * Effect.succeed(InclusiveOr.RightOnly({ right: right.toUpperCase() }))
1149
+ * )
1150
+ *
1151
+ * assert.deepStrictEqual(
1152
+ * Effect.runSync(chain(InclusiveOr.RightOnly({ right: "a" }))),
1153
+ * InclusiveOr.RightOnly({ right: "A" })
1154
+ * )
1155
+ * assert.deepStrictEqual(
1156
+ * Effect.runSync(chain(InclusiveOr.LeftOnly({ left: 1 }))),
1157
+ * InclusiveOr.LeftOnly({ left: 1 })
1158
+ * )
1159
+ * ```
1160
+ *
1161
+ * @category sequencing
1162
+ * @since 0.0.0
1163
+ */
1164
+ export const flatMapRightEffect = <L2, R1, R2, ER, RR>(
1165
+ mapRight: (right: R1) => Effect.Effect<InclusiveOr<L2, R2>, ER, RR>,
1166
+ ): (<L1>(
1167
+ inclusiveOr: InclusiveOr<L1, R1>,
1168
+ ) => Effect.Effect<InclusiveOr<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
+ });
1174
+
1175
+ /**
1176
+ * Zips two arrays into one, calling `f` with an `InclusiveOr` for each index so
1177
+ * that length mismatches are handled explicitly rather than truncated.
1178
+ *
1179
+ * Unlike `Array.zipWith` (which stops at the shorter array), this walks to the
1180
+ * length of the *longer* array. The first array's element fills the `left` side
1181
+ * and the second array's element fills the `right` side, so at each index `f`
1182
+ * receives an `InclusiveOr<A, B>`: `LeftAndRight` when both arrays have an
1183
+ * element, `LeftOnly` when only the first does, and `RightOnly` when only the
1184
+ * second does. Use it when the "extra" tail of either array still carries
1185
+ * meaning.
1186
+ *
1187
+ * @example
1188
+ * ```ts
1189
+ * import { InclusiveOr } from "@nunofyobiz/effect-extras"
1190
+ * import { pipe } from "effect"
1191
+ *
1192
+ * const describe = InclusiveOr.match({
1193
+ * LeftOnly: ({ left }) => `left ${left}`,
1194
+ * RightOnly: ({ right }) => `right ${right}`,
1195
+ * LeftAndRight: ({ left, right }) => `both ${left}/${right}`
1196
+ * })
1197
+ *
1198
+ * // data-first
1199
+ * assert.deepStrictEqual(InclusiveOr.zip([1, 2, 3], [10, 20], describe), [
1200
+ * "both 1/10",
1201
+ * "both 2/20",
1202
+ * "left 3"
1203
+ * ])
1204
+ *
1205
+ * // data-last (pipeable)
1206
+ * assert.deepStrictEqual(pipe([1, 2, 3], InclusiveOr.zip([10, 20], describe)), [
1207
+ * "both 1/10",
1208
+ * "both 2/20",
1209
+ * "left 3"
1210
+ * ])
1211
+ * ```
1212
+ *
1213
+ * @category combinators
1214
+ * @since 0.0.0
1215
+ */
1216
+ export const zip: {
1217
+ <A, B, C>(
1218
+ array2: readonly B[],
1219
+ f: (inclusiveOr: InclusiveOr<A, B>) => C,
1220
+ ): (array1: readonly A[]) => C[];
1221
+ <A, B, C>(
1222
+ array1: readonly A[],
1223
+ array2: readonly B[],
1224
+ f: (inclusiveOr: InclusiveOr<A, B>) => C,
1225
+ ): C[];
1226
+ } = dual(
1227
+ 3,
1228
+ <A, B, C>(
1229
+ array1: readonly A[],
1230
+ array2: readonly B[],
1231
+ f: (inclusiveOr: InclusiveOr<A, B>) => C,
1232
+ ): C[] => {
1233
+ const newLength = Math.max(array1.length, array2.length);
1234
+
1235
+ if (newLength === 0) {
1236
+ return [];
1237
+ }
1238
+
1239
+ return Array.makeBy(newLength, (index) => {
1240
+ if (index < array1.length && index < array2.length) {
1241
+ return f(LeftAndRight({ left: array1[index], right: array2[index] }));
1242
+ }
1243
+
1244
+ if (index < array1.length) {
1245
+ return f(LeftOnly({ left: array1[index] }));
1246
+ }
1247
+
1248
+ if (index < array2.length) {
1249
+ return f(RightOnly({ right: array2[index] }));
1250
+ }
1251
+
1252
+ throw new Error(`Index ${index} is out of bounds for array1 and array2`);
1253
+ });
1254
+ },
1255
+ );