ts-data-forge 1.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 (143) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +534 -0
  3. package/package.json +101 -0
  4. package/src/array/array-utils-creation.test.mts +443 -0
  5. package/src/array/array-utils-modification.test.mts +197 -0
  6. package/src/array/array-utils-overload-type-error.test.mts +149 -0
  7. package/src/array/array-utils-reducing-value.test.mts +425 -0
  8. package/src/array/array-utils-search.test.mts +169 -0
  9. package/src/array/array-utils-set-op.test.mts +335 -0
  10. package/src/array/array-utils-slice-clamped.test.mts +113 -0
  11. package/src/array/array-utils-slicing.test.mts +316 -0
  12. package/src/array/array-utils-transformation.test.mts +790 -0
  13. package/src/array/array-utils-validation.test.mts +492 -0
  14. package/src/array/array-utils.mts +4000 -0
  15. package/src/array/array.test.mts +146 -0
  16. package/src/array/index.mts +2 -0
  17. package/src/array/tuple-utils.mts +519 -0
  18. package/src/array/tuple-utils.test.mts +518 -0
  19. package/src/collections/imap-mapped.mts +801 -0
  20. package/src/collections/imap-mapped.test.mts +860 -0
  21. package/src/collections/imap.mts +651 -0
  22. package/src/collections/imap.test.mts +932 -0
  23. package/src/collections/index.mts +6 -0
  24. package/src/collections/iset-mapped.mts +889 -0
  25. package/src/collections/iset-mapped.test.mts +1187 -0
  26. package/src/collections/iset.mts +682 -0
  27. package/src/collections/iset.test.mts +1084 -0
  28. package/src/collections/queue.mts +390 -0
  29. package/src/collections/queue.test.mts +282 -0
  30. package/src/collections/stack.mts +423 -0
  31. package/src/collections/stack.test.mts +225 -0
  32. package/src/expect-type.mts +206 -0
  33. package/src/functional/index.mts +4 -0
  34. package/src/functional/match.mts +300 -0
  35. package/src/functional/match.test.mts +177 -0
  36. package/src/functional/optional.mts +733 -0
  37. package/src/functional/optional.test.mts +619 -0
  38. package/src/functional/pipe.mts +212 -0
  39. package/src/functional/pipe.test.mts +85 -0
  40. package/src/functional/result.mts +1134 -0
  41. package/src/functional/result.test.mts +777 -0
  42. package/src/globals.d.mts +38 -0
  43. package/src/guard/has-key.mts +119 -0
  44. package/src/guard/has-key.test.mts +219 -0
  45. package/src/guard/index.mts +7 -0
  46. package/src/guard/is-non-empty-string.mts +108 -0
  47. package/src/guard/is-non-empty-string.test.mts +91 -0
  48. package/src/guard/is-non-null-object.mts +106 -0
  49. package/src/guard/is-non-null-object.test.mts +90 -0
  50. package/src/guard/is-primitive.mts +165 -0
  51. package/src/guard/is-primitive.test.mts +102 -0
  52. package/src/guard/is-record.mts +153 -0
  53. package/src/guard/is-record.test.mts +112 -0
  54. package/src/guard/is-type.mts +450 -0
  55. package/src/guard/is-type.test.mts +496 -0
  56. package/src/guard/key-is-in.mts +163 -0
  57. package/src/guard/key-is-in.test.mts +19 -0
  58. package/src/index.mts +10 -0
  59. package/src/iterator/index.mts +1 -0
  60. package/src/iterator/range.mts +120 -0
  61. package/src/iterator/range.test.mts +33 -0
  62. package/src/json/index.mts +1 -0
  63. package/src/json/json.mts +711 -0
  64. package/src/json/json.test.mts +628 -0
  65. package/src/number/branded-types/finite-number.mts +354 -0
  66. package/src/number/branded-types/finite-number.test.mts +135 -0
  67. package/src/number/branded-types/index.mts +26 -0
  68. package/src/number/branded-types/int.mts +278 -0
  69. package/src/number/branded-types/int.test.mts +140 -0
  70. package/src/number/branded-types/int16.mts +192 -0
  71. package/src/number/branded-types/int16.test.mts +170 -0
  72. package/src/number/branded-types/int32.mts +193 -0
  73. package/src/number/branded-types/int32.test.mts +170 -0
  74. package/src/number/branded-types/non-negative-finite-number.mts +223 -0
  75. package/src/number/branded-types/non-negative-finite-number.test.mts +188 -0
  76. package/src/number/branded-types/non-negative-int16.mts +187 -0
  77. package/src/number/branded-types/non-negative-int16.test.mts +201 -0
  78. package/src/number/branded-types/non-negative-int32.mts +187 -0
  79. package/src/number/branded-types/non-negative-int32.test.mts +204 -0
  80. package/src/number/branded-types/non-zero-finite-number.mts +229 -0
  81. package/src/number/branded-types/non-zero-finite-number.test.mts +198 -0
  82. package/src/number/branded-types/non-zero-int.mts +167 -0
  83. package/src/number/branded-types/non-zero-int.test.mts +177 -0
  84. package/src/number/branded-types/non-zero-int16.mts +196 -0
  85. package/src/number/branded-types/non-zero-int16.test.mts +195 -0
  86. package/src/number/branded-types/non-zero-int32.mts +196 -0
  87. package/src/number/branded-types/non-zero-int32.test.mts +197 -0
  88. package/src/number/branded-types/non-zero-safe-int.mts +196 -0
  89. package/src/number/branded-types/non-zero-safe-int.test.mts +232 -0
  90. package/src/number/branded-types/non-zero-uint16.mts +189 -0
  91. package/src/number/branded-types/non-zero-uint16.test.mts +199 -0
  92. package/src/number/branded-types/non-zero-uint32.mts +189 -0
  93. package/src/number/branded-types/non-zero-uint32.test.mts +199 -0
  94. package/src/number/branded-types/positive-finite-number.mts +241 -0
  95. package/src/number/branded-types/positive-finite-number.test.mts +204 -0
  96. package/src/number/branded-types/positive-int.mts +304 -0
  97. package/src/number/branded-types/positive-int.test.mts +176 -0
  98. package/src/number/branded-types/positive-int16.mts +188 -0
  99. package/src/number/branded-types/positive-int16.test.mts +197 -0
  100. package/src/number/branded-types/positive-int32.mts +188 -0
  101. package/src/number/branded-types/positive-int32.test.mts +197 -0
  102. package/src/number/branded-types/positive-safe-int.mts +187 -0
  103. package/src/number/branded-types/positive-safe-int.test.mts +210 -0
  104. package/src/number/branded-types/positive-uint16.mts +188 -0
  105. package/src/number/branded-types/positive-uint16.test.mts +203 -0
  106. package/src/number/branded-types/positive-uint32.mts +188 -0
  107. package/src/number/branded-types/positive-uint32.test.mts +203 -0
  108. package/src/number/branded-types/safe-int.mts +291 -0
  109. package/src/number/branded-types/safe-int.test.mts +170 -0
  110. package/src/number/branded-types/safe-uint.mts +187 -0
  111. package/src/number/branded-types/safe-uint.test.mts +176 -0
  112. package/src/number/branded-types/uint.mts +179 -0
  113. package/src/number/branded-types/uint.test.mts +158 -0
  114. package/src/number/branded-types/uint16.mts +186 -0
  115. package/src/number/branded-types/uint16.test.mts +170 -0
  116. package/src/number/branded-types/uint32.mts +218 -0
  117. package/src/number/branded-types/uint32.test.mts +170 -0
  118. package/src/number/enum/index.mts +2 -0
  119. package/src/number/enum/int8.mts +344 -0
  120. package/src/number/enum/int8.test.mts +180 -0
  121. package/src/number/enum/uint8.mts +293 -0
  122. package/src/number/enum/uint8.test.mts +164 -0
  123. package/src/number/index.mts +4 -0
  124. package/src/number/num.mts +604 -0
  125. package/src/number/num.test.mts +242 -0
  126. package/src/number/refined-number-utils.mts +566 -0
  127. package/src/object/index.mts +1 -0
  128. package/src/object/object.mts +447 -0
  129. package/src/object/object.test.mts +124 -0
  130. package/src/others/cast-mutable.mts +113 -0
  131. package/src/others/cast-readonly.mts +192 -0
  132. package/src/others/cast-readonly.test.mts +89 -0
  133. package/src/others/if-then.mts +98 -0
  134. package/src/others/if-then.test.mts +75 -0
  135. package/src/others/index.mts +7 -0
  136. package/src/others/map-nullable.mts +172 -0
  137. package/src/others/map-nullable.test.mts +297 -0
  138. package/src/others/memoize-function.mts +196 -0
  139. package/src/others/memoize-function.test.mts +168 -0
  140. package/src/others/tuple.mts +160 -0
  141. package/src/others/tuple.test.mts +11 -0
  142. package/src/others/unknown-to-string.mts +215 -0
  143. package/src/others/unknown-to-string.test.mts +114 -0
