opencode-effect-enforcer 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +278 -0
  3. package/guidance/effect-first-development.md +1247 -0
  4. package/guidance/post__effect-and-the-near-inexpressible-majesty-of-layers.md +490 -0
  5. package/guidance/post__parse-dont-validate.md +109 -0
  6. package/guidance/progressive-disclosure-guidance.md +38 -0
  7. package/package.json +63 -0
  8. package/patterns/avoid-any.md +37 -0
  9. package/patterns/avoid-data-tagged-error.md +34 -0
  10. package/patterns/avoid-direct-json.md +51 -0
  11. package/patterns/avoid-direct-tag-checks.md +54 -0
  12. package/patterns/avoid-expect-in-if.md +52 -0
  13. package/patterns/avoid-mutable-state.md +70 -0
  14. package/patterns/avoid-native-fetch.md +61 -0
  15. package/patterns/avoid-node-imports.md +86 -0
  16. package/patterns/avoid-non-null-assertion.md +44 -0
  17. package/patterns/avoid-object-type.md +46 -0
  18. package/patterns/avoid-option-getorthrow.md +39 -0
  19. package/patterns/avoid-platform-coupling.md +43 -0
  20. package/patterns/avoid-process-env.md +43 -0
  21. package/patterns/avoid-react-hooks.md +73 -0
  22. package/patterns/avoid-schema-suffix.md +45 -0
  23. package/patterns/avoid-sync-fs.md +68 -0
  24. package/patterns/avoid-try-catch.md +47 -0
  25. package/patterns/avoid-ts-ignore.md +38 -0
  26. package/patterns/avoid-untagged-errors.md +67 -0
  27. package/patterns/avoid-yield-ref.md +46 -0
  28. package/patterns/casting-awareness.md +46 -0
  29. package/patterns/context-tag-extends.md +84 -0
  30. package/patterns/effect-catchall-default.md +61 -0
  31. package/patterns/effect-promise-vs-trypromise.md +47 -0
  32. package/patterns/effect-run-in-body.md +58 -0
  33. package/patterns/imperative-loops.md +76 -0
  34. package/patterns/prefer-arr-sort.md +52 -0
  35. package/patterns/prefer-duration-values.md +56 -0
  36. package/patterns/prefer-effect-fn.md +161 -0
  37. package/patterns/prefer-match-over-switch.md +48 -0
  38. package/patterns/prefer-option-over-null.md +56 -0
  39. package/patterns/prefer-redacted-config.md +70 -0
  40. package/patterns/prefer-schema-class.md +54 -0
  41. package/patterns/require-effect-concurrency.md +83 -0
  42. package/patterns/stream-large-files.md +63 -0
  43. package/patterns/throw-in-effect-gen.md +62 -0
  44. package/patterns/use-clock-service.md +45 -0
  45. package/patterns/use-command-executor-service.md +54 -0
  46. package/patterns/use-console-service.md +54 -0
  47. package/patterns/use-filesystem-service.md +59 -0
  48. package/patterns/use-http-client-service.md +77 -0
  49. package/patterns/use-path-service.md +53 -0
  50. package/patterns/use-random-service.md +45 -0
  51. package/patterns/use-temp-file-scoped.md +66 -0
  52. package/patterns/vm-in-wrong-file.md +51 -0
  53. package/patterns/yield-in-for-loop.md +61 -0
  54. package/skills/effect-ai-chat/SKILL.md +472 -0
  55. package/skills/effect-ai-language-model/SKILL.md +652 -0
  56. package/skills/effect-ai-prompt/SKILL.md +752 -0
  57. package/skills/effect-ai-provider/SKILL.md +668 -0
  58. package/skills/effect-ai-streaming/SKILL.md +418 -0
  59. package/skills/effect-ai-tool/SKILL.md +1132 -0
  60. package/skills/effect-atom-rpc/SKILL.md +488 -0
  61. package/skills/effect-atom-state/SKILL.md +640 -0
  62. package/skills/effect-batching/SKILL.md +614 -0
  63. package/skills/effect-cache/SKILL.md +570 -0
  64. package/skills/effect-cli/SKILL.md +523 -0
  65. package/skills/effect-command-executor/SKILL.md +675 -0
  66. package/skills/effect-concurrency-testing/SKILL.md +612 -0
  67. package/skills/effect-config/SKILL.md +580 -0
  68. package/skills/effect-context-witness/SKILL.md +274 -0
  69. package/skills/effect-domain-modeling/SKILL.md +1212 -0
  70. package/skills/effect-domain-predicates/SKILL.md +867 -0
  71. package/skills/effect-error-handling/SKILL.md +1581 -0
  72. package/skills/effect-fiber/SKILL.md +731 -0
  73. package/skills/effect-filesystem/SKILL.md +624 -0
  74. package/skills/effect-graph/SKILL.md +571 -0
  75. package/skills/effect-http-api/SKILL.md +1760 -0
  76. package/skills/effect-http-client/SKILL.md +989 -0
  77. package/skills/effect-http-server/SKILL.md +920 -0
  78. package/skills/effect-incremental-migration/SKILL.md +362 -0
  79. package/skills/effect-layer-design/SKILL.md +642 -0
  80. package/skills/effect-managed-runtime/SKILL.md +395 -0
  81. package/skills/effect-mcp-server/SKILL.md +608 -0
  82. package/skills/effect-observability/SKILL.md +719 -0
  83. package/skills/effect-optics/SKILL.md +554 -0
  84. package/skills/effect-parallelization/SKILL.md +668 -0
  85. package/skills/effect-path/SKILL.md +296 -0
  86. package/skills/effect-pattern-matching/SKILL.md +914 -0
  87. package/skills/effect-platform-abstraction/SKILL.md +1175 -0
  88. package/skills/effect-platform-layers/SKILL.md +514 -0
  89. package/skills/effect-pubsub-event-bus/SKILL.md +384 -0
  90. package/skills/effect-react-composition/SKILL.md +986 -0
  91. package/skills/effect-react-vm/SKILL.md +675 -0
  92. package/skills/effect-rpc-api/SKILL.md +624 -0
  93. package/skills/effect-rpc-client/SKILL.md +666 -0
  94. package/skills/effect-rpc-cluster/SKILL.md +1623 -0
  95. package/skills/effect-rpc-server/SKILL.md +767 -0
  96. package/skills/effect-scheduling/SKILL.md +124 -0
  97. package/skills/effect-schema-composition/SKILL.md +975 -0
  98. package/skills/effect-schema-v4/SKILL.md +691 -0
  99. package/skills/effect-scope/SKILL.md +682 -0
  100. package/skills/effect-service-implementation/SKILL.md +656 -0
  101. package/skills/effect-socket/SKILL.md +703 -0
  102. package/skills/effect-sql/SKILL.md +781 -0
  103. package/skills/effect-stream/SKILL.md +765 -0
  104. package/skills/effect-testing/SKILL.md +1331 -0
  105. package/skills/effect-typeclass-design/SKILL.md +161 -0
  106. package/skills/effect-wide-events/Article.md +66 -0
  107. package/skills/effect-wide-events/SKILL.md +95 -0
  108. package/skills/effect-workflow/SKILL.md +810 -0
  109. package/src/agent-policy.ts +22 -0
  110. package/src/enforcer.ts +104 -0
  111. package/src/frontmatter.ts +34 -0
  112. package/src/guidance.ts +66 -0
  113. package/src/index.ts +38 -0
  114. package/src/pattern-catalog.ts +115 -0
  115. package/src/pattern-matcher.ts +178 -0
  116. package/src/pattern.ts +97 -0
  117. package/src/skills.ts +29 -0
  118. package/src/write-projection.ts +66 -0
