@dojocho/effect-ts 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/DOJO.md +22 -0
  2. package/dojo.json +50 -0
  3. package/katas/001-hello-effect/SENSEI.md +72 -0
  4. package/katas/001-hello-effect/solution.test.ts +35 -0
  5. package/katas/001-hello-effect/solution.ts +16 -0
  6. package/katas/002-transform-with-map/SENSEI.md +72 -0
  7. package/katas/002-transform-with-map/solution.test.ts +33 -0
  8. package/katas/002-transform-with-map/solution.ts +16 -0
  9. package/katas/003-generator-pipelines/SENSEI.md +72 -0
  10. package/katas/003-generator-pipelines/solution.test.ts +40 -0
  11. package/katas/003-generator-pipelines/solution.ts +29 -0
  12. package/katas/004-flatmap-and-chaining/SENSEI.md +80 -0
  13. package/katas/004-flatmap-and-chaining/solution.test.ts +34 -0
  14. package/katas/004-flatmap-and-chaining/solution.ts +18 -0
  15. package/katas/005-pipe-composition/SENSEI.md +81 -0
  16. package/katas/005-pipe-composition/solution.test.ts +41 -0
  17. package/katas/005-pipe-composition/solution.ts +19 -0
  18. package/katas/006-handle-errors/SENSEI.md +86 -0
  19. package/katas/006-handle-errors/solution.test.ts +53 -0
  20. package/katas/006-handle-errors/solution.ts +30 -0
  21. package/katas/007-tagged-errors/SENSEI.md +79 -0
  22. package/katas/007-tagged-errors/solution.test.ts +82 -0
  23. package/katas/007-tagged-errors/solution.ts +37 -0
  24. package/katas/008-error-patterns/SENSEI.md +89 -0
  25. package/katas/008-error-patterns/solution.test.ts +41 -0
  26. package/katas/008-error-patterns/solution.ts +38 -0
  27. package/katas/009-option-type/SENSEI.md +96 -0
  28. package/katas/009-option-type/solution.test.ts +49 -0
  29. package/katas/009-option-type/solution.ts +26 -0
  30. package/katas/010-either-and-exit/SENSEI.md +86 -0
  31. package/katas/010-either-and-exit/solution.test.ts +33 -0
  32. package/katas/010-either-and-exit/solution.ts +17 -0
  33. package/katas/011-services-and-context/SENSEI.md +82 -0
  34. package/katas/011-services-and-context/solution.test.ts +23 -0
  35. package/katas/011-services-and-context/solution.ts +17 -0
  36. package/katas/012-layers/SENSEI.md +73 -0
  37. package/katas/012-layers/solution.test.ts +23 -0
  38. package/katas/012-layers/solution.ts +26 -0
  39. package/katas/013-testing-effects/SENSEI.md +88 -0
  40. package/katas/013-testing-effects/solution.test.ts +41 -0
  41. package/katas/013-testing-effects/solution.ts +20 -0
  42. package/katas/014-schema-basics/SENSEI.md +81 -0
  43. package/katas/014-schema-basics/solution.test.ts +35 -0
  44. package/katas/014-schema-basics/solution.ts +25 -0
  45. package/katas/015-domain-modeling/SENSEI.md +85 -0
  46. package/katas/015-domain-modeling/solution.test.ts +46 -0
  47. package/katas/015-domain-modeling/solution.ts +42 -0
  48. package/katas/016-retry-and-schedule/SENSEI.md +72 -0
  49. package/katas/016-retry-and-schedule/solution.test.ts +26 -0
  50. package/katas/016-retry-and-schedule/solution.ts +23 -0
  51. package/katas/017-parallel-effects/SENSEI.md +70 -0
  52. package/katas/017-parallel-effects/solution.test.ts +33 -0
  53. package/katas/017-parallel-effects/solution.ts +17 -0
  54. package/katas/018-race-and-timeout/SENSEI.md +75 -0
  55. package/katas/018-race-and-timeout/solution.test.ts +30 -0
  56. package/katas/018-race-and-timeout/solution.ts +27 -0
  57. package/katas/019-ref-and-state/SENSEI.md +72 -0
  58. package/katas/019-ref-and-state/solution.test.ts +29 -0
  59. package/katas/019-ref-and-state/solution.ts +16 -0
  60. package/katas/020-fibers/SENSEI.md +80 -0
  61. package/katas/020-fibers/solution.test.ts +23 -0
  62. package/katas/020-fibers/solution.ts +23 -0
  63. package/katas/021-acquire-release/SENSEI.md +57 -0
  64. package/katas/021-acquire-release/solution.test.ts +23 -0
  65. package/katas/021-acquire-release/solution.ts +22 -0
  66. package/katas/022-scoped-layers/SENSEI.md +52 -0
  67. package/katas/022-scoped-layers/solution.test.ts +35 -0
  68. package/katas/022-scoped-layers/solution.ts +19 -0
  69. package/katas/023-resource-patterns/SENSEI.md +52 -0
  70. package/katas/023-resource-patterns/solution.test.ts +20 -0
  71. package/katas/023-resource-patterns/solution.ts +13 -0
  72. package/katas/024-streams-basics/SENSEI.md +61 -0
  73. package/katas/024-streams-basics/solution.test.ts +30 -0
  74. package/katas/024-streams-basics/solution.ts +16 -0
  75. package/katas/025-stream-operations/SENSEI.md +59 -0
  76. package/katas/025-stream-operations/solution.test.ts +26 -0
  77. package/katas/025-stream-operations/solution.ts +17 -0
  78. package/katas/026-combining-streams/SENSEI.md +54 -0
  79. package/katas/026-combining-streams/solution.test.ts +20 -0
  80. package/katas/026-combining-streams/solution.ts +16 -0
  81. package/katas/027-data-pipelines/SENSEI.md +58 -0
  82. package/katas/027-data-pipelines/solution.test.ts +22 -0
  83. package/katas/027-data-pipelines/solution.ts +16 -0
  84. package/katas/028-logging-and-spans/SENSEI.md +58 -0
  85. package/katas/028-logging-and-spans/solution.test.ts +50 -0
  86. package/katas/028-logging-and-spans/solution.ts +20 -0
  87. package/katas/029-http-client/SENSEI.md +59 -0
  88. package/katas/029-http-client/solution.test.ts +49 -0
  89. package/katas/029-http-client/solution.ts +24 -0
  90. package/katas/030-capstone/SENSEI.md +63 -0
  91. package/katas/030-capstone/solution.test.ts +67 -0
  92. package/katas/030-capstone/solution.ts +55 -0
  93. package/katas/031-config-and-environment/SENSEI.md +77 -0
  94. package/katas/031-config-and-environment/solution.test.ts +38 -0
  95. package/katas/031-config-and-environment/solution.ts +11 -0
  96. package/katas/032-cause-and-defects/SENSEI.md +90 -0
  97. package/katas/032-cause-and-defects/solution.test.ts +50 -0
  98. package/katas/032-cause-and-defects/solution.ts +23 -0
  99. package/katas/033-pattern-matching/SENSEI.md +86 -0
  100. package/katas/033-pattern-matching/solution.test.ts +36 -0
  101. package/katas/033-pattern-matching/solution.ts +28 -0
  102. package/katas/034-deferred-and-coordination/SENSEI.md +85 -0
  103. package/katas/034-deferred-and-coordination/solution.test.ts +25 -0
  104. package/katas/034-deferred-and-coordination/solution.ts +24 -0
  105. package/katas/035-queue-and-backpressure/SENSEI.md +100 -0
  106. package/katas/035-queue-and-backpressure/solution.test.ts +25 -0
  107. package/katas/035-queue-and-backpressure/solution.ts +21 -0
  108. package/katas/036-schema-advanced/SENSEI.md +81 -0
  109. package/katas/036-schema-advanced/solution.test.ts +55 -0
  110. package/katas/036-schema-advanced/solution.ts +19 -0
  111. package/katas/037-cache-and-memoization/SENSEI.md +73 -0
  112. package/katas/037-cache-and-memoization/solution.test.ts +47 -0
  113. package/katas/037-cache-and-memoization/solution.ts +24 -0
  114. package/katas/038-metrics/SENSEI.md +91 -0
  115. package/katas/038-metrics/solution.test.ts +39 -0
  116. package/katas/038-metrics/solution.ts +23 -0
  117. package/katas/039-managed-runtime/SENSEI.md +75 -0
  118. package/katas/039-managed-runtime/solution.test.ts +29 -0
  119. package/katas/039-managed-runtime/solution.ts +19 -0
  120. package/katas/040-request-batching/SENSEI.md +87 -0
  121. package/katas/040-request-batching/solution.test.ts +56 -0
  122. package/katas/040-request-batching/solution.ts +32 -0
  123. package/package.json +22 -0
  124. package/skills/effect-patterns-building-apis/SKILL.md +2393 -0
  125. package/skills/effect-patterns-building-data-pipelines/SKILL.md +1876 -0
  126. package/skills/effect-patterns-concurrency/SKILL.md +2999 -0
  127. package/skills/effect-patterns-concurrency-getting-started/SKILL.md +351 -0
  128. package/skills/effect-patterns-core-concepts/SKILL.md +3199 -0
  129. package/skills/effect-patterns-domain-modeling/SKILL.md +1385 -0
  130. package/skills/effect-patterns-error-handling/SKILL.md +1212 -0
  131. package/skills/effect-patterns-error-handling-resilience/SKILL.md +179 -0
  132. package/skills/effect-patterns-error-management/SKILL.md +1668 -0
  133. package/skills/effect-patterns-getting-started/SKILL.md +237 -0
  134. package/skills/effect-patterns-making-http-requests/SKILL.md +1756 -0
  135. package/skills/effect-patterns-observability/SKILL.md +1586 -0
  136. package/skills/effect-patterns-platform/SKILL.md +1195 -0
  137. package/skills/effect-patterns-platform-getting-started/SKILL.md +179 -0
  138. package/skills/effect-patterns-project-setup--execution/SKILL.md +233 -0
  139. package/skills/effect-patterns-resource-management/SKILL.md +827 -0
  140. package/skills/effect-patterns-scheduling/SKILL.md +451 -0
  141. package/skills/effect-patterns-scheduling-periodic-tasks/SKILL.md +763 -0
  142. package/skills/effect-patterns-streams/SKILL.md +2052 -0
  143. package/skills/effect-patterns-streams-getting-started/SKILL.md +421 -0
  144. package/skills/effect-patterns-streams-sinks/SKILL.md +1181 -0
  145. package/skills/effect-patterns-testing/SKILL.md +1632 -0
  146. package/skills/effect-patterns-tooling-and-debugging/SKILL.md +1125 -0
  147. package/skills/effect-patterns-value-handling/SKILL.md +676 -0
  148. package/tsconfig.json +20 -0
  149. package/vitest.config.ts +3 -0
