@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.
- package/DOJO.md +22 -0
- package/dojo.json +50 -0
- package/katas/001-hello-effect/SENSEI.md +72 -0
- package/katas/001-hello-effect/solution.test.ts +35 -0
- package/katas/001-hello-effect/solution.ts +16 -0
- package/katas/002-transform-with-map/SENSEI.md +72 -0
- package/katas/002-transform-with-map/solution.test.ts +33 -0
- package/katas/002-transform-with-map/solution.ts +16 -0
- package/katas/003-generator-pipelines/SENSEI.md +72 -0
- package/katas/003-generator-pipelines/solution.test.ts +40 -0
- package/katas/003-generator-pipelines/solution.ts +29 -0
- package/katas/004-flatmap-and-chaining/SENSEI.md +80 -0
- package/katas/004-flatmap-and-chaining/solution.test.ts +34 -0
- package/katas/004-flatmap-and-chaining/solution.ts +18 -0
- package/katas/005-pipe-composition/SENSEI.md +81 -0
- package/katas/005-pipe-composition/solution.test.ts +41 -0
- package/katas/005-pipe-composition/solution.ts +19 -0
- package/katas/006-handle-errors/SENSEI.md +86 -0
- package/katas/006-handle-errors/solution.test.ts +53 -0
- package/katas/006-handle-errors/solution.ts +30 -0
- package/katas/007-tagged-errors/SENSEI.md +79 -0
- package/katas/007-tagged-errors/solution.test.ts +82 -0
- package/katas/007-tagged-errors/solution.ts +37 -0
- package/katas/008-error-patterns/SENSEI.md +89 -0
- package/katas/008-error-patterns/solution.test.ts +41 -0
- package/katas/008-error-patterns/solution.ts +38 -0
- package/katas/009-option-type/SENSEI.md +96 -0
- package/katas/009-option-type/solution.test.ts +49 -0
- package/katas/009-option-type/solution.ts +26 -0
- package/katas/010-either-and-exit/SENSEI.md +86 -0
- package/katas/010-either-and-exit/solution.test.ts +33 -0
- package/katas/010-either-and-exit/solution.ts +17 -0
- package/katas/011-services-and-context/SENSEI.md +82 -0
- package/katas/011-services-and-context/solution.test.ts +23 -0
- package/katas/011-services-and-context/solution.ts +17 -0
- package/katas/012-layers/SENSEI.md +73 -0
- package/katas/012-layers/solution.test.ts +23 -0
- package/katas/012-layers/solution.ts +26 -0
- package/katas/013-testing-effects/SENSEI.md +88 -0
- package/katas/013-testing-effects/solution.test.ts +41 -0
- package/katas/013-testing-effects/solution.ts +20 -0
- package/katas/014-schema-basics/SENSEI.md +81 -0
- package/katas/014-schema-basics/solution.test.ts +35 -0
- package/katas/014-schema-basics/solution.ts +25 -0
- package/katas/015-domain-modeling/SENSEI.md +85 -0
- package/katas/015-domain-modeling/solution.test.ts +46 -0
- package/katas/015-domain-modeling/solution.ts +42 -0
- package/katas/016-retry-and-schedule/SENSEI.md +72 -0
- package/katas/016-retry-and-schedule/solution.test.ts +26 -0
- package/katas/016-retry-and-schedule/solution.ts +23 -0
- package/katas/017-parallel-effects/SENSEI.md +70 -0
- package/katas/017-parallel-effects/solution.test.ts +33 -0
- package/katas/017-parallel-effects/solution.ts +17 -0
- package/katas/018-race-and-timeout/SENSEI.md +75 -0
- package/katas/018-race-and-timeout/solution.test.ts +30 -0
- package/katas/018-race-and-timeout/solution.ts +27 -0
- package/katas/019-ref-and-state/SENSEI.md +72 -0
- package/katas/019-ref-and-state/solution.test.ts +29 -0
- package/katas/019-ref-and-state/solution.ts +16 -0
- package/katas/020-fibers/SENSEI.md +80 -0
- package/katas/020-fibers/solution.test.ts +23 -0
- package/katas/020-fibers/solution.ts +23 -0
- package/katas/021-acquire-release/SENSEI.md +57 -0
- package/katas/021-acquire-release/solution.test.ts +23 -0
- package/katas/021-acquire-release/solution.ts +22 -0
- package/katas/022-scoped-layers/SENSEI.md +52 -0
- package/katas/022-scoped-layers/solution.test.ts +35 -0
- package/katas/022-scoped-layers/solution.ts +19 -0
- package/katas/023-resource-patterns/SENSEI.md +52 -0
- package/katas/023-resource-patterns/solution.test.ts +20 -0
- package/katas/023-resource-patterns/solution.ts +13 -0
- package/katas/024-streams-basics/SENSEI.md +61 -0
- package/katas/024-streams-basics/solution.test.ts +30 -0
- package/katas/024-streams-basics/solution.ts +16 -0
- package/katas/025-stream-operations/SENSEI.md +59 -0
- package/katas/025-stream-operations/solution.test.ts +26 -0
- package/katas/025-stream-operations/solution.ts +17 -0
- package/katas/026-combining-streams/SENSEI.md +54 -0
- package/katas/026-combining-streams/solution.test.ts +20 -0
- package/katas/026-combining-streams/solution.ts +16 -0
- package/katas/027-data-pipelines/SENSEI.md +58 -0
- package/katas/027-data-pipelines/solution.test.ts +22 -0
- package/katas/027-data-pipelines/solution.ts +16 -0
- package/katas/028-logging-and-spans/SENSEI.md +58 -0
- package/katas/028-logging-and-spans/solution.test.ts +50 -0
- package/katas/028-logging-and-spans/solution.ts +20 -0
- package/katas/029-http-client/SENSEI.md +59 -0
- package/katas/029-http-client/solution.test.ts +49 -0
- package/katas/029-http-client/solution.ts +24 -0
- package/katas/030-capstone/SENSEI.md +63 -0
- package/katas/030-capstone/solution.test.ts +67 -0
- package/katas/030-capstone/solution.ts +55 -0
- package/katas/031-config-and-environment/SENSEI.md +77 -0
- package/katas/031-config-and-environment/solution.test.ts +38 -0
- package/katas/031-config-and-environment/solution.ts +11 -0
- package/katas/032-cause-and-defects/SENSEI.md +90 -0
- package/katas/032-cause-and-defects/solution.test.ts +50 -0
- package/katas/032-cause-and-defects/solution.ts +23 -0
- package/katas/033-pattern-matching/SENSEI.md +86 -0
- package/katas/033-pattern-matching/solution.test.ts +36 -0
- package/katas/033-pattern-matching/solution.ts +28 -0
- package/katas/034-deferred-and-coordination/SENSEI.md +85 -0
- package/katas/034-deferred-and-coordination/solution.test.ts +25 -0
- package/katas/034-deferred-and-coordination/solution.ts +24 -0
- package/katas/035-queue-and-backpressure/SENSEI.md +100 -0
- package/katas/035-queue-and-backpressure/solution.test.ts +25 -0
- package/katas/035-queue-and-backpressure/solution.ts +21 -0
- package/katas/036-schema-advanced/SENSEI.md +81 -0
- package/katas/036-schema-advanced/solution.test.ts +55 -0
- package/katas/036-schema-advanced/solution.ts +19 -0
- package/katas/037-cache-and-memoization/SENSEI.md +73 -0
- package/katas/037-cache-and-memoization/solution.test.ts +47 -0
- package/katas/037-cache-and-memoization/solution.ts +24 -0
- package/katas/038-metrics/SENSEI.md +91 -0
- package/katas/038-metrics/solution.test.ts +39 -0
- package/katas/038-metrics/solution.ts +23 -0
- package/katas/039-managed-runtime/SENSEI.md +75 -0
- package/katas/039-managed-runtime/solution.test.ts +29 -0
- package/katas/039-managed-runtime/solution.ts +19 -0
- package/katas/040-request-batching/SENSEI.md +87 -0
- package/katas/040-request-batching/solution.test.ts +56 -0
- package/katas/040-request-batching/solution.ts +32 -0
- package/package.json +22 -0
- package/skills/effect-patterns-building-apis/SKILL.md +2393 -0
- package/skills/effect-patterns-building-data-pipelines/SKILL.md +1876 -0
- package/skills/effect-patterns-concurrency/SKILL.md +2999 -0
- package/skills/effect-patterns-concurrency-getting-started/SKILL.md +351 -0
- package/skills/effect-patterns-core-concepts/SKILL.md +3199 -0
- package/skills/effect-patterns-domain-modeling/SKILL.md +1385 -0
- package/skills/effect-patterns-error-handling/SKILL.md +1212 -0
- package/skills/effect-patterns-error-handling-resilience/SKILL.md +179 -0
- package/skills/effect-patterns-error-management/SKILL.md +1668 -0
- package/skills/effect-patterns-getting-started/SKILL.md +237 -0
- package/skills/effect-patterns-making-http-requests/SKILL.md +1756 -0
- package/skills/effect-patterns-observability/SKILL.md +1586 -0
- package/skills/effect-patterns-platform/SKILL.md +1195 -0
- package/skills/effect-patterns-platform-getting-started/SKILL.md +179 -0
- package/skills/effect-patterns-project-setup--execution/SKILL.md +233 -0
- package/skills/effect-patterns-resource-management/SKILL.md +827 -0
- package/skills/effect-patterns-scheduling/SKILL.md +451 -0
- package/skills/effect-patterns-scheduling-periodic-tasks/SKILL.md +763 -0
- package/skills/effect-patterns-streams/SKILL.md +2052 -0
- package/skills/effect-patterns-streams-getting-started/SKILL.md +421 -0
- package/skills/effect-patterns-streams-sinks/SKILL.md +1181 -0
- package/skills/effect-patterns-testing/SKILL.md +1632 -0
- package/skills/effect-patterns-tooling-and-debugging/SKILL.md +1125 -0
- package/skills/effect-patterns-value-handling/SKILL.md +676 -0
- package/tsconfig.json +20 -0
- 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
|
+
|