@@ -0,0 +1,624 @@
1
+ ---
2
+ name: effect-filesystem
3
+ description: Use Effect FileSystem for platform-abstract file I/O with Node.js/Bun layers or custom implementations.
4
+ ---
5
+
6
+ # FileSystem Platform Abstraction
7
+
8
+ Use `effect` FileSystem for platform-abstract file I/O. Stock layers are provided for Node.js and Bun; `@effect/platform-browser` does not provide a FileSystem layer, so browser code needs a custom/injected implementation.
9
+
10
+ ## Basic Pattern
11
+
12
+ ```typescript
13
+ import { FileSystem } from 'effect';
14
+ import { Effect } from 'effect';
15
+
16
+ // Service injection via yield*
17
+ const program = Effect.gen(function* () {
18
+ const fs = yield* FileSystem.FileSystem;
19
+
20
+ // Use fs methods here
21
+ const content = yield* fs.readFileString('path/to/file.txt');
22
+ return content;
23
+ });
24
+ ```
25
+
26
+ ## Reading Operations
27
+
28
+ ### Read File (Binary)
29
+
30
+ ```typescript
31
+ import { FileSystem } from 'effect';
32
+ import { Effect } from 'effect';
33
+
34
+ const readBinary = Effect.gen(function* () {
35
+ const fs = yield* FileSystem.FileSystem;
36
+ const bytes = yield* fs.readFile('data.bin');
37
+ return bytes; // Uint8Array
38
+ });
39
+ ```
40
+
41
+ ### Read File (String)
42
+
43
+ ```typescript
44
+ import { FileSystem } from 'effect';
45
+ import { Effect } from 'effect';
46
+
47
+ const readText = Effect.gen(function* () {
48
+ const fs = yield* FileSystem.FileSystem;
49
+ const content = yield* fs.readFileString('config.json');
50
+ return content; // string
51
+ });
52
+ ```
53
+
54
+ ### Stream File
55
+
56
+ ```typescript
57
+ import { FileSystem } from 'effect';
58
+ import { Effect } from 'effect';
59
+
60
+ const streamFile = Effect.gen(function* () {
61
+ const fs = yield* FileSystem.FileSystem;
62
+ // fs.stream() returns a Stream directly — not an Effect
63
+ const stream = fs.stream('large-file.log');
64
+ return stream; // Stream<Uint8Array, PlatformError>
65
+ });
66
+ ```
67
+
68
+ ### Read Directory
69
+
70
+ ```typescript
71
+ import { FileSystem } from 'effect';
72
+ import { Effect } from 'effect';
73
+
74
+ const listFiles = Effect.gen(function* () {
75
+ const fs = yield* FileSystem.FileSystem;
76
+ const entries = yield* fs.readDirectory('src/');
77
+ return entries; // ReadonlyArray<string>
78
+ });
79
+ ```
80
+
81
+ ### Read Symbolic Link
82
+
83
+ ```typescript
84
+ import { FileSystem } from 'effect';
85
+ import { Effect } from 'effect';
86
+
87
+ const readLink = Effect.gen(function* () {
88
+ const fs = yield* FileSystem.FileSystem;
89
+ const target = yield* fs.readLink('symlink');
90
+ return target; // string
91
+ });
92
+ ```
93
+
94
+ ## Writing Operations
95
+
96
+ ### Write File (Binary)
97
+
98
+ ```typescript
99
+ import { FileSystem } from 'effect';
100
+ import { Effect } from 'effect';
101
+
102
+ const writeBinary = Effect.gen(function* () {
103
+ const fs = yield* FileSystem.FileSystem;
104
+ const data = new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f]);
105
+ yield* fs.writeFile('output.bin', data);
106
+ });
107
+ ```
108
+
109
+ ### Write File (String)
110
+
111
+ ```typescript
112
+ import { FileSystem } from 'effect';
113
+ import { Effect } from 'effect';
114
+
115
+ const writeText = Effect.gen(function* () {
116
+ const fs = yield* FileSystem.FileSystem;
117
+ yield* fs.writeFileString('output.txt', 'Hello, World!');
118
+ });
119
+ ```
120
+
121
+ ### Sink (Stream Writing)
122
+
123
+ ```typescript
124
+ import { FileSystem } from 'effect';
125
+ import { Effect, Stream, pipe } from 'effect';
126
+
127
+ const writeStream = Effect.gen(function* () {
128
+ const fs = yield* FileSystem.FileSystem;
129
+ // fs.sink() returns a Sink directly — not an Effect
130
+ const sink = fs.sink('output.log');
131
+
132
+ yield* pipe(
133
+ Stream.fromIterable(['line 1\n', 'line 2\n', 'line 3\n']),
134
+ Stream.mapEffect((s) => Effect.succeed(new TextEncoder().encode(s))),
135
+ Stream.run(sink)
136
+ );
137
+ });
138
+ ```
139
+
140
+ ## File Operations
141
+
142
+ ### Copy File
143
+
144
+ ```typescript
145
+ import { FileSystem } from 'effect';
146
+ import { Effect } from 'effect';
147
+
148
+ const copyFile = Effect.gen(function* () {
149
+ const fs = yield* FileSystem.FileSystem;
150
+ yield* fs.copyFile('source.txt', 'dest.txt');
151
+ });
152
+ ```
153
+
154
+ ### Copy (Recursive Directory)
155
+
156
+ ```typescript
157
+ import { FileSystem } from 'effect';
158
+ import { Effect } from 'effect';
159
+
160
+ const copyDir = Effect.gen(function* () {
161
+ const fs = yield* FileSystem.FileSystem;
162
+ yield* fs.copy('src-dir/', 'dest-dir/');
163
+ });
164
+ ```
165
+
166
+ ### Rename/Move
167
+
168
+ ```typescript
169
+ import { FileSystem } from 'effect';
170
+ import { Effect } from 'effect';
171
+
172
+ const renameFile = Effect.gen(function* () {
173
+ const fs = yield* FileSystem.FileSystem;
174
+ yield* fs.rename('old-name.txt', 'new-name.txt');
175
+ });
176
+ ```
177
+
178
+ ### Remove
179
+
180
+ ```typescript
181
+ import { FileSystem } from 'effect';
182
+ import { Effect } from 'effect';
183
+
184
+ const removeFile = Effect.gen(function* () {
185
+ const fs = yield* FileSystem.FileSystem;
186
+ yield* fs.remove('file.txt');
187
+ });
188
+
189
+ // Remove directory recursively
190
+ const removeDir = Effect.gen(function* () {
191
+ const fs = yield* FileSystem.FileSystem;
192
+ yield* fs.remove('directory/', { recursive: true });
193
+ });
194
+ ```
195
+
196
+ ### Open File Handle
197
+
198
+ ```typescript
199
+ import { FileSystem } from 'effect';
200
+ import { Effect } from 'effect';
201
+
202
+ const useFileHandle = Effect.gen(function* () {
203
+ const fs = yield* FileSystem.FileSystem;
204
+
205
+ // File handles are scoped — automatically closed when scope exits
206
+ yield* Effect.scoped(
207
+ Effect.gen(function* () {
208
+ const file = yield* fs.open('data.txt', { flag: 'r' });
209
+
210
+ // File methods return branded Size values for byte counts and offsets.
211
+ const buffer = new Uint8Array(1024);
212
+ const bytesRead = yield* file.read(buffer);
213
+ const offset = yield* file.seek(FileSystem.Size(0), 'start');
214
+ })
215
+ );
216
+ });
217
+ ```
218
+
219
+ `file.seek(offset, from)` accepts a `SizeInput`, supports `from: 'start' | 'current'`, and returns the new offset as `FileSystem.Size` (not `void` or a plain number). Open `File` handles no longer expose a `descriptor` property or `File.Descriptor` type as of beta.103; use the scoped handle operations (`read`, `readAlloc`, `write`, `writeAll`, `seek`, `stat`, `sync`, and `truncate`) instead.
220
+
221
+ ## Directory Operations
222
+
223
+ ### Make Directory
224
+
225
+ ```typescript
226
+ import { FileSystem } from 'effect';
227
+ import { Effect } from 'effect';
228
+
229
+ const createDir = Effect.gen(function* () {
230
+ const fs = yield* FileSystem.FileSystem;
231
+ yield* fs.makeDirectory('new-dir/');
232
+
233
+ // Recursive directory creation
234
+ yield* fs.makeDirectory('path/to/nested/dir/', { recursive: true });
235
+ });
236
+ ```
237
+
238
+ ### Make Temp Directory
239
+
240
+ ```typescript
241
+ import { FileSystem } from 'effect';
242
+ import { Effect } from 'effect';
243
+
244
+ const useTempDir = Effect.gen(function* () {
245
+ const fs = yield* FileSystem.FileSystem;
246
+ const tempPath = yield* fs.makeTempDirectory();
247
+
248
+ // Use tempPath
249
+ yield* fs.writeFileString(`${tempPath}/temp-file.txt`, 'data');
250
+
251
+ // Manual cleanup required
252
+ yield* fs.remove(tempPath, { recursive: true });
253
+ });
254
+ ```
255
+
256
+ ### Make Temp Directory (Scoped)
257
+
258
+ ```typescript
259
+ import { FileSystem } from 'effect';
260
+ import { Effect } from 'effect';
261
+
262
+ const useScopedTempDir = Effect.gen(function* () {
263
+ const fs = yield* FileSystem.FileSystem;
264
+ const tempPath = yield* fs.makeTempDirectoryScoped();
265
+
266
+ // Use tempPath within scope
267
+ yield* fs.writeFileString(`${tempPath}/temp-file.txt`, 'data');
268
+
269
+ // Automatically cleaned up when scope exits
270
+ }).pipe(Effect.scoped);
271
+ ```
272
+
273
+ ## Metadata Operations
274
+
275
+ ### Stat (File Info)
276
+
277
+ ```typescript
278
+ import { FileSystem } from 'effect';
279
+ import { Effect, Console } from 'effect';
280
+
281
+ const getFileInfo = Effect.gen(function* () {
282
+ const fs = yield* FileSystem.FileSystem;
283
+ const info = yield* fs.stat('file.txt');
284
+
285
+ yield* Console.log(`Type: ${info.type}`);
286
+ // "File" | "Directory" | "SymbolicLink" | "BlockDevice" | "CharacterDevice" | "FIFO" | "Socket" | "Unknown"
287
+ yield* Console.log(`Size: ${info.size}`); // FileSystem.Size (branded bigint)
288
+ yield* Console.log(`Modified: ${info.mtime}`); // Option<Date>
289
+ yield* Console.log(`Accessed: ${info.atime}`); // Option<Date>
290
+ yield* Console.log(`Created: ${info.birthtime}`); // Option<Date>
291
+ });
292
+ ```
293
+
294
+ ### Access (Check Permissions)
295
+
296
+ ```typescript
297
+ import { FileSystem } from 'effect';
298
+ import { Effect } from 'effect';
299
+
300
+ const checkAccess = Effect.gen(function* () {
301
+ const fs = yield* FileSystem.FileSystem;
302
+
303
+ // Check if file exists and is readable
304
+ yield* fs.access('file.txt', { readable: true });
305
+
306
+ // Check writable
307
+ yield* fs.access('file.txt', { writable: true });
308
+
309
+ // Check if file exists (ok)
310
+ yield* fs.access('script.sh', { ok: true });
311
+ });
312
+ ```
313
+
314
+ ### Exists
315
+
316
+ ```typescript
317
+ import { FileSystem } from 'effect';
318
+ import { Effect } from 'effect';
319
+
320
+ const fileExists = Effect.gen(function* () {
321
+ const fs = yield* FileSystem.FileSystem;
322
+ const exists = yield* fs.exists('file.txt');
323
+ return exists; // boolean
324
+ });
325
+ ```
326
+
327
+ ### Real Path (Resolve Symlinks)
328
+
329
+ ```typescript
330
+ import { FileSystem } from 'effect';
331
+ import { Effect } from 'effect';
332
+
333
+ const resolvePath = Effect.gen(function* () {
334
+ const fs = yield* FileSystem.FileSystem;
335
+ const realPath = yield* fs.realPath('symlink-or-relative-path');
336
+ return realPath; // string (absolute path)
337
+ });
338
+ ```
339
+
340
+ ## Permission Operations
341
+
342
+ ### Change Mode (chmod)
343
+
344
+ ```typescript
345
+ import { FileSystem } from 'effect';
346
+ import { Effect } from 'effect';
347
+
348
+ const changeMode = Effect.gen(function* () {
349
+ const fs = yield* FileSystem.FileSystem;
350
+ yield* fs.chmod('script.sh', 0o755);
351
+ });
352
+ ```
353
+
354
+ ### Change Owner (chown)
355
+
356
+ ```typescript
357
+ import { FileSystem } from 'effect';
358
+ import { Effect } from 'effect';
359
+
360
+ const changeOwner = Effect.gen(function* () {
361
+ const fs = yield* FileSystem.FileSystem;
362
+ yield* fs.chown('file.txt', 1000, 1000); // uid, gid
363
+ });
364
+ ```
365
+
366
+ ### Update Times (utimes)
367
+
368
+ ```typescript
369
+ import { FileSystem } from 'effect';
370
+ import { Effect } from 'effect';
371
+
372
+ const updateTimes = Effect.gen(function* () {
373
+ const fs = yield* FileSystem.FileSystem;
374
+ const now = new Date();
375
+ yield* fs.utimes('file.txt', now, now); // atime, mtime
376
+ });
377
+ ```
378
+
379
+ ## Links
380
+
381
+ ### Hard Link
382
+
383
+ ```typescript
384
+ import { FileSystem } from 'effect';
385
+ import { Effect } from 'effect';
386
+
387
+ const createHardLink = Effect.gen(function* () {
388
+ const fs = yield* FileSystem.FileSystem;
389
+ yield* fs.link('original.txt', 'hardlink.txt');
390
+ });
391
+ ```
392
+
393
+ ### Symbolic Link
394
+
395
+ ```typescript
396
+ import { FileSystem } from 'effect';
397
+ import { Effect } from 'effect';
398
+
399
+ const createSymlink = Effect.gen(function* () {
400
+ const fs = yield* FileSystem.FileSystem;
401
+ yield* fs.symlink('target.txt', 'symlink.txt');
402
+ });
403
+ ```
404
+
405
+ ## Watching
406
+
407
+ ### Watch Files/Directories
408
+
409
+ ```typescript
410
+ import { FileSystem } from 'effect';
411
+ import { Effect, Stream, Console, pipe } from 'effect';
412
+
413
+ const watchFiles = Effect.gen(function* () {
414
+ const fs = yield* FileSystem.FileSystem;
415
+ // fs.watch() returns a Stream directly — not an Effect
416
+ const events = fs.watch('src/'); // direct children only
417
+
418
+ return events; // Stream<WatchEvent, PlatformError>
419
+ });
420
+
421
+ // Consume watch events
422
+ const consumeWatchEvents = Effect.gen(function* () {
423
+ const fs = yield* FileSystem.FileSystem;
424
+ const events = fs.watch('config/', { recursive: true });
425
+
426
+ yield* pipe(
427
+ events,
428
+ Stream.runForEach((event) =>
429
+ Console.log(`Event: ${event._tag}, Path: ${event.path}`)
430
+ )
431
+ );
432
+ });
433
+ ```
434
+
435
+ Directory watching is non-recursive by default. Pass `{ recursive: true }` to include changes in nested subdirectories. Watching a file watches that file; the recursive option matters for directory trees.
436
+
437
+ ## Size Helpers
438
+
439
+ ```typescript
440
+ import { FileSystem } from 'effect';
441
+ import { Effect } from 'effect';
442
+
443
+ const { Size, KiB, MiB, GiB, TiB, PiB } = FileSystem;
444
+
445
+ // Create size values
446
+ const oneKb = Size(1024);
447
+ const tenKb = KiB(10);
448
+ const oneMb = MiB(1);
449
+ const fiveGb = GiB(5);
450
+ const oneTb = TiB(1);
451
+ const onePb = PiB(1);
452
+
453
+ // Use with file operations
454
+ const checkFileSize = Effect.gen(function* () {
455
+ const fs = yield* FileSystem.FileSystem;
456
+ const info = yield* fs.stat('large-file.bin');
457
+
458
+ const maxSize = MiB(100);
459
+ if (info.size > maxSize) {
460
+ yield* Effect.fail(new Error('File too large'));
461
+ }
462
+ });
463
+ ```
464
+
465
+ ## Error Handling
466
+
467
+ ### SystemErrorTag Values
468
+
469
+ FileSystem operations fail with `PlatformError` containing a `SystemErrorTag`:
470
+
471
+ - `AlreadyExists` - File/directory already exists
472
+ - `BadResource` - Invalid file descriptor or handle
473
+ - `Busy` - Resource is busy
474
+ - `InvalidData` - Invalid data format
475
+ - `NotFound` - File/directory not found
476
+ - `PermissionDenied` - Insufficient permissions
477
+ - `TimedOut` - Operation timed out
478
+ - `UnexpectedEof` - Unexpected end of file
479
+ - `Unknown` - Unknown error
480
+ - `WouldBlock` - Operation would block
481
+ - `WriteZero` - Write operation wrote zero bytes
482
+
483
+ ### Error Handling Pattern
484
+
485
+ ```typescript
486
+ import { FileSystem } from 'effect';
487
+ import { Effect, pipe } from 'effect';
488
+
489
+ const readConfigWithFallback = pipe(
490
+ Effect.gen(function* () {
491
+ const fs = yield* FileSystem.FileSystem;
492
+ return yield* fs.readFileString('config.json');
493
+ }),
494
+ Effect.catchTag('PlatformError', (error) => {
495
+ if (error.reason._tag === 'NotFound') {
496
+ return Effect.succeed('{}');
497
+ }
498
+ if (error.reason._tag === 'PermissionDenied') {
499
+ return Effect.fail(
500
+ new Error('Cannot read config: permission denied')
501
+ );
502
+ }
503
+ return Effect.fail(error);
504
+ })
505
+ );
506
+ ```
507
+
508
+ ### Typed Error Recovery
509
+
510
+ ```typescript
511
+ import { FileSystem } from 'effect';
512
+ import { Effect, Schema, pipe } from 'effect';
513
+
514
+ class ConfigNotFound extends Schema.TaggedError<ConfigNotFound>()(
515
+ 'ConfigNotFound',
516
+ {
517
+ path: Schema.String
518
+ }
519
+ ) {}
520
+
521
+ class ConfigInvalid extends Schema.TaggedError<ConfigInvalid>()(
522
+ 'ConfigInvalid',
523
+ {
524
+ path: Schema.String,
525
+ reason: Schema.String
526
+ }
527
+ ) {}
528
+
529
+ const readConfig = (path: string) =>
530
+ Effect.gen(function* () {
531
+ const fs = yield* FileSystem.FileSystem;
532
+
533
+ const content = yield* pipe(
534
+ fs.readFileString(path),
535
+ Effect.mapError((error) =>
536
+ error.reason._tag === 'NotFound'
537
+ ? new ConfigNotFound({ path })
538
+ : new ConfigInvalid({ path, reason: error.message })
539
+ )
540
+ );
541
+
542
+ return content;
543
+ });
544
+ ```
545
+
546
+ ## Scoped Resources Pattern
547
+
548
+ ```typescript
549
+ import { FileSystem } from 'effect';
550
+ import { Effect } from 'effect';
551
+
552
+ const processInTempDir = Effect.gen(function* () {
553
+ const fs = yield* FileSystem.FileSystem;
554
+
555
+ // Create temp directory with automatic cleanup
556
+ const tempDir = yield* fs.makeTempDirectoryScoped();
557
+
558
+ // Do work in temp directory
559
+ const inputPath = `${tempDir}/input.txt`;
560
+ const outputPath = `${tempDir}/output.txt`;
561
+
562
+ yield* fs.writeFileString(inputPath, 'data');
563
+ const content = yield* fs.readFileString(inputPath);
564
+ yield* fs.writeFileString(outputPath, content.toUpperCase());
565
+
566
+ const result = yield* fs.readFileString(outputPath);
567
+
568
+ // Temp directory is automatically removed when scope exits
569
+ return result;
570
+ }).pipe(Effect.scoped);
571
+ ```
572
+
573
+ ## Layer Provision
574
+
575
+ ### Node.js
576
+
577
+ ```typescript
578
+ import { FileSystem } from 'effect';
579
+ import { NodeFileSystem } from '@effect/platform-node';
580
+ import { Effect } from 'effect';
581
+
582
+ const program = Effect.gen(function* () {
583
+ const fs = yield* FileSystem.FileSystem;
584
+ return yield* fs.readFileString('data.txt');
585
+ });
586
+
587
+ // Provide Node.js implementation
588
+ const runnable = program.pipe(Effect.provide(NodeFileSystem.layer));
589
+
590
+ Effect.runPromise(runnable);
591
+ ```
592
+
593
+ ### Bun
594
+
595
+ ```typescript
596
+ import { FileSystem } from 'effect';
597
+ import { BunFileSystem } from '@effect/platform-bun';
598
+ import { Effect } from 'effect';
599
+
600
+ declare const program: Effect.Effect<string, never, FileSystem.FileSystem>;
601
+
602
+ const runnable = program.pipe(Effect.provide(BunFileSystem.layer));
603
+
604
+ Effect.runPromise(runnable);
605
+ ```
606
+
607
+ ## DO
608
+
609
+ - Import from `effect`
610
+ - Use `yield* FileSystem.FileSystem` for service injection
611
+ - Provide platform layer at entry point only
612
+ - Use scoped temp directories with `makeTempDirectoryScoped`
613
+ - Handle `PlatformError` with `catchTag("PlatformError", ...)`
614
+ - Use size helpers: `Size()`, `KiB()`, `MiB()`, `GiB()`, `TiB()`, `PiB()`
615
+ - Stream large files with `stream()` and `sink()`
616
+
617
+ ## DON'T
618
+
619
+ - Import `node:fs`, `fs/promises`, or platform-specific modules in business logic
620
+ - Use synchronous fs operations
621
+ - Forget to cleanup temp directories (use scoped version)
622
+ - Mix platform-specific code with business logic
623
+ - Use `Date.now()` - use `Clock` service instead (see testability requirements)
624
+ - Hardcode platform-specific paths - use `Path` service for path operations