functype 0.9.0 → 0.9.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 (43) hide show
  1. package/dist/{Either-C-PDWX2U.d.ts → Either-BHep7I0d.d.ts} +7 -2
  2. package/dist/{Serializable-D9GKEo30.d.ts → Serializable-BbKuhDDL.d.ts} +14 -3
  3. package/dist/branded/index.d.ts +4 -6
  4. package/dist/branded/index.mjs +1 -1
  5. package/dist/chunk-GHBOC52G.mjs +43 -0
  6. package/dist/chunk-GHBOC52G.mjs.map +1 -0
  7. package/dist/chunk-R2TQJN3P.mjs +2 -0
  8. package/dist/chunk-R2TQJN3P.mjs.map +1 -0
  9. package/dist/either/index.d.ts +2 -2
  10. package/dist/either/index.mjs +1 -1
  11. package/dist/fpromise/index.d.ts +2 -2
  12. package/dist/fpromise/index.mjs +1 -1
  13. package/dist/index.d.ts +28 -20
  14. package/dist/index.mjs +1 -1
  15. package/dist/list/index.d.ts +2 -2
  16. package/dist/list/index.mjs +1 -1
  17. package/dist/map/index.d.ts +2 -2
  18. package/dist/map/index.mjs +1 -1
  19. package/dist/option/index.d.ts +2 -2
  20. package/dist/option/index.mjs +1 -1
  21. package/dist/set/index.d.ts +2 -2
  22. package/dist/set/index.mjs +1 -1
  23. package/dist/try/index.d.ts +2 -2
  24. package/dist/try/index.mjs +1 -1
  25. package/dist/tuple/index.d.ts +1 -1
  26. package/package.json +3 -3
  27. package/readme/BUNDLE_OPTIMIZATION.md +74 -0
  28. package/readme/FPromise-Assessment.md +43 -0
  29. package/readme/HKT.md +110 -0
  30. package/readme/ROADMAP.md +113 -0
  31. package/readme/TASK-TODO.md +33 -0
  32. package/readme/TUPLE-EXAMPLES.md +76 -0
  33. package/readme/TaskMigration.md +129 -0
  34. package/readme/ai-guide.md +406 -0
  35. package/readme/examples.md +2093 -0
  36. package/readme/quick-reference.md +514 -0
  37. package/readme/task-error-handling.md +283 -0
  38. package/readme/tasks.md +203 -0
  39. package/readme/type-index.md +238 -0
  40. package/dist/chunk-4EYCKDDF.mjs +0 -43
  41. package/dist/chunk-4EYCKDDF.mjs.map +0 -1
  42. package/dist/chunk-V6LFV5LW.mjs +0 -2
  43. package/dist/chunk-V6LFV5LW.mjs.map +0 -1
