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,1175 @@
1
+ ---
2
+ name: effect-platform-abstraction
3
+ description: Use Effect platform abstractions for cross-platform file I/O, process spawning, HTTP clients, cryptography, and terminal I/O. Apply when writing filesystem/process/HTTP/crypto/console code that must stay portable across Node.js, Bun, and browser adapters.
4
+ ---
5
+
6
+ # Platform Abstraction with Effect
7
+
8
+ ## Effect Source Reference
9
+
10
+ The Effect v4 source is available at `~/.local/share/opencode/repos/github.com/Effect-TS/effect@main/`.
11
+ Browse and read files there directly to look up APIs, types, and implementations.
12
+
13
+ Reference this for:
14
+
15
+ - FileSystem source: `packages/effect/src/FileSystem.ts`
16
+ - Path source: `packages/effect/src/Path.ts`
17
+ - Crypto source: `packages/effect/src/Crypto.ts`
18
+ - Socket source: `packages/effect/src/unstable/socket/`
19
+ - Platform layers: `packages/platform-node/`, `packages/platform-bun/`, and `packages/platform-browser/`
20
+ - Migration guide: `MIGRATION.md`
21
+ - Effect source: `packages/effect/src/`
22
+
23
+ ## Overview
24
+
25
+ Effect provides platform-independent abstractions with Node.js and Bun adapters, plus browser adapters for supported services such as HTTP and Crypto. Instead of using runtime-specific APIs directly, you write code once using Effect Platform services and provide the appropriate layer at the edge.
26
+
27
+ **When to use this skill:**
28
+
29
+ - Writing file system operations
30
+ - Spawning child processes or executing commands
31
+ - Making HTTP requests
32
+ - Generating cryptographic random bytes, UUIDs, or digests
33
+ - Reading CLI arguments or environment variables
34
+ - Performing console/terminal I/O
35
+ - Working with paths across different operating systems
36
+ - Building cross-platform applications or libraries
37
+
38
+ ## Why Effect Platform?
39
+
40
+ ### 1. Cross-Platform Compatibility
41
+
42
+ Write once, run anywhere:
43
+
44
+ ```typescript
45
+ import { Effect, FileSystem } from 'effect';
46
+
47
+ // Works on Node.js and Bun
48
+ const readConfig = Effect.gen(function* () {
49
+ const fs = yield* FileSystem.FileSystem;
50
+ return yield* fs.readFileString('config.json');
51
+ });
52
+ ```
53
+
54
+ ### 2. Type-Safe Error Handling
55
+
56
+ All operations track errors in the Effect type signature:
57
+
58
+ ```typescript
59
+ import { Effect, FileSystem } from 'effect';
60
+
61
+ // Effect<string, PlatformError, FileSystem>
62
+ // ↓ ↓ ↓
63
+ // Required service
64
+ // Typed error channel
65
+ // Success value
66
+ ```
67
+
68
+ ### 3. Resource Safety
69
+
70
+ Automatic cleanup with `Scope`:
71
+
72
+ ```typescript
73
+ import { Effect, FileSystem } from 'effect';
74
+
75
+ const program = Effect.gen(function* () {
76
+ const fs = yield* FileSystem.FileSystem;
77
+ // Read entire file into memory
78
+ return yield* fs.readFile('data.txt');
79
+ });
80
+ ```
81
+
82
+ ### 4. Testability
83
+
84
+ Easy to mock and stub services:
85
+
86
+ ```typescript
87
+ import { Effect, FileSystem, Layer } from 'effect';
88
+
89
+ declare const myProgram: Effect.Effect<void, never, FileSystem.FileSystem>;
90
+
91
+ const TestFileSystem = Layer.succeed(
92
+ FileSystem.FileSystem,
93
+ FileSystem.make({
94
+ readFile: () => Effect.succeed(new Uint8Array())
95
+ })
96
+ );
97
+
98
+ const test = myProgram.pipe(Effect.provide(TestFileSystem));
99
+ ```
100
+
101
+ ### 5. Composability
102
+
103
+ Integrates naturally with Effect's service system:
104
+
105
+ ```typescript
106
+ import { Effect, FileSystem, Layer, Path, Context } from 'effect';
107
+
108
+ interface ConfigService {
109
+ readonly load: (name: string) => Effect.Effect<string>;
110
+ }
111
+
112
+ const ConfigService = Context.Service<ConfigService>('ConfigService');
113
+
114
+ const ConfigServiceLive = Layer.effect(
115
+ ConfigService,
116
+ Effect.gen(function* () {
117
+ const fs = yield* FileSystem.FileSystem;
118
+ const path = yield* Path.Path;
119
+
120
+ return {
121
+ load: (name: string) =>
122
+ Effect.gen(function* () {
123
+ const configPath = path.join('configs', name);
124
+ return yield* fs.readFileString(configPath);
125
+ })
126
+ };
127
+ })
128
+ );
129
+ ```
130
+
131
+ ## Core Platform Modules
132
+
133
+ ### FileSystem - File Operations
134
+
135
+ The `FileSystem` service provides comprehensive file and directory operations.
136
+
137
+ **Anti-Pattern - Direct Node/Bun APIs:**
138
+
139
+ ```typescript
140
+ // ❌ WRONG - Platform-specific, not testable
141
+ import * as fs from 'fs';
142
+ import { readFile } from 'fs/promises';
143
+
144
+ const content = fs.readFileSync('file.txt', 'utf-8');
145
+ const asyncContent = await readFile('file.txt', 'utf-8');
146
+
147
+ // ❌ WRONG - Bun-specific
148
+ declare const Bun: {
149
+ file: (path: string) => { text: () => Promise<string> };
150
+ };
151
+
152
+ const file = Bun.file('file.txt');
153
+ const content = await file.text();
154
+ ```
155
+
156
+ **Correct Pattern - FileSystem Service:**
157
+
158
+ ```typescript
159
+ import { Effect, FileSystem } from 'effect';
160
+
161
+ // ✅ CORRECT - Cross-platform, type-safe, testable
162
+ const readFile = (path: string) =>
163
+ Effect.gen(function* () {
164
+ const fs = yield* FileSystem.FileSystem;
165
+ return yield* fs.readFileString(path);
166
+ });
167
+
168
+ // Effect<string, PlatformError, FileSystem>
169
+ ```
170
+
171
+ **Common Operations:**
172
+
173
+ ```typescript
174
+ import { Effect, FileSystem } from 'effect';
175
+
176
+ const fileOperations = Effect.gen(function* () {
177
+ const fs = yield* FileSystem.FileSystem;
178
+
179
+ // Read files
180
+ const text = yield* fs.readFileString('data.txt');
181
+ const bytes = yield* fs.readFile('binary.dat');
182
+
183
+ // Write files
184
+ yield* fs.writeFileString('output.txt', 'Hello World');
185
+
186
+ // Directory operations
187
+ yield* fs.makeDirectory('new-dir', { recursive: true });
188
+ const files = yield* fs.readDirectory('src');
189
+
190
+ // File metadata
191
+ const stats = yield* fs.stat('file.txt');
192
+ const exists = yield* fs.exists('config.json');
193
+
194
+ // Copy and move
195
+ yield* fs.copy('source.txt', 'dest.txt');
196
+ yield* fs.rename('old.txt', 'new.txt');
197
+
198
+ // Remove files/directories
199
+ yield* fs.remove('temp-file.txt');
200
+ yield* fs.remove('temp-dir', { recursive: true });
201
+
202
+ // Temporary files (auto-cleanup with Scope)
203
+ const tempFile = yield* fs.makeTempFileScoped();
204
+ yield* fs.writeFileString(tempFile, 'temporary data');
205
+ // File automatically deleted when scope closes
206
+ });
207
+ ```
208
+
209
+ `fs.watch(directory)` reports direct-child changes by default; pass `{ recursive: true }` to include nested subdirectories. For open handles, `file.seek(offset, 'start' | 'current')` returns the new offset as a branded `FileSystem.Size`. The old `FileSystem.File.Descriptor` type and `file.descriptor` property were removed in beta.103; use scoped `File` methods instead.
210
+
211
+ **Streaming Files:**
212
+
213
+ ```typescript
214
+ import { Effect, FileSystem, Stream } from 'effect';
215
+
216
+ declare const processChunk: (chunk: Uint8Array) => Effect.Effect<void>;
217
+
218
+ // Stream large files efficiently
219
+ const processLargeFile = Effect.gen(function* () {
220
+ const fs = yield* FileSystem.FileSystem;
221
+
222
+ // Read as stream
223
+ const stream = fs.stream('large-file.txt', { chunkSize: 64 * 1024 });
224
+
225
+ // Process stream
226
+ yield* stream.pipe(
227
+ Stream.mapEffect((chunk) => processChunk(chunk)),
228
+ Stream.run(fs.sink('output.txt'))
229
+ );
230
+ });
231
+ ```
232
+
233
+ ### Path - Path Manipulation
234
+
235
+ The `Path` service provides cross-platform path operations.
236
+
237
+ **Anti-Pattern - Manual String Manipulation:**
238
+
239
+ ```typescript
240
+ // ❌ WRONG - Breaks on Windows, brittle
241
+ import path from 'path';
242
+
243
+ declare const process: { cwd: () => string };
244
+ declare const filename: string;
245
+
246
+ const configPath = './config/' + filename + '.json';
247
+ const absPath = process.cwd() + '/' + configPath;
248
+
249
+ // ❌ WRONG - Node-specific
250
+ const joined = path.join('src', 'components', 'Button.tsx');
251
+ ```
252
+
253
+ **Correct Pattern - Path Service:**
254
+
255
+ ```typescript
256
+ import { Effect, Path } from 'effect';
257
+
258
+ // ✅ CORRECT - Cross-platform path handling
259
+ const buildPath = (filename: string) =>
260
+ Effect.gen(function* () {
261
+ const path = yield* Path.Path;
262
+
263
+ // Join paths correctly for any OS
264
+ const configPath = path.join('config', `${filename}.json`);
265
+
266
+ // Resolve to absolute path
267
+ const absolutePath = path.resolve(configPath);
268
+
269
+ // Extract path components
270
+ const dir = path.dirname(absolutePath);
271
+ const base = path.basename(absolutePath);
272
+ const ext = path.extname(absolutePath);
273
+
274
+ // Parse path into components
275
+ const parsed = path.parse(absolutePath);
276
+ // { root, dir, base, ext, name }
277
+
278
+ return absolutePath;
279
+ });
280
+ ```
281
+
282
+ **Path Operations:**
283
+
284
+ ```typescript
285
+ import { Effect, Path } from 'effect';
286
+
287
+ const pathOps = Effect.gen(function* () {
288
+ const path = yield* Path.Path;
289
+
290
+ // Platform-specific separator ("/" or "\")
291
+ const sep = path.sep;
292
+
293
+ // Join multiple segments
294
+ const filePath = path.join('src', 'lib', 'utils.ts');
295
+
296
+ // Resolve relative paths
297
+ const absolute = path.resolve('..', 'config', 'app.json');
298
+
299
+ // Get relative path between two paths
300
+ const rel = path.relative('/app/src', '/app/dist');
301
+
302
+ // Check if path is absolute
303
+ const isAbs = path.isAbsolute('/usr/local');
304
+
305
+ // Normalize path (remove "..", ".", etc.)
306
+ const normalized = path.normalize('src/../lib/./utils.ts');
307
+
308
+ // Work with file URLs
309
+ const url = yield* path.toFileUrl('/path/to/file');
310
+ const fromUrl = yield* path.fromFileUrl(new URL('file:///path/to/file'));
311
+ });
312
+ ```
313
+
314
+ In Effect v4, `Migrator.fromFileSystem` requires both `FileSystem.FileSystem` and `Path.Path` because migration module paths are converted to file URLs before dynamic import. Aggregate Node/Bun service layers satisfy both requirements. When providing services individually on Windows, use the host-aware path layer rather than core `Path.layer`, which has POSIX semantics.
315
+
316
+ ### ChildProcess - Process Execution
317
+
318
+ The `ChildProcess` and `ChildProcessSpawner` services enable safe process spawning.
319
+
320
+ **Anti-Pattern - Direct child_process:**
321
+
322
+ ```typescript
323
+ // ❌ WRONG - Node-specific, no resource safety
324
+ import { spawn, exec } from 'child_process';
325
+ import { promisify } from 'util';
326
+
327
+ const execAsync = promisify(exec);
328
+ const { stdout } = await execAsync('ls -la');
329
+
330
+ // ❌ WRONG - Bun-specific
331
+ declare const Bun: {
332
+ spawn: (cmd: string[]) => { stdout: ReadableStream };
333
+ };
334
+ declare const Response: {
335
+ new (stream: ReadableStream): { text: () => Promise<string> };
336
+ };
337
+
338
+ const proc = Bun.spawn(['ls', '-la']);
339
+ const output = await new Response(proc.stdout).text();
340
+ ```
341
+
342
+ **Correct Pattern - ChildProcess + ChildProcessSpawner:**
343
+
344
+ ```typescript
345
+ import { ChildProcess, ChildProcessSpawner } from 'effect/unstable/process';
346
+ import { Effect, Stream } from 'effect';
347
+
348
+ // ✅ CORRECT - Cross-platform command execution
349
+ const runCommand = Effect.gen(function* () {
350
+ const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
351
+
352
+ // Create command and collect output as string
353
+ const output = yield* spawner.string(ChildProcess.make('ls', ['-la']));
354
+
355
+ return output;
356
+ });
357
+ ```
358
+
359
+ **Advanced ChildProcess Usage:**
360
+
361
+ ```typescript
362
+ import { ChildProcess, ChildProcessSpawner } from 'effect/unstable/process';
363
+ import { Console, Effect, Stream } from 'effect';
364
+
365
+ const commandExamples = Effect.gen(function* () {
366
+ const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
367
+
368
+ // Collect output as string
369
+ const stdout = yield* spawner.string(ChildProcess.make('git', ['status']));
370
+
371
+ // Collect output as lines
372
+ const lines = yield* spawner.lines(
373
+ ChildProcess.make('git', ['log', '--pretty=format:%s', '-n', '10'])
374
+ );
375
+
376
+ // Pipe commands together
377
+ const pipeline = ChildProcess.make('cat', ['file.txt']).pipe(
378
+ ChildProcess.pipeTo(ChildProcess.make('grep', ['error'])),
379
+ ChildProcess.pipeTo(ChildProcess.make('wc', ['-l']))
380
+ );
381
+ const pipelineOutput = yield* spawner.string(pipeline);
382
+
383
+ // Set environment variables
384
+ const withEnv = ChildProcess.make('node', ['script.js'], {
385
+ env: { NODE_ENV: 'production', API_KEY: 'secret' },
386
+ extendEnv: true
387
+ });
388
+
389
+ // Spawn process and stream output
390
+ const handle = yield* spawner.spawn(
391
+ ChildProcess.make('npm', ['run', 'build'])
392
+ );
393
+ yield* handle.all.pipe(
394
+ Stream.decodeText(),
395
+ Stream.splitLines,
396
+ Stream.runForEach((line) => Console.log(`[build] ${line}`))
397
+ );
398
+ const exitCode = yield* handle.exitCode;
399
+
400
+ return exitCode;
401
+ });
402
+ ```
403
+
404
+ ### Terminal - Terminal I/O
405
+
406
+ The `Terminal` service provides interactive terminal capabilities.
407
+
408
+ **Anti-Pattern - Direct Console:**
409
+
410
+ ```typescript
411
+ // ❌ WRONG - Uses global console, not testable
412
+ declare const console: {
413
+ log: (msg: string) => void;
414
+ error: (msg: string) => void;
415
+ };
416
+ declare const process: {
417
+ stdout: { write: (msg: string) => void };
418
+ };
419
+ declare const prompt: (msg: string) => string | null;
420
+
421
+ console.log('Hello World');
422
+ console.error('Error occurred');
423
+ process.stdout.write('Output\n');
424
+
425
+ // ❌ WRONG - Not trackable in Effect type
426
+ const input = prompt('Enter name:');
427
+ ```
428
+
429
+ **Correct Pattern - Terminal Service:**
430
+
431
+ ```typescript
432
+ import { Effect, Terminal } from 'effect';
433
+
434
+ // ✅ CORRECT - Trackable, testable terminal I/O
435
+ const interactiveProgram = Effect.gen(function* () {
436
+ const terminal = yield* Terminal.Terminal;
437
+
438
+ // Display output
439
+ yield* terminal.display('Hello World\n');
440
+
441
+ // Read user input
442
+ const name = yield* terminal.readLine;
443
+ yield* terminal.display(`Welcome, ${name}!\n`);
444
+
445
+ // Get terminal dimensions
446
+ const cols = yield* terminal.columns;
447
+ const rows = yield* terminal.rows;
448
+ yield* terminal.display(`Terminal size: ${cols}x${rows}\n`);
449
+ });
450
+ ```
451
+
452
+ **For Simple Logging - Use Console or Effect.log:**
453
+
454
+ ```typescript
455
+ import { Console, Effect } from 'effect';
456
+
457
+ // ✅ CORRECT - Console service (from effect)
458
+ const logging = Effect.gen(function* () {
459
+ yield* Console.log('Info message');
460
+ yield* Console.error('Error message');
461
+ yield* Console.warn('Warning');
462
+ yield* Console.debug('Debug info');
463
+ });
464
+
465
+ // ✅ CORRECT - Effect.log with structured logging
466
+ const structuredLog = Effect.gen(function* () {
467
+ yield* Effect.log('Operation started');
468
+ yield* Effect.logDebug('Debug details');
469
+ yield* Effect.logError('Error occurred');
470
+
471
+ // With annotations
472
+ yield* Effect.log('User action').pipe(
473
+ Effect.annotateLogs('userId', '123'),
474
+ Effect.annotateLogs('action', 'login')
475
+ );
476
+ });
477
+ ```
478
+
479
+ ### Crypto - Cryptographic Randomness, UUIDs, and Digests
480
+
481
+ The `Crypto.Crypto` service provides platform-backed cryptographic random bytes, UUIDv4/v7 generation, and message digests. Prefer it over `globalThis.crypto`, `crypto.randomUUID()`, or ad-hoc randomness when code should stay platform-abstract and testable.
482
+
483
+ ```typescript
484
+ import { Crypto, Effect } from 'effect';
485
+
486
+ const cryptoProgram = Effect.gen(function* () {
487
+ const crypto = yield* Crypto.Crypto;
488
+
489
+ const bytes = yield* crypto.randomBytes(32);
490
+ const uuidV4 = yield* crypto.randomUUIDv4;
491
+ const uuidV7 = yield* crypto.randomUUIDv7;
492
+ const digest = yield* crypto.digest('SHA-256', bytes);
493
+
494
+ return { bytes, uuidV4, uuidV7, digest };
495
+ });
496
+ ```
497
+
498
+ `NodeServices.layer` and `BunServices.layer` include `Crypto.Crypto`. Browser applications can provide `BrowserCrypto.layer` from `@effect/platform-browser`.
499
+
500
+ ### HttpClient - HTTP Requests
501
+
502
+ The `HttpClient` service provides type-safe HTTP operations. Runtime application and provider code must use it rather than raw `fetch`. Raw fetch is reserved for an explicitly named low-level platform adapter whose documentation justifies why an Effect transport cannot be used; that adapter must own interruption, status classification, schema decoding, and typed error mapping.
503
+
504
+ **Anti-Pattern - Direct fetch/axios:**
505
+
506
+ ```typescript
507
+ // ❌ WRONG - No Effect integration, manual error handling
508
+ declare const fetch: (url: string) => Promise<{ json: () => Promise<unknown> }>;
509
+
510
+ const response = await fetch('https://api.example.com/data');
511
+ const data = await response.json();
512
+
513
+ // ❌ WRONG - External dependency, not in Effect system
514
+ import axios from 'axios';
515
+
516
+ declare const axios: {
517
+ get: (url: string) => Promise<{ data: unknown }>;
518
+ };
519
+
520
+ const result = await axios.get('https://api.example.com/data');
521
+ ```
522
+
523
+ **Correct Pattern - HttpClient Service:**
524
+
525
+ ```typescript
526
+ import { HttpClient, HttpClientResponse } from 'effect/unstable/http';
527
+ import { Effect, Schema } from 'effect';
528
+
529
+ // ✅ CORRECT - Integrated with Effect type system
530
+ class ProviderData extends Schema.Class<ProviderData>('ProviderData')({
531
+ value: Schema.String
532
+ }) {}
533
+
534
+ const fetchData = Effect.gen(function* () {
535
+ const client = yield* HttpClient.HttpClient;
536
+
537
+ return yield* client.get('https://api.example.com/data').pipe(
538
+ Effect.flatMap(HttpClientResponse.filterStatusOk),
539
+ Effect.flatMap(HttpClientResponse.schemaBodyJson(ProviderData))
540
+ );
541
+ });
542
+ ```
543
+
544
+ Name the adapter service and its effects after the upstream operation. The adapter owns request/auth construction, executes outside database transactions, classifies status before decoding, validates unknown bodies with `Schema`, and maps failures into typed domain errors. Preserve bounded evidence such as status, provider error code, request ID, and retry metadata, but redact credentials, private fields, query secrets, and full response bodies.
545
+
546
+ **Advanced HTTP Operations:**
547
+
548
+ ```typescript
549
+ import {
550
+ HttpClient,
551
+ HttpClientRequest,
552
+ HttpClientResponse
553
+ } from 'effect/unstable/http';
554
+ import { Effect, Schema, Schedule } from 'effect';
555
+
556
+ class User extends Schema.Class<User>('User')({
557
+ id: Schema.Number,
558
+ name: Schema.String,
559
+ email: Schema.String
560
+ }) {}
561
+
562
+ const httpExamples = Effect.gen(function* () {
563
+ const client = yield* HttpClient.HttpClient;
564
+
565
+ // GET with query parameters
566
+ const getUsers = client.get('https://api.example.com/users', {
567
+ urlParams: { page: '1', limit: '10' }
568
+ });
569
+
570
+ // POST with JSON body
571
+ const createUser = HttpClientRequest.post(
572
+ 'https://api.example.com/users'
573
+ ).pipe(
574
+ HttpClientRequest.bodyJsonUnsafe({
575
+ name: 'John Doe',
576
+ email: 'john@example.com'
577
+ }),
578
+ client.execute
579
+ );
580
+
581
+ // Custom headers — construct the request, set headers, then execute
582
+ const withAuthRequest = HttpClientRequest.get(
583
+ 'https://api.example.com/protected'
584
+ ).pipe(HttpClientRequest.setHeader('Authorization', 'Bearer token'));
585
+ const withAuth = client.execute(withAuthRequest);
586
+
587
+ // Classify status, then parse the successful response with Schema
588
+ const users = yield* client
589
+ .get('https://api.example.com/users')
590
+ .pipe(
591
+ Effect.flatMap(HttpClientResponse.filterStatusOk),
592
+ Effect.flatMap(
593
+ HttpClientResponse.schemaBodyJson(Schema.Array(User))
594
+ )
595
+ );
596
+
597
+ // Error handling — all HttpClient errors are "HttpClientError" with a reason field
598
+ const safeRequest = client.get('https://api.example.com/data').pipe(
599
+ Effect.flatMap(HttpClientResponse.filterStatusOk),
600
+ Effect.catchTag('HttpClientError', (error) => {
601
+ switch (error.reason._tag) {
602
+ case 'TransportError':
603
+ return Effect.succeed({ error: 'Network error' });
604
+ case 'StatusCodeError':
605
+ return Effect.succeed({
606
+ error: `HTTP ${error.response?.status}`
607
+ });
608
+ default:
609
+ return Effect.succeed({
610
+ error: `Client error: ${error.reason._tag}`
611
+ });
612
+ }
613
+ })
614
+ );
615
+
616
+ // Retries with backoff: GET is idempotent and attempts are bounded.
617
+ const withRetries = client.get('https://api.example.com/data').pipe(
618
+ Effect.flatMap(HttpClientResponse.filterStatusOk),
619
+ Effect.retry({
620
+ times: 3,
621
+ schedule: Schedule.exponential('100 millis')
622
+ }),
623
+ Effect.tapError((error) =>
624
+ Effect.logError('Provider read exhausted retries').pipe(
625
+ Effect.annotateLogs({ operation: 'Provider.getData', errorTag: error._tag })
626
+ )
627
+ )
628
+ );
629
+
630
+ return users;
631
+ });
632
+ ```
633
+
634
+ Never install retry automatically on a shared client that also sends non-idempotent POST/PATCH requests. Retry only operations proven idempotent by method/provider contract or protected by a provider-supported idempotency key. Keep exhaustion observable: retain the final typed error and log/measure only redacted status, provider code, request ID, operation, and attempt evidence.
635
+
636
+ `HttpClient.withRateLimiter` can automatically retry 429 responses. Use positive `times` only on a client restricted to proven-idempotent operations; set `times: 0` for mixed/non-idempotent clients so the 429 remains visible.
637
+
638
+ ### KeyValueStore - Key-Value Storage
639
+
640
+ The `KeyValueStore` service provides platform-independent key-value storage.
641
+
642
+ **Anti-Pattern - Direct localStorage/file-based storage:**
643
+
644
+ ```typescript
645
+ // ❌ WRONG - Browser-specific
646
+ declare const localStorage: {
647
+ setItem: (key: string, value: string) => void;
648
+ getItem: (key: string) => string | null;
649
+ };
650
+
651
+ localStorage.setItem('key', 'value');
652
+ const value = localStorage.getItem('key');
653
+
654
+ // ❌ WRONG - Node-specific, manual file handling
655
+ import fs from 'fs';
656
+
657
+ declare const fs: {
658
+ writeFileSync: (path: string, data: string) => void;
659
+ readFileSync: (path: string, encoding: string) => string;
660
+ };
661
+
662
+ fs.writeFileSync('.cache/key', 'value');
663
+ const value2 = fs.readFileSync('.cache/key', 'utf-8');
664
+ ```
665
+
666
+ **Correct Pattern - KeyValueStore Service:**
667
+
668
+ ```typescript
669
+ import { KeyValueStore } from 'effect/unstable/persistence';
670
+ import { Effect, Schema } from 'effect';
671
+
672
+ // ✅ CORRECT - Works on all platforms
673
+ const cacheData = Effect.gen(function* () {
674
+ const store = yield* KeyValueStore.KeyValueStore;
675
+
676
+ // Set value
677
+ yield* store.set('user:123', 'John Doe');
678
+
679
+ // Get value — raw string stores return string | undefined
680
+ const name = yield* store.get('user:123');
681
+
682
+ // Binary stores return Uint8Array | undefined
683
+ const bytes = yield* store.getUint8Array('avatar:123');
684
+
685
+ // Check existence
686
+ const hasUser = yield* store.has('user:123');
687
+
688
+ // Remove value
689
+ yield* store.remove('user:123');
690
+
691
+ // Clear all
692
+ yield* store.clear;
693
+
694
+ return name;
695
+ });
696
+ ```
697
+
698
+ **Schema-Based Store:**
699
+
700
+ ```typescript
701
+ import { KeyValueStore } from 'effect/unstable/persistence';
702
+ import { Effect, Schema } from 'effect';
703
+
704
+ class User extends Schema.Class<User>('User')({
705
+ id: Schema.Number,
706
+ name: Schema.String,
707
+ email: Schema.String
708
+ }) {}
709
+
710
+ const typedStore = Effect.gen(function* () {
711
+ const store = yield* KeyValueStore.KeyValueStore;
712
+
713
+ // Create schema-based store
714
+ const userStore = KeyValueStore.toSchemaStore(store, User);
715
+
716
+ // Type-safe operations
717
+ yield* userStore.set('user:123', new User({
718
+ id: 123,
719
+ name: 'John Doe',
720
+ email: 'john@example.com'
721
+ }));
722
+
723
+ const user = yield* userStore.get('user:123');
724
+ // user: Option.Option<{ id: number, name: string, email: string }>
725
+ });
726
+ ```
727
+
728
+ ### Redis - Commands and Scoped Subscriptions
729
+
730
+ The portable `Redis.Redis` service in `effect/unstable/persistence` provides `send`, cached script evaluation, and scoped pub/sub. Platform-specific layers provide the client: `NodeRedis.layer(...)`, `DenoRedis.layer(...)`, or `BunRedis.layer(...)`. These are specialized layers and are not included in `NodeServices.layer` or `BunServices.layer`.
731
+
732
+ ```typescript
733
+ import { NodeRedis } from '@effect/platform-node';
734
+ import { Effect, Queue } from 'effect';
735
+ import { Redis } from 'effect/unstable/persistence';
736
+
737
+ const RedisLayer = NodeRedis.layer({
738
+ database: 1,
739
+ socket: { host: '127.0.0.1', port: 6379 }
740
+ });
741
+
742
+ const receiveOne = Effect.gen(function* () {
743
+ const redis = yield* Redis.Redis;
744
+ const subscription = yield* redis.subscribe('events');
745
+ return yield* Queue.take(subscription);
746
+ }).pipe(Effect.scoped, Effect.provide(RedisLayer));
747
+ ```
748
+
749
+ `redis.subscribe(channel)` requires `Scope` and returns a `Queue.Dequeue<RedisMessage, RedisError>`. Closing the scope shuts down the queue and releases the dedicated subscriber. Node and Deno subscribers reconnect and re-subscribe after interruptions, which can leave message-delivery gaps; Bun subscriptions do not reconnect, so a dropped connection fails the dequeue and callers must subscribe again.
750
+
751
+ ### CLI Arguments - effect/unstable/cli
752
+
753
+ For CLI applications, use `effect/unstable/cli` instead of direct `process.argv`.
754
+
755
+ **Anti-Pattern - Direct process.argv:**
756
+
757
+ ```typescript
758
+ // ❌ WRONG - Manual parsing, no validation
759
+ declare const process: { argv: string[] };
760
+
761
+ const args = process.argv.slice(2);
762
+ const input = args[0];
763
+ const verbose = args.includes('--verbose');
764
+
765
+ // ❌ WRONG - Third-party parser, not Effect-integrated
766
+ import yargs from 'yargs';
767
+
768
+ declare const yargs: (args: string[]) => { argv: Record<string, unknown> };
769
+
770
+ const argv = yargs(process.argv.slice(2)).argv;
771
+ ```
772
+
773
+ **Correct Pattern - effect/unstable/cli:**
774
+
775
+ ```typescript
776
+ import { Argument, Command as CliCommand, Flag } from 'effect/unstable/cli';
777
+ import { NodeServices, NodeRuntime } from '@effect/platform-node';
778
+ import { Console, Effect } from 'effect';
779
+
780
+ declare const process: { argv: string[] };
781
+ declare const someOperation: Effect.Effect<string>;
782
+
783
+ // ✅ CORRECT - Type-safe CLI with full Effect integration
784
+ // Define arguments
785
+ const inputArg = Argument.file('input');
786
+
787
+ // Define flags
788
+ const verboseFlag = Flag.boolean('verbose').pipe(Flag.withAlias('v'));
789
+
790
+ // Define command
791
+ const command = CliCommand.make(
792
+ 'process',
793
+ { input: inputArg, verbose: verboseFlag },
794
+ Effect.fn(function* ({ input, verbose }) {
795
+ if (verbose) {
796
+ yield* Console.log(`Processing file: ${input}`);
797
+ }
798
+ // Process the file
799
+ yield* someOperation;
800
+ })
801
+ );
802
+
803
+ // Run CLI
804
+ command.pipe(
805
+ CliCommand.run({ version: '1.0.0' }),
806
+ Effect.provide(NodeServices.layer),
807
+ NodeRuntime.runMain
808
+ );
809
+ ```
810
+
811
+ ## Platform Module Reference
812
+
813
+ Complete reference table of platform abstractions:
814
+
815
+ | Need | Use | Instead of | Import from |
816
+ | ------------------------- | -------------------------------------- | ---------------------------- | ----------------------------- |
817
+ | **File I/O** | `FileSystem.FileSystem` | `fs`, `Bun.file` | `effect` |
818
+ | **Path Operations** | `Path.Path` | `path`, string concat | `effect` |
819
+ | **Process Spawning** | `ChildProcess` + `ChildProcessSpawner` | `child_process`, `Bun.spawn` | `effect/unstable/process` |
820
+ | **Terminal I/O** | `Terminal.Terminal` | `process.stdin/stdout` | `effect` |
821
+ | **Console Logging** | `Console.log` or `Effect.log` | `console.log` | `effect` |
822
+ | **Crypto** | `Crypto.Crypto` | `globalThis.crypto`, `crypto.randomUUID()` | `effect` |
823
+ | **HTTP Client** | `HttpClient.HttpClient` | `fetch`, `axios` | `effect/unstable/http` |
824
+ | **HTTP Server** | `HttpServer.HttpServer` | `http.createServer` | `effect/unstable/http` |
825
+ | **Sockets** | `Socket.Socket` / `SocketServer.SocketServer` | raw TCP/WebSocket APIs | `effect/unstable/socket` |
826
+ | **Key-Value Store** | `KeyValueStore.KeyValueStore` | `localStorage`, manual files | `effect/unstable/persistence` |
827
+ | **Redis** | `Redis.Redis` | direct Redis clients | `effect/unstable/persistence` |
828
+ | **CLI Arguments** | `Argument` + `Flag` + `Command` | `process.argv`, `yargs` | `effect/unstable/cli` |
829
+ | **Environment Variables** | `Config` from effect | `process.env` | `effect` |
830
+ | **Streams** | `Stream` | Node streams, ReadableStream | `effect` |
831
+
832
+ Socket services live in `effect/unstable/socket`. Use `Socket.Socket` for scoped bidirectional string/binary frame transports and `SocketServer.SocketServer` for accepting connections. Provide service-specific layers such as `BrowserSocket.layerWebSocket(url)`, `NodeSocket.layerWebSocket(url)`, `NodeSocket.layerNet(options)`, `BunSocket.layerWebSocket(url)`, or Node/Bun socket-server layers; `NodeServices.layer` and `BunServices.layer` do not provide sockets.
833
+
834
+ ## Setting Up Platform-Specific Layers
835
+
836
+ To use platform services, provide the appropriate platform layer.
837
+
838
+ `NodeServices.layer` and `BunServices.layer` provide core process services such as `FileSystem`, `Path`, `ChildProcessSpawner`, `Stdio`/`Terminal`, and `Crypto.Crypto`. They do **not** provide specialized integrations such as HTTP clients/servers, sockets, workers, or Redis; provide those with service-specific platform layers. For HTTP clients, provide an HTTP-specific layer such as `FetchHttpClient.layer`, Node's `NodeHttpClient.{layerFetch, layerUndici, layerNodeHttp}`, `BunHttpClient.layer`, or Browser's `BrowserHttpClient.{layerFetch, layerXMLHttpRequest}`. A named provider adapter should export a raw `layer` that requires `HttpClient.HttpClient`, plus an optional `defaultLayer = layer.pipe(Layer.provide(...transport...))`; this keeps the transport dependency graph explicit while offering runtime convenience. For HTTP servers, use server layers such as `NodeHttpServer.layer(...)`, `BunHttpServer.layer(...)`, or their `layerHttpServices` variants where appropriate.
839
+
840
+ **Node.js:**
841
+
842
+ ```typescript
843
+ import { NodeServices, NodeRuntime } from '@effect/platform-node';
844
+ import { Effect, FileSystem } from 'effect';
845
+
846
+ const program = Effect.gen(function* () {
847
+ const fs = yield* FileSystem.FileSystem;
848
+ return yield* fs.readFileString('file.txt');
849
+ });
850
+
851
+ program.pipe(Effect.provide(NodeServices.layer), NodeRuntime.runMain);
852
+ ```
853
+
854
+ **Bun:**
855
+
856
+ ```typescript
857
+ import { BunServices, BunRuntime } from '@effect/platform-bun';
858
+ import { Effect, FileSystem } from 'effect';
859
+
860
+ const program = Effect.gen(function* () {
861
+ const fs = yield* FileSystem.FileSystem;
862
+ return yield* fs.readFileString('file.txt');
863
+ });
864
+
865
+ program.pipe(Effect.provide(BunServices.layer), BunRuntime.runMain);
866
+ ```
867
+
868
+ ## Complete Example: Cross-Platform File Processor
869
+
870
+ ```typescript
871
+ import { NodeServices, NodeRuntime } from '@effect/platform-node';
872
+ import { BunServices, BunRuntime } from '@effect/platform-bun';
873
+ import { Console, Effect, FileSystem, Path, Schema } from 'effect';
874
+ import { ChildProcess, ChildProcessSpawner } from 'effect/unstable/process';
875
+
876
+ class FileProcessorConfig extends Schema.Class<FileProcessorConfig>(
877
+ 'FileProcessorConfig'
878
+ )({
879
+ inputDir: Schema.String,
880
+ outputDir: Schema.String,
881
+ compress: Schema.Boolean
882
+ }) {}
883
+
884
+ const processFiles = Effect.gen(function* () {
885
+ const fs = yield* FileSystem.FileSystem;
886
+ const path = yield* Path.Path;
887
+ const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
888
+
889
+ // Load configuration
890
+ const configData = yield* fs.readFileString('config.json');
891
+ const config = yield* Schema.decodeUnknownEffect(
892
+ Schema.fromJsonString(FileProcessorConfig)
893
+ )(configData);
894
+
895
+ // Ensure output directory exists
896
+ yield* fs.makeDirectory(config.outputDir, { recursive: true });
897
+
898
+ // Read input files
899
+ const files = yield* fs.readDirectory(config.inputDir);
900
+
901
+ yield* Console.log(`Processing ${files.length} files...`);
902
+
903
+ // Process each file
904
+ yield* Effect.forEach(
905
+ files,
906
+ (file) =>
907
+ Effect.gen(function* () {
908
+ const inputPath = path.join(config.inputDir, file);
909
+ const outputPath = path.join(config.outputDir, file);
910
+
911
+ // Copy file
912
+ yield* fs.copy(inputPath, outputPath);
913
+
914
+ // Optionally compress
915
+ if (config.compress) {
916
+ yield* spawner.string(
917
+ ChildProcess.make('gzip', [outputPath])
918
+ );
919
+ }
920
+
921
+ yield* Console.log(`Processed: ${file}`);
922
+ }),
923
+ { concurrency: 4 }
924
+ );
925
+
926
+ yield* Console.log('All files processed!');
927
+ });
928
+
929
+ // Run on Node.js
930
+ processFiles.pipe(Effect.provide(NodeServices.layer), NodeRuntime.runMain);
931
+
932
+ // Or run on Bun - same code!
933
+ processFiles.pipe(Effect.provide(BunServices.layer), BunRuntime.runMain);
934
+ ```
935
+
936
+ ## Testing with Platform Abstractions
937
+
938
+ One major benefit of platform abstractions is testability:
939
+
940
+ ```typescript
941
+ import { Effect, FileSystem, Layer } from 'effect';
942
+
943
+ declare const myFileProcessor: Effect.Effect<
944
+ void,
945
+ never,
946
+ FileSystem.FileSystem
947
+ >;
948
+
949
+ // Create mock FileSystem using makeNoop for testing
950
+ const TestFileSystem = Layer.succeed(
951
+ FileSystem.FileSystem,
952
+ FileSystem.makeNoop({
953
+ readFile: (path) => {
954
+ if (path === 'config.json') {
955
+ const data = JSON.stringify({ key: 'value' });
956
+ return Effect.succeed(new TextEncoder().encode(data));
957
+ }
958
+ return Effect.fail(new Error('File not found'));
959
+ },
960
+ exists: (path) => Effect.succeed(true)
961
+ })
962
+ );
963
+
964
+ // Test your code
965
+ const testProgram = myFileProcessor.pipe(Effect.provide(TestFileSystem));
966
+
967
+ Effect.runPromise(testProgram);
968
+ ```
969
+
970
+ ## Quality Checklist
971
+
972
+ Before completing code that uses platform operations:
973
+
974
+ - [ ] All file I/O uses `FileSystem.FileSystem` service
975
+ - [ ] All path operations use `Path.Path` service
976
+ - [ ] Process spawning uses `ChildProcess` + `ChildProcessSpawner`
977
+ - [ ] Console output uses `Console.log` or `Effect.log` (not `console.log`)
978
+ - [ ] CLI arguments parsed with `effect/unstable/cli` (not `process.argv`)
979
+ - [ ] HTTP requests use `HttpClient.HttpClient` (not `fetch`/`axios`)
980
+ - [ ] Any raw `fetch` is isolated in a named low-level platform adapter with documented justification
981
+ - [ ] HTTP status is classified before success-body schema decoding
982
+ - [ ] Provider evidence is bounded and redacted; retry exhaustion remains a typed, observable failure
983
+ - [ ] Provider/network calls execute outside database transactions
984
+ - [ ] Automatic retries apply only to operations proven idempotent
985
+ - [ ] Adapter `layer` keeps `HttpClient.HttpClient` visible; optional `defaultLayer` owns transport wiring
986
+ - [ ] Cryptographic operations use `Crypto.Crypto` (not direct platform crypto APIs)
987
+ - [ ] Platform services accessed through Effect type system
988
+ - [ ] Appropriate platform/service layer provided (HTTP, sockets, workers, Redis, and other specialized integrations need service-specific layers, not just `NodeServices.layer` / `BunServices.layer`)
989
+ - [ ] No direct imports from `fs`, `path`, `child_process`, `http`, etc.
990
+ - [ ] No Bun-specific APIs (`Bun.file`, `Bun.spawn`, etc.)
991
+ - [ ] No browser-specific APIs without platform abstraction
992
+ - [ ] Code is testable with mock platform services
993
+
994
+ ## Common Mistakes to Avoid
995
+
996
+ ### 1. Mixing Platform APIs
997
+
998
+ ```typescript
999
+ import { Effect, FileSystem } from 'effect';
1000
+ import fs from 'fs';
1001
+
1002
+ // ❌ WRONG - Mixing Effect Platform with direct APIs
1003
+ const bad = Effect.gen(function* () {
1004
+ const filesystem = yield* FileSystem.FileSystem;
1005
+ const content1 = yield* filesystem.readFileString('file1.txt');
1006
+ const content2 = fs.readFileSync('file2.txt', 'utf-8'); // Don't mix!
1007
+ });
1008
+
1009
+ // ✅ CORRECT - Use platform abstractions consistently
1010
+ const good = Effect.gen(function* () {
1011
+ const fs = yield* FileSystem.FileSystem;
1012
+ const content1 = yield* fs.readFileString('file1.txt');
1013
+ const content2 = yield* fs.readFileString('file2.txt');
1014
+ });
1015
+ ```
1016
+
1017
+ ### 2. Forgetting Platform Layer
1018
+
1019
+ ```typescript
1020
+ import { NodeServices, NodeRuntime } from '@effect/platform-node';
1021
+ import { Effect, FileSystem } from 'effect';
1022
+
1023
+ // ❌ WRONG - No platform layer provided
1024
+ const program = Effect.gen(function* () {
1025
+ const fs = yield* FileSystem.FileSystem;
1026
+ return yield* fs.readFileString('file.txt');
1027
+ });
1028
+
1029
+ Effect.runPromise(program); // Runtime error!
1030
+
1031
+ // ✅ CORRECT - Provide platform layer
1032
+ program.pipe(Effect.provide(NodeServices.layer), NodeRuntime.runMain);
1033
+ ```
1034
+
1035
+ ### 3. Using console.log
1036
+
1037
+ ```typescript
1038
+ import { Console, Effect } from 'effect';
1039
+
1040
+ declare const someOperation: () => Effect.Effect<string>;
1041
+
1042
+ // ❌ WRONG - Direct console usage
1043
+ const badProgram = Effect.gen(function* () {
1044
+ console.log('Starting...');
1045
+ const result = yield* someOperation();
1046
+ console.log('Done!');
1047
+ return result;
1048
+ });
1049
+
1050
+ // ✅ CORRECT - Use Console or Effect.log
1051
+ const goodProgram = Effect.gen(function* () {
1052
+ yield* Console.log('Starting...');
1053
+ const result = yield* someOperation();
1054
+ yield* Console.log('Done!');
1055
+ return result;
1056
+ });
1057
+ ```
1058
+
1059
+ ## Migration Guide
1060
+
1061
+ ### From Node.js fs to FileSystem
1062
+
1063
+ ```typescript
1064
+ import { Effect, FileSystem } from 'effect';
1065
+ import fs from 'fs/promises';
1066
+
1067
+ // Before (Node.js)
1068
+ declare const fs: {
1069
+ readFile: (path: string, encoding: string) => Promise<string>;
1070
+ writeFile: (path: string, data: string) => Promise<void>;
1071
+ existsSync: (path: string) => boolean;
1072
+ };
1073
+
1074
+ const data = await fs.readFile('file.txt', 'utf-8');
1075
+ await fs.writeFile('output.txt', data);
1076
+ const exists = fs.existsSync('config.json');
1077
+
1078
+ // After (Effect Platform)
1079
+ const program = Effect.gen(function* () {
1080
+ const fs = yield* FileSystem.FileSystem;
1081
+
1082
+ const data = yield* fs.readFileString('file.txt');
1083
+ yield* fs.writeFileString('output.txt', data);
1084
+ const exists = yield* fs.exists('config.json');
1085
+ });
1086
+ ```
1087
+
1088
+ ### From fetch to HttpClient
1089
+
1090
+ ```typescript
1091
+ import { Effect, Schema } from 'effect';
1092
+ import {
1093
+ HttpClient,
1094
+ HttpClientRequest,
1095
+ HttpClientResponse
1096
+ } from 'effect/unstable/http';
1097
+
1098
+ // Before (fetch)
1099
+ declare const fetch: (
1100
+ url: string,
1101
+ options: {
1102
+ method: string;
1103
+ headers: Record<string, string>;
1104
+ body: string;
1105
+ }
1106
+ ) => Promise<{ json: () => Promise<unknown> }>;
1107
+
1108
+ const response = await fetch('https://api.example.com/data', {
1109
+ method: 'POST',
1110
+ headers: { 'Content-Type': 'application/json' },
1111
+ body: JSON.stringify({ key: 'value' })
1112
+ });
1113
+ const data = await response.json();
1114
+
1115
+ // After (Effect HttpClient)
1116
+ class CreateData extends Schema.Class<CreateData>('CreateData')({
1117
+ key: Schema.String
1118
+ }) {}
1119
+
1120
+ class CreatedData extends Schema.Class<CreatedData>('CreatedData')({
1121
+ id: Schema.String,
1122
+ key: Schema.String
1123
+ }) {}
1124
+
1125
+ const program = Effect.gen(function* () {
1126
+ const client = yield* HttpClient.HttpClient;
1127
+
1128
+ return yield* HttpClientRequest.post('https://api.example.com/data').pipe(
1129
+ HttpClientRequest.schemaBodyJson(CreateData)(new CreateData({ key: 'value' })),
1130
+ Effect.flatMap(client.execute),
1131
+ Effect.flatMap(HttpClientResponse.filterStatusOk),
1132
+ Effect.flatMap(HttpClientResponse.schemaBodyJson(CreatedData))
1133
+ );
1134
+ });
1135
+ ```
1136
+
1137
+ This POST is intentionally not retried. Add retry only if the provider offers a documented idempotency guarantee and the request supplies the required idempotency key.
1138
+
1139
+ ### From child_process to ChildProcess
1140
+
1141
+ ```typescript
1142
+ import { Effect } from 'effect';
1143
+ import { ChildProcess, ChildProcessSpawner } from 'effect/unstable/process';
1144
+ import { exec } from 'child_process';
1145
+ import { promisify } from 'util';
1146
+
1147
+ // Before (child_process)
1148
+ declare const exec: (
1149
+ cmd: string,
1150
+ callback: (error: Error | null, result: { stdout: string }) => void
1151
+ ) => void;
1152
+ declare const promisify: <T>(fn: T) => (...args: any[]) => Promise<any>;
1153
+
1154
+ const execAsync = promisify(exec);
1155
+ const { stdout } = await execAsync('git status');
1156
+
1157
+ // After (Effect ChildProcess)
1158
+ const program = Effect.gen(function* () {
1159
+ const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
1160
+ const stdout = yield* spawner.string(ChildProcess.make('git', ['status']));
1161
+ return stdout;
1162
+ });
1163
+ ```
1164
+
1165
+ ## Summary
1166
+
1167
+ Effect provides a complete abstraction layer over platform-specific APIs, enabling you to:
1168
+
1169
+ 1. **Write once, run anywhere** - Same code works on Node.js and Bun
1170
+ 2. **Type-safe operations** - All errors tracked in Effect type signatures
1171
+ 3. **Resource safety** - Automatic cleanup with Scope
1172
+ 4. **Easy testing** - Mock services without touching the filesystem
1173
+ 5. **Full Effect integration** - Compose with services, layers, and error handling
1174
+
1175
+ Always prefer Effect platform abstractions over direct platform APIs for maximum portability, safety, and testability.