@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,1195 @@
1
+ ---
2
+ name: effect-patterns-platform
3
+ description: Effect-TS patterns for Platform. Use when working with platform in Effect-TS applications.
4
+ ---
5
+ # Effect-TS Patterns: Platform
6
+ This skill provides 6 curated Effect-TS patterns for platform.
7
+ Use this skill when working on tasks related to:
8
+ - platform
9
+ - Best practices in Effect-TS applications
10
+ - Real-world patterns and solutions
11
+
12
+ ---
13
+
14
+ ## 🟢 Beginner Patterns
15
+
16
+ ### Platform Pattern 4: Interactive Terminal I/O
17
+
18
+ **Rule:** Use Terminal for user input/output in CLI applications, providing proper buffering and cross-platform character encoding.
19
+
20
+ **Good Example:**
21
+
22
+ This example demonstrates building an interactive CLI application.
23
+
24
+ ```typescript
25
+ import { Terminal, Effect } from "@effect/platform";
26
+
27
+ interface UserInput {
28
+ readonly name: string;
29
+ readonly email: string;
30
+ readonly age: number;
31
+ }
32
+
33
+ const program = Effect.gen(function* () {
34
+ console.log(`\n[INTERACTIVE CLI] User Information Form\n`);
35
+
36
+ // Example 1: Simple prompts
37
+ yield* Terminal.writeLine(`=== User Setup ===`);
38
+ yield* Terminal.writeLine(``);
39
+
40
+ yield* Terminal.write(`What is your name? `);
41
+ const name = yield* Terminal.readLine();
42
+
43
+ yield* Terminal.write(`What is your email? `);
44
+ const email = yield* Terminal.readLine();
45
+
46
+ yield* Terminal.write(`What is your age? `);
47
+ const ageStr = yield* Terminal.readLine();
48
+
49
+ const age = parseInt(ageStr);
50
+
51
+ // Example 2: Display collected information
52
+ yield* Terminal.writeLine(``);
53
+ yield* Terminal.writeLine(`=== Summary ===`);
54
+ yield* Terminal.writeLine(`Name: ${name}`);
55
+ yield* Terminal.writeLine(`Email: ${email}`);
56
+ yield* Terminal.writeLine(`Age: ${age}`);
57
+
58
+ // Example 3: Confirmation
59
+ yield* Terminal.writeLine(``);
60
+ yield* Terminal.write(`Confirm information? (yes/no) `);
61
+ const confirm = yield* Terminal.readLine();
62
+
63
+ if (confirm.toLowerCase() === "yes") {
64
+ yield* Terminal.writeLine(`✓ Information saved`);
65
+ } else {
66
+ yield* Terminal.writeLine(`✗ Cancelled`);
67
+ }
68
+ });
69
+
70
+ Effect.runPromise(program);
71
+ ```
72
+
73
+ ---
74
+
75
+ **Rationale:**
76
+
77
+ Terminal operations:
78
+
79
+ - **readLine**: Read single line of user input
80
+ - **readPassword**: Read input without echoing (passwords)
81
+ - **writeLine**: Write line with newline
82
+ - **write**: Write without newline
83
+ - **clearScreen**: Clear terminal
84
+
85
+ Pattern: `Terminal.readLine().pipe(...)`
86
+
87
+ ---
88
+
89
+
90
+ Direct stdin/stdout causes issues:
91
+
92
+ - **No buffering**: Interleaved output in concurrent context
93
+ - **Encoding issues**: Special characters corrupted
94
+ - **Password echo**: Security vulnerability
95
+ - **No type safety**: String manipulation error-prone
96
+
97
+ Terminal enables:
98
+
99
+ - **Buffered I/O**: Safe concurrent output
100
+ - **Encoding handling**: UTF-8 and special chars
101
+ - **Password input**: No echo mode
102
+ - **Structured interaction**: Prompts and validation
103
+
104
+ Real-world example: CLI setup wizard
105
+ - **Direct**: console.log mixed with readline, no error handling
106
+ - **With Terminal**: Structured input, validation, formatted output
107
+
108
+ ---
109
+
110
+ ---
111
+
112
+ ### Platform Pattern 2: Filesystem Operations
113
+
114
+ **Rule:** Use FileSystem module for safe, resource-managed file operations with proper error handling and cleanup.
115
+
116
+ **Good Example:**
117
+
118
+ This example demonstrates reading, writing, and manipulating files.
119
+
120
+ ```typescript
121
+ import { FileSystem, Effect, Stream } from "@effect/platform";
122
+ import * as fs from "fs/promises";
123
+
124
+ const program = Effect.gen(function* () {
125
+ console.log(`\n[FILESYSTEM] Demonstrating file operations\n`);
126
+
127
+ // Example 1: Write a file
128
+ console.log(`[1] Writing file:\n`);
129
+
130
+ const content = `Hello, Effect-TS!\nThis is a test file.\nCreated at ${new Date().toISOString()}`;
131
+
132
+ yield* FileSystem.writeFileUtf8("test.txt", content);
133
+
134
+ yield* Effect.log(`✓ File written: test.txt`);
135
+
136
+ // Example 2: Read the file
137
+ console.log(`\n[2] Reading file:\n`);
138
+
139
+ const readContent = yield* FileSystem.readFileUtf8("test.txt");
140
+
141
+ console.log(readContent);
142
+
143
+ // Example 3: Get file stats
144
+ console.log(`\n[3] File stats:\n`);
145
+
146
+ const stats = yield* FileSystem.stat("test.txt").pipe(
147
+ Effect.flatMap((stat) =>
148
+ Effect.succeed({
149
+ size: stat.size,
150
+ isFile: stat.isFile(),
151
+ modified: stat.mtimeMs,
152
+ })
153
+ )
154
+ );
155
+
156
+ console.log(` Size: ${stats.size} bytes`);
157
+ console.log(` Is file: ${stats.isFile}`);
158
+ console.log(` Modified: ${new Date(stats.modified).toISOString()}`);
159
+
160
+ // Example 4: Create directory and write multiple files
161
+ console.log(`\n[4] Creating directory and files:\n`);
162
+
163
+ yield* FileSystem.mkdir("test-dir");
164
+
165
+ yield* Effect.all(
166
+ Array.from({ length: 3 }, (_, i) =>
167
+ FileSystem.writeFileUtf8(
168
+ `test-dir/file-${i + 1}.txt`,
169
+ `Content of file ${i + 1}`
170
+ )
171
+ )
172
+ );
173
+
174
+ yield* Effect.log(`✓ Created directory with 3 files`);
175
+
176
+ // Example 5: List directory contents
177
+ console.log(`\n[5] Listing directory:\n`);
178
+
179
+ const entries = yield* FileSystem.readDirectory("test-dir");
180
+
181
+ entries.forEach((entry) => {
182
+ console.log(` - ${entry}`);
183
+ });
184
+
185
+ // Example 6: Append to file
186
+ console.log(`\n[6] Appending to file:\n`);
187
+
188
+ const appendContent = `\nAppended line at ${new Date().toISOString()}`;
189
+
190
+ yield* FileSystem.appendFileUtf8("test.txt", appendContent);
191
+
192
+ const finalContent = yield* FileSystem.readFileUtf8("test.txt");
193
+
194
+ console.log(`File now has ${finalContent.split("\n").length} lines`);
195
+
196
+ // Example 7: Clean up
197
+ console.log(`\n[7] Cleaning up:\n`);
198
+
199
+ yield* Effect.all(
200
+ Array.from({ length: 3 }, (_, i) =>
201
+ FileSystem.remove(`test-dir/file-${i + 1}.txt`)
202
+ )
203
+ );
204
+
205
+ yield* FileSystem.remove("test-dir");
206
+ yield* FileSystem.remove("test.txt");
207
+
208
+ yield* Effect.log(`✓ Cleanup complete`);
209
+ });
210
+
211
+ Effect.runPromise(program);
212
+ ```
213
+
214
+ ---
215
+
216
+ **Rationale:**
217
+
218
+ FileSystem operations:
219
+
220
+ - **read**: Read file as string
221
+ - **readDirectory**: List files in directory
222
+ - **write**: Write string to file
223
+ - **remove**: Delete file or directory
224
+ - **stat**: Get file metadata
225
+
226
+ Pattern: `FileSystem.read(path).pipe(...)`
227
+
228
+ ---
229
+
230
+
231
+ Direct file operations without FileSystem create issues:
232
+
233
+ - **Resource leaks**: Files not closed on errors
234
+ - **No error context**: Missing file names in errors
235
+ - **Blocking**: No async/await integration
236
+ - **Cross-platform**: Path handling differences
237
+
238
+ FileSystem enables:
239
+
240
+ - **Resource safety**: Automatic cleanup
241
+ - **Error context**: Full error messages
242
+ - **Async integration**: Effect-native
243
+ - **Cross-platform**: Handles path separators
244
+
245
+ Real-world example: Process log files
246
+ - **Direct**: Open file, read, close, handle exceptions manually
247
+ - **With FileSystem**: `FileSystem.read(path).pipe(...)`
248
+
249
+ ---
250
+
251
+ ---
252
+
253
+
254
+ ## 🟡 Intermediate Patterns
255
+
256
+ ### Platform Pattern 3: Persistent Key-Value Storage
257
+
258
+ **Rule:** Use KeyValueStore for simple persistent storage of key-value pairs, enabling lightweight caching and session management.
259
+
260
+ **Good Example:**
261
+
262
+ This example demonstrates storing and retrieving persistent data.
263
+
264
+ ```typescript
265
+ import { KeyValueStore, Effect } from "@effect/platform";
266
+
267
+ interface UserSession {
268
+ readonly userId: string;
269
+ readonly token: string;
270
+ readonly expiresAt: number;
271
+ }
272
+
273
+ const program = Effect.gen(function* () {
274
+ console.log(`\n[KEYVALUESTORE] Persistent storage example\n`);
275
+
276
+ const store = yield* KeyValueStore.KeyValueStore;
277
+
278
+ // Example 1: Store session data
279
+ console.log(`[1] Storing session:\n`);
280
+
281
+ const session: UserSession = {
282
+ userId: "user-123",
283
+ token: "token-abc-def",
284
+ expiresAt: Date.now() + 3600000, // 1 hour
285
+ };
286
+
287
+ yield* store.set("session:user-123", JSON.stringify(session));
288
+
289
+ yield* Effect.log(`✓ Session stored`);
290
+
291
+ // Example 2: Retrieve stored data
292
+ console.log(`\n[2] Retrieving session:\n`);
293
+
294
+ const stored = yield* store.get("session:user-123");
295
+
296
+ if (stored._tag === "Some") {
297
+ const retrievedSession = JSON.parse(stored.value) as UserSession;
298
+
299
+ console.log(` User ID: ${retrievedSession.userId}`);
300
+ console.log(` Token: ${retrievedSession.token}`);
301
+ console.log(
302
+ ` Expires: ${new Date(retrievedSession.expiresAt).toISOString()}`
303
+ );
304
+ }
305
+
306
+ // Example 3: Check if key exists
307
+ console.log(`\n[3] Checking keys:\n`);
308
+
309
+ const hasSession = yield* store.has("session:user-123");
310
+ const hasOther = yield* store.has("session:user-999");
311
+
312
+ console.log(` Has session:user-123: ${hasSession}`);
313
+ console.log(` Has session:user-999: ${hasOther}`);
314
+
315
+ // Example 4: Store multiple cache entries
316
+ console.log(`\n[4] Caching API responses:\n`);
317
+
318
+ const apiResponses = [
319
+ { endpoint: "/api/users", data: [{ id: 1, name: "Alice" }] },
320
+ { endpoint: "/api/posts", data: [{ id: 1, title: "First Post" }] },
321
+ { endpoint: "/api/comments", data: [] },
322
+ ];
323
+
324
+ yield* Effect.all(
325
+ apiResponses.map((item) =>
326
+ store.set(
327
+ `cache:${item.endpoint}`,
328
+ JSON.stringify(item.data)
329
+ )
330
+ )
331
+ );
332
+
333
+ yield* Effect.log(`✓ Cached ${apiResponses.length} endpoints`);
334
+
335
+ // Example 5: Retrieve cache with expiration
336
+ console.log(`\n[5] Checking cached data:\n`);
337
+
338
+ for (const item of apiResponses) {
339
+ const cached = yield* store.get(`cache:${item.endpoint}`);
340
+
341
+ if (cached._tag === "Some") {
342
+ const data = JSON.parse(cached.value);
343
+
344
+ console.log(
345
+ ` ${item.endpoint}: ${Array.isArray(data) ? data.length : 1} items`
346
+ );
347
+ }
348
+ }
349
+
350
+ // Example 6: Remove specific entry
351
+ console.log(`\n[6] Removing entry:\n`);
352
+
353
+ yield* store.remove("cache:/api/comments");
354
+
355
+ const removed = yield* store.has("cache:/api/comments");
356
+
357
+ console.log(` Exists after removal: ${removed}`);
358
+
359
+ // Example 7: Iterate and count entries
360
+ console.log(`\n[7] Counting entries:\n`);
361
+
362
+ const allKeys = yield* store.entries.pipe(
363
+ Effect.map((entries) => entries.length)
364
+ );
365
+
366
+ console.log(` Total entries: ${allKeys}`);
367
+ });
368
+
369
+ Effect.runPromise(program);
370
+ ```
371
+
372
+ ---
373
+
374
+ **Rationale:**
375
+
376
+ KeyValueStore operations:
377
+
378
+ - **set**: Store key-value pair
379
+ - **get**: Retrieve value by key
380
+ - **remove**: Delete key
381
+ - **has**: Check if key exists
382
+ - **clear**: Remove all entries
383
+
384
+ Pattern: `KeyValueStore.set(key, value).pipe(...)`
385
+
386
+ ---
387
+
388
+
389
+ Without persistent storage, transient data is lost:
390
+
391
+ - **Session data**: Lost on restart
392
+ - **Caches**: Rebuilt from scratch
393
+ - **Configuration**: Hardcoded or file-based
394
+ - **State**: Scattered across code
395
+
396
+ KeyValueStore enables:
397
+
398
+ - **Transparent persistence**: Automatic backend handling
399
+ - **Simple API**: Key-value abstraction
400
+ - **Pluggable backends**: Memory, filesystem, database
401
+ - **Effect integration**: Type-safe, composable
402
+
403
+ Real-world example: Caching API responses
404
+ - **Direct**: Cache in memory Map (lost on restart)
405
+ - **With KeyValueStore**: Persistent across restarts
406
+
407
+ ---
408
+
409
+ ---
410
+
411
+ ### Platform Pattern 1: Execute Shell Commands
412
+
413
+ **Rule:** Use Command to spawn and manage external processes, capturing output and handling exit codes reliably with proper error handling.
414
+
415
+ **Good Example:**
416
+
417
+ This example demonstrates executing commands and handling their output.
418
+
419
+ ```typescript
420
+ import { Command, Effect, Chunk } from "@effect/platform";
421
+
422
+ // Simple command execution
423
+ const program = Effect.gen(function* () {
424
+ console.log(`\n[COMMAND] Executing shell commands\n`);
425
+
426
+ // Example 1: List files
427
+ console.log(`[1] List files in current directory:\n`);
428
+
429
+ const lsResult = yield* Command.make("ls", ["-la"]).pipe(
430
+ Command.string
431
+ );
432
+
433
+ console.log(lsResult);
434
+
435
+ // Example 2: Get current date
436
+ console.log(`\n[2] Get current date:\n`);
437
+
438
+ const dateResult = yield* Command.make("date", ["+%Y-%m-%d %H:%M:%S"]).pipe(
439
+ Command.string
440
+ );
441
+
442
+ console.log(`Current date: ${dateResult.trim()}`);
443
+
444
+ // Example 3: Capture exit code
445
+ console.log(`\n[3] Check if file exists:\n`);
446
+
447
+ const fileCheckCmd = yield* Command.make("test", [
448
+ "-f",
449
+ "/etc/passwd",
450
+ ]).pipe(
451
+ Command.exitCode,
452
+ Effect.either
453
+ );
454
+
455
+ if (fileCheckCmd._tag === "Right") {
456
+ console.log(`✓ File exists (exit code: 0)`);
457
+ } else {
458
+ console.log(`✗ File not found (exit code: ${fileCheckCmd.left})`);
459
+ }
460
+
461
+ // Example 4: Execute with custom working directory
462
+ console.log(`\n[4] List TypeScript files:\n`);
463
+
464
+ const findResult = yield* Command.make("find", [
465
+ ".",
466
+ "-name",
467
+ "*.ts",
468
+ "-type",
469
+ "f",
470
+ ]).pipe(
471
+ Command.lines
472
+ );
473
+
474
+ const tsFiles = Chunk.take(findResult, 5); // First 5
475
+
476
+ Chunk.forEach(tsFiles, (file) => {
477
+ console.log(` - ${file}`);
478
+ });
479
+
480
+ if (Chunk.size(findResult) > 5) {
481
+ console.log(` ... and ${Chunk.size(findResult) - 5} more`);
482
+ }
483
+
484
+ // Example 5: Handle command failure
485
+ console.log(`\n[5] Handle command failure gracefully:\n`);
486
+
487
+ const failResult = yield* Command.make("false").pipe(
488
+ Command.exitCode,
489
+ Effect.catchAll((error) =>
490
+ Effect.succeed(-1) // Return -1 for any error
491
+ )
492
+ );
493
+
494
+ console.log(`Exit code: ${failResult}`);
495
+ });
496
+
497
+ Effect.runPromise(program);
498
+ ```
499
+
500
+ ---
501
+
502
+ **Rationale:**
503
+
504
+ Execute shell commands with Command:
505
+
506
+ - **Spawn**: Start external process
507
+ - **Capture**: Get stdout/stderr/exit code
508
+ - **Wait**: Block until completion
509
+ - **Handle errors**: Exit codes indicate failure
510
+
511
+ Pattern: `Command.exec("command args").pipe(...)`
512
+
513
+ ---
514
+
515
+
516
+ Shell integration without proper handling causes issues:
517
+
518
+ - **Unhandled errors**: Non-zero exit codes lost
519
+ - **Deadlocks**: Stdout buffer fills if not drained
520
+ - **Resource leaks**: Processes left running
521
+ - **Output loss**: stderr ignored
522
+ - **Race conditions**: Unsafe concurrent execution
523
+
524
+ Command enables:
525
+
526
+ - **Type-safe execution**: Success/failure handled in Effect
527
+ - **Output capture**: Both stdout and stderr available
528
+ - **Resource cleanup**: Automatic process termination
529
+ - **Exit code handling**: Explicit error mapping
530
+
531
+ Real-world example: Build pipeline
532
+ - **Direct**: Process spawned, output mixed with app logs, exit code ignored
533
+ - **With Command**: Output captured, exit code checked, errors propagated
534
+
535
+ ---
536
+
537
+ ---
538
+
539
+ ### Platform Pattern 5: Cross-Platform Path Manipulation
540
+
541
+ **Rule:** Use Effect's platform-aware path utilities to handle separators, absolute/relative paths, and environment variables consistently.
542
+
543
+ **Good Example:**
544
+
545
+ This example demonstrates cross-platform path manipulation.
546
+
547
+ ```typescript
548
+ import { Effect, FileSystem } from "@effect/platform";
549
+ import * as Path from "node:path";
550
+ import * as OS from "node:os";
551
+
552
+ interface PathOperation {
553
+ readonly input: string;
554
+ readonly description: string;
555
+ }
556
+
557
+ // Platform info
558
+ const getPlatformInfo = () =>
559
+ Effect.gen(function* () {
560
+ const platform = process.platform;
561
+ const separator = Path.sep;
562
+ const delimiter = Path.delimiter;
563
+ const homeDir = OS.homedir();
564
+
565
+ yield* Effect.log(
566
+ `[PLATFORM] OS: ${platform}, Separator: "${separator}", Home: ${homeDir}`
567
+ );
568
+
569
+ return { platform, separator, delimiter, homeDir };
570
+ });
571
+
572
+ const program = Effect.gen(function* () {
573
+ console.log(`\n[PATH MANIPULATION] Cross-platform path operations\n`);
574
+
575
+ const platformInfo = yield* getPlatformInfo();
576
+
577
+ // Example 1: Path joining (handles separators)
578
+ console.log(`\n[1] Joining paths (handles separators automatically):\n`);
579
+
580
+ const segments = ["data", "reports", "2024"];
581
+
582
+ const joinedPath = Path.join(...segments);
583
+
584
+ yield* Effect.log(`[JOIN] Input: ${segments.join(" + ")}`);
585
+ yield* Effect.log(`[JOIN] Output: ${joinedPath}`);
586
+
587
+ // Example 2: Resolving to absolute paths
588
+ console.log(`\n[2] Resolving relative → absolute:\n`);
589
+
590
+ const relativePath = "./config/settings.json";
591
+
592
+ const absolutePath = Path.resolve(relativePath);
593
+
594
+ yield* Effect.log(`[RESOLVE] Relative: ${relativePath}`);
595
+ yield* Effect.log(`[RESOLVE] Absolute: ${absolutePath}`);
596
+
597
+ // Example 3: Path parsing
598
+ console.log(`\n[3] Parsing path components:\n`);
599
+
600
+ const filePath = "/home/user/documents/report.pdf";
601
+
602
+ const parsed = Path.parse(filePath);
603
+
604
+ yield* Effect.log(`[PARSE] Input: ${filePath}`);
605
+ yield* Effect.log(` root: ${parsed.root}`);
606
+ yield* Effect.log(` dir: ${parsed.dir}`);
607
+ yield* Effect.log(` base: ${parsed.base}`);
608
+ yield* Effect.log(` name: ${parsed.name}`);
609
+ yield* Effect.log(` ext: ${parsed.ext}`);
610
+
611
+ // Example 4: Environment variable expansion
612
+ console.log(`\n[4] Environment variable expansion:\n`);
613
+
614
+ const expandPath = (pathStr: string): string => {
615
+ let result = pathStr;
616
+
617
+ // Expand common variables
618
+ result = result.replace("$HOME", OS.homedir());
619
+ result = result.replace("~", OS.homedir());
620
+ result = result.replace("$USER", process.env.USER || "user");
621
+ result = result.replace("$PWD", process.cwd());
622
+
623
+ // Handle Windows-style env vars
624
+ result = result.replace(/%USERPROFILE%/g, OS.homedir());
625
+ result = result.replace(/%USERNAME%/g, process.env.USERNAME || "user");
626
+ result = result.replace(/%TEMP%/g, OS.tmpdir());
627
+
628
+ return result;
629
+ };
630
+
631
+ const envPaths = [
632
+ "$HOME/myapp/data",
633
+ "~/documents/file.txt",
634
+ "$PWD/config",
635
+ "/var/log/app.log",
636
+ ];
637
+
638
+ for (const envPath of envPaths) {
639
+ const expanded = expandPath(envPath);
640
+
641
+ yield* Effect.log(
642
+ `[EXPAND] ${envPath} → ${expanded}`
643
+ );
644
+ }
645
+
646
+ // Example 5: Path normalization (remove redundant separators)
647
+ console.log(`\n[5] Path normalization:\n`);
648
+
649
+ const messyPaths = [
650
+ "/home//user///documents",
651
+ "C:\\Users\\\\documents\\\\file.txt",
652
+ "./config/../config/./settings",
653
+ "../data/../../root",
654
+ ];
655
+
656
+ for (const messy of messyPaths) {
657
+ const normalized = Path.normalize(messy);
658
+
659
+ yield* Effect.log(
660
+ `[NORMALIZE] ${messy}`
661
+ );
662
+ yield* Effect.log(
663
+ `[NORMALIZE] → ${normalized}`
664
+ );
665
+ }
666
+
667
+ // Example 6: Safe path construction with base directory
668
+ console.log(`\n[6] Safe path construction (path traversal prevention):\n`);
669
+
670
+ const baseDir = "/var/app/data";
671
+
672
+ const safeJoin = (base: string, userPath: string): Result<string> => {
673
+ // Reject absolute paths from untrusted input
674
+ if (Path.isAbsolute(userPath)) {
675
+ return { success: false, reason: "Absolute paths not allowed" };
676
+ }
677
+
678
+ // Reject paths with ..
679
+ if (userPath.includes("..")) {
680
+ return { success: false, reason: "Path traversal attempt detected" };
681
+ }
682
+
683
+ // Resolve and verify within base
684
+ const fullPath = Path.resolve(base, userPath);
685
+
686
+ if (!fullPath.startsWith(base)) {
687
+ return { success: false, reason: "Path escapes base directory" };
688
+ }
689
+
690
+ return { success: true, path: fullPath };
691
+ };
692
+
693
+ interface Result<T> {
694
+ success: boolean;
695
+ reason?: string;
696
+ path?: T;
697
+ }
698
+
699
+ const testPaths = [
700
+ "reports/2024.json",
701
+ "/etc/passwd",
702
+ "../../../root",
703
+ "data/file.txt",
704
+ ];
705
+
706
+ for (const test of testPaths) {
707
+ const result = safeJoin(baseDir, test);
708
+
709
+ if (result.success) {
710
+ yield* Effect.log(`[SAFE] ✓ ${test} → ${result.path}`);
711
+ } else {
712
+ yield* Effect.log(`[SAFE] ✗ ${test} (${result.reason})`);
713
+ }
714
+ }
715
+
716
+ // Example 7: Relative path calculation
717
+ console.log(`\n[7] Computing relative paths:\n`);
718
+
719
+ const fromDir = "/home/user/projects/myapp";
720
+ const toPath = "/home/user/data/config.json";
721
+
722
+ const relativePath2 = Path.relative(fromDir, toPath);
723
+
724
+ yield* Effect.log(`[RELATIVE] From: ${fromDir}`);
725
+ yield* Effect.log(`[RELATIVE] To: ${toPath}`);
726
+ yield* Effect.log(`[RELATIVE] Relative: ${relativePath2}`);
727
+
728
+ // Example 8: Common path patterns
729
+ console.log(`\n[8] Common patterns:\n`);
730
+
731
+ // Get file extension
732
+ const fileName = "document.tar.gz";
733
+ const ext = Path.extname(fileName);
734
+ const baseName = Path.basename(fileName);
735
+ const dirName = Path.dirname("/home/user/file.txt");
736
+
737
+ yield* Effect.log(`[PATTERNS] File: ${fileName}`);
738
+ yield* Effect.log(` basename: ${baseName}`);
739
+ yield* Effect.log(` dirname: ${dirName}`);
740
+ yield* Effect.log(` extname: ${ext}`);
741
+
742
+ // Example 9: Path segments array
743
+ console.log(`\n[9] Path segments:\n`);
744
+
745
+ const segmentPath = "/home/user/documents/report.pdf";
746
+
747
+ const segments2 = segmentPath.split(Path.sep).filter((s) => s);
748
+
749
+ yield* Effect.log(`[SEGMENTS] ${segmentPath}`);
750
+ yield* Effect.log(`[SEGMENTS] → [${segments2.map((s) => `"${s}"`).join(", ")}]`);
751
+ });
752
+
753
+ Effect.runPromise(program);
754
+ ```
755
+
756
+ ---
757
+
758
+ **Rationale:**
759
+
760
+ Path manipulation requires platform awareness:
761
+
762
+ - **Separators**: Windows uses `\`, Unix uses `/`
763
+ - **Absolute vs relative**: `/root` vs `./file`
764
+ - **Environment variables**: `$HOME`, `%APPDATA%`
765
+ - **Resolution**: Normalize, resolve symlinks
766
+ - **Validation**: Prevent path traversal attacks
767
+
768
+ Pattern: Avoid string concatenation, use `path.join()`, `path.resolve()`
769
+
770
+ ---
771
+
772
+
773
+ String-based path handling causes problems:
774
+
775
+ **Problem 1: Platform inconsistency**
776
+ - Write path: `"C:\data\file.txt"` (Windows)
777
+ - Ship to Linux, gets interpreted as literal "C:\data\file.txt"
778
+ - File not found errors, production outage
779
+
780
+ **Problem 2: Path traversal attacks**
781
+ - User supplies path: `"../../../../etc/passwd"`
782
+ - No validation → reads sensitive files
783
+ - Security vulnerability
784
+
785
+ **Problem 3: Environment variable expansion**
786
+ - User's config: `"$HOME/myapp/data"`
787
+ - Without expansion: literal `$HOME` in path
788
+ - Can't find files
789
+
790
+ **Problem 4: Symlink resolution**
791
+ - File at `/etc/ssl/certs/ca-bundle.crt` (symlink)
792
+ - Real file at `/usr/share/ca-certificates/ca-bundle.crt`
793
+ - Both point to same file, but string equality fails
794
+
795
+ Solutions:
796
+
797
+ **Platform-aware API**:
798
+ - `path.join()` handles separators
799
+ - `path.resolve()` creates absolute paths
800
+ - `path.parse()` components
801
+ - Auto-handles platform differences
802
+
803
+ **Variable expansion**:
804
+ - `$HOME`, `~` → user home
805
+ - `$USER` → username
806
+ - `$PWD` → current directory
807
+
808
+ **Validation**:
809
+ - Reject paths with `..`
810
+ - Reject absolute paths from untrusted input
811
+ - Contain paths within base directory
812
+
813
+ ---
814
+
815
+ ---
816
+
817
+
818
+ ## 🟠 Advanced Patterns
819
+
820
+ ### Platform Pattern 6: Advanced FileSystem Operations
821
+
822
+ **Rule:** Use advanced file system patterns to implement efficient, reliable file operations with proper error handling and resource cleanup.
823
+
824
+ **Good Example:**
825
+
826
+ This example demonstrates advanced file system patterns.
827
+
828
+ ```typescript
829
+ import { Effect, Stream, Ref, FileSystem } from "@effect/platform";
830
+ import * as Path from "node:path";
831
+ import * as FS from "node:fs";
832
+ import * as PromiseFS from "node:fs/promises";
833
+
834
+ const program = Effect.gen(function* () {
835
+ console.log(`\n[ADVANCED FILESYSTEM] Complex file operations\n`);
836
+
837
+ // Example 1: Atomic file write with temporary file
838
+ console.log(`[1] Atomic write (crash-safe):\n`);
839
+
840
+ const atomicWrite = (
841
+ filePath: string,
842
+ content: string
843
+ ): Effect.Effect<void> =>
844
+ Effect.gen(function* () {
845
+ const tempPath = `${filePath}.tmp`;
846
+
847
+ try {
848
+ // Step 1: Write to temporary file
849
+ yield* Effect.promise(() =>
850
+ PromiseFS.writeFile(tempPath, content, "utf-8")
851
+ );
852
+
853
+ yield* Effect.log(`[WRITE] Wrote to temporary file`);
854
+
855
+ // Step 2: Ensure on disk (fsync)
856
+ yield* Effect.promise(() =>
857
+ PromiseFS.writeFile(tempPath, content, "utf-8")
858
+ );
859
+
860
+ yield* Effect.log(`[FSYNC] Data on disk`);
861
+
862
+ // Step 3: Atomic rename
863
+ yield* Effect.promise(() =>
864
+ PromiseFS.rename(tempPath, filePath)
865
+ );
866
+
867
+ yield* Effect.log(`[RENAME] Atomic rename complete`);
868
+ } catch (error) {
869
+ // Cleanup on failure
870
+ try {
871
+ yield* Effect.promise(() => PromiseFS.unlink(tempPath));
872
+ } catch {
873
+ // Ignore cleanup errors
874
+ }
875
+
876
+ yield* Effect.fail(error);
877
+ }
878
+ });
879
+
880
+ // Test atomic write
881
+ const testFile = "./test-file.txt";
882
+
883
+ yield* atomicWrite(testFile, "Important configuration\n");
884
+
885
+ // Verify file
886
+ const content = yield* Effect.promise(() =>
887
+ PromiseFS.readFile(testFile, "utf-8")
888
+ );
889
+
890
+ yield* Effect.log(`[READ] Got: "${content.trim()}"\n`);
891
+
892
+ // Example 2: Streaming read (memory efficient)
893
+ console.log(`[2] Streaming read (handle large files):\n`);
894
+
895
+ const streamingRead = (filePath: string) =>
896
+ Effect.gen(function* () {
897
+ let byteCount = 0;
898
+ let lineCount = 0;
899
+
900
+ const readStream = FS.createReadStream(filePath, {
901
+ encoding: "utf-8",
902
+ highWaterMark: 64 * 1024, // 64KB chunks
903
+ });
904
+
905
+ yield* Effect.log(`[STREAM] Starting read with 64KB chunks`);
906
+
907
+ const processLine = (line: string) =>
908
+ Effect.gen(function* () {
909
+ byteCount += line.length;
910
+ lineCount++;
911
+
912
+ if (lineCount <= 2 || lineCount % 1000 === 0) {
913
+ yield* Effect.log(
914
+ `[LINE ${lineCount}] Length: ${line.length} bytes`
915
+ );
916
+ }
917
+ });
918
+
919
+ // In real code, process all lines
920
+ yield* processLine("line 1");
921
+ yield* processLine("line 2");
922
+
923
+ yield* Effect.log(
924
+ `[TOTAL] Read ${lineCount} lines, ${byteCount} bytes`
925
+ );
926
+ });
927
+
928
+ yield* streamingRead(testFile);
929
+
930
+ // Example 3: Recursive directory listing
931
+ console.log(`\n[3] Recursive directory traversal:\n`);
932
+
933
+ const recursiveList = (
934
+ dir: string,
935
+ maxDepth: number = 3
936
+ ): Effect.Effect<Array<{ path: string; type: "file" | "dir" }>> =>
937
+ Effect.gen(function* () {
938
+ const results: Array<{ path: string; type: "file" | "dir" }> = [];
939
+
940
+ const traverse = (currentDir: string, depth: number) =>
941
+ Effect.gen(function* () {
942
+ if (depth > maxDepth) {
943
+ return;
944
+ }
945
+
946
+ const entries = yield* Effect.promise(() =>
947
+ PromiseFS.readdir(currentDir, { withFileTypes: true })
948
+ );
949
+
950
+ for (const entry of entries) {
951
+ const fullPath = Path.join(currentDir, entry.name);
952
+
953
+ if (entry.isDirectory()) {
954
+ results.push({ path: fullPath, type: "dir" });
955
+
956
+ yield* traverse(fullPath, depth + 1);
957
+ } else {
958
+ results.push({ path: fullPath, type: "file" });
959
+ }
960
+ }
961
+ });
962
+
963
+ yield* traverse(dir, 0);
964
+
965
+ return results;
966
+ });
967
+
968
+ // List files in current directory
969
+ const entries = yield* recursiveList(".", 1);
970
+
971
+ yield* Effect.log(
972
+ `[ENTRIES] Found ${entries.length} items:`
973
+ );
974
+
975
+ for (const entry of entries.slice(0, 5)) {
976
+ const type = entry.type === "file" ? "📄" : "📁";
977
+
978
+ yield* Effect.log(` ${type} ${entry.path}`);
979
+ }
980
+
981
+ // Example 4: Bulk file operations
982
+ console.log(`\n[4] Bulk operations (efficient batching):\n`);
983
+
984
+ const bulkCreate = (files: Array<{ name: string; content: string }>) =>
985
+ Effect.gen(function* () {
986
+ yield* Effect.log(`[BULK] Creating ${files.length} files...`);
987
+
988
+ for (const file of files) {
989
+ yield* atomicWrite(`./${file.name}`, file.content);
990
+ }
991
+
992
+ yield* Effect.log(`[BULK] Created ${files.length} files`);
993
+ });
994
+
995
+ const testFiles = [
996
+ { name: "config1.txt", content: "Config 1" },
997
+ { name: "config2.txt", content: "Config 2" },
998
+ { name: "config3.txt", content: "Config 3" },
999
+ ];
1000
+
1001
+ yield* bulkCreate(testFiles);
1002
+
1003
+ // Example 5: File watching (detect changes)
1004
+ console.log(`\n[5] File watching (react to changes):\n`);
1005
+
1006
+ const watchFile = (filePath: string) =>
1007
+ Effect.gen(function* () {
1008
+ yield* Effect.log(`[WATCH] Starting to watch: ${filePath}`);
1009
+
1010
+ let changeCount = 0;
1011
+
1012
+ // Simulate file watcher
1013
+ const checkForChanges = () =>
1014
+ Effect.gen(function* () {
1015
+ for (let i = 0; i < 3; i++) {
1016
+ yield* Effect.sleep("100 millis");
1017
+
1018
+ // Check file modification time
1019
+ const stat = yield* Effect.promise(() =>
1020
+ PromiseFS.stat(filePath)
1021
+ );
1022
+
1023
+ // In real implementation, compare previous mtime
1024
+ if (i === 1) {
1025
+ changeCount++;
1026
+
1027
+ yield* Effect.log(
1028
+ `[CHANGE] File modified (${stat.size} bytes)`
1029
+ );
1030
+ }
1031
+ }
1032
+ });
1033
+
1034
+ yield* checkForChanges();
1035
+
1036
+ yield* Effect.log(`[WATCH] Detected ${changeCount} changes`);
1037
+ });
1038
+
1039
+ yield* watchFile(testFile);
1040
+
1041
+ // Example 6: Safe concurrent file operations
1042
+ console.log(`\n[6] Concurrent file operations with safety:\n`);
1043
+
1044
+ const lockFile = (filePath: string) =>
1045
+ Effect.gen(function* () {
1046
+ const lockPath = `${filePath}.lock`;
1047
+
1048
+ // Acquire lock
1049
+ yield* atomicWrite(lockPath, "locked");
1050
+
1051
+ yield* Effect.log(`[LOCK] Acquired: ${lockPath}`);
1052
+
1053
+ try {
1054
+ // Critical section
1055
+ yield* Effect.sleep("50 millis");
1056
+
1057
+ yield* Effect.log(`[CRITICAL] Operating on locked file`);
1058
+ } finally {
1059
+ // Release lock
1060
+ yield* Effect.promise(() =>
1061
+ PromiseFS.unlink(lockPath)
1062
+ );
1063
+
1064
+ yield* Effect.log(`[UNLOCK] Released: ${lockPath}`);
1065
+ }
1066
+ });
1067
+
1068
+ yield* lockFile(testFile);
1069
+
1070
+ // Example 7: Efficient file copying
1071
+ console.log(`\n[7] Efficient file copying:\n`);
1072
+
1073
+ const efficientCopy = (
1074
+ source: string,
1075
+ destination: string
1076
+ ): Effect.Effect<void> =>
1077
+ Effect.gen(function* () {
1078
+ const stat = yield* Effect.promise(() =>
1079
+ PromiseFS.stat(source)
1080
+ );
1081
+
1082
+ yield* Effect.log(
1083
+ `[COPY] Reading ${(stat.size / 1024).toFixed(2)}KB`
1084
+ );
1085
+
1086
+ const content = yield* Effect.promise(() =>
1087
+ PromiseFS.readFile(source)
1088
+ );
1089
+
1090
+ yield* atomicWrite(destination, content.toString());
1091
+
1092
+ yield* Effect.log(`[COPY] Complete: ${destination}`);
1093
+ });
1094
+
1095
+ yield* efficientCopy(testFile, "./test-file-copy.txt");
1096
+
1097
+ // Cleanup
1098
+ yield* Effect.log(`\n[CLEANUP] Removing test files`);
1099
+
1100
+ for (const name of [testFile, "test-file-copy.txt", ...testFiles.map((f) => `./${f.name}`)]) {
1101
+ try {
1102
+ yield* Effect.promise(() =>
1103
+ PromiseFS.unlink(name)
1104
+ );
1105
+
1106
+ yield* Effect.log(`[REMOVED] ${name}`);
1107
+ } catch {
1108
+ // File doesn't exist, that's ok
1109
+ }
1110
+ }
1111
+ });
1112
+
1113
+ Effect.runPromise(program);
1114
+ ```
1115
+
1116
+ ---
1117
+
1118
+ **Rationale:**
1119
+
1120
+ Advanced file system operations require careful handling:
1121
+
1122
+ - **Atomic writes**: Prevent partial file corruption
1123
+ - **File watching**: React to file changes
1124
+ - **Recursive operations**: Handle directory trees
1125
+ - **Bulk operations**: Efficient batch processing
1126
+ - **Streaming**: Handle large files without loading all in memory
1127
+ - **Permissions**: Handle access control safely
1128
+
1129
+ Pattern: Combine `FileSystem` API with `Ref` for state, `Stream` for data
1130
+
1131
+ ---
1132
+
1133
+
1134
+ Simple file operations cause problems at scale:
1135
+
1136
+ **Problem 1: Corrupted files**
1137
+ - Write config file
1138
+ - Server crashes mid-write
1139
+ - File is partial/corrupted
1140
+ - Application fails to start
1141
+ - Production outage
1142
+
1143
+ **Problem 2: Large file handling**
1144
+ - Load 10GB file into memory
1145
+ - Server runs out of memory
1146
+ - Everything crashes
1147
+ - Now handling outages instead of serving
1148
+
1149
+ **Problem 3: Directory synchronization**
1150
+ - Copy directory tree
1151
+ - Process interrupted
1152
+ - Some files copied, some not
1153
+ - Directory in inconsistent state
1154
+ - Hard to recover
1155
+
1156
+ **Problem 4: Inefficient updates**
1157
+ - Update 10,000 files one by one
1158
+ - Each file system call is slow
1159
+ - Takes hours
1160
+ - Meanwhile, users can't access data
1161
+
1162
+ **Problem 5: File locking**
1163
+ - Process A reads file
1164
+ - Process B writes file
1165
+ - Process A gets partially written file
1166
+ - Data corruption
1167
+
1168
+ Solutions:
1169
+
1170
+ **Atomic writes**:
1171
+ - Write to temporary file
1172
+ - Fsync (guarantee on disk)
1173
+ - Atomic rename
1174
+ - No corruption even on crash
1175
+
1176
+ **Streaming**:
1177
+ - Process large files in chunks
1178
+ - Keep memory constant
1179
+ - Efficient for any file size
1180
+
1181
+ **Bulk operations**:
1182
+ - Batch multiple operations
1183
+ - Reduce system calls
1184
+ - Faster overall completion
1185
+
1186
+ **File watching**:
1187
+ - React to changes
1188
+ - Avoid polling
1189
+ - Real-time responsiveness
1190
+
1191
+ ---
1192
+
1193
+ ---
1194
+
1195
+