@@ -0,0 +1,676 @@
1
+ ---
2
+ name: effect-patterns-value-handling
3
+ description: Effect-TS patterns for Value Handling. Use when working with value handling in Effect-TS applications.
4
+ ---
5
+ # Effect-TS Patterns: Value Handling
6
+ This skill provides 2 curated Effect-TS patterns for value handling.
7
+ Use this skill when working on tasks related to:
8
+ - value handling
9
+ - Best practices in Effect-TS applications
10
+ - Real-world patterns and solutions
11
+
12
+ ---
13
+
14
+ ## 🟡 Intermediate Patterns
15
+
16
+ ### Optional Pattern 1: Handling None and Some Values
17
+
18
+ **Rule:** Use Option to represent values that may not exist, replacing null/undefined with type-safe Option that forces explicit handling.
19
+
20
+ **Good Example:**
21
+
22
+ This example demonstrates Option handling patterns.
23
+
24
+ ```typescript
25
+ import { Effect, Option } from "effect";
26
+
27
+ interface User {
28
+ id: string;
29
+ name: string;
30
+ email: string;
31
+ }
32
+
33
+ interface Profile {
34
+ bio: string;
35
+ website?: string;
36
+ location?: string;
37
+ }
38
+
39
+ const program = Effect.gen(function* () {
40
+ console.log(
41
+ `\n[OPTION HANDLING] None/Some values and pattern matching\n`
42
+ );
43
+
44
+ // Example 1: Creating Options
45
+ console.log(`[1] Creating Option values:\n`);
46
+
47
+ const someValue: Option.Option<string> = Option.some("data");
48
+ const noneValue: Option.Option<string> = Option.none();
49
+
50
+ const displayOption = <T,>(opt: Option.Option<T>, label: string) =>
51
+ Effect.gen(function* () {
52
+ if (Option.isSome(opt)) {
53
+ yield* Effect.log(`${label}: Some(${opt.value})`);
54
+ } else {
55
+ yield* Effect.log(`${label}: None`);
56
+ }
57
+ });
58
+
59
+ yield* displayOption(someValue, "someValue");
60
+ yield* displayOption(noneValue, "noneValue");
61
+
62
+ // Example 2: Creating from nullable values
63
+ console.log(`\n[2] Converting nullable to Option:\n`);
64
+
65
+ const possiblyNull = (shouldExist: boolean): string | null =>
66
+ shouldExist ? "found" : null;
67
+
68
+ const toOption = (value: string | null | undefined): Option.Option<string> =>
69
+ value ? Option.some(value) : Option.none();
70
+
71
+ const opt1 = toOption(possiblyNull(true));
72
+ const opt2 = toOption(possiblyNull(false));
73
+
74
+ yield* displayOption(opt1, "toOption(found)");
75
+ yield* displayOption(opt2, "toOption(null)");
76
+
77
+ // Example 3: Pattern matching on Option
78
+ console.log(`\n[3] Pattern matching with match():\n`);
79
+
80
+ const userId: Option.Option<string> = Option.some("user-123");
81
+
82
+ const message = Option.match(userId, {
83
+ onSome: (id) => `User ID: ${id}`,
84
+ onNone: () => "No user found",
85
+ });
86
+
87
+ yield* Effect.log(`[MATCH] ${message}`);
88
+
89
+ const emptyUserId: Option.Option<string> = Option.none();
90
+
91
+ const emptyMessage = Option.match(emptyUserId, {
92
+ onSome: (id) => `User ID: ${id}`,
93
+ onNone: () => "No user found",
94
+ });
95
+
96
+ yield* Effect.log(`[MATCH] ${emptyMessage}\n`);
97
+
98
+ // Example 4: Transforming with map
99
+ console.log(`[4] Transforming values with map():\n`);
100
+
101
+ const userCount: Option.Option<number> = Option.some(42);
102
+
103
+ const doubled = Option.map(userCount, (count) => count * 2);
104
+
105
+ yield* displayOption(doubled, "doubled");
106
+
107
+ // Chaining maps
108
+ const email: Option.Option<string> = Option.some("user@example.com");
109
+
110
+ const domain = Option.map(email, (e) =>
111
+ e.split("@")[1] ?? "unknown"
112
+ );
113
+
114
+ yield* displayOption(domain, "email domain");
115
+
116
+ // Example 5: Chaining with flatMap
117
+ console.log(`\n[5] Chaining operations with flatMap():\n`);
118
+
119
+ const findUser = (id: string): Option.Option<User> =>
120
+ id === "user-1"
121
+ ? Option.some({ id, name: "Alice", email: "alice@example.com" })
122
+ : Option.none();
123
+
124
+ const getProfile = (userId: string): Option.Option<Profile> =>
125
+ userId === "user-1"
126
+ ? Option.some({ bio: "Developer", website: "alice.dev" })
127
+ : Option.none();
128
+
129
+ const userId2 = Option.some("user-1");
130
+
131
+ // Chained operations: userId -> user -> profile
132
+ const profileChain = Option.flatMap(userId2, (id) =>
133
+ Option.flatMap(findUser(id), (user) =>
134
+ getProfile(user.id)
135
+ )
136
+ );
137
+
138
+ const profileResult = Option.match(profileChain, {
139
+ onSome: (profile) => `Bio: ${profile.bio}, Website: ${profile.website}`,
140
+ onNone: () => "No profile found",
141
+ });
142
+
143
+ yield* Effect.log(`[CHAIN] ${profileResult}\n`);
144
+
145
+ // Example 6: Fallback values with getOrElse
146
+ console.log(`[6] Default values with getOrElse():\n`);
147
+
148
+ const optionalStatus: Option.Option<string> = Option.none();
149
+
150
+ const status = Option.getOrElse(optionalStatus, () => "unknown");
151
+
152
+ yield* Effect.log(`[DEFAULT] Status: ${status}`);
153
+
154
+ // Real value
155
+ const knownStatus: Option.Option<string> = Option.some("active");
156
+
157
+ const realStatus = Option.getOrElse(knownStatus, () => "unknown");
158
+
159
+ yield* Effect.log(`[VALUE] Status: ${realStatus}\n`);
160
+
161
+ // Example 7: Filter with predicate
162
+ console.log(`[7] Filtering with conditions:\n`);
163
+
164
+ const ageOption: Option.Option<number> = Option.some(25);
165
+
166
+ const isAdult = Option.filter(ageOption, (age) => age >= 18);
167
+
168
+ yield* displayOption(isAdult, "Adult check (25)");
169
+
170
+ const ageOption2: Option.Option<number> = Option.some(15);
171
+
172
+ const isAdult2 = Option.filter(ageOption2, (age) => age >= 18);
173
+
174
+ yield* displayOption(isAdult2, "Adult check (15)");
175
+
176
+ // Example 8: Multiple Options (all present?)
177
+ console.log(`\n[8] Combining multiple Options:\n`);
178
+
179
+ const firstName: Option.Option<string> = Option.some("John");
180
+ const lastName: Option.Option<string> = Option.some("Doe");
181
+ const middleName: Option.Option<string> = Option.none();
182
+
183
+ // All three present?
184
+ const allPresent = Option.all([firstName, lastName, middleName]);
185
+
186
+ yield* displayOption(allPresent, "All present");
187
+
188
+ // Just two
189
+ const twoPresent = Option.all([firstName, lastName]);
190
+
191
+ yield* displayOption(twoPresent, "Two present");
192
+
193
+ // Example 9: Converting Option to Error
194
+ console.log(`\n[9] Converting Option to Result/Error:\n`);
195
+
196
+ const optionalConfig: Option.Option<{ apiKey: string }> = Option.none();
197
+
198
+ const configOrError = Option.match(optionalConfig, {
199
+ onSome: (config) => config,
200
+ onNone: () => {
201
+ throw new Error("Configuration not found");
202
+ },
203
+ });
204
+
205
+ // In real code, would catch error
206
+ const result = Option.match(optionalConfig, {
207
+ onSome: (config) => ({ success: true, value: config }),
208
+ onNone: () => ({ success: false, error: "config-not-found" }),
209
+ });
210
+
211
+ yield* Effect.log(`[CONVERT] ${JSON.stringify(result)}\n`);
212
+
213
+ // Example 10: Option in business logic
214
+ console.log(`[10] Practical: Optional user settings:\n`);
215
+
216
+ const userSettings: Option.Option<{
217
+ theme: string;
218
+ notifications: boolean;
219
+ }> = Option.some({
220
+ theme: "dark",
221
+ notifications: true,
222
+ });
223
+
224
+ const getTheme = Option.map(userSettings, (s) => s.theme);
225
+ const theme = Option.getOrElse(getTheme, () => "light"); // Default
226
+
227
+ yield* Effect.log(`[SETTING] Theme: ${theme}`);
228
+
229
+ // No settings
230
+ const noSettings: Option.Option<{ theme: string; notifications: boolean }> =
231
+ Option.none();
232
+
233
+ const noTheme = Option.map(noSettings, (s) => s.theme);
234
+ const defaultTheme = Option.getOrElse(noTheme, () => "light");
235
+
236
+ yield* Effect.log(`[DEFAULT] Theme: ${defaultTheme}`);
237
+ });
238
+
239
+ Effect.runPromise(program);
240
+ ```
241
+
242
+ ---
243
+
244
+ **Rationale:**
245
+
246
+ Option enables null-safe programming:
247
+
248
+ - **Some(value)**: Value exists
249
+ - **None**: Value doesn't exist
250
+ - **Pattern matching**: Handle both cases
251
+ - **Chaining**: Compose operations safely
252
+ - **Fallbacks**: Default values
253
+ - **Conversions**: Option ↔ Error
254
+
255
+ Pattern: Use `Option.isSome()`, `Option.isNone()`, `match()`, `map()`, `flatMap()`
256
+
257
+ ---
258
+
259
+
260
+ Null/undefined causes widespread bugs:
261
+
262
+ **Problem 1: Billion-dollar mistake**
263
+ - Tony Hoare invented null in ALGOL in 1965
264
+ - Created "billion-dollar mistake"
265
+ - 90% of security vulnerabilities involve null handling
266
+
267
+ **Problem 2: Undefined behavior**
268
+ - `user.profile.name` - any property could be null
269
+ - Runtime error: "Cannot read property 'name' of undefined"
270
+ - No compile-time warning
271
+ - Production crash
272
+
273
+ **Problem 3: Silent failures**
274
+ - Function returns null on failure
275
+ - Caller doesn't check
276
+ - Uses null as if it's a value
277
+ - Corrupts state downstream
278
+
279
+ **Problem 4: Conditional hell**
280
+ ```javascript
281
+ if (user !== null && user.profile !== null && user.profile.name !== null) {
282
+ // Do thing
283
+ }
284
+ ```
285
+
286
+ Solutions:
287
+
288
+ **Option type**:
289
+ - `Some(value)` = value exists
290
+ - `None` = value doesn't exist
291
+ - Type system forces checking
292
+ - No silent null checks possible
293
+
294
+ **Pattern matching**:
295
+ - `Option.match()`
296
+ - Handle both cases explicitly
297
+ - Compiler warns if you miss one
298
+
299
+ **Chaining**:
300
+ - `option.map().flatMap().match()`
301
+ - Pipeline of operations
302
+ - Null-safe by design
303
+
304
+ ---
305
+
306
+ ---
307
+
308
+
309
+ ## 🟠 Advanced Patterns
310
+
311
+ ### Optional Pattern 2: Optional Chaining and Composition
312
+
313
+ **Rule:** Use Option combinators (map, flatMap, ap) to compose operations that may fail, creating readable and maintainable pipelines.
314
+
315
+ **Good Example:**
316
+
317
+ This example demonstrates optional chaining patterns.
318
+
319
+ ```typescript
320
+ import { Effect, Option, pipe } from "effect";
321
+
322
+ interface User {
323
+ id: string;
324
+ name: string;
325
+ email: string;
326
+ }
327
+
328
+ interface Profile {
329
+ bio: string;
330
+ website?: string;
331
+ avatar?: string;
332
+ }
333
+
334
+ interface Settings {
335
+ theme: "light" | "dark";
336
+ notifications: boolean;
337
+ language: string;
338
+ }
339
+
340
+ const program = Effect.gen(function* () {
341
+ console.log(`\n[OPTIONAL CHAINING] Composing Option operations\n`);
342
+
343
+ // Example 1: Simple chain with map
344
+ console.log(`[1] Chaining transformations with map():\n`);
345
+
346
+ const userId: Option.Option<string> = Option.some("user-42");
347
+
348
+ const userDisplayId = Option.map(userId, (id) => `User#${id}`);
349
+
350
+ const idMessage = Option.match(userDisplayId, {
351
+ onSome: (display) => display,
352
+ onNone: () => "No user ID",
353
+ });
354
+
355
+ yield* Effect.log(`[CHAIN 1] ${idMessage}`);
356
+
357
+ // Chained maps
358
+ const email: Option.Option<string> = Option.some("alice@example.com");
359
+
360
+ const emailParts = pipe(
361
+ email,
362
+ Option.map((e) => e.toLowerCase()),
363
+ Option.map((e) => e.split("@")),
364
+ Option.map((parts) => parts[0]) // username
365
+ );
366
+
367
+ const username = Option.getOrElse(emailParts, () => "unknown");
368
+
369
+ yield* Effect.log(`[USERNAME] ${username}\n`);
370
+
371
+ // Example 2: FlatMap for chaining operations that return Option
372
+ console.log(`[2] Chaining operations with flatMap():\n`);
373
+
374
+ const findUser = (id: string): Option.Option<User> =>
375
+ id === "user-42"
376
+ ? Option.some({
377
+ id,
378
+ name: "Alice",
379
+ email: "alice@example.com",
380
+ })
381
+ : Option.none();
382
+
383
+ const getProfile = (userId: string): Option.Option<Profile> =>
384
+ userId === "user-42"
385
+ ? Option.some({
386
+ bio: "Software engineer",
387
+ website: "alice.dev",
388
+ avatar: "https://example.com/avatar.jpg",
389
+ })
390
+ : Option.none();
391
+
392
+ const userProfile = pipe(
393
+ Option.some("user-42"),
394
+ Option.flatMap((id) => findUser(id)),
395
+ Option.flatMap((user) => getProfile(user.id))
396
+ );
397
+
398
+ const profileInfo = Option.match(userProfile, {
399
+ onSome: (profile) => `Bio: ${profile.bio}, Website: ${profile.website}`,
400
+ onNone: () => "Profile not found",
401
+ });
402
+
403
+ yield* Effect.log(`[PROFILE] ${profileInfo}\n`);
404
+
405
+ // Example 3: Complex pipeline
406
+ console.log(`[3] Complex pipeline (user → profile → settings → theme):\n`);
407
+
408
+ const getSettings = (userId: string): Option.Option<Settings> =>
409
+ userId === "user-42"
410
+ ? Option.some({
411
+ theme: "dark",
412
+ notifications: true,
413
+ language: "en",
414
+ })
415
+ : Option.none();
416
+
417
+ const userTheme = pipe(
418
+ Option.some("user-42"),
419
+ Option.flatMap((id) => findUser(id)),
420
+ Option.flatMap((user) => getSettings(user.id)),
421
+ Option.map((settings) => settings.theme)
422
+ );
423
+
424
+ const theme = Option.getOrElse(userTheme, () => "light");
425
+
426
+ yield* Effect.log(`[THEME] ${theme}`);
427
+
428
+ // Even if any step is None, result is None
429
+ const invalidUserTheme = pipe(
430
+ Option.some("invalid-user"),
431
+ Option.flatMap((id) => findUser(id)),
432
+ Option.flatMap((user) => getSettings(user.id)),
433
+ Option.map((settings) => settings.theme)
434
+ );
435
+
436
+ const invalidTheme = Option.getOrElse(invalidUserTheme, () => "light");
437
+
438
+ yield* Effect.log(`[DEFAULT THEME] ${invalidTheme}\n`);
439
+
440
+ // Example 4: Apply (ap) for combining independent Options
441
+ console.log(`[4] Combining values with ap():\n`);
442
+
443
+ const firstName: Option.Option<string> = Option.some("John");
444
+ const lastName: Option.Option<string> = Option.some("Doe");
445
+
446
+ // Create a function wrapped in Option
447
+ const combineNames = (first: string) => (last: string) =>
448
+ `${first} ${last}`;
449
+
450
+ const fullName = pipe(
451
+ Option.some(combineNames),
452
+ Option.ap(firstName),
453
+ Option.ap(lastName)
454
+ );
455
+
456
+ const name = Option.getOrElse(fullName, () => "Unknown");
457
+
458
+ yield* Effect.log(`[COMBINED] ${name}`);
459
+
460
+ // If any is None
461
+ const noLastName: Option.Option<string> = Option.none();
462
+
463
+ const incompleteName = pipe(
464
+ Option.some(combineNames),
465
+ Option.ap(firstName),
466
+ Option.ap(noLastName)
467
+ );
468
+
469
+ const incompleteFull = Option.getOrElse(incompleteName, () => "Incomplete");
470
+
471
+ yield* Effect.log(`[INCOMPLETE] ${incompleteFull}\n`);
472
+
473
+ // Example 5: Traverse for mapping over collections
474
+ console.log(`[5] Working with collections (traverse):\n`);
475
+
476
+ const userIds: string[] = ["user-42", "user-99", "user-1"];
477
+
478
+ // Try to load all users
479
+ const allUsers = Option.all(
480
+ userIds.map((id) => findUser(id))
481
+ );
482
+
483
+ const usersMessage = Option.match(allUsers, {
484
+ onSome: (users) => `Loaded ${users.length} users`,
485
+ onNone: () => "Some users not found",
486
+ });
487
+
488
+ yield* Effect.log(`[TRAVERSE] ${usersMessage}\n`);
489
+
490
+ // Example 6: Or/recovery with multiple options
491
+ console.log(`[6] Fallback chains with orElse():\n`);
492
+
493
+ const getPrimaryEmail = (): Option.Option<string> => Option.none();
494
+ const getSecondaryEmail = (): Option.Option<string> =>
495
+ Option.some("backup@example.com");
496
+ const getTertiaryEmail = (): Option.Option<string> =>
497
+ Option.some("tertiary@example.com");
498
+
499
+ const email1 = pipe(
500
+ getPrimaryEmail(),
501
+ Option.orElse(() => getSecondaryEmail()),
502
+ Option.orElse(() => getTertiaryEmail())
503
+ );
504
+
505
+ const contactEmail = Option.getOrElse(email1, () => "no-email@example.com");
506
+
507
+ yield* Effect.log(`[FALLBACK] Using email: ${contactEmail}\n`);
508
+
509
+ // Example 7: Filtering options
510
+ console.log(`[7] Filtering with predicates:\n`);
511
+
512
+ const age: Option.Option<number> = Option.some(25);
513
+
514
+ const canVote = pipe(
515
+ age,
516
+ Option.filter((a) => a >= 18)
517
+ );
518
+
519
+ const voteStatus = Option.match(canVote, {
520
+ onSome: () => "Can vote",
521
+ onNone: () => "Too young to vote",
522
+ });
523
+
524
+ yield* Effect.log(`[FILTER] ${voteStatus}`);
525
+
526
+ // Multiple filters in chain
527
+ const score: Option.Option<number> = Option.some(85);
528
+
529
+ const isAGrade = pipe(
530
+ score,
531
+ Option.filter((s) => s >= 80),
532
+ Option.filter((s) => s < 90)
533
+ );
534
+
535
+ const grade = Option.match(isAGrade, {
536
+ onSome: () => "Grade A",
537
+ onNone: () => "Not in A range",
538
+ });
539
+
540
+ yield* Effect.log(`[GRADES] ${grade}\n`);
541
+
542
+ // Example 8: Practical: Database query chain
543
+ console.log(`[8] Real-world: Database record chain:\n`);
544
+
545
+ const getRecord = (id: string): Option.Option<{ data: string; nested: { value: number } }> =>
546
+ id === "rec-1"
547
+ ? Option.some({
548
+ data: "content",
549
+ nested: { value: 42 },
550
+ })
551
+ : Option.none();
552
+
553
+ const recordValue = pipe(
554
+ Option.some("rec-1"),
555
+ Option.flatMap((id) => getRecord(id)),
556
+ Option.map((rec) => rec.nested),
557
+ Option.map((nested) => nested.value),
558
+ Option.map((value) => value * 2)
559
+ );
560
+
561
+ const finalValue = Option.getOrElse(recordValue, () => 0);
562
+
563
+ yield* Effect.log(`[VALUE] ${finalValue}`);
564
+
565
+ // Missing record
566
+ const missingValue = pipe(
567
+ Option.some("rec-999"),
568
+ Option.flatMap((id) => getRecord(id)),
569
+ Option.map((rec) => rec.nested),
570
+ Option.map((nested) => nested.value),
571
+ Option.map((value) => value * 2)
572
+ );
573
+
574
+ const defaultValue = Option.getOrElse(missingValue, () => 0);
575
+
576
+ yield* Effect.log(`[DEFAULT] ${defaultValue}\n`);
577
+
578
+ // Example 9: Conditional chaining
579
+ console.log(`[9] Conditional paths:\n`);
580
+
581
+ const loadUserWithFallback = (id: string) =>
582
+ pipe(
583
+ findUser(id),
584
+ Option.flatMap((user) =>
585
+ // Only get premium features if user exists
586
+ user.name.includes("Alice")
587
+ ? Option.some({ ...user, isPremium: true })
588
+ : Option.none()
589
+ ),
590
+ Option.orElse(() =>
591
+ // Fallback: return basic user
592
+ findUser(id)
593
+ )
594
+ );
595
+
596
+ const result1 = loadUserWithFallback("user-42");
597
+ const result2 = loadUserWithFallback("user-99");
598
+
599
+ yield* Effect.log(
600
+ `[CONDITIONAL 1] ${Option.match(result1, { onSome: (u) => `${u.name}`, onNone: () => "Not found" })}`
601
+ );
602
+
603
+ yield* Effect.log(
604
+ `[CONDITIONAL 2] ${Option.match(result2, { onSome: (u) => `${u.name}`, onNone: () => "Not found" })}`
605
+ );
606
+ });
607
+
608
+ Effect.runPromise(program);
609
+ ```
610
+
611
+ ---
612
+
613
+ **Rationale:**
614
+
615
+ Option chaining enables elegant data flows:
616
+
617
+ - **map**: Transform value if present
618
+ - **flatMap**: Chain operations that return Option
619
+ - **ap**: Apply functions wrapped in Option
620
+ - **traverse**: Map over collections with Option
621
+ - **composition**: Combine multiple chains
622
+ - **recovery**: Provide fallbacks
623
+
624
+ Pattern: Use `Option.map()`, `flatMap()`, `ap()`, pipe operators
625
+
626
+ ---
627
+
628
+
629
+ Nested option handling becomes complex:
630
+
631
+ **Problem 1: Pyramid of doom**
632
+ ```typescript
633
+ if (user !== null) {
634
+ if (user.profile !== null) {
635
+ if (user.profile.preferences !== null) {
636
+ if (user.profile.preferences.theme !== null) {
637
+ // Finally do thing
638
+ }
639
+ }
640
+ }
641
+ }
642
+ ```
643
+
644
+ **Problem 2: Repeated null checks**
645
+ - Every step needs its own check
646
+ - Code duplicates
647
+ - Hard to refactor
648
+ - Bugs easy to introduce
649
+
650
+ **Problem 3: Logic scattered**
651
+ - Transformation logic mixed with null checks
652
+ - Hard to understand intent
653
+ - Error-prone
654
+
655
+ Solutions:
656
+
657
+ **Option chaining**:
658
+ - `None` flows through automatically
659
+ - Transform only if `Some`
660
+ - No intermediate checks needed
661
+
662
+ **Composition**:
663
+ - Combine functions cleanly
664
+ - Separate concerns
665
+ - Reusable pieces
666
+
667
+ **Fallbacks**:
668
+ - `orElse()` for recovery
669
+ - Chain multiple alternatives
670
+ - Graceful degradation
671
+
672
+ ---
673
+
674
+ ---
675
+
676
+
package/tsconfig.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ES2022",
5
+ "moduleResolution": "bundler",
6
+ "lib": ["ES2022"],
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true,
10
+ "declaration": true,
11
+ "declarationMap": true,
12
+ "sourceMap": true,
13
+ "outDir": "dist",
14
+ "rootDir": ".",
15
+ "paths": {
16
+ "@/katas/*": ["../../../katas/*"]
17
+ }
18
+ },
19
+ "include": ["katas/**/*.ts"]
20
+ }
@@ -0,0 +1,3 @@
1
+ import { defineConfig } from "@dojocho/config/vitest"
2
+
3
+ export default defineConfig()