@@ -0,0 +1,2093 @@
1
+ # Functype Examples
2
+
3
+ This document provides comprehensive examples of using the Functype library. These examples are designed to help you understand the core concepts and practical applications of functional programming with TypeScript.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Option](#option)
8
+ - [Either](#either)
9
+ - [Try](#try)
10
+ - [List](#list)
11
+ - [Map](#map)
12
+ - [Set](#set)
13
+ - [FPromise](#fpromise)
14
+ - [Task](#task)
15
+ - [Branded Types](#branded-types)
16
+ - [Tuple](#tuple)
17
+ - [Foldable](#foldable)
18
+ - [Matchable](#matchable)
19
+ - [Common Patterns](#common-patterns)
20
+ - [Error Handling](#error-handling)
21
+ - [Real-World Examples](#real-world-examples)
22
+
23
+ ## Option
24
+
25
+ The `Option` type represents a value that may or may not exist, similar to Maybe in other functional languages.
26
+
27
+ ### Basic Usage
28
+
29
+ ```typescript
30
+ import { Option, Some, None } from "functype"
31
+
32
+ // Creating Options
33
+ const withValue = Option(42) // Some(42)
34
+ const withoutValue = Option(null) // None
35
+ const withoutValue2 = Option(undefined) // None
36
+ const explicit1 = Some(42) // Some(42)
37
+ const explicit2 = None() // None
38
+
39
+ // Check if value exists
40
+ console.log(withValue.isDefined()) // true
41
+ console.log(withoutValue.isDefined()) // false
42
+ console.log(withValue.isEmpty()) // false
43
+ console.log(withoutValue.isEmpty()) // true
44
+
45
+ // Safe access to values
46
+ console.log(withValue.get()) // 42
47
+ // console.log(withoutValue.get()) // Would throw error - use getOrElse instead
48
+
49
+ // Default values
50
+ console.log(withValue.getOrElse(0)) // 42
51
+ console.log(withoutValue.getOrElse(0)) // 0
52
+
53
+ // Optional chaining alternative
54
+ const user = Option({ name: "John", address: { city: "New York" } })
55
+ const noUser = Option(null)
56
+
57
+ // With Option
58
+ const city1 = user
59
+ .flatMap((u) => Option(u.address))
60
+ .flatMap((a) => Option(a.city))
61
+ .getOrElse("Unknown")
62
+ const city2 = noUser
63
+ .flatMap((u) => Option(u.address))
64
+ .flatMap((a) => Option(a.city))
65
+ .getOrElse("Unknown")
66
+
67
+ console.log(city1) // "New York"
68
+ console.log(city2) // "Unknown"
69
+ ```
70
+
71
+ ### Transformations
72
+
73
+ ```typescript
74
+ import { Option } from "functype"
75
+
76
+ const opt1 = Option(5)
77
+ const opt2 = Option(null)
78
+
79
+ // Map: transform the value if present
80
+ const doubled = opt1.map((x) => x * 2) // Some(10)
81
+ const doubledEmpty = opt2.map((x) => x * 2) // None
82
+
83
+ // FlatMap: chain operations that return Options
84
+ const stringLength = (s: string): Option<number> => (s ? Option(s.length) : None())
85
+
86
+ const name = Option("Alice")
87
+ const nameLength = name.flatMap(stringLength) // Some(5)
88
+
89
+ // Filter: keep value only if predicate is true
90
+ const greaterThan3 = opt1.filter((x) => x > 3) // Some(5)
91
+ const lessThan3 = opt1.filter((x) => x < 3) // None
92
+ ```
93
+
94
+ ### Pattern Matching
95
+
96
+ ```typescript
97
+ import { Option } from "functype"
98
+
99
+ const opt = Option(42)
100
+ const empty = Option(null)
101
+
102
+ // Using fold for pattern matching
103
+ const result1 = opt.fold(
104
+ () => "No value",
105
+ (value) => `Value: ${value}`,
106
+ )
107
+ console.log(result1) // "Value: 42"
108
+
109
+ const result2 = empty.fold(
110
+ () => "No value",
111
+ (value) => `Value: ${value}`,
112
+ )
113
+ console.log(result2) // "No value"
114
+
115
+ // Using match for pattern matching
116
+ const display1 = opt.match({
117
+ Some: (value) => `Found: ${value}`,
118
+ None: () => "Not found",
119
+ })
120
+ console.log(display1) // "Found: 42"
121
+
122
+ const display2 = empty.match({
123
+ Some: (value) => `Found: ${value}`,
124
+ None: () => "Not found",
125
+ })
126
+ console.log(display2) // "Not found"
127
+ ```
128
+
129
+ ### Factory Methods
130
+
131
+ ```typescript
132
+ import { Option } from "functype"
133
+
134
+ // Creating from nullable values
135
+ const fromNullable = Option.fromNullable(null) // None
136
+ const fromValue = Option.fromNullable(42) // Some(42)
137
+
138
+ // Creating from predicates
139
+ const fromPredicate = Option.fromPredicate(42, (n) => n > 0) // Some(42)
140
+
141
+ const fromFalsePredicate = Option.fromPredicate(-5, (n) => n > 0) // None
142
+
143
+ // Creating from try-catch blocks
144
+ const fromTry = Option.fromTry(() => JSON.parse('{"name":"John"}')) // Some({name: "John"})
145
+ const fromBadTry = Option.fromTry(() => JSON.parse("invalid json")) // None
146
+ ```
147
+
148
+ ## Either
149
+
150
+ The `Either` type represents a value of one of two possible types, typically used for error handling.
151
+
152
+ ### Basic Usage
153
+
154
+ ```typescript
155
+ import { Either, Left, Right } from "functype"
156
+
157
+ // Creating Either values
158
+ const success = Right<string, number>(42) // Right<number>(42)
159
+ const failure = Left<string, number>("error") // Left<string>("error")
160
+
161
+ // Check variants
162
+ console.log(success.isRight()) // true
163
+ console.log(success.isLeft()) // false
164
+ console.log(failure.isRight()) // false
165
+ console.log(failure.isLeft()) // true
166
+
167
+ // Safe access to values
168
+ console.log(success.get()) // 42
169
+ // console.log(failure.get()) // Would throw error - use getOrElse instead
170
+
171
+ // Default values
172
+ console.log(success.getOrElse(0)) // 42
173
+ console.log(failure.getOrElse(0)) // 0
174
+
175
+ // Get the left value
176
+ console.log(failure.getLeft()) // "error"
177
+ // console.log(success.getLeft()) // Would throw error
178
+ ```
179
+
180
+ ### Transformations
181
+
182
+ ```typescript
183
+ import { Either, Left, Right } from "functype"
184
+
185
+ const success = Right<string, number>(5)
186
+ const failure = Left<string, number>("invalid input")
187
+
188
+ // Map: transform right value
189
+ const doubled = success.map((x) => x * 2) // Right(10)
190
+ const failureMap = failure.map((x) => x * 2) // Left("invalid input")
191
+
192
+ // MapLeft: transform left value
193
+ const upperError = failure.mapLeft((e) => e.toUpperCase()) // Left("INVALID INPUT")
194
+ const successMapLeft = success.mapLeft((e) => e.toUpperCase()) // Right(5)
195
+
196
+ // Flat map (chain operations)
197
+ const divide = (n: number): Either<string, number> => (n === 0 ? Left("Division by zero") : Right(10 / n))
198
+
199
+ const result1 = success.flatMap(divide) // Right(2)
200
+ const result2 = Right<string, number>(0).flatMap(divide) // Left("Division by zero")
201
+ const result3 = failure.flatMap(divide) // Left("invalid input")
202
+
203
+ // Swap left and right
204
+ const swapped = success.swap() // Left(5)
205
+ ```
206
+
207
+ ### Pattern Matching
208
+
209
+ ```typescript
210
+ import { Either, Left, Right } from "functype"
211
+
212
+ const success = Right<string, number>(42)
213
+ const failure = Left<string, number>("error")
214
+
215
+ // Using fold for pattern matching
216
+ const result1 = success.fold(
217
+ (left) => `Error: ${left}`,
218
+ (right) => `Success: ${right}`,
219
+ )
220
+ console.log(result1) // "Success: 42"
221
+
222
+ const result2 = failure.fold(
223
+ (left) => `Error: ${left}`,
224
+ (right) => `Success: ${right}`,
225
+ )
226
+ console.log(result2) // "Error: error"
227
+
228
+ // Using match for pattern matching
229
+ const message1 = success.match({
230
+ Right: (value) => `Result: ${value}`,
231
+ Left: (error) => `Failed: ${error}`,
232
+ })
233
+ console.log(message1) // "Result: 42"
234
+
235
+ const message2 = failure.match({
236
+ Right: (value) => `Result: ${value}`,
237
+ Left: (error) => `Failed: ${error}`,
238
+ })
239
+ console.log(message2) // "Failed: error"
240
+ ```
241
+
242
+ ### Error Handling
243
+
244
+ ```typescript
245
+ import { Either } from "functype"
246
+
247
+ // tryCatch for synchronous operations
248
+ const jsonResult = Either.tryCatch(
249
+ () => JSON.parse('{"name":"John"}'),
250
+ (e) => (e instanceof Error ? e.message : String(e)),
251
+ ) // Right({name: "John"})
252
+
253
+ const badJsonResult = Either.tryCatch(
254
+ () => JSON.parse("invalid json"),
255
+ (e) => (e instanceof Error ? e.message : String(e)),
256
+ ) // Left("Unexpected token i in JSON at position 0")
257
+
258
+ // tryCatchAsync for asynchronous operations
259
+ async function fetchData() {
260
+ const result = await Either.tryCatchAsync(
261
+ async () => {
262
+ const res = await fetch("https://api.example.com/data")
263
+ if (!res.ok) throw new Error(`HTTP error: ${res.status}`)
264
+ return res.json()
265
+ },
266
+ (e) => (e instanceof Error ? e.message : String(e)),
267
+ )
268
+
269
+ return result.fold(
270
+ (error) => console.error("Failed to fetch:", error),
271
+ (data) => console.log("Data:", data),
272
+ )
273
+ }
274
+ ```
275
+
276
+ ### Factory Methods
277
+
278
+ ```typescript
279
+ import { Either } from "functype"
280
+
281
+ // Create from nullable values
282
+ const fromNull = Either.fromNullable(null, "Value was null") // Left("Value was null")
283
+ const fromValue = Either.fromNullable(42, "Value was null") // Right(42)
284
+
285
+ // Create from try-catch
286
+ const fromTry = Either.fromTry(
287
+ () => JSON.parse('{"name":"John"}'),
288
+ (e) => `Parse error: ${e}`,
289
+ ) // Right({name: "John"})
290
+
291
+ // Create from predicate
292
+ const validNumber = Either.fromPredicate(
293
+ 42,
294
+ (n) => n > 0,
295
+ (n) => `Number ${n} is not positive`,
296
+ ) // Right(42)
297
+
298
+ const invalidNumber = Either.fromPredicate(
299
+ -5,
300
+ (n) => n > 0,
301
+ (n) => `Number ${n} is not positive`,
302
+ ) // Left("Number -5 is not positive")
303
+ ```
304
+
305
+ ## Try
306
+
307
+ The `Try` type represents a computation that might throw an exception.
308
+
309
+ ### Basic Usage
310
+
311
+ ```typescript
312
+ import { Try, Success, Failure } from "functype"
313
+
314
+ // Creating Try values
315
+ const success = Try(() => 42) // Success(42)
316
+ const failure = Try(() => {
317
+ throw new Error("Something went wrong")
318
+ }) // Failure(Error)
319
+
320
+ // Check variants
321
+ console.log(success.isSuccess()) // true
322
+ console.log(success.isFailure()) // false
323
+ console.log(failure.isSuccess()) // false
324
+ console.log(failure.isFailure()) // true
325
+
326
+ // Safe access
327
+ console.log(success.get()) // 42
328
+ // console.log(failure.get()) // Would throw the original error
329
+
330
+ // Default values
331
+ console.log(success.getOrElse(0)) // 42
332
+ console.log(failure.getOrElse(0)) // 0
333
+
334
+ // Access the error
335
+ console.log(failure.error.message) // "Something went wrong"
336
+ ```
337
+
338
+ ### Transformations
339
+
340
+ ```typescript
341
+ import { Try } from "functype"
342
+
343
+ const success = Try(() => 5)
344
+ const failure = Try(() => {
345
+ throw new Error("Division error")
346
+ })
347
+
348
+ // Map: transform success values
349
+ const doubled = success.map((x) => x * 2) // Success(10)
350
+ const failureMap = failure.map((x) => x * 2) // Failure(Error)
351
+
352
+ // Flat map (chain operations)
353
+ const divide = (n: number) =>
354
+ Try(() => {
355
+ if (n === 0) throw new Error("Division by zero")
356
+ return 10 / n
357
+ })
358
+
359
+ const result1 = success.flatMap(divide) // Success(2)
360
+ const result2 = Try(() => 0).flatMap(divide) // Failure(Error: Division by zero)
361
+ ```
362
+
363
+ ### Pattern Matching
364
+
365
+ ```typescript
366
+ import { Try } from "functype"
367
+
368
+ const success = Try(() => 42)
369
+ const failure = Try(() => {
370
+ throw new Error("Something went wrong")
371
+ })
372
+
373
+ // Using fold for pattern matching
374
+ const result1 = success.fold(
375
+ (error) => `Error: ${error.message}`,
376
+ (value) => `Success: ${value}`,
377
+ )
378
+ console.log(result1) // "Success: 42"
379
+
380
+ const result2 = failure.fold(
381
+ (error) => `Error: ${error.message}`,
382
+ (value) => `Success: ${value}`,
383
+ )
384
+ console.log(result2) // "Error: Something went wrong"
385
+
386
+ // Using match for pattern matching
387
+ const message1 = success.match({
388
+ Success: (value) => `Result: ${value}`,
389
+ Failure: (error) => `Failed: ${error.message}`,
390
+ })
391
+ console.log(message1) // "Result: 42"
392
+
393
+ const message2 = failure.match({
394
+ Success: (value) => `Result: ${value}`,
395
+ Failure: (error) => `Failed: ${error.message}`,
396
+ })
397
+ console.log(message2) // "Failed: Something went wrong"
398
+ ```
399
+
400
+ ### Recovery
401
+
402
+ ```typescript
403
+ import { Try } from "functype"
404
+
405
+ const failure = Try(() => {
406
+ throw new Error("Network error")
407
+ })
408
+
409
+ // Recover with a default value
410
+ const recovered = failure.recover(0) // Success(0)
411
+
412
+ // Recover with another Try
413
+ const recoveredTry = failure.recoverWith(() => Try(() => "Fallback")) // Success("Fallback")
414
+
415
+ // Convert to other types
416
+ const asEither = failure.toEither() // Left(Error: Network error)
417
+ const asOption = failure.toOption() // None
418
+ ```
419
+
420
+ ## List
421
+
422
+ The `List` type is an immutable list with functional operations.
423
+
424
+ ### Basic Usage
425
+
426
+ ```typescript
427
+ import { List } from "functype"
428
+
429
+ // Creating lists
430
+ const empty = List([]) // List([])
431
+ const numbers = List([1, 2, 3, 4, 5]) // List([1, 2, 3, 4, 5])
432
+ const mixed = List([1, "two", true]) // List([1, "two", true])
433
+
434
+ // Access elements
435
+ console.log(numbers.head()) // Option(1)
436
+ console.log(numbers.tail()) // List([2, 3, 4, 5])
437
+ console.log(empty.head()) // None
438
+
439
+ // Check properties
440
+ console.log(numbers.isEmpty()) // false
441
+ console.log(empty.isEmpty()) // true
442
+ console.log(numbers.size()) // 5
443
+
444
+ // Add/remove elements (immutably)
445
+ const withSix = numbers.add(6) // List([1, 2, 3, 4, 5, 6])
446
+ const without3 = numbers.remove(3) // List([1, 2, 4, 5])
447
+
448
+ // Convert to array
449
+ console.log(numbers.toArray()) // [1, 2, 3, 4, 5]
450
+ ```
451
+
452
+ ### Transformations
453
+
454
+ ```typescript
455
+ import { List } from "functype"
456
+
457
+ const numbers = List([1, 2, 3, 4, 5])
458
+
459
+ // Map: transform each element
460
+ const doubled = numbers.map((x) => x * 2) // List([2, 4, 6, 8, 10])
461
+
462
+ // Filter: keep elements matching predicate
463
+ const evens = numbers.filter((x) => x % 2 === 0) // List([2, 4])
464
+
465
+ // FlatMap: transform and flatten
466
+ const pairs = numbers.flatMap((x) => List([x, x])) // List([1, 1, 2, 2, 3, 3, 4, 4, 5, 5])
467
+
468
+ // Take/Drop
469
+ const firstThree = numbers.take(3) // List([1, 2, 3])
470
+ const lastTwo = numbers.drop(3) // List([4, 5])
471
+
472
+ // Slicing
473
+ const middle = numbers.slice(1, 4) // List([2, 3, 4])
474
+ ```
475
+
476
+ ### Aggregations
477
+
478
+ ```typescript
479
+ import { List } from "functype"
480
+
481
+ const numbers = List([1, 2, 3, 4, 5])
482
+
483
+ // Reduce (foldLeft)
484
+ const sum = numbers.foldLeft(0)((acc, x) => acc + x) // 15
485
+
486
+ // Right-associative fold
487
+ const sumRight = numbers.foldRight(0)((x, acc) => x + acc) // 15
488
+
489
+ // Find elements
490
+ const firstEven = numbers.find((x) => x % 2 === 0) // Some(2)
491
+ const noMatch = numbers.find((x) => x > 10) // None
492
+
493
+ // Check if elements exist
494
+ console.log(numbers.exists((x) => x === 3)) // true
495
+ console.log(numbers.forAll((x) => x < 10)) // true
496
+
497
+ // Count elements
498
+ console.log(numbers.count((x) => x % 2 === 0)) // 2
499
+ ```
500
+
501
+ ### Operations
502
+
503
+ ```typescript
504
+ import { List } from "functype"
505
+
506
+ const list1 = List([1, 2, 3])
507
+ const list2 = List([4, 5, 6])
508
+
509
+ // Concatenation
510
+ const combined = list1.concat(list2) // List([1, 2, 3, 4, 5, 6])
511
+
512
+ // Reverse
513
+ const reversed = list1.reverse() // List([3, 2, 1])
514
+
515
+ // Sort
516
+ const unsorted = List([3, 1, 4, 2, 5])
517
+ const sorted = unsorted.sort((a, b) => a - b) // List([1, 2, 3, 4, 5])
518
+
519
+ // Unique values
520
+ const duplicates = List([1, 2, 2, 3, 3, 3])
521
+ const unique = duplicates.distinct() // List([1, 2, 3])
522
+
523
+ // Grouping
524
+ const grouped = List([1, 2, 3, 4, 5, 6]).groupBy((x) => (x % 2 === 0 ? "even" : "odd"))
525
+ // Map({ 'odd': List([1, 3, 5]), 'even': List([2, 4, 6]) })
526
+ ```
527
+
528
+ ### Pattern Matching
529
+
530
+ ```typescript
531
+ import { List } from "functype"
532
+
533
+ const numbers = List([1, 2, 3])
534
+ const empty = List([])
535
+
536
+ // Using match for pattern matching
537
+ const result1 = numbers.match({
538
+ NonEmpty: (values) => `Values: ${values.join(", ")}`,
539
+ Empty: () => "No values",
540
+ })
541
+ console.log(result1) // "Values: 1, 2, 3"
542
+
543
+ const result2 = empty.match({
544
+ NonEmpty: (values) => `Values: ${values.join(", ")}`,
545
+ Empty: () => "No values",
546
+ })
547
+ console.log(result2) // "No values"
548
+ ```
549
+
550
+ ## Map
551
+
552
+ The `Map` type is an immutable key-value map with functional operations.
553
+
554
+ ### Basic Usage
555
+
556
+ ```typescript
557
+ import { Map } from "functype"
558
+
559
+ // Creating maps
560
+ const empty = Map<string, number>({})
561
+ const scores = Map({
562
+ alice: 95,
563
+ bob: 87,
564
+ charlie: 92,
565
+ })
566
+
567
+ // Access elements
568
+ console.log(scores.get("alice")) // Some(95)
569
+ console.log(scores.get("dave")) // None
570
+ console.log(scores.getOrElse("dave", 0)) // 0
571
+
572
+ // Check properties
573
+ console.log(scores.isEmpty()) // false
574
+ console.log(empty.isEmpty()) // true
575
+ console.log(scores.size()) // 3
576
+ console.log(scores.has("bob")) // true
577
+
578
+ // Keys and values
579
+ console.log(scores.keys()) // List(["alice", "bob", "charlie"])
580
+ console.log(scores.values()) // List([95, 87, 92])
581
+
582
+ // Add/remove entries (immutably)
583
+ const withDave = scores.add("dave", 83) // Map with dave added
584
+ const withoutBob = scores.remove("bob") // Map without bob
585
+
586
+ // Convert to object
587
+ console.log(scores.toObject()) // { alice: 95, bob: 87, charlie: 92 }
588
+ ```
589
+
590
+ ### Transformations
591
+
592
+ ```typescript
593
+ import { Map } from "functype"
594
+
595
+ const scores = Map({
596
+ alice: 95,
597
+ bob: 87,
598
+ charlie: 92,
599
+ })
600
+
601
+ // Map: transform values
602
+ const grades = scores.map((score) => {
603
+ if (score >= 90) return "A"
604
+ if (score >= 80) return "B"
605
+ return "C"
606
+ })
607
+ // Map({ alice: 'A', bob: 'B', charlie: 'A' })
608
+
609
+ // Filter entries
610
+ const highScores = scores.filter((score, key) => score >= 90)
611
+ // Map({ alice: 95, charlie: 92 })
612
+
613
+ // Map entries (both key and value)
614
+ const prefixed = scores.mapEntries(([key, value]) => [`student_${key}`, value])
615
+ // Map({ student_alice: 95, student_bob: 87, student_charlie: 92 })
616
+ ```
617
+
618
+ ### Aggregations
619
+
620
+ ```typescript
621
+ import { Map } from "functype"
622
+
623
+ const scores = Map({
624
+ alice: 95,
625
+ bob: 87,
626
+ charlie: 92,
627
+ })
628
+
629
+ // Fold
630
+ const total = scores.foldLeft(0)((acc, score) => acc + score) // 274
631
+ const report = scores.foldLeft("")((acc, score, name) => `${acc}${name}: ${score}\n`)
632
+ // "alice: 95\nbob: 87\ncharlie: 92\n"
633
+
634
+ // Find entries
635
+ const found = scores.find((score) => score > 90) // Some([alice, 95])
636
+ const notFound = scores.find((score) => score > 100) // None
637
+ ```
638
+
639
+ ### Operations
640
+
641
+ ```typescript
642
+ import { Map } from "functype"
643
+
644
+ const group1 = Map({ alice: 95, bob: 87 })
645
+ const group2 = Map({ charlie: 92, dave: 88 })
646
+
647
+ // Merge maps
648
+ const allScores = group1.merge(group2)
649
+ // Map({ alice: 95, bob: 87, charlie: 92, dave: 88 })
650
+
651
+ // Override values
652
+ const updated = group1.merge(Map({ bob: 90 }))
653
+ // Map({ alice: 95, bob: 90 })
654
+
655
+ // Custom merge logic
656
+ const merged = group1.mergeWith(Map({ bob: 90 }), (v1, v2) => Math.max(v1, v2))
657
+ // Map({ alice: 95, bob: 90 })
658
+ ```
659
+
660
+ ## Set
661
+
662
+ The `Set` type is an immutable set collection with functional operations.
663
+
664
+ ### Basic Usage
665
+
666
+ ```typescript
667
+ import { Set } from "functype"
668
+
669
+ // Creating sets
670
+ const empty = Set<number>([])
671
+ const numbers = Set([1, 2, 3, 4, 5])
672
+ const withDuplicates = Set([1, 1, 2, 2, 3]) // Set([1, 2, 3])
673
+
674
+ // Check properties
675
+ console.log(numbers.isEmpty()) // false
676
+ console.log(empty.isEmpty()) // true
677
+ console.log(numbers.size()) // 5
678
+ console.log(numbers.has(3)) // true
679
+ console.log(numbers.has(10)) // false
680
+
681
+ // Add/remove elements (immutably)
682
+ const withSix = numbers.add(6) // Set([1, 2, 3, 4, 5, 6])
683
+ const without3 = numbers.remove(3) // Set([1, 2, 4, 5])
684
+
685
+ // Convert to array
686
+ console.log(numbers.toArray()) // [1, 2, 3, 4, 5]
687
+ ```
688
+
689
+ ### Transformations
690
+
691
+ ```typescript
692
+ import { Set } from "functype"
693
+
694
+ const numbers = Set([1, 2, 3, 4, 5])
695
+
696
+ // Map: transform each element
697
+ const doubled = numbers.map((x) => x * 2) // Set([2, 4, 6, 8, 10])
698
+
699
+ // Filter: keep elements matching predicate
700
+ const evens = numbers.filter((x) => x % 2 === 0) // Set([2, 4])
701
+
702
+ // FlatMap: transform and flatten
703
+ const adjacents = numbers.flatMap((x) => Set([x - 1, x, x + 1]))
704
+ // Set([0, 1, 2, 3, 4, 5, 6])
705
+ ```
706
+
707
+ ### Set Operations
708
+
709
+ ```typescript
710
+ import { Set } from "functype"
711
+
712
+ const set1 = Set([1, 2, 3, 4])
713
+ const set2 = Set([3, 4, 5, 6])
714
+
715
+ // Union
716
+ const union = set1.union(set2) // Set([1, 2, 3, 4, 5, 6])
717
+
718
+ // Intersection
719
+ const intersection = set1.intersect(set2) // Set([3, 4])
720
+
721
+ // Difference
722
+ const difference = set1.difference(set2) // Set([1, 2])
723
+
724
+ // Symmetric difference
725
+ const symmetricDiff = set1.symmetricDifference(set2) // Set([1, 2, 5, 6])
726
+
727
+ // Subset check
728
+ const subset = Set([1, 2])
729
+ console.log(subset.isSubsetOf(set1)) // true
730
+ console.log(set1.isSubsetOf(subset)) // false
731
+ ```
732
+
733
+ ### Aggregations
734
+
735
+ ```typescript
736
+ import { Set } from "functype"
737
+
738
+ const numbers = Set([1, 2, 3, 4, 5])
739
+
740
+ // Fold
741
+ const sum = numbers.foldLeft(0)((acc, x) => acc + x) // 15
742
+
743
+ // Find elements
744
+ const firstEven = numbers.find((x) => x % 2 === 0) // Some(2)
745
+ const noMatch = numbers.find((x) => x > 10) // None
746
+
747
+ // Check if elements exist
748
+ console.log(numbers.exists((x) => x % 2 === 0)) // true
749
+ console.log(numbers.forAll((x) => x < 10)) // true
750
+ ```
751
+
752
+ ## FPromise
753
+
754
+ The `FPromise` type enhances JavaScript's Promise with functional operations and better error handling.
755
+
756
+ ### Basic Usage
757
+
758
+ ```typescript
759
+ import { FPromise } from "functype"
760
+
761
+ // Creating FPromises
762
+ const success = FPromise.resolve(42)
763
+ const failure = FPromise.reject(new Error("Something went wrong"))
764
+ const fromPromise = FPromise.fromPromise(fetch("https://api.example.com/data"))
765
+
766
+ // From regular functions
767
+ const compute = () => 42
768
+ const fp1 = FPromise.tryCatch(compute) // FPromise<number, Error>
769
+
770
+ // From async functions
771
+ const fetchData = async () => {
772
+ const response = await fetch("https://api.example.com/data")
773
+ if (!response.ok) throw new Error(`HTTP error: ${response.status}`)
774
+ return response.json()
775
+ }
776
+
777
+ const fp2 = FPromise.tryCatch(fetchData) // FPromise<Data, Error>
778
+ ```
779
+
780
+ ### Transformations
781
+
782
+ ```typescript
783
+ import { FPromise } from "functype"
784
+
785
+ const promise = FPromise.resolve(5)
786
+
787
+ // Map: transform success value
788
+ const doubled = promise.map((x) => x * 2) // FPromise<10, never>
789
+
790
+ // MapError: transform error value
791
+ const mappedError = promise.mapError((e) => new Error(`Enhanced error: ${e.message}`))
792
+
793
+ // FlatMap: chain operations
794
+ const nextOperation = (n: number) => FPromise.resolve(n.toString())
795
+ const chained = promise.flatMap(nextOperation) // FPromise<"5", never>
796
+
797
+ // Tap: perform side effects
798
+ const withLogging = promise.tap((value) => {
799
+ console.log("Processing value:", value)
800
+ })
801
+
802
+ // TapError: side effects for errors
803
+ const withErrorLogging = promise.tapError((error) => {
804
+ console.error("Error occurred:", error)
805
+ })
806
+ ```
807
+
808
+ ### Error Handling
809
+
810
+ ```typescript
811
+ import { FPromise } from "functype"
812
+
813
+ const failedPromise = FPromise.reject(new Error("Network error"))
814
+
815
+ // Recover with a default value
816
+ const recovered = failedPromise.recover(0) // FPromise<0, never>
817
+
818
+ // Recover with another operation
819
+ const recoveredWith = failedPromise.recoverWith((err) => FPromise.resolve(`Recovered from: ${err.message}`))
820
+
821
+ // Handle both success and error paths
822
+ const handled = FPromise.resolve(42).fold(
823
+ (err) => `Error: ${err.message}`,
824
+ (value) => `Success: ${value}`,
825
+ ) // FPromise<"Success: 42", never>
826
+
827
+ // Convert to standard Promise
828
+ const stdPromise = FPromise.resolve(42).toPromise()
829
+ stdPromise.then((value) => console.log(value)) // 42
830
+ ```
831
+
832
+ ### Parallel Operations
833
+
834
+ ```typescript
835
+ import { FPromise } from "functype"
836
+
837
+ const p1 = FPromise.resolve(1)
838
+ const p2 = FPromise.resolve(2)
839
+ const p3 = FPromise.resolve(3)
840
+
841
+ // Parallel execution
842
+ const all = FPromise.all([p1, p2, p3]) // FPromise<[1, 2, 3], never>
843
+
844
+ // Race
845
+ const race = FPromise.race([FPromise.delay(100).map(() => "fast"), FPromise.delay(200).map(() => "slow")]) // FPromise<"fast", never>
846
+
847
+ // With timeout
848
+ const withTimeout = FPromise.timeout(
849
+ FPromise.delay(2000).map(() => "result"),
850
+ 1000,
851
+ () => new Error("Operation timed out"),
852
+ ) // FPromise<never, Error> (times out)
853
+ ```
854
+
855
+ ### Retry Logic
856
+
857
+ ```typescript
858
+ import { FPromise, retry } from "functype/fpromise"
859
+
860
+ const unreliableOperation = () => {
861
+ // Simulate an operation that sometimes fails
862
+ if (Math.random() < 0.7) {
863
+ return FPromise.reject(new Error("Temporary failure"))
864
+ }
865
+ return FPromise.resolve("Success!")
866
+ }
867
+
868
+ // Basic retry
869
+ const retried = retry({
870
+ task: unreliableOperation,
871
+ maxRetries: 5,
872
+ }) // Will retry up to 5 times
873
+
874
+ // Advanced retry with backoff
875
+ const retriedWithBackoff = retry({
876
+ task: unreliableOperation,
877
+ maxRetries: 5,
878
+ delay: 1000, // Start with 1 second delay
879
+ backoffFactor: 2, // Double delay after each attempt
880
+ maxDelay: 10000, // Cap delay at 10 seconds
881
+ retryIf: (error) => error.message.includes("Temporary"), // Only retry certain errors
882
+ })
883
+ ```
884
+
885
+ ## Task
886
+
887
+ The `Task` type represents synchronous and asynchronous operations with error handling.
888
+
889
+ ### Basic Usage
890
+
891
+ ```typescript
892
+ import { Task } from "functype"
893
+
894
+ // Synchronous tasks
895
+ const syncTask = Task().Sync(
896
+ () => 42, // Success function
897
+ (error) => new Error(`Failed: ${error}`), // Error function
898
+ )
899
+
900
+ // Asynchronous tasks
901
+ const asyncTask = Task().Async(
902
+ async () => {
903
+ const response = await fetch("https://api.example.com/data")
904
+ if (!response.ok) throw new Error(`HTTP error: ${response.status}`)
905
+ return response.json()
906
+ },
907
+ async (error) => new Error(`Fetch failed: ${error}`),
908
+ )
909
+
910
+ // Named tasks (for debugging)
911
+ const namedTask = Task({ name: "UserFetch" }).Sync(
912
+ () => ({ id: 1, name: "John Doe" }),
913
+ (error) => new Error(`User fetch failed: ${error}`),
914
+ )
915
+
916
+ // Running tasks
917
+ syncTask.then((value) => console.log("Success:", value)).catch((error) => console.error("Error:", error))
918
+
919
+ // With async/await
920
+ async function runTask() {
921
+ try {
922
+ const result = await asyncTask
923
+ console.log("Data:", result)
924
+ } catch (error) {
925
+ console.error("Failed:", error)
926
+ }
927
+ }
928
+ ```
929
+
930
+ ### Adapting External APIs
931
+
932
+ ```typescript
933
+ import { Task } from "functype"
934
+
935
+ // Convert promise-based APIs to Task
936
+ const fetchUser = (id: string): Promise<User> => fetch(`/api/users/${id}`).then((r) => r.json())
937
+
938
+ // Create a Task adapter
939
+ const getUser = Task({ name: "UserFetch" }).fromPromise(fetchUser)
940
+
941
+ // Use the task
942
+ getUser("user123")
943
+ .then((user) => console.log(user))
944
+ .catch((error) => console.error(error))
945
+
946
+ // Convert back to Promise when needed
947
+ const task = Task().Sync(() => "hello world")
948
+ const promise = Task().toPromise(task) // Promise<string>
949
+ ```
950
+
951
+ ### Composition
952
+
953
+ ```typescript
954
+ import { Task } from "functype"
955
+
956
+ // Define component tasks
957
+ const fetchUser = Task({ name: "FetchUser" }).Async(
958
+ async (userId: string) => {
959
+ const response = await fetch(`/api/users/${userId}`)
960
+ if (!response.ok) throw new Error(`HTTP error: ${response.status}`)
961
+ return response.json()
962
+ },
963
+ async (error) => new Error(`User fetch failed: ${error}`),
964
+ )
965
+
966
+ const fetchPosts = Task({ name: "FetchPosts" }).Async(
967
+ async (userId: string) => {
968
+ const response = await fetch(`/api/users/${userId}/posts`)
969
+ if (!response.ok) throw new Error(`HTTP error: ${response.status}`)
970
+ return response.json()
971
+ },
972
+ async (error) => new Error(`Posts fetch failed: ${error}`),
973
+ )
974
+
975
+ // Compose tasks
976
+ async function getUserWithPosts(userId: string) {
977
+ try {
978
+ // Run tasks in sequence with dependencies
979
+ const user = await fetchUser(userId)
980
+ const posts = await fetchPosts(userId)
981
+ return { user, posts }
982
+ } catch (error) {
983
+ console.error("Error:", error)
984
+ throw error
985
+ }
986
+ }
987
+
988
+ // Run parallel tasks
989
+ async function getMultipleUsers(userIds: string[]) {
990
+ try {
991
+ const userTasks = userIds.map((id) => fetchUser(id))
992
+ const users = await Promise.all(userTasks)
993
+ return users
994
+ } catch (error) {
995
+ console.error("Error:", error)
996
+ throw error
997
+ }
998
+ }
999
+ ```
1000
+
1001
+ ## Branded Types
1002
+
1003
+ Branded types provide nominal typing in TypeScript's structural type system, giving stronger type safety.
1004
+
1005
+ ### Basic Usage
1006
+
1007
+ ```typescript
1008
+ import { Brand } from "functype/branded"
1009
+
1010
+ // Create branded types
1011
+ type UserId = Brand<string, "UserId">
1012
+ type Email = Brand<string, "Email">
1013
+
1014
+ // Create factory functions with validation
1015
+ const UserId = (id: string): UserId => {
1016
+ if (!/^U\d{6}$/.test(id)) {
1017
+ throw new Error("Invalid user ID format")
1018
+ }
1019
+ return id as UserId
1020
+ }
1021
+
1022
+ const Email = (email: string): Email => {
1023
+ if (!/^[^@]+@[^@]+\.[^@]+$/.test(email)) {
1024
+ throw new Error("Invalid email format")
1025
+ }
1026
+ return email as Email
1027
+ }
1028
+
1029
+ // Usage
1030
+ function getUserByEmail(email: Email): User {
1031
+ /* ... */
1032
+ return { id: "U123456" as UserId, email }
1033
+ }
1034
+
1035
+ // Type safety in action
1036
+ const email = Email("user@example.com")
1037
+ const user = getUserByEmail(email) // Works
1038
+
1039
+ // These would cause type errors
1040
+ // getUserByEmail("invalid") // Error: Argument of type 'string' is not assignable to parameter of type 'Email'
1041
+ // getUserByEmail(UserId("U123456")) // Error: Argument of type 'UserId' is not assignable to parameter of type 'Email'
1042
+ ```
1043
+
1044
+ ### Advanced Branded Types
1045
+
1046
+ ```typescript
1047
+ import { Brand } from "functype/branded"
1048
+
1049
+ // Numeric constraints
1050
+ type PositiveInt = Brand<number, "PositiveInt">
1051
+ type Percentage = Brand<number, "Percentage">
1052
+
1053
+ const PositiveInt = (n: number): PositiveInt => {
1054
+ if (!Number.isInteger(n) || n <= 0) {
1055
+ throw new Error("Must be a positive integer")
1056
+ }
1057
+ return n as PositiveInt
1058
+ }
1059
+
1060
+ const Percentage = (n: number): Percentage => {
1061
+ if (n < 0 || n > 100) {
1062
+ throw new Error("Percentage must be between 0 and 100")
1063
+ }
1064
+ return n as Percentage
1065
+ }
1066
+
1067
+ // String format validation
1068
+ type ISBN = Brand<string, "ISBN">
1069
+ type CreditCardNumber = Brand<string, "CreditCardNumber">
1070
+
1071
+ const ISBN = (isbn: string): ISBN => {
1072
+ // Simplified validation
1073
+ if (!/^\d{10}(\d{3})?$/.test(isbn.replace(/-/g, ""))) {
1074
+ throw new Error("Invalid ISBN format")
1075
+ }
1076
+ return isbn as ISBN
1077
+ }
1078
+
1079
+ // Combining with Option for safe creation
1080
+ import { Option } from "functype/option"
1081
+
1082
+ const SafeEmail = (email: string): Option<Email> => {
1083
+ try {
1084
+ return Option(Email(email))
1085
+ } catch (e) {
1086
+ return Option(null)
1087
+ }
1088
+ }
1089
+
1090
+ // Usage
1091
+ const maybeEmail = SafeEmail("invalid") // None
1092
+ const validEmail = SafeEmail("user@example.com") // Some(Email)
1093
+ ```
1094
+
1095
+ ## Tuple
1096
+
1097
+ The `Tuple` type provides a type-safe fixed-length array.
1098
+
1099
+ ### Basic Usage
1100
+
1101
+ ```typescript
1102
+ import { Tuple } from "functype"
1103
+
1104
+ // Create tuples of different sizes and types
1105
+ const pair = Tuple(42, "hello") // Tuple<[number, string]>
1106
+ const triple = Tuple(true, 100, "world") // Tuple<[boolean, number, string]>
1107
+
1108
+ // Access elements (type-safe)
1109
+ console.log(pair.first()) // 42
1110
+ console.log(pair.second()) // "hello"
1111
+ console.log(triple.third()) // "world"
1112
+
1113
+ // Destructuring
1114
+ const [a, b] = pair.toArray() // a: number, b: string
1115
+ console.log(a, b) // 42 "hello"
1116
+
1117
+ // Map individual elements
1118
+ const mappedPair = pair.mapFirst((n) => n * 2) // Tuple(84, "hello")
1119
+ const mappedPair2 = pair.mapSecond((s) => s.toUpperCase()) // Tuple(42, "HELLO")
1120
+
1121
+ // Map the entire tuple
1122
+ const mapped = pair.map(([num, str]) => {
1123
+ return [num * 2, str.toUpperCase()] as [number, string]
1124
+ }) // Tuple(84, "HELLO")
1125
+ ```
1126
+
1127
+ ### Operations
1128
+
1129
+ ```typescript
1130
+ import { Tuple } from "functype"
1131
+
1132
+ // Swap elements
1133
+ const pair = Tuple("first", "second")
1134
+ const swapped = pair.swap() // Tuple("second", "first")
1135
+
1136
+ // Apply a function to tuple elements
1137
+ const nums = Tuple(5, 10)
1138
+ const sum = nums.apply((a, b) => a + b) // 15
1139
+
1140
+ // Combine tuples
1141
+ const t1 = Tuple(1, "a")
1142
+ const t2 = Tuple(true, 42)
1143
+ const combined = t1.concat(t2) // Tuple(1, "a", true, 42)
1144
+
1145
+ // Convert to object with keys
1146
+ const person = Tuple("John", 30)
1147
+ const obj = person.toObject(["name", "age"]) // { name: "John", age: 30 }
1148
+ ```
1149
+
1150
+ ### With Other Types
1151
+
1152
+ ```typescript
1153
+ import { Tuple, Option, Either } from "functype"
1154
+
1155
+ // Create tuples with Options
1156
+ const maybePair = Tuple(Option(42), Option("hello"))
1157
+
1158
+ // Map with Options
1159
+ const optResult = maybePair.mapFirst((opt) => opt.map((n) => n * 2))
1160
+
1161
+ // Create tuples with Either
1162
+ const validationPair = Tuple(
1163
+ Either.fromNullable(42, "Missing first value"),
1164
+ Either.fromNullable(null, "Missing second value"),
1165
+ )
1166
+
1167
+ // Check if all Either values are valid
1168
+ const allValid = validationPair.toArray().every((either) => either.isRight()) // false
1169
+
1170
+ // Combine Tuple and Task
1171
+ import { Task } from "functype"
1172
+
1173
+ const taskPair = Tuple(
1174
+ Task().Sync(() => "hello"),
1175
+ Task().Sync(() => 42),
1176
+ )
1177
+
1178
+ // Run tasks in parallel
1179
+ async function runBoth() {
1180
+ const [str, num] = await Promise.all(taskPair.toArray())
1181
+ console.log(str, num) // "hello" 42
1182
+ }
1183
+ ```
1184
+
1185
+ ## Foldable
1186
+
1187
+ The `Foldable` type class provides a common interface for data structures that can be "folded" to a single value.
1188
+
1189
+ ### Using Foldable Interface
1190
+
1191
+ ```typescript
1192
+ import { FoldableUtils, Option, List, Try } from "functype"
1193
+
1194
+ // Different data structures implementing Foldable
1195
+ const option = Option(5)
1196
+ const list = List([1, 2, 3, 4, 5])
1197
+ const tryVal = Try(() => 10)
1198
+
1199
+ // Using fold to pattern-match on data structures
1200
+ option.fold(
1201
+ () => console.log("Empty option"),
1202
+ (value) => console.log(`Option value: ${value}`),
1203
+ ) // "Option value: 5"
1204
+
1205
+ // Left-associative fold (reduce from left to right)
1206
+ const sum = list.foldLeft(0)((acc, value) => acc + value) // 15
1207
+
1208
+ // Right-associative fold (reduce from right to left)
1209
+ const product = list.foldRight(1)((value, acc) => value * acc) // 120
1210
+
1211
+ // Using FoldableUtils to work with any Foldable
1212
+ const isEmpty = FoldableUtils.isEmpty(option) // false
1213
+ const size = FoldableUtils.size(list) // 5
1214
+ ```
1215
+
1216
+ ### Converting Between Types
1217
+
1218
+ ```typescript
1219
+ import { FoldableUtils, Option, List, Either, Try } from "functype"
1220
+
1221
+ // Convert between data structure types
1222
+ const opt = Option(42)
1223
+ const list = List([1, 2, 3])
1224
+ const either = Either.right<string, number>(10)
1225
+ const tryVal = Try(() => "hello")
1226
+
1227
+ // Convert to List
1228
+ const optAsList = FoldableUtils.toList(opt) // List([42])
1229
+ const eitherAsList = FoldableUtils.toList(either) // List([10])
1230
+
1231
+ // Convert to Option (takes first element from collections)
1232
+ const listAsOption = FoldableUtils.toOption(list) // Some(1)
1233
+
1234
+ // Convert to Either
1235
+ const optAsEither = FoldableUtils.toEither(opt, "Empty") // Right(42)
1236
+ const tryAsEither = FoldableUtils.toEither(tryVal, "Failed") // Right("hello")
1237
+
1238
+ // Extract all values from foldable structures
1239
+ const listValues = FoldableUtils.values(list) // [1, 2, 3]
1240
+ const optValues = FoldableUtils.values(opt) // [42]
1241
+ ```
1242
+
1243
+ ## Matchable
1244
+
1245
+ The `Matchable` type class provides pattern matching capabilities for data structures.
1246
+
1247
+ ### Basic Pattern Matching
1248
+
1249
+ ```typescript
1250
+ import { Option, Either, Try, List } from "functype"
1251
+
1252
+ // Pattern matching on Option
1253
+ const opt = Option(42)
1254
+ const optResult = opt.match({
1255
+ Some: (value) => `Found: ${value}`,
1256
+ None: () => "Not found",
1257
+ })
1258
+ console.log(optResult) // "Found: 42"
1259
+
1260
+ // Pattern matching on Either
1261
+ const either = Either.fromNullable(null, "Missing value")
1262
+ const eitherResult = either.match({
1263
+ Left: (error) => `Error: ${error}`,
1264
+ Right: (value) => `Value: ${value}`,
1265
+ })
1266
+ console.log(eitherResult) // "Error: Missing value"
1267
+
1268
+ // Pattern matching on Try
1269
+ const tryVal = Try(() => JSON.parse('{"name":"John"}'))
1270
+ const tryResult = tryVal.match({
1271
+ Success: (data) => `Name: ${data.name}`,
1272
+ Failure: (error) => `Parse error: ${error.message}`,
1273
+ })
1274
+ console.log(tryResult) // "Name: John"
1275
+
1276
+ // Pattern matching on List
1277
+ const list = List([1, 2, 3])
1278
+ const listResult = list.match({
1279
+ NonEmpty: (values) => `Values: ${values.join(", ")}`,
1280
+ Empty: () => "No values",
1281
+ })
1282
+ console.log(listResult) // "Values: 1, 2, 3"
1283
+ ```
1284
+
1285
+ ### Advanced Pattern Matching
1286
+
1287
+ ```typescript
1288
+ import { MatchableUtils } from "functype"
1289
+
1290
+ // Create pattern matchers with guards
1291
+ const isPositive = MatchableUtils.when(
1292
+ (n: number) => n > 0,
1293
+ (n) => `Positive: ${n}`,
1294
+ )
1295
+
1296
+ const isZero = MatchableUtils.when(
1297
+ (n: number) => n === 0,
1298
+ () => "Zero",
1299
+ )
1300
+
1301
+ const isNegative = MatchableUtils.when(
1302
+ (n: number) => n < 0,
1303
+ (n) => `Negative: ${n}`,
1304
+ )
1305
+
1306
+ const defaultCase = MatchableUtils.default((n: number) => `Default: ${n}`)
1307
+
1308
+ // Using pattern matching with multiple conditions
1309
+ function describeNumber(num: number): string {
1310
+ return isPositive(num) ?? isZero(num) ?? isNegative(num) ?? defaultCase(num)
1311
+ }
1312
+
1313
+ console.log(describeNumber(42)) // "Positive: 42"
1314
+ console.log(describeNumber(0)) // "Zero"
1315
+ console.log(describeNumber(-10)) // "Negative: -10"
1316
+ ```
1317
+
1318
+ ## Common Patterns
1319
+
1320
+ ### Chaining Operations
1321
+
1322
+ ```typescript
1323
+ import { Option, Either, List } from "functype"
1324
+
1325
+ // Option chaining
1326
+ const userInput = Option(" John Doe ")
1327
+ const processedName = userInput
1328
+ .map((s) => s.trim())
1329
+ .filter((s) => s.length > 0)
1330
+ .map((s) => s.toUpperCase())
1331
+ .getOrElse("ANONYMOUS")
1332
+ console.log(processedName) // "JOHN DOE"
1333
+
1334
+ // Either chaining
1335
+ function validateAge(age: number): Either<string, number> {
1336
+ if (!Number.isInteger(age)) return Either.left("Age must be an integer")
1337
+ if (age < 0) return Either.left("Age cannot be negative")
1338
+ if (age > 120) return Either.left("Age is too high")
1339
+ return Either.right(age)
1340
+ }
1341
+
1342
+ const processAge = (input: string): Either<string, string> => {
1343
+ return Either.tryCatch(
1344
+ () => parseInt(input, 10),
1345
+ () => "Invalid number format",
1346
+ )
1347
+ .flatMap(validateAge)
1348
+ .map((age) => `Valid age: ${age}`)
1349
+ }
1350
+
1351
+ console.log(processAge("35").getOrElse("Invalid age")) // "Valid age: 35"
1352
+ console.log(processAge("abc").getOrElse("Invalid age")) // "Invalid age"
1353
+
1354
+ // List chaining
1355
+ const numbers = List([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
1356
+ const result = numbers
1357
+ .filter((n) => n % 2 === 0) // even numbers
1358
+ .map((n) => n * n) // square them
1359
+ .filter((n) => n > 20) // keep those > 20
1360
+ .take(2) // take first 2
1361
+ .foldLeft(0)((acc, n) => acc + n) // sum them
1362
+ console.log(result) // 36 + 64 = 100
1363
+ ```
1364
+
1365
+ ### Composition with Pipe
1366
+
1367
+ ```typescript
1368
+ import { Option, Either, List, pipe } from "functype"
1369
+
1370
+ // Pipe with Option
1371
+ const result1 = pipe(
1372
+ Option(5),
1373
+ (opt) => opt.map((n) => n * 2),
1374
+ (opt) => opt.filter((n) => n > 5),
1375
+ (opt) => opt.getOrElse(0),
1376
+ )
1377
+ console.log(result1) // 10
1378
+
1379
+ // Pipe with Either
1380
+ const result2 = pipe(
1381
+ Either.right<string, number>(42),
1382
+ (either) => either.map((n) => n.toString()),
1383
+ (either) => either.mapLeft((e) => new Error(e)),
1384
+ (either) => either.getOrElse("error"),
1385
+ )
1386
+ console.log(result2) // "42"
1387
+
1388
+ // Pipe with List and multiple data types
1389
+ const result3 = pipe(
1390
+ List([1, 2, 3, 4]),
1391
+ (list) => list.map((n) => n * 2),
1392
+ (list) => Option(list.head().getOrElse(0)),
1393
+ (opt) => opt.filter((n) => n > 5),
1394
+ (opt) => Either.fromNullable(opt.getOrElse(null), "No valid value"),
1395
+ (either) =>
1396
+ either.fold(
1397
+ (err) => `Error: ${err}`,
1398
+ (val) => `Success: ${val}`,
1399
+ ),
1400
+ )
1401
+ console.log(result3) // "Success: 8"
1402
+ ```
1403
+
1404
+ ## Error Handling
1405
+
1406
+ ### Option for Nullable Values
1407
+
1408
+ ```typescript
1409
+ import { Option } from "functype"
1410
+
1411
+ // Option instead of null checks
1412
+ function findUserById(id: string): Option<User> {
1413
+ const user = database.findUser(id) // might return null
1414
+ return Option(user)
1415
+ }
1416
+
1417
+ // Usage
1418
+ const userId = "user123"
1419
+ const user = findUserById(userId)
1420
+
1421
+ // No need for null checks
1422
+ const greet = user.map((u) => `Hello, ${u.name}!`).getOrElse("User not found")
1423
+
1424
+ // Method chaining without worrying about null
1425
+ const userCity = user
1426
+ .flatMap((u) => Option(u.address))
1427
+ .flatMap((a) => Option(a.city))
1428
+ .getOrElse("Unknown location")
1429
+ ```
1430
+
1431
+ ### Either for Error Handling
1432
+
1433
+ ```typescript
1434
+ import { Either } from "functype"
1435
+
1436
+ // Define domain-specific errors
1437
+ class ValidationError extends Error {
1438
+ constructor(message: string) {
1439
+ super(message)
1440
+ this.name = "ValidationError"
1441
+ }
1442
+ }
1443
+
1444
+ class NotFoundError extends Error {
1445
+ constructor(message: string) {
1446
+ super(message)
1447
+ this.name = "NotFoundError"
1448
+ }
1449
+ }
1450
+
1451
+ // Functions return Either instead of throwing
1452
+ function validateEmail(email: string): Either<ValidationError, string> {
1453
+ const emailRegex = /^[^@]+@[^@]+\.[^@]+$/
1454
+ return emailRegex.test(email) ? Either.right(email) : Either.left(new ValidationError(`Invalid email: ${email}`))
1455
+ }
1456
+
1457
+ function findUserByEmail(email: string): Either<NotFoundError, User> {
1458
+ const user = database.findUserByEmail(email)
1459
+ return user ? Either.right(user) : Either.left(new NotFoundError(`User not found for email: ${email}`))
1460
+ }
1461
+
1462
+ // Chain operations with proper error handling
1463
+ function processUserEmail(email: string): Either<Error, string> {
1464
+ return validateEmail(email)
1465
+ .flatMap((validEmail) => findUserByEmail(validEmail))
1466
+ .map((user) => `User ${user.name} found with email ${email}`)
1467
+ }
1468
+
1469
+ // Usage
1470
+ const result = processUserEmail("user@example.com")
1471
+ result.fold(
1472
+ (error) => console.error(`Error: ${error.message}`),
1473
+ (success) => console.log(success),
1474
+ )
1475
+ ```
1476
+
1477
+ ### Try for Exception Handling
1478
+
1479
+ ```typescript
1480
+ import { Try, Option, Either } from "functype"
1481
+
1482
+ // Safely handle potentially throwing code
1483
+ function parseJSON(json: string): Try<unknown> {
1484
+ return Try(() => JSON.parse(json))
1485
+ }
1486
+
1487
+ // Parse configuration from JSON file
1488
+ function loadConfig(filePath: string): Try<Config> {
1489
+ return Try(() => {
1490
+ const fileContents = fs.readFileSync(filePath, "utf8")
1491
+ return JSON.parse(fileContents) as Config
1492
+ })
1493
+ }
1494
+
1495
+ // Usage
1496
+ const config = loadConfig("/path/to/config.json").recover({ host: "localhost", port: 8080 }) // Default if loading fails
1497
+
1498
+ // Convert to other types as needed
1499
+ const configOption: Option<Config> = config.toOption()
1500
+ const configEither: Either<Error, Config> = config.toEither()
1501
+ ```
1502
+
1503
+ ### FPromise for Async Error Handling
1504
+
1505
+ ```typescript
1506
+ import { FPromise } from "functype"
1507
+
1508
+ // API client with proper error handling
1509
+ class ApiClient {
1510
+ private baseUrl: string
1511
+
1512
+ constructor(baseUrl: string) {
1513
+ this.baseUrl = baseUrl
1514
+ }
1515
+
1516
+ fetchData<T>(endpoint: string): FPromise<T, Error> {
1517
+ return FPromise.tryCatchAsync(
1518
+ async () => {
1519
+ const response = await fetch(`${this.baseUrl}${endpoint}`)
1520
+
1521
+ if (!response.ok) {
1522
+ throw new Error(`HTTP error: ${response.status}`)
1523
+ }
1524
+
1525
+ return response.json() as Promise<T>
1526
+ },
1527
+ (error) => {
1528
+ if (error instanceof Error) {
1529
+ return error
1530
+ }
1531
+ return new Error(String(error))
1532
+ },
1533
+ )
1534
+ }
1535
+
1536
+ submitData<T, R>(endpoint: string, data: T): FPromise<R, Error> {
1537
+ return FPromise.tryCatchAsync(
1538
+ async () => {
1539
+ const response = await fetch(`${this.baseUrl}${endpoint}`, {
1540
+ method: "POST",
1541
+ headers: { "Content-Type": "application/json" },
1542
+ body: JSON.stringify(data),
1543
+ })
1544
+
1545
+ if (!response.ok) {
1546
+ throw new Error(`HTTP error: ${response.status}`)
1547
+ }
1548
+
1549
+ return response.json() as Promise<R>
1550
+ },
1551
+ (error) => {
1552
+ if (error instanceof Error) {
1553
+ return error
1554
+ }
1555
+ return new Error(String(error))
1556
+ },
1557
+ )
1558
+ }
1559
+ }
1560
+
1561
+ // Usage
1562
+ const api = new ApiClient("https://api.example.com")
1563
+
1564
+ api
1565
+ .fetchData<User[]>("/users")
1566
+ .map((users) => users.map((u) => u.name))
1567
+ .fold(
1568
+ (error) => console.error(`Failed to fetch users: ${error.message}`),
1569
+ (names) => console.log(`User names: ${names.join(", ")}`),
1570
+ )
1571
+
1572
+ api
1573
+ .submitData<NewUser, User>("/users", { name: "Alice", email: "alice@example.com" })
1574
+ .map((user) => `Created user with ID: ${user.id}`)
1575
+ .recover("Failed to create user")
1576
+ .then((result) => console.log(result))
1577
+ ```
1578
+
1579
+ ## Real-World Examples
1580
+
1581
+ ### Form Validation
1582
+
1583
+ ```typescript
1584
+ import { Either, pipe } from "functype"
1585
+
1586
+ // Define validation types
1587
+ type ValidationError = {
1588
+ field: string
1589
+ message: string
1590
+ }
1591
+
1592
+ type FormData = {
1593
+ username: string
1594
+ email: string
1595
+ password: string
1596
+ age: string
1597
+ }
1598
+
1599
+ // Validation functions
1600
+ const validateUsername = (username: string): Either<ValidationError, string> => {
1601
+ if (!username) {
1602
+ return Either.left({ field: "username", message: "Username is required" })
1603
+ }
1604
+ if (username.length < 3) {
1605
+ return Either.left({ field: "username", message: "Username must be at least 3 characters" })
1606
+ }
1607
+ return Either.right(username)
1608
+ }
1609
+
1610
+ const validateEmail = (email: string): Either<ValidationError, string> => {
1611
+ if (!email) {
1612
+ return Either.left({ field: "email", message: "Email is required" })
1613
+ }
1614
+ if (!/^[^@]+@[^@]+\.[^@]+$/.test(email)) {
1615
+ return Either.left({ field: "email", message: "Invalid email format" })
1616
+ }
1617
+ return Either.right(email)
1618
+ }
1619
+
1620
+ const validatePassword = (password: string): Either<ValidationError, string> => {
1621
+ if (!password) {
1622
+ return Either.left({ field: "password", message: "Password is required" })
1623
+ }
1624
+ if (password.length < 8) {
1625
+ return Either.left({ field: "password", message: "Password must be at least 8 characters" })
1626
+ }
1627
+ return Either.right(password)
1628
+ }
1629
+
1630
+ const validateAge = (age: string): Either<ValidationError, number> => {
1631
+ if (!age) {
1632
+ return Either.left({ field: "age", message: "Age is required" })
1633
+ }
1634
+
1635
+ const numAge = parseInt(age, 10)
1636
+ if (isNaN(numAge)) {
1637
+ return Either.left({ field: "age", message: "Age must be a number" })
1638
+ }
1639
+
1640
+ if (numAge < 18) {
1641
+ return Either.left({ field: "age", message: "You must be at least 18 years old" })
1642
+ }
1643
+
1644
+ return Either.right(numAge)
1645
+ }
1646
+
1647
+ // Validate form data
1648
+ type ValidatedForm = {
1649
+ username: string
1650
+ email: string
1651
+ password: string
1652
+ age: number
1653
+ }
1654
+
1655
+ function validateForm(form: FormData): Either<ValidationError[], ValidatedForm> {
1656
+ const usernameResult = validateUsername(form.username)
1657
+ const emailResult = validateEmail(form.email)
1658
+ const passwordResult = validatePassword(form.password)
1659
+ const ageResult = validateAge(form.age)
1660
+
1661
+ // Collect all errors
1662
+ const errors: ValidationError[] = []
1663
+
1664
+ if (usernameResult.isLeft()) errors.push(usernameResult.getLeft())
1665
+ if (emailResult.isLeft()) errors.push(emailResult.getLeft())
1666
+ if (passwordResult.isLeft()) errors.push(passwordResult.getLeft())
1667
+ if (ageResult.isLeft()) errors.push(ageResult.getLeft())
1668
+
1669
+ // If there are any errors, return them
1670
+ if (errors.length > 0) {
1671
+ return Either.left(errors)
1672
+ }
1673
+
1674
+ // Otherwise return the validated form
1675
+ return Either.right({
1676
+ username: usernameResult.get(),
1677
+ email: emailResult.get(),
1678
+ password: passwordResult.get(),
1679
+ age: ageResult.get(),
1680
+ })
1681
+ }
1682
+
1683
+ // Usage
1684
+ const formData: FormData = {
1685
+ username: "john",
1686
+ email: "john@example.com",
1687
+ password: "password123",
1688
+ age: "25",
1689
+ }
1690
+
1691
+ const validationResult = validateForm(formData)
1692
+
1693
+ validationResult.fold(
1694
+ (errors) => {
1695
+ console.log("Validation failed:")
1696
+ errors.forEach((err) => {
1697
+ console.log(`- ${err.field}: ${err.message}`)
1698
+ })
1699
+ },
1700
+ (validData) => {
1701
+ console.log("Form is valid:", validData)
1702
+ // Process the valid form data...
1703
+ },
1704
+ )
1705
+ ```
1706
+
1707
+ ### Data Fetching and Processing
1708
+
1709
+ ```typescript
1710
+ import { FPromise, Option, Either, pipe, List } from "functype"
1711
+
1712
+ // Define types
1713
+ type User = {
1714
+ id: string
1715
+ name: string
1716
+ email: string
1717
+ }
1718
+
1719
+ type Post = {
1720
+ id: string
1721
+ userId: string
1722
+ title: string
1723
+ body: string
1724
+ }
1725
+
1726
+ type Comment = {
1727
+ id: string
1728
+ postId: string
1729
+ name: string
1730
+ email: string
1731
+ body: string
1732
+ }
1733
+
1734
+ // API client
1735
+ class ApiClient {
1736
+ private baseUrl: string
1737
+
1738
+ constructor(baseUrl: string) {
1739
+ this.baseUrl = baseUrl
1740
+ }
1741
+
1742
+ fetchUser(userId: string): FPromise<User, Error> {
1743
+ return this.fetchResource<User>(`/users/${userId}`)
1744
+ }
1745
+
1746
+ fetchUserPosts(userId: string): FPromise<Post[], Error> {
1747
+ return this.fetchResource<Post[]>(`/users/${userId}/posts`)
1748
+ }
1749
+
1750
+ fetchPostComments(postId: string): FPromise<Comment[], Error> {
1751
+ return this.fetchResource<Comment[]>(`/posts/${postId}/comments`)
1752
+ }
1753
+
1754
+ private fetchResource<T>(endpoint: string): FPromise<T, Error> {
1755
+ return FPromise.tryCatchAsync(
1756
+ async () => {
1757
+ const response = await fetch(`${this.baseUrl}${endpoint}`)
1758
+
1759
+ if (!response.ok) {
1760
+ throw new Error(`HTTP error: ${response.status}`)
1761
+ }
1762
+
1763
+ return response.json() as Promise<T>
1764
+ },
1765
+ (error) => new Error(`API error: ${error}`),
1766
+ )
1767
+ }
1768
+ }
1769
+
1770
+ // Use case: fetch a user's posts with comments
1771
+ async function getUserContentSummary(userId: string) {
1772
+ const api = new ApiClient("https://jsonplaceholder.typicode.com")
1773
+
1774
+ // Fetch user
1775
+ const userResult = await api.fetchUser(userId)
1776
+
1777
+ if (userResult.isLeft()) {
1778
+ return `Error fetching user: ${userResult.getLeft().message}`
1779
+ }
1780
+
1781
+ const user = userResult.get()
1782
+
1783
+ // Fetch user's posts
1784
+ const postsResult = await api.fetchUserPosts(userId)
1785
+
1786
+ if (postsResult.isLeft()) {
1787
+ return `Error fetching posts: ${postsResult.getLeft().message}`
1788
+ }
1789
+
1790
+ const posts = postsResult.get()
1791
+
1792
+ if (posts.length === 0) {
1793
+ return `User ${user.name} has no posts.`
1794
+ }
1795
+
1796
+ // Take the latest post
1797
+ const latestPost = posts[0]
1798
+
1799
+ // Fetch comments for the latest post
1800
+ const commentsResult = await api.fetchPostComments(latestPost.id)
1801
+
1802
+ if (commentsResult.isLeft()) {
1803
+ return `Error fetching comments: ${commentsResult.getLeft().message}`
1804
+ }
1805
+
1806
+ const comments = commentsResult.get()
1807
+
1808
+ // Create summary
1809
+ return {
1810
+ user: {
1811
+ name: user.name,
1812
+ email: user.email,
1813
+ },
1814
+ postsCount: posts.length,
1815
+ latestPost: {
1816
+ title: latestPost.title,
1817
+ body: latestPost.body.substring(0, 100) + "...",
1818
+ commentsCount: comments.length,
1819
+ },
1820
+ }
1821
+ }
1822
+
1823
+ // Usage
1824
+ getUserContentSummary("1")
1825
+ .then((result) => console.log(JSON.stringify(result, null, 2)))
1826
+ .catch((error) => console.error("Error:", error))
1827
+ ```
1828
+
1829
+ ### Event Sourcing
1830
+
1831
+ ```typescript
1832
+ import { Either, Option, List, Map } from "functype"
1833
+
1834
+ // Define domain types
1835
+ type Event = {
1836
+ id: string
1837
+ timestamp: number
1838
+ type: string
1839
+ payload: unknown
1840
+ }
1841
+
1842
+ type UserCreatedEvent = Event & {
1843
+ type: "UserCreated"
1844
+ payload: {
1845
+ id: string
1846
+ name: string
1847
+ email: string
1848
+ }
1849
+ }
1850
+
1851
+ type UserUpdatedEvent = Event & {
1852
+ type: "UserUpdated"
1853
+ payload: {
1854
+ id: string
1855
+ name?: string
1856
+ email?: string
1857
+ }
1858
+ }
1859
+
1860
+ type ItemAddedToCartEvent = Event & {
1861
+ type: "ItemAddedToCart"
1862
+ payload: {
1863
+ userId: string
1864
+ productId: string
1865
+ quantity: number
1866
+ }
1867
+ }
1868
+
1869
+ type ItemRemovedFromCartEvent = Event & {
1870
+ type: "ItemRemovedFromCart"
1871
+ payload: {
1872
+ userId: string
1873
+ productId: string
1874
+ }
1875
+ }
1876
+
1877
+ type CheckoutCompletedEvent = Event & {
1878
+ type: "CheckoutCompleted"
1879
+ payload: {
1880
+ userId: string
1881
+ total: number
1882
+ }
1883
+ }
1884
+
1885
+ // Type guard functions
1886
+ const isUserCreated = (event: Event): event is UserCreatedEvent => event.type === "UserCreated"
1887
+
1888
+ const isUserUpdated = (event: Event): event is UserUpdatedEvent => event.type === "UserUpdated"
1889
+
1890
+ const isItemAddedToCart = (event: Event): event is ItemAddedToCartEvent => event.type === "ItemAddedToCart"
1891
+
1892
+ const isItemRemovedFromCart = (event: Event): event is ItemRemovedFromCartEvent => event.type === "ItemRemovedFromCart"
1893
+
1894
+ const isCheckoutCompleted = (event: Event): event is CheckoutCompletedEvent => event.type === "CheckoutCompleted"
1895
+
1896
+ // User state derived from events
1897
+ type User = {
1898
+ id: string
1899
+ name: string
1900
+ email: string
1901
+ cart: Map<string, number> // productId -> quantity
1902
+ checkoutHistory: List<{
1903
+ timestamp: number
1904
+ total: number
1905
+ }>
1906
+ }
1907
+
1908
+ // Event store
1909
+ class EventStore {
1910
+ private events: List<Event> = List([])
1911
+
1912
+ append(event: Event): void {
1913
+ this.events = this.events.add(event)
1914
+ }
1915
+
1916
+ appendMany(events: Event[]): void {
1917
+ this.events = this.events.concat(List(events))
1918
+ }
1919
+
1920
+ getAllEvents(): List<Event> {
1921
+ return this.events
1922
+ }
1923
+
1924
+ getEventsByUserId(userId: string): List<Event> {
1925
+ return this.events.filter((event) => {
1926
+ if (isUserCreated(event) || isUserUpdated(event)) {
1927
+ return (event.payload as { id: string }).id === userId
1928
+ }
1929
+ if (isItemAddedToCart(event) || isItemRemovedFromCart(event) || isCheckoutCompleted(event)) {
1930
+ return (event.payload as { userId: string }).userId === userId
1931
+ }
1932
+ return false
1933
+ })
1934
+ }
1935
+ }
1936
+
1937
+ // User projection
1938
+ class UserProjection {
1939
+ getUserState(userId: string, events: List<Event>): Option<User> {
1940
+ // Filter events for this user
1941
+ const userEvents = events.filter((event) => {
1942
+ if (isUserCreated(event) || isUserUpdated(event)) {
1943
+ return (event.payload as { id: string }).id === userId
1944
+ }
1945
+ if (isItemAddedToCart(event) || isItemRemovedFromCart(event) || isCheckoutCompleted(event)) {
1946
+ return (event.payload as { userId: string }).userId === userId
1947
+ }
1948
+ return false
1949
+ })
1950
+
1951
+ // Find user creation event
1952
+ const creationEvent = userEvents.find(isUserCreated)
1953
+
1954
+ if (creationEvent.isEmpty()) {
1955
+ return Option(null) // User not found
1956
+ }
1957
+
1958
+ // Initial state from creation event
1959
+ let user: User = {
1960
+ id: creationEvent.get().payload.id,
1961
+ name: creationEvent.get().payload.name,
1962
+ email: creationEvent.get().payload.email,
1963
+ cart: Map<string, number>({}),
1964
+ checkoutHistory: List([]),
1965
+ }
1966
+
1967
+ // Apply all other events in order
1968
+ const remainingEvents = userEvents.filter((e) => e.id !== creationEvent.get().id)
1969
+
1970
+ return Option(
1971
+ remainingEvents.foldLeft(user)((state, event) => {
1972
+ if (isUserUpdated(event)) {
1973
+ return {
1974
+ ...state,
1975
+ name: event.payload.name ?? state.name,
1976
+ email: event.payload.email ?? state.email,
1977
+ }
1978
+ }
1979
+
1980
+ if (isItemAddedToCart(event)) {
1981
+ return {
1982
+ ...state,
1983
+ cart: state.cart.add(event.payload.productId, event.payload.quantity),
1984
+ }
1985
+ }
1986
+
1987
+ if (isItemRemovedFromCart(event)) {
1988
+ return {
1989
+ ...state,
1990
+ cart: state.cart.remove(event.payload.productId),
1991
+ }
1992
+ }
1993
+
1994
+ if (isCheckoutCompleted(event)) {
1995
+ return {
1996
+ ...state,
1997
+ cart: Map<string, number>({}), // Clear cart
1998
+ checkoutHistory: state.checkoutHistory.add({
1999
+ timestamp: event.timestamp,
2000
+ total: event.payload.total,
2001
+ }),
2002
+ }
2003
+ }
2004
+
2005
+ return state
2006
+ }),
2007
+ )
2008
+ }
2009
+ }
2010
+
2011
+ // Usage example
2012
+ const eventStore = new EventStore()
2013
+ const userProjection = new UserProjection()
2014
+
2015
+ // Create some events
2016
+ const events: Event[] = [
2017
+ {
2018
+ id: "e1",
2019
+ timestamp: Date.now() - 5000,
2020
+ type: "UserCreated",
2021
+ payload: {
2022
+ id: "user1",
2023
+ name: "John Doe",
2024
+ email: "john@example.com",
2025
+ },
2026
+ },
2027
+ {
2028
+ id: "e2",
2029
+ timestamp: Date.now() - 4000,
2030
+ type: "ItemAddedToCart",
2031
+ payload: {
2032
+ userId: "user1",
2033
+ productId: "product1",
2034
+ quantity: 2,
2035
+ },
2036
+ },
2037
+ {
2038
+ id: "e3",
2039
+ timestamp: Date.now() - 3500,
2040
+ type: "ItemAddedToCart",
2041
+ payload: {
2042
+ userId: "user1",
2043
+ productId: "product2",
2044
+ quantity: 1,
2045
+ },
2046
+ },
2047
+ {
2048
+ id: "e4",
2049
+ timestamp: Date.now() - 3000,
2050
+ type: "ItemRemovedFromCart",
2051
+ payload: {
2052
+ userId: "user1",
2053
+ productId: "product1",
2054
+ },
2055
+ },
2056
+ {
2057
+ id: "e5",
2058
+ timestamp: Date.now() - 2000,
2059
+ type: "CheckoutCompleted",
2060
+ payload: {
2061
+ userId: "user1",
2062
+ total: 29.99,
2063
+ },
2064
+ },
2065
+ {
2066
+ id: "e6",
2067
+ timestamp: Date.now() - 1000,
2068
+ type: "UserUpdated",
2069
+ payload: {
2070
+ id: "user1",
2071
+ email: "john.doe@example.com",
2072
+ },
2073
+ },
2074
+ ]
2075
+
2076
+ // Add events to store
2077
+ eventStore.appendMany(events)
2078
+
2079
+ // Get user state
2080
+ const userState = userProjection.getUserState("user1", eventStore.getAllEvents())
2081
+
2082
+ userState.fold(
2083
+ () => console.log("User not found"),
2084
+ (user) => {
2085
+ console.log("User:", user.name, user.email)
2086
+ console.log("Cart items:", user.cart.size())
2087
+ console.log("Checkout history:", user.checkoutHistory.size(), "orders")
2088
+ console.log("Latest order total:", user.checkoutHistory.head().getOrElse({ total: 0 }).total)
2089
+ },
2090
+ )
2091
+ ```
2092
+
2093
+ These examples cover a wide range of use cases and demonstrate how to use Functype effectively in real-world applications. Feel free to adapt them to your specific needs!