@@ -0,0 +1,733 @@
1
+ /* eslint-disable @typescript-eslint/no-unsafe-type-assertion */
2
+ import { isRecord } from '../guard/index.mjs';
3
+ import { pipe } from './pipe.mjs';
4
+
5
+ /** @internal Symbol to identify the 'Some' variant of Optional. */
6
+ const SomeTypeSymbol: unique symbol = Symbol('Optional.some');
7
+
8
+ /** @internal Symbol to identify the 'None' variant of Optional. */
9
+ const NoneTypeSymbol: unique symbol = Symbol('Optional.none');
10
+
11
+ /**
12
+ * @internal
13
+ * Represents the 'Some' variant of an Optional, containing a value.
14
+ * @template S The type of the contained value.
15
+ */
16
+ type Some_<S> = Readonly<{
17
+ /** Discriminant property for the 'Some' type. */
18
+ type: typeof SomeTypeSymbol;
19
+ /** The contained value. */
20
+ value: S;
21
+ }>;
22
+
23
+ /**
24
+ * @internal
25
+ * Represents the 'None' variant of an Optional, indicating the absence of a value.
26
+ */
27
+ type None_ = Readonly<{
28
+ /** Discriminant property for the 'None' type. */
29
+ type: typeof NoneTypeSymbol;
30
+ }>;
31
+
32
+ /**
33
+ * Represents an optional value that can either be 'Some' (containing a value) or 'None' (empty).
34
+ * @template S The type of the value that might be present.
35
+ */
36
+ export type Optional<S> = None_ | Some_<S>;
37
+
38
+ /**
39
+ * Namespace for the {@link Optional} type and related functions.
40
+ * Provides utilities to handle values that might be absent, similar to Option types in other languages.
41
+ */
42
+ export namespace Optional {
43
+ /**
44
+ * Checks if the given value is an {@link Optional}.
45
+ * @param maybeOptional The value to check.
46
+ * @returns `true` if the value is an {@link Optional}, otherwise `false`.
47
+ */
48
+ export const isOptional = (
49
+ maybeOptional: unknown,
50
+ ): maybeOptional is Optional<unknown> =>
51
+ isRecord(maybeOptional) &&
52
+ Object.hasOwn(maybeOptional, 'type') &&
53
+ ((maybeOptional['type'] === SomeTypeSymbol &&
54
+ Object.hasOwn(maybeOptional, 'value')) ||
55
+ maybeOptional['type'] === NoneTypeSymbol);
56
+
57
+ /**
58
+ * Represents an {@link Optional} that contains a value.
59
+ * @template S The type of the contained value.
60
+ */
61
+ export type Some<S> = Some_<S>;
62
+
63
+ /**
64
+ * Represents an {@link Optional} that does not contain a value (is empty).
65
+ */
66
+ export type None = None_;
67
+
68
+ /**
69
+ * Base type for any {@link Optional}, used for generic constraints.
70
+ * Represents an {@link Optional} with an unknown value type.
71
+ */
72
+ export type Base = Optional<unknown>;
73
+
74
+ /**
75
+ * Extracts the value type `S` from an {@link Optional.Some}<S>.
76
+ * If the {@link Optional} is {@link Optional.None}, resolves to `never`.
77
+ * @template O The {@link Optional.Base} type to unwrap.
78
+ */
79
+ export type Unwrap<O extends Base> = O extends Some<infer S> ? S : never;
80
+
81
+ /**
82
+ * Narrows an {@link Optional.Base} type to {@link Optional.Some}<S> if it is a {@link Optional.Some}.
83
+ * If the {@link Optional} is {@link Optional.None}, resolves to `never`.
84
+ * @template O The {@link Optional.Base} type to narrow.
85
+ */
86
+ export type NarrowToSome<O extends Base> = O extends None ? never : O;
87
+
88
+ /**
89
+ * Narrows an {@link Optional.Base} type to {@link Optional.None} if it is a {@link Optional.None}.
90
+ * If the {@link Optional} is {@link Optional.Some}<S>, resolves to `never`.
91
+ * @template O The {@link Optional.Base} type to narrow.
92
+ */
93
+ export type NarrowToNone<O extends Base> = O extends None ? O : never;
94
+
95
+ /**
96
+ * Creates an {@link Optional.Some} containing the given value.
97
+ * @template S The type of the value.
98
+ * @param value The value to wrap in an {@link Optional.Some}.
99
+ * @returns An {@link Optional.Some}<S> containing the value.
100
+ * @example
101
+ * ```typescript
102
+ * const someValue = Optional.some(42);
103
+ * const someString = Optional.some("hello");
104
+ * const someObject = Optional.some({ name: "Alice", age: 30 });
105
+ *
106
+ * console.log(Optional.isSome(someValue)); // true
107
+ * console.log(Optional.unwrap(someValue)); // 42
108
+ * ```
109
+ */
110
+ export const some = <S,>(value: S): Some<S> => ({
111
+ type: SomeTypeSymbol,
112
+ value,
113
+ });
114
+
115
+ /**
116
+ * The singleton instance representing {@link Optional.None} (an empty Optional).
117
+ * @example
118
+ * ```typescript
119
+ * const emptyValue = Optional.none;
120
+ *
121
+ * console.log(Optional.isNone(emptyValue)); // true
122
+ * console.log(Optional.unwrap(emptyValue)); // undefined
123
+ * console.log(Optional.unwrapOr(emptyValue, "default")); // "default"
124
+ * ```
125
+ */
126
+ export const none: None = { type: NoneTypeSymbol } as const;
127
+
128
+ /**
129
+ * Checks if an {@link Optional} is {@link Optional.Some}.
130
+ * Acts as a type guard.
131
+ * @template O The {@link Optional.Base} type to check.
132
+ * @param optional The {@link Optional} to check.
133
+ * @returns `true` if the {@link Optional} is {@link Optional.Some}, `false` otherwise.
134
+ */
135
+ export const isSome = <O extends Base>(
136
+ optional: O,
137
+ ): optional is NarrowToSome<O> => optional.type === SomeTypeSymbol;
138
+
139
+ /**
140
+ * Checks if an {@link Optional} is {@link Optional.None}.
141
+ * Acts as a type guard.
142
+ * @template O The {@link Optional.Base} type to check.
143
+ * @param optional The {@link Optional} to check.
144
+ * @returns `true` if the {@link Optional} is {@link Optional.None}, `false` otherwise.
145
+ */
146
+ export const isNone = <O extends Base>(
147
+ optional: O,
148
+ ): optional is NarrowToNone<O> => optional.type === NoneTypeSymbol;
149
+
150
+ /**
151
+ * Unwraps an `Optional`, returning the contained value.
152
+ * Throws an error if the `Optional` is `Optional.None`.
153
+ *
154
+ * This is a safer alternative to direct value access when you know the Optional
155
+ * should contain a value. Use this method when an empty Optional represents
156
+ * a programming error or unexpected condition.
157
+ *
158
+ * @template O The `Optional.Base` type to unwrap.
159
+ * @param optional The `Optional` to unwrap.
160
+ * @returns The contained value if `Optional.Some`.
161
+ * @throws {Error} Error with message "`unwrapThrow()` has failed because it is `None`" if the `Optional` is `Optional.None`.
162
+ * @example
163
+ * ```typescript
164
+ * // Safe unwrapping when you know the value exists
165
+ * const config = loadConfig(); // returns Optional<Config>
166
+ * if (Optional.isSome(config)) {
167
+ * const value = Optional.unwrapThrow(config); // Safe to unwrap
168
+ * console.log(value); // Config object
169
+ * }
170
+ *
171
+ * // Unsafe unwrapping - will throw if empty
172
+ * const userInput = Optional.some(42);
173
+ * console.log(Optional.unwrapThrow(userInput)); // 42
174
+ *
175
+ * const empty = Optional.none;
176
+ * try {
177
+ * Optional.unwrapThrow(empty); // throws Error
178
+ * } catch (error) {
179
+ * console.log(error.message); // "`unwrapThrow()` has failed because it is `None`"
180
+ * }
181
+ * ```
182
+ */
183
+ export const unwrapThrow = <O extends Base>(optional: O): Unwrap<O> => {
184
+ if (isSome(optional)) {
185
+ return optional.value as Unwrap<O>;
186
+ }
187
+
188
+ throw new Error('`unwrapThrow()` has failed because it is `None`');
189
+ };
190
+
191
+ /**
192
+ * Unwraps an `Optional`, returning the contained value or `undefined` if empty.
193
+ *
194
+ * This function provides a safe way to extract values from Optionals without
195
+ * throwing exceptions. It has overloaded behavior based on the type:
196
+ * - For `Optional.Some<T>`: Always returns `T` (guaranteed by type system)
197
+ * - For general `Optional<T>`: Returns `T | undefined`
198
+ *
199
+ * @template O The `Optional.Base` type to unwrap.
200
+ * @param optional The `Optional` to unwrap.
201
+ * @returns The contained value if `Optional.Some`, otherwise `undefined`.
202
+ * @example
203
+ * ```typescript
204
+ * // With Some - guaranteed to return value
205
+ * const some = Optional.some(42);
206
+ * const value = Optional.unwrap(some); // Type: number, Value: 42
207
+ *
208
+ * // With general Optional - may return undefined
209
+ * const maybeValue: Optional<string> = getOptionalString();
210
+ * const result = Optional.unwrap(maybeValue); // Type: string | undefined
211
+ *
212
+ * // Safe pattern for handling both cases
213
+ * const optional = Optional.some("hello");
214
+ * const unwrapped = Optional.unwrap(optional);
215
+ * if (unwrapped !== undefined) {
216
+ * console.log(unwrapped.toUpperCase()); // "HELLO"
217
+ * }
218
+ * ```
219
+ */
220
+ export const unwrap: UnwrapFnOverload = (<O extends Base>(
221
+ optional: O,
222
+ ): Unwrap<O> | undefined =>
223
+ isNone(optional)
224
+ ? undefined
225
+ : ((optional as NarrowToSome<O>).value as Unwrap<O>)) as UnwrapFnOverload;
226
+
227
+ type UnwrapFnOverload = {
228
+ <O extends Some<unknown>>(optional: O): Unwrap<O>;
229
+
230
+ <O extends Base>(optional: O): Unwrap<O> | undefined;
231
+ };
232
+
233
+ /**
234
+ * Unwraps an `Optional`, returning the contained value or a default value if it's `Optional.None`.
235
+ *
236
+ * Supports both direct usage and curried form for functional composition.
237
+ * This is often preferred over `unwrap()` when you have a sensible fallback value.
238
+ *
239
+ * @template O The `Optional.Base` type to unwrap.
240
+ * @template D The type of the default value.
241
+ * @param optional The `Optional` to unwrap.
242
+ * @param defaultValue The value to return if `optional` is `Optional.None`.
243
+ * @returns The contained value if `Optional.Some`, otherwise `defaultValue`.
244
+ * @example
245
+ * ```typescript
246
+ * // Direct usage - most common pattern
247
+ * const userAge = Optional.fromNullable(user.age);
248
+ * const displayAge = Optional.unwrapOr(userAge, "Unknown");
249
+ * console.log(`Age: ${displayAge}`); // "Age: 25" or "Age: Unknown"
250
+ *
251
+ * // With different Optional types
252
+ * const some = Optional.some(42);
253
+ * const value1 = Optional.unwrapOr(some, 0);
254
+ * console.log(value1); // 42
255
+ *
256
+ * const none = Optional.none;
257
+ * const value2 = Optional.unwrapOr(none, 0);
258
+ * console.log(value2); // 0
259
+ *
260
+ * // Curried usage for functional composition
261
+ * const unwrapWithDefault = Optional.unwrapOr("default");
262
+ * const result = pipe(Optional.some("hello"))
263
+ * .map(unwrapWithDefault)
264
+ * .value;
265
+ * console.log(result); // "hello"
266
+ *
267
+ * // Chaining with other Optional operations
268
+ * const processValue = (input: string) =>
269
+ * pipe(Optional.fromNullable(input))
270
+ * .map(Optional.map(s => s.toUpperCase()))
271
+ * .map(Optional.unwrapOr("NO INPUT"))
272
+ * .value;
273
+ * ```
274
+ */
275
+ export const unwrapOr: UnwrapOrFnOverload = (<O extends Base, D>(
276
+ ...args:
277
+ | readonly [optional: O, defaultValue: D]
278
+ | readonly [defaultValue: D]
279
+ ): (D | Unwrap<O>) | ((optional: Optional<Unwrap<O>>) => D | Unwrap<O>) => {
280
+ switch (args.length) {
281
+ case 2: {
282
+ const [optional, defaultValue] = args;
283
+ return isNone(optional)
284
+ ? defaultValue
285
+ : ((optional as NarrowToSome<O>).value as Unwrap<O>);
286
+ }
287
+
288
+ case 1: {
289
+ // Curried version: first argument is default value
290
+ const [defaultValue] = args;
291
+ return (optional: Optional<Unwrap<O>>) =>
292
+ unwrapOr(optional, defaultValue);
293
+ }
294
+ }
295
+ }) as UnwrapOrFnOverload;
296
+
297
+ type UnwrapOrFnOverload = {
298
+ <O extends Base, D>(optional: O, defaultValue: D): D | Unwrap<O>;
299
+
300
+ // Curried version
301
+ <S, D>(defaultValue: D): (optional: Optional<S>) => D | S;
302
+ };
303
+
304
+ /**
305
+ * Returns the `Optional` if it is `Some`, otherwise returns the alternative.
306
+ *
307
+ * Provides a way to chain Optional operations with fallback values. This is
308
+ * particularly useful for implementing default behavior or cascading lookups.
309
+ * Supports both direct usage and curried form for functional composition.
310
+ *
311
+ * @template O The input `Optional.Base` type.
312
+ * @param optional The `Optional` to check.
313
+ * @param alternative The alternative `Optional` to return if the first is `None`.
314
+ * @returns The first `Optional` if `Some`, otherwise the alternative.
315
+ * @example
316
+ * ```typescript
317
+ * // Direct usage - cascading lookups
318
+ * const primaryConfig = loadPrimaryConfig(); // Optional<Config>
319
+ * const fallbackConfig = loadFallbackConfig(); // Optional<Config>
320
+ * const config = Optional.orElse(primaryConfig, fallbackConfig);
321
+ *
322
+ * // Multiple fallbacks
323
+ * const userPreference = getUserPreference(); // Optional<string>
324
+ * const systemDefault = Optional.some("default-theme");
325
+ * const theme = Optional.orElse(userPreference, systemDefault);
326
+ * console.log(Optional.unwrap(theme)); // User's preference or "default-theme"
327
+ *
328
+ * // Regular usage example
329
+ * const primary = Optional.none;
330
+ * const fallback = Optional.some("default");
331
+ * const result = Optional.orElse(primary, fallback);
332
+ * console.log(Optional.unwrap(result)); // "default"
333
+ *
334
+ * // Curried usage for functional composition
335
+ * const withFallback = Optional.orElse(Optional.some("fallback"));
336
+ * const result2 = pipe(Optional.none)
337
+ * .map(withFallback)
338
+ * .value;
339
+ * console.log(Optional.unwrap(result2)); // "fallback"
340
+ *
341
+ * // Chaining multiple orElse operations
342
+ * const finalResult = pipe(Optional.none)
343
+ * .map(Optional.orElse(Optional.none)) // Still none
344
+ * .map(Optional.orElse(Optional.some("last resort")))
345
+ * .value;
346
+ * ```
347
+ */
348
+ export const orElse: OrElseFnOverload = (<
349
+ O extends Base,
350
+ const O2 extends Base,
351
+ >(
352
+ ...args:
353
+ | readonly [optional: O, alternative: O2]
354
+ | readonly [alternative: O2]
355
+ ):
356
+ | (O | O2)
357
+ | ((optional: Optional<Unwrap<O>>) => Optional<Unwrap<O>> | O2) => {
358
+ switch (args.length) {
359
+ case 2: {
360
+ const [optional, alternative] = args;
361
+ return isNone(optional) ? alternative : optional;
362
+ }
363
+
364
+ case 1: {
365
+ const [alternative] = args;
366
+ return (optional: Optional<Unwrap<O>>) => orElse(optional, alternative);
367
+ }
368
+ }
369
+ }) as OrElseFnOverload;
370
+
371
+ type OrElseFnOverload = {
372
+ <O extends Base, const O2 extends Base>(
373
+ optional: O,
374
+ alternative: O2,
375
+ ): O | O2;
376
+
377
+ // Curried version
378
+ <S, S2>(
379
+ alternative: Optional<S2>,
380
+ ): (optional: Optional<S>) => Optional<S> | Optional<S2>;
381
+ };
382
+
383
+ /**
384
+ * Maps an {@link Optional}<S> to {@link Optional}<S2> by applying a function to a contained value.
385
+ * If the {@link Optional} is {@link Optional.None}, it returns {@link Optional.none}.
386
+ * Otherwise, it applies the `mapFn` to the value in `Optional.Some` and returns a new `Optional.Some` with the result.
387
+ * @template O The input `Optional.Base` type.
388
+ * @template S2 The type of the value returned by the mapping function.
389
+ * @param optional The `Optional` to map.
390
+ * @param mapFn The function to apply to the value if it exists.
391
+ * @returns A new `Optional<S2>` resulting from the mapping, or `Optional.None` if the input was `Optional.None`.
392
+ * @example
393
+ * ```typescript
394
+ * const someNumber = Optional.some(5);
395
+ * const mapped = Optional.map(someNumber, x => x * 2);
396
+ * console.log(Optional.unwrap(mapped)); // 10
397
+ *
398
+ * const noneValue = Optional.none;
399
+ * const mappedNone = Optional.map(noneValue, x => x * 2);
400
+ * console.log(Optional.isNone(mappedNone)); // true
401
+ *
402
+ * // Chaining maps
403
+ * const result = Optional.map(
404
+ * Optional.map(Optional.some("hello"), s => s.toUpperCase()),
405
+ * s => s.length
406
+ * );
407
+ * console.log(Optional.unwrap(result)); // 5
408
+ *
409
+ * // Curried version for use with pipe
410
+ * const doubler = Optional.map((x: number) => x * 2);
411
+ * const result2 = pipe(Optional.some(5)).map(doubler).value;
412
+ * console.log(Optional.unwrap(result2)); // 10
413
+ * ```
414
+ */
415
+ export const map: MapFnOverload = (<O extends Base, S2>(
416
+ ...args:
417
+ | readonly [optional: O, mapFn: (value: Unwrap<O>) => S2]
418
+ | readonly [mapFn: (value: Unwrap<O>) => S2]
419
+ ): Optional<S2> | ((optional: O) => Optional<S2>) => {
420
+ switch (args.length) {
421
+ case 2: {
422
+ const [optional, mapFn] = args;
423
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
424
+ return isSome(optional) ? some(mapFn(unwrap(optional)!)) : none;
425
+ }
426
+ case 1: {
427
+ // Curried version: first argument is mapping function
428
+ const [mapFn] = args;
429
+ return (optional: O) => map(optional, mapFn);
430
+ }
431
+ }
432
+ }) as MapFnOverload;
433
+
434
+ type MapFnOverload = {
435
+ <O extends Base, S2>(
436
+ optional: O,
437
+ mapFn: (value: Unwrap<O>) => S2,
438
+ ): Optional<S2>;
439
+
440
+ // Curried version
441
+ <S, S2>(mapFn: (value: S) => S2): (optional: Optional<S>) => Optional<S2>;
442
+ };
443
+
444
+ /**
445
+ * Applies a function that returns an `Optional` to the value in an `Optional.Some`.
446
+ * If the input is `Optional.None`, returns `Optional.None`.
447
+ * This is the monadic bind operation for `Optional`.
448
+ * @template O The input `Optional.Base` type.
449
+ * @template S2 The value type of the `Optional` returned by the function.
450
+ * @param optional The `Optional` to flat map.
451
+ * @param flatMapFn The function to apply that returns an `Optional`.
452
+ * @returns The result of applying the function, or `Optional.None`.
453
+ * @example
454
+ * ```typescript
455
+ * // Regular usage
456
+ * const parseNumber = (s: string): Optional<number> => {
457
+ * const n = Number(s);
458
+ * return isNaN(n) ? Optional.none : Optional.some(n);
459
+ * };
460
+ *
461
+ * const result = Optional.flatMap(Optional.some("42"), parseNumber);
462
+ * console.log(Optional.unwrap(result)); // 42
463
+ *
464
+ * // Curried usage for pipe composition
465
+ * const parser = Optional.flatMap(parseNumber);
466
+ * const result2 = pipe(Optional.some("42")).map(parser).value;
467
+ * console.log(Optional.unwrap(result2)); // 42
468
+ * ```
469
+ */
470
+ export const flatMap: FlatMapFnOverload = (<O extends Base, S2>(
471
+ ...args:
472
+ | readonly [optional: O, flatMapFn: (value: Unwrap<O>) => Optional<S2>]
473
+ | readonly [flatMapFn: (value: Unwrap<O>) => Optional<S2>]
474
+ ): Optional<S2> | ((optional: O) => Optional<S2>) => {
475
+ switch (args.length) {
476
+ case 2: {
477
+ const [optional, flatMapFn] = args;
478
+ return isSome(optional) ? flatMapFn(unwrap(optional)) : none;
479
+ }
480
+
481
+ case 1: {
482
+ const [flatMapFn] = args;
483
+ return (optional: O) => flatMap(optional, flatMapFn);
484
+ }
485
+ }
486
+ }) as FlatMapFnOverload;
487
+
488
+ type FlatMapFnOverload = {
489
+ <O extends Base, S2>(
490
+ optional: O,
491
+ flatMapFn: (value: Unwrap<O>) => Optional<S2>,
492
+ ): Optional<S2>;
493
+
494
+ // Curried version
495
+ <S, S2>(
496
+ flatMapFn: (value: S) => Optional<S2>,
497
+ ): (optional: Optional<S>) => Optional<S2>;
498
+ };
499
+
500
+ /**
501
+ * Filters an `Optional` based on a predicate.
502
+ * If the `Optional` is `Some` and the predicate returns true, returns the original `Optional`.
503
+ * Otherwise returns `None`.
504
+ * @template O The input `Optional.Base` type.
505
+ * @param optional The `Optional` to filter.
506
+ * @param predicate The predicate function.
507
+ * @returns The filtered `Optional`.
508
+ * @example
509
+ * ```typescript
510
+ * // Regular usage
511
+ * const someEven = Optional.some(4);
512
+ * const filtered = Optional.filter(someEven, x => x % 2 === 0);
513
+ * console.log(Optional.unwrap(filtered)); // 4
514
+ *
515
+ * // Curried usage for pipe composition
516
+ * const evenFilter = Optional.filter((x: number) => x % 2 === 0);
517
+ * const result = pipe(Optional.some(4)).map(evenFilter).value;
518
+ * console.log(Optional.unwrap(result)); // 4
519
+ * ```
520
+ */
521
+ export const filter: FilterFnOverload = (<O extends Base>(
522
+ ...args:
523
+ | readonly [optional: O, predicate: (value: Unwrap<O>) => boolean]
524
+ | readonly [predicate: (value: Unwrap<O>) => boolean]
525
+ ): Optional<Unwrap<O>> | ((optional: O) => Optional<Unwrap<O>>) => {
526
+ switch (args.length) {
527
+ case 2: {
528
+ const [optional, predicate] = args;
529
+ return isSome(optional)
530
+ ? pipe(unwrap(optional)).map((value) =>
531
+ predicate(value) ? some(value) : none,
532
+ ).value
533
+ : none;
534
+ }
535
+
536
+ case 1: {
537
+ // Curried version: first argument is predicate function
538
+ const [predicate] = args;
539
+ return (optional: O) => filter(optional, predicate);
540
+ }
541
+ }
542
+ }) as FilterFnOverload;
543
+
544
+ type FilterFnOverload = {
545
+ <O extends Base>(
546
+ optional: O,
547
+ predicate: (value: Unwrap<O>) => boolean,
548
+ ): Optional<Unwrap<O>>;
549
+
550
+ // Curried version
551
+ <S>(
552
+ predicate: (value: S) => boolean,
553
+ ): (optional: Optional<S>) => Optional<S>;
554
+ };
555
+
556
+ /**
557
+ * Unwraps an `Optional`, returning the contained value or throwing an error with the provided message.
558
+ * @template O The `Optional.Base` type to unwrap.
559
+ * @param optional The `Optional` to unwrap.
560
+ * @param message The error message to throw if the `Optional` is `Optional.None`.
561
+ * @returns The contained value if `Optional.Some`.
562
+ * @throws Error with the provided message if the `Optional` is `Optional.None`.
563
+ * @example
564
+ * ```typescript
565
+ * // Regular usage
566
+ * const some = Optional.some(42);
567
+ * const value = Optional.expectToBe(some, "Value must exist");
568
+ * console.log(value); // 42
569
+ *
570
+ * // Curried usage for pipe composition
571
+ * const getValue = Optional.expectToBe("Value must exist");
572
+ * const value2 = pipe(Optional.some(42)).map(getValue).value;
573
+ * console.log(value2); // 42
574
+ * ```
575
+ */
576
+ export const expectToBe: ExpectToBeFnOverload = (<O extends Base>(
577
+ ...args:
578
+ | readonly [optional: O, message: string]
579
+ | readonly [message: string]
580
+ ): Unwrap<O> | ((optional: Optional<Unwrap<O>>) => Unwrap<O>) => {
581
+ switch (args.length) {
582
+ case 2: {
583
+ const [optional, message] = args;
584
+ if (isSome(optional)) {
585
+ return unwrap(optional);
586
+ }
587
+ throw new Error(message);
588
+ }
589
+
590
+ case 1: {
591
+ // Curried version: first argument is message
592
+ const [message] = args;
593
+ return (optional: Optional<Unwrap<O>>): Unwrap<O> =>
594
+ expectToBe(optional, message);
595
+ }
596
+ }
597
+ }) as ExpectToBeFnOverload;
598
+
599
+ type ExpectToBeFnOverload = {
600
+ <O extends Base>(optional: O, message: string): Unwrap<O>;
601
+
602
+ // Curried version
603
+ <S>(message: string): (optional: Optional<S>) => S;
604
+ };
605
+
606
+ /**
607
+ * Combines two `Optional` values into a single `Optional` containing a tuple.
608
+ * If either `Optional` is `None`, returns `None`.
609
+ * @template A The value type of the first `Optional`.
610
+ * @template B The value type of the second `Optional`.
611
+ * @param optionalA The first `Optional`.
612
+ * @param optionalB The second `Optional`.
613
+ * @returns An `Optional` containing a tuple of both values, or `None`.
614
+ * @example
615
+ * ```typescript
616
+ * const a = Optional.some(1);
617
+ * const b = Optional.some("hello");
618
+ * const zipped = Optional.zip(a, b);
619
+ * console.log(Optional.unwrap(zipped)); // [1, "hello"]
620
+ *
621
+ * const withNone = Optional.zip(a, Optional.none);
622
+ * console.log(Optional.isNone(withNone)); // true
623
+ * ```
624
+ */
625
+ export const zip = <A, const B>(
626
+ optionalA: Optional<A>,
627
+ optionalB: Optional<B>,
628
+ ): Optional<readonly [A, B]> =>
629
+ isSome(optionalA) && isSome(optionalB)
630
+ ? some([optionalA.value, optionalB.value] as const)
631
+ : none;
632
+
633
+ /**
634
+ * Converts a nullable value to an `Optional`.
635
+ *
636
+ * This is the primary way to lift nullable values (null or undefined) into
637
+ * the Optional type system. The function treats both `null` and `undefined`
638
+ * as empty values, converting them to `Optional.None`.
639
+ *
640
+ * @template T The type of the nullable value.
641
+ * @param value The nullable value to convert.
642
+ * @returns `Optional.Some<NonNullable<T>>` if the value is not null or undefined, otherwise `Optional.None`.
643
+ * @example
644
+ * ```typescript
645
+ * // Basic nullable conversion
646
+ * const value: string | null = "hello";
647
+ * const optional = Optional.fromNullable(value);
648
+ * console.log(Optional.unwrap(optional)); // "hello"
649
+ * console.log(Optional.isSome(optional)); // true
650
+ *
651
+ * // Handling null values
652
+ * const nullValue: string | null = null;
653
+ * const noneOptional = Optional.fromNullable(nullValue);
654
+ * console.log(Optional.isNone(noneOptional)); // true
655
+ *
656
+ * // Handling undefined values
657
+ * const undefinedValue: number | undefined = undefined;
658
+ * const alsoNone = Optional.fromNullable(undefinedValue);
659
+ * console.log(Optional.isNone(alsoNone)); // true
660
+ *
661
+ * // Common use case with API responses
662
+ * interface User {
663
+ * name: string;
664
+ * email?: string; // Optional field
665
+ * }
666
+ *
667
+ * const user: User = { name: "John" };
668
+ * const email = Optional.fromNullable(user.email);
669
+ * const emailDisplay = Optional.unwrapOr(email, "No email provided");
670
+ * console.log(emailDisplay); // "No email provided"
671
+ *
672
+ * // Chaining with other Optional operations
673
+ * const processNullableInput = (input: string | null) =>
674
+ * Optional.fromNullable(input)
675
+ * .map(Optional.map(s => s.trim()))
676
+ * .map(Optional.filter(s => s.length > 0))
677
+ * .map(Optional.unwrapOr("empty input"));
678
+ * ```
679
+ */
680
+ export const fromNullable = <T,>(
681
+ value: T | null | undefined,
682
+ ): Optional<NonNullable<T>> => (value == null ? none : some(value));
683
+
684
+ /**
685
+ * Converts an `Optional` to a nullable value.
686
+ *
687
+ * This function extracts the value from an Optional, returning `undefined`
688
+ * for empty Optionals. This is useful when interfacing with APIs or systems
689
+ * that expect nullable values rather than Optional types.
690
+ *
691
+ * Note: This returns `undefined` (not `null`) for consistency with JavaScript's
692
+ * undefined semantics and TypeScript's optional properties.
693
+ *
694
+ * @template O The `Optional.Base` type to convert.
695
+ * @param optional The `Optional` to convert.
696
+ * @returns The contained value if `Some`, otherwise `undefined`.
697
+ * @example
698
+ * ```typescript
699
+ * // Basic conversion
700
+ * const some = Optional.some(42);
701
+ * console.log(Optional.toNullable(some)); // 42
702
+ *
703
+ * const none = Optional.none;
704
+ * console.log(Optional.toNullable(none)); // undefined
705
+ *
706
+ * // Interface with nullable APIs
707
+ * interface ApiResponse {
708
+ * data?: string;
709
+ * }
710
+ *
711
+ * const optionalData: Optional<string> = processData();
712
+ * const response: ApiResponse = {
713
+ * data: Optional.toNullable(optionalData)
714
+ * };
715
+ *
716
+ * // Converting back and forth
717
+ * const original: string | undefined = getValue();
718
+ * const optional = Optional.fromNullable(original);
719
+ * const processed = Optional.map(optional, s => s.toUpperCase());
720
+ * const result: string | undefined = Optional.toNullable(processed);
721
+ *
722
+ * // Useful in conditional logic
723
+ * const maybeUser = findUser(id);
724
+ * const userName = Optional.toNullable(maybeUser);
725
+ * if (userName !== undefined) {
726
+ * console.log(`Found user: ${userName}`);
727
+ * }
728
+ * ```
729
+ */
730
+ export const toNullable = <O extends Base>(
731
+ optional: O,
732
+ ): Unwrap<O> | undefined => (isSome(optional) ? unwrap(optional) : undefined);
733
+ }