@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,1125 @@
1
+ ---
2
+ name: effect-patterns-tooling-and-debugging
3
+ description: Effect-TS patterns for Tooling And Debugging. Use when working with tooling and debugging in Effect-TS applications.
4
+ ---
5
+ # Effect-TS Patterns: Tooling And Debugging
6
+ This skill provides 8 curated Effect-TS patterns for tooling and debugging.
7
+ Use this skill when working on tasks related to:
8
+ - tooling and debugging
9
+ - Best practices in Effect-TS applications
10
+ - Real-world patterns and solutions
11
+
12
+ ---
13
+
14
+ ## 🟢 Beginner Patterns
15
+
16
+ ### Read Effect Type Errors
17
+
18
+ **Rule:** Effect errors are verbose but structured - learn to extract the key information.
19
+
20
+ **Rationale:**
21
+
22
+ Effect type errors can be long, but they follow a pattern. Learn to scan for the key parts.
23
+
24
+ ---
25
+
26
+
27
+ Effect's type system catches many bugs at compile time, but:
28
+
29
+ 1. **Effect types are complex** - Three type parameters
30
+ 2. **Errors are nested** - Multiple layers of generics
31
+ 3. **Messages are verbose** - TypeScript shows everything
32
+
33
+ Understanding the pattern makes errors manageable.
34
+
35
+ ---
36
+
37
+ ---
38
+
39
+ ### Set Up Your Effect Development Environment
40
+
41
+ **Rule:** Install the Effect extension and configure TypeScript for optimal Effect development.
42
+
43
+ **Rationale:**
44
+
45
+ Set up your development environment with the Effect extension and proper TypeScript configuration for the best experience.
46
+
47
+ ---
48
+
49
+
50
+ A well-configured environment helps you:
51
+
52
+ 1. **See types clearly** - Effect types can be complex
53
+ 2. **Get better autocomplete** - Know what methods are available
54
+ 3. **Catch errors early** - TypeScript finds problems
55
+ 4. **Navigate easily** - Go to definitions, find references
56
+
57
+ ---
58
+
59
+ ---
60
+
61
+
62
+ ## 🟡 Intermediate Patterns
63
+
64
+ ### Supercharge Your Editor with the Effect LSP
65
+
66
+ **Rule:** Install and use the Effect LSP extension for enhanced type information and error checking in your editor.
67
+
68
+ **Good Example:**
69
+
70
+ Imagine you have the following code. Without the LSP, hovering over `program` might show a complex, hard-to-read inferred type.
71
+
72
+ ```typescript
73
+ import { Effect } from "effect";
74
+
75
+ // Define Logger service using Effect.Service pattern
76
+ class Logger extends Effect.Service<Logger>()("Logger", {
77
+ sync: () => ({
78
+ log: (msg: string) => Effect.log(`LOG: ${msg}`),
79
+ }),
80
+ }) {}
81
+
82
+ const program = Effect.succeed(42).pipe(
83
+ Effect.map((n) => n.toString()),
84
+ Effect.flatMap((s) => Effect.log(s)),
85
+ Effect.provide(Logger.Default)
86
+ );
87
+
88
+ // Run the program
89
+ Effect.runPromise(program);
90
+ ```
91
+
92
+ With the Effect LSP installed, your editor would display a clear, readable overlay right above the `program` variable, looking something like this:
93
+
94
+ ```
95
+ // (LSP Inlay Hint)
96
+ // program: Effect<void, never, never>
97
+ ```
98
+
99
+ This immediately tells you that the final program returns nothing (`void`), has no expected failures (`never`), and has no remaining requirements (`never`), so it's ready to be run.
100
+
101
+ ---
102
+
103
+ **Anti-Pattern:**
104
+
105
+ Going without the LSP. While your code will still compile and work perfectly fine, you are essentially "flying blind." You miss out on the rich, real-time feedback that the LSP provides, forcing you to rely more heavily on manual type checking, `tsc` runs, and deciphering complex inferred types from your editor's default tooltips. This leads to a slower, less efficient development cycle.
106
+
107
+ **Rationale:**
108
+
109
+ To significantly improve your development experience with Effect, install the official **Effect Language Server (LSP)** extension for your code editor (e.g., the "Effect" extension in VS Code).
110
+
111
+ ---
112
+
113
+
114
+ Effect's type system is incredibly powerful, but TypeScript's default language server doesn't always display the rich information contained within the `A`, `E`, and `R` channels in the most intuitive way.
115
+
116
+ The Effect LSP is a specialized tool that understands the semantics of Effect. It hooks into your editor to provide a superior experience:
117
+
118
+ - **Rich Inline Types:** It displays the full `Effect<A, E, R>` signature directly in your code as you work, so you always know exactly what an effect produces, how it can fail, and what it requires.
119
+ - **Clear Error Messages:** It provides more specific and helpful error messages tailored to Effect's APIs.
120
+ - **Enhanced Autocompletion:** It can offer more context-aware suggestions.
121
+
122
+ This tool essentially makes the compiler's knowledge visible at a glance, reducing the mental overhead of tracking complex types and allowing you to catch errors before you even save the file.
123
+
124
+ ---
125
+
126
+ ---
127
+
128
+ ### Use Effect DevTools
129
+
130
+ **Rule:** Use Effect's built-in debugging features and logging for development.
131
+
132
+ **Good Example:**
133
+
134
+ ### 1. Enable Debug Mode
135
+
136
+ ```typescript
137
+ import { Effect, Logger, LogLevel, FiberRef, Cause } from "effect"
138
+
139
+ // ============================================
140
+ // 1. Verbose logging for development
141
+ // ============================================
142
+
143
+ const debugProgram = Effect.gen(function* () {
144
+ yield* Effect.logDebug("Starting operation")
145
+
146
+ const result = yield* someEffect.pipe(
147
+ Effect.tap((value) => Effect.logDebug(`Got value: ${value}`))
148
+ )
149
+
150
+ yield* Effect.logDebug("Operation complete")
151
+ return result
152
+ })
153
+
154
+ // Run with debug logging enabled
155
+ const runWithDebug = debugProgram.pipe(
156
+ Logger.withMinimumLogLevel(LogLevel.Debug),
157
+ Effect.runPromise
158
+ )
159
+
160
+ // ============================================
161
+ // 2. Fiber supervision and introspection
162
+ // ============================================
163
+
164
+ const inspectFibers = Effect.gen(function* () {
165
+ // Fork some fibers
166
+ const fiber1 = yield* Effect.fork(Effect.sleep("1 second"))
167
+ const fiber2 = yield* Effect.fork(Effect.sleep("2 seconds"))
168
+
169
+ // Get fiber IDs
170
+ yield* Effect.log(`Fiber 1 ID: ${fiber1.id()}`)
171
+ yield* Effect.log(`Fiber 2 ID: ${fiber2.id()}`)
172
+
173
+ // Check fiber status
174
+ const status1 = yield* fiber1.status
175
+ yield* Effect.log(`Fiber 1 status: ${status1._tag}`)
176
+ })
177
+
178
+ // ============================================
179
+ // 3. Trace execution with spans
180
+ // ============================================
181
+
182
+ const tracedProgram = Effect.gen(function* () {
183
+ yield* Effect.log("=== Starting traced program ===")
184
+
185
+ yield* Effect.gen(function* () {
186
+ yield* Effect.log("Step 1: Initialize")
187
+ yield* Effect.sleep("100 millis")
188
+ }).pipe(Effect.withLogSpan("initialization"))
189
+
190
+ yield* Effect.gen(function* () {
191
+ yield* Effect.log("Step 2: Process")
192
+ yield* Effect.sleep("200 millis")
193
+ }).pipe(Effect.withLogSpan("processing"))
194
+
195
+ yield* Effect.gen(function* () {
196
+ yield* Effect.log("Step 3: Finalize")
197
+ yield* Effect.sleep("50 millis")
198
+ }).pipe(Effect.withLogSpan("finalization"))
199
+
200
+ yield* Effect.log("=== Program complete ===")
201
+ })
202
+
203
+ // ============================================
204
+ // 4. Error cause inspection
205
+ // ============================================
206
+
207
+ const debugErrors = Effect.gen(function* () {
208
+ const failingEffect = Effect.gen(function* () {
209
+ yield* Effect.fail(new Error("Inner error"))
210
+ }).pipe(
211
+ Effect.flatMap(() => Effect.fail(new Error("Outer error")))
212
+ )
213
+
214
+ yield* failingEffect.pipe(
215
+ Effect.catchAllCause((cause) =>
216
+ Effect.gen(function* () {
217
+ yield* Effect.log("=== Error Cause Analysis ===")
218
+ yield* Effect.log(`Pretty printed:\n${Cause.pretty(cause)}`)
219
+ yield* Effect.log(`Is failure: ${Cause.isFailure(cause)}`)
220
+ yield* Effect.log(`Is interrupted: ${Cause.isInterrupted(cause)}`)
221
+
222
+ // Extract all failures
223
+ const failures = Cause.failures(cause)
224
+ yield* Effect.log(`Failures: ${JSON.stringify([...failures])}`)
225
+
226
+ return "recovered"
227
+ })
228
+ )
229
+ )
230
+ })
231
+
232
+ // ============================================
233
+ // 5. Context inspection
234
+ // ============================================
235
+
236
+ import { Context } from "effect"
237
+
238
+ class Config extends Context.Tag("Config")<Config, { debug: boolean }>() {}
239
+
240
+ const inspectContext = Effect.gen(function* () {
241
+ const context = yield* Effect.context<Config>()
242
+
243
+ yield* Effect.log("=== Context Contents ===")
244
+ yield* Effect.log(`Has Config: ${Context.getOption(context, Config)._tag}`)
245
+ })
246
+
247
+ // ============================================
248
+ // 6. Custom logger for development
249
+ // ============================================
250
+
251
+ const devLogger = Logger.make(({ logLevel, message, date, annotations, spans }) => {
252
+ const timestamp = date.toISOString()
253
+ const level = logLevel.label.padEnd(7)
254
+ const spanInfo = spans.length > 0
255
+ ? ` [${[...spans].map(([name]) => name).join(" > ")}]`
256
+ : ""
257
+ const annotationInfo = Object.keys(annotations).length > 0
258
+ ? ` ${JSON.stringify(Object.fromEntries(annotations))}`
259
+ : ""
260
+
261
+ console.log(`${timestamp} ${level}${spanInfo} ${message}${annotationInfo}`)
262
+ })
263
+
264
+ const withDevLogger = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
265
+ effect.pipe(
266
+ Effect.provide(Logger.replace(Logger.defaultLogger, devLogger))
267
+ )
268
+
269
+ // ============================================
270
+ // 7. Runtime metrics
271
+ // ============================================
272
+
273
+ const showRuntimeMetrics = Effect.gen(function* () {
274
+ const runtime = yield* Effect.runtime()
275
+
276
+ yield* Effect.log("=== Runtime Info ===")
277
+ // Access runtime configuration
278
+ const fiberRefs = runtime.fiberRefs
279
+
280
+ yield* Effect.log("FiberRefs available")
281
+ })
282
+
283
+ // ============================================
284
+ // 8. Putting it all together
285
+ // ============================================
286
+
287
+ const debugSession = Effect.gen(function* () {
288
+ yield* Effect.log("Starting debug session")
289
+
290
+ // Run with all debugging enabled
291
+ yield* tracedProgram.pipe(
292
+ withDevLogger,
293
+ Logger.withMinimumLogLevel(LogLevel.Debug)
294
+ )
295
+
296
+ yield* debugErrors
297
+
298
+ yield* Effect.log("Debug session complete")
299
+ })
300
+
301
+ Effect.runPromise(debugSession)
302
+ ```
303
+
304
+ ### Debug Output Example
305
+
306
+ ```
307
+ 2024-01-15T10:30:00.000Z DEBUG [initialization] Step 1: Initialize
308
+ 2024-01-15T10:30:00.100Z DEBUG [processing] Step 2: Process
309
+ 2024-01-15T10:30:00.300Z DEBUG [finalization] Step 3: Finalize
310
+ 2024-01-15T10:30:00.350Z INFO Program complete
311
+ ```
312
+
313
+ **Rationale:**
314
+
315
+ Use Effect's built-in debugging capabilities, logging, and fiber introspection for development.
316
+
317
+ ---
318
+
319
+
320
+ Effect DevTools help you:
321
+
322
+ 1. **See fiber state** - What's running, blocked, completed
323
+ 2. **Trace execution** - Follow the flow of effects
324
+ 3. **Debug errors** - Understand failure chains
325
+ 4. **Profile performance** - Find slow operations
326
+
327
+ ---
328
+
329
+ ---
330
+
331
+ ### Configure Linting for Effect
332
+
333
+ **Rule:** Use Biome for fast linting with Effect-friendly configuration.
334
+
335
+ **Good Example:**
336
+
337
+ ### 1. Biome Configuration (Recommended)
338
+
339
+ ```json
340
+ // biome.json
341
+ {
342
+ "$schema": "https://biomejs.dev/schemas/1.8.0/schema.json",
343
+ "organizeImports": {
344
+ "enabled": true
345
+ },
346
+ "linter": {
347
+ "enabled": true,
348
+ "rules": {
349
+ "recommended": true,
350
+ "complexity": {
351
+ "noExcessiveCognitiveComplexity": "warn",
352
+ "noForEach": "off", // Effect uses forEach patterns
353
+ "useLiteralKeys": "off" // Effect uses computed keys
354
+ },
355
+ "correctness": {
356
+ "noUnusedVariables": "error",
357
+ "noUnusedImports": "error",
358
+ "useExhaustiveDependencies": "warn"
359
+ },
360
+ "style": {
361
+ "noNonNullAssertion": "warn",
362
+ "useConst": "error",
363
+ "noParameterAssign": "error"
364
+ },
365
+ "suspicious": {
366
+ "noExplicitAny": "warn",
367
+ "noConfusingVoidType": "off" // Effect uses void
368
+ },
369
+ "nursery": {
370
+ "noRestrictedImports": {
371
+ "level": "error",
372
+ "options": {
373
+ "paths": {
374
+ "lodash": "Use Effect functions instead",
375
+ "ramda": "Use Effect functions instead"
376
+ }
377
+ }
378
+ }
379
+ }
380
+ }
381
+ },
382
+ "formatter": {
383
+ "enabled": true,
384
+ "indentStyle": "space",
385
+ "indentWidth": 2,
386
+ "lineWidth": 100
387
+ },
388
+ "javascript": {
389
+ "formatter": {
390
+ "semicolons": "asNeeded",
391
+ "quoteStyle": "double",
392
+ "trailingComma": "es5"
393
+ }
394
+ },
395
+ "files": {
396
+ "ignore": [
397
+ "node_modules",
398
+ "dist",
399
+ "coverage",
400
+ "*.gen.ts"
401
+ ]
402
+ }
403
+ }
404
+ ```
405
+
406
+ ### 2. ESLint Configuration (Alternative)
407
+
408
+ ```javascript
409
+ // eslint.config.js
410
+ import eslint from "@eslint/js"
411
+ import tseslint from "typescript-eslint"
412
+
413
+ export default tseslint.config(
414
+ eslint.configs.recommended,
415
+ ...tseslint.configs.strictTypeChecked,
416
+ {
417
+ languageOptions: {
418
+ parserOptions: {
419
+ project: true,
420
+ tsconfigRootDir: import.meta.dirname,
421
+ },
422
+ },
423
+ rules: {
424
+ // TypeScript strict rules
425
+ "@typescript-eslint/no-unused-vars": [
426
+ "error",
427
+ { argsIgnorePattern: "^_" }
428
+ ],
429
+ "@typescript-eslint/no-explicit-any": "warn",
430
+ "@typescript-eslint/explicit-function-return-type": "off",
431
+ "@typescript-eslint/no-floating-promises": "error",
432
+
433
+ // Effect-friendly rules
434
+ "@typescript-eslint/no-confusing-void-expression": "off",
435
+ "@typescript-eslint/no-misused-promises": [
436
+ "error",
437
+ { checksVoidReturn: false }
438
+ ],
439
+
440
+ // Style rules
441
+ "prefer-const": "error",
442
+ "no-var": "error",
443
+ "object-shorthand": "error",
444
+ "prefer-template": "error",
445
+ },
446
+ },
447
+ {
448
+ files: ["**/*.test.ts"],
449
+ rules: {
450
+ "@typescript-eslint/no-explicit-any": "off",
451
+ },
452
+ },
453
+ {
454
+ ignores: ["dist/", "coverage/", "node_modules/"],
455
+ }
456
+ )
457
+ ```
458
+
459
+ ### 3. Package.json Scripts
460
+
461
+ ```json
462
+ {
463
+ "scripts": {
464
+ "lint": "biome check .",
465
+ "lint:fix": "biome check --apply .",
466
+ "lint:ci": "biome ci .",
467
+ "format": "biome format --write .",
468
+ "format:check": "biome format ."
469
+ }
470
+ }
471
+ ```
472
+
473
+ ### 4. VS Code Integration
474
+
475
+ ```json
476
+ // .vscode/settings.json
477
+ {
478
+ "editor.defaultFormatter": "biomejs.biome",
479
+ "editor.formatOnSave": true,
480
+ "editor.codeActionsOnSave": {
481
+ "quickfix.biome": "explicit",
482
+ "source.organizeImports.biome": "explicit"
483
+ },
484
+ "[typescript]": {
485
+ "editor.defaultFormatter": "biomejs.biome"
486
+ },
487
+ "[typescriptreact]": {
488
+ "editor.defaultFormatter": "biomejs.biome"
489
+ }
490
+ }
491
+ ```
492
+
493
+ ### 5. Pre-commit Hook
494
+
495
+ ```json
496
+ // package.json
497
+ {
498
+ "scripts": {
499
+ "prepare": "husky"
500
+ }
501
+ }
502
+ ```
503
+
504
+ ```bash
505
+ # .husky/pre-commit
506
+ bun run lint:ci
507
+ bun run typecheck
508
+ ```
509
+
510
+ ### 6. Effect-Specific Rules to Consider
511
+
512
+ ```typescript
513
+ // Custom rules you might want
514
+
515
+ // ❌ Bad: Using Promise where Effect should be used
516
+ const fetchData = async () => { } // Warn in Effect codebase
517
+
518
+ // ✅ Good: Using Effect
519
+ const fetchData = Effect.gen(function* () { })
520
+
521
+ // ❌ Bad: Throwing errors
522
+ const validate = (x: unknown) => {
523
+ if (!x) throw new Error("Invalid") // Error
524
+ }
525
+
526
+ // ✅ Good: Returning Effect with error
527
+ const validate = (x: unknown) =>
528
+ x ? Effect.succeed(x) : Effect.fail(new ValidationError())
529
+
530
+ // ❌ Bad: Using null/undefined directly
531
+ const maybeValue: string | null = null // Warn
532
+
533
+ // ✅ Good: Using Option
534
+ const maybeValue: Option.Option<string> = Option.none()
535
+ ```
536
+
537
+ **Rationale:**
538
+
539
+ Configure Biome (recommended) or ESLint with rules that work well with Effect's functional patterns.
540
+
541
+ ---
542
+
543
+
544
+ Good linting for Effect:
545
+
546
+ 1. **Catches errors** - Unused variables, missing awaits
547
+ 2. **Enforces style** - Consistent code across team
548
+ 3. **Avoids antipatterns** - No implicit any, proper typing
549
+ 4. **Fast feedback** - Errors in editor immediately
550
+
551
+ ---
552
+
553
+ ---
554
+
555
+ ### Set Up CI/CD for Effect Projects
556
+
557
+ **Rule:** Use GitHub Actions with proper caching for fast Effect project CI/CD.
558
+
559
+ **Good Example:**
560
+
561
+ ### 1. Basic GitHub Actions Workflow
562
+
563
+ ```yaml
564
+ # .github/workflows/ci.yml
565
+ name: CI
566
+
567
+ on:
568
+ push:
569
+ branches: [main]
570
+ pull_request:
571
+ branches: [main]
572
+
573
+ jobs:
574
+ build:
575
+ runs-on: ubuntu-latest
576
+
577
+ strategy:
578
+ matrix:
579
+ node-version: [20.x, 22.x]
580
+
581
+ steps:
582
+ - uses: actions/checkout@v4
583
+
584
+ - name: Setup Bun
585
+ uses: oven-sh/setup-bun@v1
586
+ with:
587
+ bun-version: latest
588
+
589
+ - name: Install dependencies
590
+ run: bun install
591
+
592
+ - name: Type check
593
+ run: bun run typecheck
594
+
595
+ - name: Lint
596
+ run: bun run lint
597
+
598
+ - name: Test
599
+ run: bun run test
600
+
601
+ - name: Build
602
+ run: bun run build
603
+ ```
604
+
605
+ ### 2. With Caching
606
+
607
+ ```yaml
608
+ # .github/workflows/ci-cached.yml
609
+ name: CI (Cached)
610
+
611
+ on:
612
+ push:
613
+ branches: [main]
614
+ pull_request:
615
+ branches: [main]
616
+
617
+ jobs:
618
+ build:
619
+ runs-on: ubuntu-latest
620
+
621
+ steps:
622
+ - uses: actions/checkout@v4
623
+
624
+ - name: Setup Bun
625
+ uses: oven-sh/setup-bun@v1
626
+ with:
627
+ bun-version: latest
628
+
629
+ # Cache Bun dependencies
630
+ - name: Cache dependencies
631
+ uses: actions/cache@v4
632
+ with:
633
+ path: |
634
+ ~/.bun/install/cache
635
+ node_modules
636
+ key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lockb') }}
637
+ restore-keys: |
638
+ ${{ runner.os }}-bun-
639
+
640
+ - name: Install dependencies
641
+ run: bun install
642
+
643
+ # Cache TypeScript build info
644
+ - name: Cache TypeScript
645
+ uses: actions/cache@v4
646
+ with:
647
+ path: |
648
+ .tsbuildinfo
649
+ dist
650
+ key: ${{ runner.os }}-tsc-${{ hashFiles('**/tsconfig.json', 'src/**/*.ts') }}
651
+ restore-keys: |
652
+ ${{ runner.os }}-tsc-
653
+
654
+ - name: Type check
655
+ run: bun run typecheck
656
+
657
+ - name: Lint
658
+ run: bun run lint
659
+
660
+ - name: Test
661
+ run: bun run test --coverage
662
+
663
+ - name: Upload coverage
664
+ uses: codecov/codecov-action@v4
665
+ with:
666
+ files: ./coverage/lcov.info
667
+ ```
668
+
669
+ ### 3. Package.json Scripts
670
+
671
+ ```json
672
+ {
673
+ "scripts": {
674
+ "typecheck": "tsc --noEmit",
675
+ "lint": "biome check .",
676
+ "lint:fix": "biome check --apply .",
677
+ "test": "vitest run",
678
+ "test:watch": "vitest",
679
+ "test:coverage": "vitest run --coverage",
680
+ "build": "tsc",
681
+ "clean": "rm -rf dist .tsbuildinfo"
682
+ }
683
+ }
684
+ ```
685
+
686
+ ### 4. Multi-Stage Workflow
687
+
688
+ ```yaml
689
+ # .github/workflows/ci-full.yml
690
+ name: CI Full
691
+
692
+ on:
693
+ push:
694
+ branches: [main]
695
+ pull_request:
696
+ branches: [main]
697
+
698
+ jobs:
699
+ lint:
700
+ runs-on: ubuntu-latest
701
+ steps:
702
+ - uses: actions/checkout@v4
703
+ - uses: oven-sh/setup-bun@v1
704
+ - run: bun install
705
+ - run: bun run lint
706
+
707
+ typecheck:
708
+ runs-on: ubuntu-latest
709
+ steps:
710
+ - uses: actions/checkout@v4
711
+ - uses: oven-sh/setup-bun@v1
712
+ - run: bun install
713
+ - run: bun run typecheck
714
+
715
+ test:
716
+ runs-on: ubuntu-latest
717
+ needs: [lint, typecheck]
718
+ steps:
719
+ - uses: actions/checkout@v4
720
+ - uses: oven-sh/setup-bun@v1
721
+ - run: bun install
722
+ - run: bun run test
723
+
724
+ build:
725
+ runs-on: ubuntu-latest
726
+ needs: [test]
727
+ steps:
728
+ - uses: actions/checkout@v4
729
+ - uses: oven-sh/setup-bun@v1
730
+ - run: bun install
731
+ - run: bun run build
732
+ - uses: actions/upload-artifact@v4
733
+ with:
734
+ name: dist
735
+ path: dist/
736
+
737
+ deploy:
738
+ runs-on: ubuntu-latest
739
+ needs: [build]
740
+ if: github.ref == 'refs/heads/main'
741
+ steps:
742
+ - uses: actions/download-artifact@v4
743
+ with:
744
+ name: dist
745
+ # Add deployment steps
746
+ ```
747
+
748
+ ### 5. Release Workflow
749
+
750
+ ```yaml
751
+ # .github/workflows/release.yml
752
+ name: Release
753
+
754
+ on:
755
+ push:
756
+ tags:
757
+ - 'v*'
758
+
759
+ jobs:
760
+ release:
761
+ runs-on: ubuntu-latest
762
+ steps:
763
+ - uses: actions/checkout@v4
764
+
765
+ - uses: oven-sh/setup-bun@v1
766
+
767
+ - run: bun install
768
+ - run: bun run build
769
+ - run: bun run test
770
+
771
+ - name: Create Release
772
+ uses: softprops/action-gh-release@v1
773
+ with:
774
+ files: dist/*
775
+ generate_release_notes: true
776
+ ```
777
+
778
+ **Rationale:**
779
+
780
+ Set up CI/CD with type checking, testing, and optional deployment stages optimized for Effect projects.
781
+
782
+ ---
783
+
784
+
785
+ CI/CD for Effect projects ensures:
786
+
787
+ 1. **Type safety** - Catch type errors before merge
788
+ 2. **Test coverage** - Run tests automatically
789
+ 3. **Consistent builds** - Same environment every time
790
+ 4. **Fast feedback** - Know quickly if something broke
791
+
792
+ ---
793
+
794
+ ---
795
+
796
+
797
+ ## 🟠 Advanced Patterns
798
+
799
+ ### Profile Effect Applications
800
+
801
+ **Rule:** Use Effect's timing features and Node.js profilers to find performance bottlenecks.
802
+
803
+ **Good Example:**
804
+
805
+ ### 1. Basic Timing with Spans
806
+
807
+ ```typescript
808
+ import { Effect, Duration } from "effect"
809
+
810
+ // ============================================
811
+ // 1. Time individual operations
812
+ // ============================================
813
+
814
+ const timeOperation = <A, E, R>(
815
+ name: string,
816
+ effect: Effect.Effect<A, E, R>
817
+ ) =>
818
+ Effect.gen(function* () {
819
+ const startTime = Date.now()
820
+
821
+ const result = yield* effect
822
+
823
+ const duration = Date.now() - startTime
824
+ yield* Effect.log(`${name}: ${duration}ms`)
825
+
826
+ return result
827
+ })
828
+
829
+ // Usage
830
+ const program = Effect.gen(function* () {
831
+ yield* timeOperation("database-query", queryDatabase())
832
+ yield* timeOperation("api-call", callExternalApi())
833
+ yield* timeOperation("processing", processData())
834
+ })
835
+
836
+ // ============================================
837
+ // 2. Use withLogSpan for nested timing
838
+ // ============================================
839
+
840
+ const timedProgram = Effect.gen(function* () {
841
+ yield* Effect.log("Starting")
842
+
843
+ yield* fetchUsers().pipe(Effect.withLogSpan("fetchUsers"))
844
+
845
+ yield* processUsers().pipe(Effect.withLogSpan("processUsers"))
846
+
847
+ yield* saveResults().pipe(Effect.withLogSpan("saveResults"))
848
+
849
+ yield* Effect.log("Complete")
850
+ }).pipe(Effect.withLogSpan("total"))
851
+
852
+ // ============================================
853
+ // 3. Collect timing metrics
854
+ // ============================================
855
+
856
+ import { Metric } from "effect"
857
+
858
+ const operationDuration = Metric.histogram("operation_duration_ms", {
859
+ description: "Operation duration in milliseconds",
860
+ boundaries: [1, 5, 10, 25, 50, 100, 250, 500, 1000],
861
+ })
862
+
863
+ const profiledEffect = <A, E, R>(
864
+ name: string,
865
+ effect: Effect.Effect<A, E, R>
866
+ ) =>
867
+ Effect.gen(function* () {
868
+ const startTime = Date.now()
869
+
870
+ const result = yield* effect
871
+
872
+ const duration = Date.now() - startTime
873
+ yield* Metric.update(
874
+ operationDuration.pipe(Metric.tagged("operation", name)),
875
+ duration
876
+ )
877
+
878
+ return result
879
+ })
880
+
881
+ // ============================================
882
+ // 4. Memory profiling
883
+ // ============================================
884
+
885
+ const logMemoryUsage = Effect.sync(() => {
886
+ const usage = process.memoryUsage()
887
+ return {
888
+ heapUsed: Math.round(usage.heapUsed / 1024 / 1024),
889
+ heapTotal: Math.round(usage.heapTotal / 1024 / 1024),
890
+ external: Math.round(usage.external / 1024 / 1024),
891
+ rss: Math.round(usage.rss / 1024 / 1024),
892
+ }
893
+ })
894
+
895
+ const withMemoryLogging = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
896
+ Effect.gen(function* () {
897
+ const before = yield* logMemoryUsage
898
+ yield* Effect.log(`Memory before: ${JSON.stringify(before)}MB`)
899
+
900
+ const result = yield* effect
901
+
902
+ const after = yield* logMemoryUsage
903
+ yield* Effect.log(`Memory after: ${JSON.stringify(after)}MB`)
904
+ yield* Effect.log(`Memory delta: ${after.heapUsed - before.heapUsed}MB`)
905
+
906
+ return result
907
+ })
908
+
909
+ // ============================================
910
+ // 5. CPU profiling with Node.js inspector
911
+ // ============================================
912
+
913
+ const withCpuProfile = <A, E, R>(
914
+ name: string,
915
+ effect: Effect.Effect<A, E, R>
916
+ ) =>
917
+ Effect.gen(function* () {
918
+ // Start CPU profiler (requires --inspect flag)
919
+ const inspector = yield* Effect.try(() => {
920
+ const { Session } = require("inspector")
921
+ const session = new Session()
922
+ session.connect()
923
+ return session
924
+ })
925
+
926
+ yield* Effect.try(() => {
927
+ inspector.post("Profiler.enable")
928
+ inspector.post("Profiler.start")
929
+ })
930
+
931
+ const result = yield* effect
932
+
933
+ // Stop and save profile
934
+ yield* Effect.async<void>((resume) => {
935
+ inspector.post("Profiler.stop", (err: Error, { profile }: any) => {
936
+ if (err) {
937
+ resume(Effect.fail(err))
938
+ } else {
939
+ const fs = require("fs")
940
+ fs.writeFileSync(
941
+ `${name}-${Date.now()}.cpuprofile`,
942
+ JSON.stringify(profile)
943
+ )
944
+ resume(Effect.void)
945
+ }
946
+ })
947
+ })
948
+
949
+ return result
950
+ })
951
+
952
+ // ============================================
953
+ // 6. Benchmark specific operations
954
+ // ============================================
955
+
956
+ const benchmark = <A, E, R>(
957
+ name: string,
958
+ effect: Effect.Effect<A, E, R>,
959
+ iterations: number = 100
960
+ ) =>
961
+ Effect.gen(function* () {
962
+ const times: number[] = []
963
+
964
+ for (let i = 0; i < iterations; i++) {
965
+ const start = performance.now()
966
+ yield* effect
967
+ times.push(performance.now() - start)
968
+ }
969
+
970
+ const sorted = times.sort((a, b) => a - b)
971
+ const stats = {
972
+ min: sorted[0],
973
+ max: sorted[sorted.length - 1],
974
+ median: sorted[Math.floor(sorted.length / 2)],
975
+ p95: sorted[Math.floor(sorted.length * 0.95)],
976
+ p99: sorted[Math.floor(sorted.length * 0.99)],
977
+ mean: times.reduce((a, b) => a + b, 0) / times.length,
978
+ }
979
+
980
+ yield* Effect.log(`Benchmark "${name}" (${iterations} iterations):`)
981
+ yield* Effect.log(` Min: ${stats.min.toFixed(2)}ms`)
982
+ yield* Effect.log(` Max: ${stats.max.toFixed(2)}ms`)
983
+ yield* Effect.log(` Mean: ${stats.mean.toFixed(2)}ms`)
984
+ yield* Effect.log(` Median: ${stats.median.toFixed(2)}ms`)
985
+ yield* Effect.log(` P95: ${stats.p95.toFixed(2)}ms`)
986
+ yield* Effect.log(` P99: ${stats.p99.toFixed(2)}ms`)
987
+
988
+ return stats
989
+ })
990
+
991
+ // ============================================
992
+ // 7. Profile concurrent operations
993
+ // ============================================
994
+
995
+ const profileConcurrency = Effect.gen(function* () {
996
+ const items = Array.from({ length: 100 }, (_, i) => i)
997
+
998
+ // Sequential
999
+ yield* benchmark(
1000
+ "sequential",
1001
+ Effect.forEach(items, (i) => Effect.succeed(i * 2), { concurrency: 1 }),
1002
+ 10
1003
+ )
1004
+
1005
+ // Parallel unbounded
1006
+ yield* benchmark(
1007
+ "parallel-unbounded",
1008
+ Effect.forEach(items, (i) => Effect.succeed(i * 2), {
1009
+ concurrency: "unbounded",
1010
+ }),
1011
+ 10
1012
+ )
1013
+
1014
+ // Parallel limited
1015
+ yield* benchmark(
1016
+ "parallel-10",
1017
+ Effect.forEach(items, (i) => Effect.succeed(i * 2), { concurrency: 10 }),
1018
+ 10
1019
+ )
1020
+ })
1021
+
1022
+ // ============================================
1023
+ // 8. Run profiling
1024
+ // ============================================
1025
+
1026
+ const profilingSession = Effect.gen(function* () {
1027
+ yield* Effect.log("=== Profiling Session ===")
1028
+
1029
+ yield* withMemoryLogging(
1030
+ benchmark("my-operation", someEffect, 50)
1031
+ )
1032
+
1033
+ yield* profileConcurrency
1034
+ })
1035
+
1036
+ Effect.runPromise(profilingSession)
1037
+ ```
1038
+
1039
+ **Rationale:**
1040
+
1041
+ Profile Effect applications using built-in timing spans, metrics, and Node.js profiling tools.
1042
+
1043
+ ---
1044
+
1045
+
1046
+ Profiling helps you:
1047
+
1048
+ 1. **Find bottlenecks** - What's slow?
1049
+ 2. **Optimize hot paths** - Focus effort where it matters
1050
+ 3. **Track regressions** - Catch slowdowns early
1051
+ 4. **Right-size resources** - Don't over-provision
1052
+
1053
+ ---
1054
+
1055
+ ---
1056
+
1057
+ ### Teach your AI Agents Effect with the MCP Server
1058
+
1059
+ **Rule:** Use the MCP server to provide live application context to AI coding agents, enabling more accurate assistance.
1060
+
1061
+ **Good Example:**
1062
+
1063
+ The "Good Example" is the workflow this pattern enables.
1064
+
1065
+ 1. **You run the MCP server** in your terminal, pointing it at your main `AppLayer`.
1066
+
1067
+ ```bash
1068
+ npx @effect/mcp-server --layer src/layers.ts:AppLayer
1069
+ ```
1070
+
1071
+ 2. **You configure your AI agent** (e.g., Cursor) to use the MCP server's endpoint (`http://localhost:3333`).
1072
+
1073
+ 3. **You ask the AI a question** that requires deep context about your app:
1074
+
1075
+ > "Refactor this code to use the `UserService` to fetch a user by ID and log the result with the `Logger`."
1076
+
1077
+ 4. **The AI, in the background, queries the MCP server:**
1078
+
1079
+ - It discovers that `UserService` and `Logger` are available in the `AppLayer`.
1080
+ - It retrieves the exact method signature for `UserService.getUser` and `Logger.log`.
1081
+
1082
+ 5. **The AI generates correct, context-aware code** because it's not guessing; it's using the live architectural information provided by the MCP server.
1083
+
1084
+ ```typescript
1085
+ // The AI generates this correct code:
1086
+ import { Effect } from "effect";
1087
+ import { UserService } from "./features/User/UserService.js";
1088
+ const program = Effect.gen(function* () {
1089
+ const userService = yield* UserService;
1090
+
1091
+ const user = yield* userService.getUser("123");
1092
+ yield* Effect.log(`Found user: ${user.name}`);
1093
+ });
1094
+ ```
1095
+
1096
+ ---
1097
+
1098
+ **Anti-Pattern:**
1099
+
1100
+ Working with an AI agent without providing it with specific context. The agent will be forced to guess based on open files or generic knowledge. This often leads to it hallucinating method names, getting dependency injection wrong, or failing to handle specific error types, requiring you to manually correct its output and defeating the purpose of using an AI assistant.
1101
+
1102
+ **Rationale:**
1103
+
1104
+ To enable AI coding agents (like Cursor or custom bots) to provide highly accurate, context-aware assistance for your Effect application, run the **Effect MCP (Meta-Circular-Protocol) server**. This tool exposes your application's entire dependency graph and service structure in a machine-readable format.
1105
+
1106
+ ---
1107
+
1108
+
1109
+ AI coding agents are powerful, but they often lack the deep, structural understanding of a complex Effect application. They might not know which services are available in the context, what a specific `Layer` provides, or how your feature modules are composed.
1110
+
1111
+ The MCP server solves this problem. It's a specialized server that runs alongside your application during development. It inspects your `AppLayer` and creates a real-time, queryable model of your entire application architecture.
1112
+
1113
+ An AI agent can then connect to this MCP server to ask specific questions before generating code, such as:
1114
+
1115
+ - "What services are available in the current context?"
1116
+ - "What is the full API of the `UserService`?"
1117
+ - "What errors can `UserRepository.findById` fail with?"
1118
+
1119
+ By providing this live, ground-truth context, you transform your AI from a generic coding assistant into a specialized expert on _your_ specific codebase, resulting in far more accurate and useful code generation and refactoring.
1120
+
1121
+ ---
1122
+
1123
+ ---
1124
+
1125
+