@martinffx/atelier 2.0.0 → 3.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/CHANGELOG.md +21 -0
- package/README.md +94 -253
- package/package.json +1 -1
- package/skills/{spec-orchestrator → atelier-orchestrator}/SKILL.md +3 -3
- package/skills/atelier-setup/SKILL.md +80 -0
- package/skills/code-review/references/reviewers.md +3 -3
- package/skills/code-review/references/rq.md +6 -6
- package/skills/spec-brainstorm/SKILL.md +5 -5
- package/skills/spec-implement/SKILL.md +2 -3
- package/skills/spec-plan/SKILL.md +21 -29
- package/skills/spec-plan/references/plan_template.md +77 -0
- package/skills/python-architecture/SKILL.md +0 -148
- package/skills/python-architecture/references/data-modeling.md +0 -558
- package/skills/python-architecture/references/ddd.md +0 -569
- package/skills/python-architecture/references/functional-core.md +0 -337
- package/skills/python-build-tools/SKILL.md +0 -153
- package/skills/python-build-tools/references/basedpyright.md +0 -321
- package/skills/python-build-tools/references/ruff.md +0 -225
- package/skills/python-build-tools/references/uv.md +0 -257
- package/skills/python-fastapi/SKILL.md +0 -184
- package/skills/python-fastapi/references/api-design.md +0 -563
- package/skills/python-fastapi/references/dependencies.md +0 -479
- package/skills/python-fastapi/references/middleware.md +0 -475
- package/skills/python-fastapi/references/validation.md +0 -497
- package/skills/python-modern-python/SKILL.md +0 -201
- package/skills/python-modern-python/references/async.md +0 -76
- package/skills/python-modern-python/references/pattern-matching.md +0 -89
- package/skills/python-modern-python/references/typing.md +0 -96
- package/skills/python-monorepo/SKILL.md +0 -146
- package/skills/python-monorepo/references/docker.md +0 -515
- package/skills/python-monorepo/references/namespace-packages.md +0 -426
- package/skills/python-monorepo/references/workspace-config.md +0 -341
- package/skills/python-sqlalchemy/SKILL.md +0 -119
- package/skills/python-sqlalchemy/references/async.md +0 -51
- package/skills/python-sqlalchemy/references/models.md +0 -67
- package/skills/python-sqlalchemy/references/queries.md +0 -57
- package/skills/python-temporal/SKILL.md +0 -128
- package/skills/python-temporal/references/patterns.md +0 -87
- package/skills/python-temporal/references/testing.md +0 -54
- package/skills/python-testing/SKILL.md +0 -161
- package/skills/python-testing/references/boundaries.md +0 -449
- package/skills/python-testing/references/mocking.md +0 -449
- package/skills/python-testing/references/pytest.md +0 -524
- package/skills/typescript-api-design/SKILL.md +0 -396
- package/skills/typescript-api-design/references/error-responses.md +0 -469
- package/skills/typescript-build-tools/SKILL.md +0 -447
- package/skills/typescript-build-tools/references/biome.md +0 -436
- package/skills/typescript-build-tools/references/bun.md +0 -432
- package/skills/typescript-build-tools/references/turborepo.md +0 -426
- package/skills/typescript-build-tools/references/vitest.md +0 -518
- package/skills/typescript-drizzle-orm/SKILL.md +0 -398
- package/skills/typescript-drizzle-orm/references/cloudflare.md +0 -392
- package/skills/typescript-drizzle-orm/references/entity-pattern.md +0 -412
- package/skills/typescript-drizzle-orm/references/postgresql.md +0 -191
- package/skills/typescript-drizzle-orm/references/repository-pattern.md +0 -479
- package/skills/typescript-drizzle-orm/references/sqlite.md +0 -291
- package/skills/typescript-dynamodb-toolbox/SKILL.md +0 -370
- package/skills/typescript-dynamodb-toolbox/references/entity-layer.md +0 -574
- package/skills/typescript-dynamodb-toolbox/references/error-handling.md +0 -495
- package/skills/typescript-dynamodb-toolbox/references/modeling.md +0 -332
- package/skills/typescript-dynamodb-toolbox/references/repository-layer.md +0 -631
- package/skills/typescript-dynamodb-toolbox/references/testing.md +0 -718
- package/skills/typescript-dynamodb-toolbox/references/transactions.md +0 -423
- package/skills/typescript-effect-ts/SKILL.md +0 -341
- package/skills/typescript-effect-ts/references/core.md +0 -412
- package/skills/typescript-effect-ts/references/error-handling.md +0 -468
- package/skills/typescript-effect-ts/references/resources.md +0 -476
- package/skills/typescript-effect-ts/references/schema.md +0 -536
- package/skills/typescript-effect-ts/references/services.md +0 -461
- package/skills/typescript-fastify/SKILL.md +0 -405
- package/skills/typescript-fastify/references/plugins.md +0 -329
- package/skills/typescript-fastify/references/typeid.md +0 -315
- package/skills/typescript-functional-patterns/SKILL.md +0 -402
- package/skills/typescript-functional-patterns/references/adts.md +0 -497
- package/skills/typescript-functional-patterns/references/branded-types.md +0 -542
- package/skills/typescript-functional-patterns/references/migration-guide.md +0 -695
- package/skills/typescript-functional-patterns/references/option-result.md +0 -585
- package/skills/typescript-testing/SKILL.md +0 -353
- package/skills/typescript-testing/references/mocking.md +0 -675
- package/skills/typescript-testing/references/msw.md +0 -852
- package/skills/typescript-testing/references/snapshot-testing.md +0 -774
|
@@ -1,468 +0,0 @@
|
|
|
1
|
-
# Error Handling in Effect
|
|
2
|
-
|
|
3
|
-
Effect provides comprehensive error handling with typed errors tracked in the type system. Errors are values, not exceptions, enabling exhaustive handling and composition.
|
|
4
|
-
|
|
5
|
-
## Error Types
|
|
6
|
-
|
|
7
|
-
### Expected Errors (E channel)
|
|
8
|
-
|
|
9
|
-
Typed errors that are part of the function contract:
|
|
10
|
-
|
|
11
|
-
```typescript
|
|
12
|
-
import { Effect } from "effect"
|
|
13
|
-
|
|
14
|
-
class UserNotFound extends Error {
|
|
15
|
-
readonly _tag = "UserNotFound"
|
|
16
|
-
constructor(readonly userId: string) {
|
|
17
|
-
super(`User not found: ${userId}`)
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
class ValidationError extends Error {
|
|
22
|
-
readonly _tag = "ValidationError"
|
|
23
|
-
constructor(readonly field: string, readonly message: string) {
|
|
24
|
-
super(`${field}: ${message}`)
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Error type is tracked: Effect<User, UserNotFound | ValidationError, never>
|
|
29
|
-
const getUser = (id: string): Effect.Effect<User, UserNotFound | ValidationError> =>
|
|
30
|
-
Effect.gen(function* () {
|
|
31
|
-
if (!isValidId(id)) {
|
|
32
|
-
return yield* Effect.fail(new ValidationError("id", "Invalid format"))
|
|
33
|
-
}
|
|
34
|
-
const user = yield* fetchFromDb(id)
|
|
35
|
-
if (!user) {
|
|
36
|
-
return yield* Effect.fail(new UserNotFound(id))
|
|
37
|
-
}
|
|
38
|
-
return user
|
|
39
|
-
})
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
### Defects (Unexpected Errors)
|
|
43
|
-
|
|
44
|
-
Untyped errors for bugs and invariant violations:
|
|
45
|
-
|
|
46
|
-
```typescript
|
|
47
|
-
import { Effect } from "effect"
|
|
48
|
-
|
|
49
|
-
// Die creates a defect - not tracked in E type
|
|
50
|
-
const assertPositive = (n: number) =>
|
|
51
|
-
n > 0
|
|
52
|
-
? Effect.succeed(n)
|
|
53
|
-
: Effect.die(`Expected positive number, got ${n}`)
|
|
54
|
-
|
|
55
|
-
// Defects propagate but aren't in the error type
|
|
56
|
-
const program: Effect.Effect<number> = assertPositive(-1)
|
|
57
|
-
// Type says never fails, but will throw at runtime
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
### Tagged Error Pattern
|
|
61
|
-
|
|
62
|
-
Always use `_tag` for discriminated union handling:
|
|
63
|
-
|
|
64
|
-
```typescript
|
|
65
|
-
// Define error classes with _tag
|
|
66
|
-
class NetworkError extends Error {
|
|
67
|
-
readonly _tag = "NetworkError" as const
|
|
68
|
-
constructor(readonly cause: unknown) {
|
|
69
|
-
super("Network error")
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
class TimeoutError extends Error {
|
|
74
|
-
readonly _tag = "TimeoutError" as const
|
|
75
|
-
constructor(readonly ms: number) {
|
|
76
|
-
super(`Timeout after ${ms}ms`)
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
class ParseError extends Error {
|
|
81
|
-
readonly _tag = "ParseError" as const
|
|
82
|
-
constructor(readonly input: string) {
|
|
83
|
-
super(`Failed to parse: ${input}`)
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
type FetchError = NetworkError | TimeoutError | ParseError
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
## Handling Errors
|
|
91
|
-
|
|
92
|
-
### catchAll - Handle All Errors
|
|
93
|
-
|
|
94
|
-
```typescript
|
|
95
|
-
import { Effect } from "effect"
|
|
96
|
-
|
|
97
|
-
const getUser = (id: string): Effect.Effect<User, UserNotFound> => // ...
|
|
98
|
-
|
|
99
|
-
// Handle any error, must return same success type
|
|
100
|
-
const recovered = getUser("123").pipe(
|
|
101
|
-
Effect.catchAll((error) =>
|
|
102
|
-
Effect.succeed({ id: "anonymous", name: "Guest" })
|
|
103
|
-
)
|
|
104
|
-
)
|
|
105
|
-
// Effect<User, never, never> - error type becomes never
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
### catchTag - Handle Specific Error by Tag
|
|
109
|
-
|
|
110
|
-
```typescript
|
|
111
|
-
import { Effect } from "effect"
|
|
112
|
-
|
|
113
|
-
const fetchData = (): Effect.Effect<Data, NetworkError | ParseError> => // ...
|
|
114
|
-
|
|
115
|
-
// Handle only NetworkError
|
|
116
|
-
const withRetry = fetchData().pipe(
|
|
117
|
-
Effect.catchTag("NetworkError", (error) =>
|
|
118
|
-
Effect.sync(() => console.log("Retrying...")).pipe(
|
|
119
|
-
Effect.flatMap(() => fetchData())
|
|
120
|
-
)
|
|
121
|
-
)
|
|
122
|
-
)
|
|
123
|
-
// Effect<Data, ParseError, never> - NetworkError handled
|
|
124
|
-
|
|
125
|
-
// Handle multiple specific errors
|
|
126
|
-
const handled = fetchData().pipe(
|
|
127
|
-
Effect.catchTags({
|
|
128
|
-
NetworkError: (e) => Effect.succeed(defaultData),
|
|
129
|
-
ParseError: (e) => Effect.succeed(emptyData)
|
|
130
|
-
})
|
|
131
|
-
)
|
|
132
|
-
// Effect<Data, never, never>
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
### catchSome - Handle Conditionally
|
|
136
|
-
|
|
137
|
-
```typescript
|
|
138
|
-
import { Effect, Option } from "effect"
|
|
139
|
-
|
|
140
|
-
const selective = effect.pipe(
|
|
141
|
-
Effect.catchSome((error) => {
|
|
142
|
-
if (error._tag === "Retryable") {
|
|
143
|
-
return Option.some(retryEffect)
|
|
144
|
-
}
|
|
145
|
-
return Option.none() // Don't handle, propagate
|
|
146
|
-
})
|
|
147
|
-
)
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
### orElse - Fallback Effect
|
|
151
|
-
|
|
152
|
-
```typescript
|
|
153
|
-
import { Effect } from "effect"
|
|
154
|
-
|
|
155
|
-
// If first fails, try second
|
|
156
|
-
const withFallback = primarySource().pipe(
|
|
157
|
-
Effect.orElse(() => fallbackSource())
|
|
158
|
-
)
|
|
159
|
-
|
|
160
|
-
// Provide default on failure
|
|
161
|
-
const withDefault = effect.pipe(
|
|
162
|
-
Effect.orElseSucceed(() => defaultValue)
|
|
163
|
-
)
|
|
164
|
-
|
|
165
|
-
// Fail with different error
|
|
166
|
-
const remapped = effect.pipe(
|
|
167
|
-
Effect.orElseFail(() => new DifferentError())
|
|
168
|
-
)
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
### mapError - Transform Error
|
|
172
|
-
|
|
173
|
-
```typescript
|
|
174
|
-
import { Effect } from "effect"
|
|
175
|
-
|
|
176
|
-
// Transform error type
|
|
177
|
-
const wrapped = lowLevelOperation().pipe(
|
|
178
|
-
Effect.mapError((error) => new HighLevelError(error))
|
|
179
|
-
)
|
|
180
|
-
|
|
181
|
-
// Add context to errors
|
|
182
|
-
const withContext = dbQuery(sql).pipe(
|
|
183
|
-
Effect.mapError((error) => ({
|
|
184
|
-
...error,
|
|
185
|
-
query: sql,
|
|
186
|
-
timestamp: Date.now()
|
|
187
|
-
}))
|
|
188
|
-
)
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
## Either and Exit
|
|
192
|
-
|
|
193
|
-
### Using Either
|
|
194
|
-
|
|
195
|
-
```typescript
|
|
196
|
-
import { Effect, Either } from "effect"
|
|
197
|
-
|
|
198
|
-
// Convert Effect to Either (errors become Left)
|
|
199
|
-
const asEither = effect.pipe(Effect.either)
|
|
200
|
-
// Effect<Either<E, A>, never, R>
|
|
201
|
-
|
|
202
|
-
// Handle without short-circuiting
|
|
203
|
-
const program = Effect.gen(function* () {
|
|
204
|
-
const result = yield* Effect.either(mayFailEffect)
|
|
205
|
-
|
|
206
|
-
if (Either.isLeft(result)) {
|
|
207
|
-
console.log("Failed:", result.left)
|
|
208
|
-
return defaultValue
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
return result.right
|
|
212
|
-
})
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
### Using Exit
|
|
216
|
-
|
|
217
|
-
```typescript
|
|
218
|
-
import { Effect, Exit } from "effect"
|
|
219
|
-
|
|
220
|
-
// Get full exit status
|
|
221
|
-
const exit = await Effect.runPromiseExit(effect)
|
|
222
|
-
|
|
223
|
-
if (Exit.isSuccess(exit)) {
|
|
224
|
-
console.log("Success:", exit.value)
|
|
225
|
-
} else {
|
|
226
|
-
// Cause contains full error information
|
|
227
|
-
console.log("Failure:", Cause.pretty(exit.cause))
|
|
228
|
-
}
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
## Cause - Rich Error Information
|
|
232
|
-
|
|
233
|
-
Effect tracks not just errors but their full cause:
|
|
234
|
-
|
|
235
|
-
```typescript
|
|
236
|
-
import { Effect, Cause } from "effect"
|
|
237
|
-
|
|
238
|
-
// Cause types:
|
|
239
|
-
// - Fail: Expected error in E channel
|
|
240
|
-
// - Die: Unexpected defect
|
|
241
|
-
// - Interrupt: Fiber was interrupted
|
|
242
|
-
// - Sequential: Multiple causes in sequence
|
|
243
|
-
// - Parallel: Multiple causes in parallel
|
|
244
|
-
|
|
245
|
-
const program = effect.pipe(
|
|
246
|
-
Effect.catchAllCause((cause) => {
|
|
247
|
-
// Full cause tree available
|
|
248
|
-
const failures = Cause.failures(cause) // E[]
|
|
249
|
-
const defects = Cause.defects(cause) // unknown[]
|
|
250
|
-
|
|
251
|
-
// Pretty print
|
|
252
|
-
console.error(Cause.pretty(cause))
|
|
253
|
-
|
|
254
|
-
return Effect.succeed(fallback)
|
|
255
|
-
})
|
|
256
|
-
)
|
|
257
|
-
```
|
|
258
|
-
|
|
259
|
-
## Retry Patterns
|
|
260
|
-
|
|
261
|
-
### Basic Retry
|
|
262
|
-
|
|
263
|
-
```typescript
|
|
264
|
-
import { Effect, Schedule } from "effect"
|
|
265
|
-
|
|
266
|
-
// Retry up to 3 times
|
|
267
|
-
const retried = effect.pipe(
|
|
268
|
-
Effect.retry(Schedule.recurs(3))
|
|
269
|
-
)
|
|
270
|
-
|
|
271
|
-
// Retry with delay
|
|
272
|
-
const retriedWithDelay = effect.pipe(
|
|
273
|
-
Effect.retry(
|
|
274
|
-
Schedule.recurs(3).pipe(
|
|
275
|
-
Schedule.addDelay(() => "1 second")
|
|
276
|
-
)
|
|
277
|
-
)
|
|
278
|
-
)
|
|
279
|
-
```
|
|
280
|
-
|
|
281
|
-
### Exponential Backoff
|
|
282
|
-
|
|
283
|
-
```typescript
|
|
284
|
-
import { Effect, Schedule } from "effect"
|
|
285
|
-
|
|
286
|
-
const exponentialRetry = effect.pipe(
|
|
287
|
-
Effect.retry(
|
|
288
|
-
Schedule.exponential("100 millis").pipe(
|
|
289
|
-
Schedule.jittered, // Add randomness
|
|
290
|
-
Schedule.compose(Schedule.recurs(5)), // Max 5 retries
|
|
291
|
-
Schedule.whileOutput(delay => delay < "30 seconds") // Cap delay
|
|
292
|
-
)
|
|
293
|
-
)
|
|
294
|
-
)
|
|
295
|
-
```
|
|
296
|
-
|
|
297
|
-
### Conditional Retry
|
|
298
|
-
|
|
299
|
-
```typescript
|
|
300
|
-
import { Effect, Schedule } from "effect"
|
|
301
|
-
|
|
302
|
-
// Only retry specific errors
|
|
303
|
-
const selectiveRetry = effect.pipe(
|
|
304
|
-
Effect.retry({
|
|
305
|
-
schedule: Schedule.recurs(3),
|
|
306
|
-
while: (error) => error._tag === "Retryable"
|
|
307
|
-
})
|
|
308
|
-
)
|
|
309
|
-
|
|
310
|
-
// Retry until condition
|
|
311
|
-
const retryUntilSuccess = effect.pipe(
|
|
312
|
-
Effect.retry({
|
|
313
|
-
schedule: Schedule.spaced("1 second"),
|
|
314
|
-
until: (error) => error._tag === "PermanentFailure"
|
|
315
|
-
})
|
|
316
|
-
)
|
|
317
|
-
```
|
|
318
|
-
|
|
319
|
-
## Timeouts
|
|
320
|
-
|
|
321
|
-
```typescript
|
|
322
|
-
import { Effect, Duration } from "effect"
|
|
323
|
-
|
|
324
|
-
// Fail with TimeoutException after duration
|
|
325
|
-
const withTimeout = effect.pipe(
|
|
326
|
-
Effect.timeout(Duration.seconds(30))
|
|
327
|
-
)
|
|
328
|
-
// Effect<A, E | TimeoutException, R>
|
|
329
|
-
|
|
330
|
-
// Return Option.none on timeout
|
|
331
|
-
const withTimeoutOption = effect.pipe(
|
|
332
|
-
Effect.timeoutOption(Duration.seconds(30))
|
|
333
|
-
)
|
|
334
|
-
// Effect<Option<A>, E, R>
|
|
335
|
-
|
|
336
|
-
// Provide fallback on timeout
|
|
337
|
-
const withTimeoutFallback = effect.pipe(
|
|
338
|
-
Effect.timeoutFail({
|
|
339
|
-
duration: Duration.seconds(30),
|
|
340
|
-
onTimeout: () => new CustomTimeoutError()
|
|
341
|
-
})
|
|
342
|
-
)
|
|
343
|
-
```
|
|
344
|
-
|
|
345
|
-
## Error Accumulation
|
|
346
|
-
|
|
347
|
-
### Collect All Errors
|
|
348
|
-
|
|
349
|
-
```typescript
|
|
350
|
-
import { Effect } from "effect"
|
|
351
|
-
|
|
352
|
-
const validations = [
|
|
353
|
-
validateEmail(email),
|
|
354
|
-
validatePassword(password),
|
|
355
|
-
validateAge(age)
|
|
356
|
-
]
|
|
357
|
-
|
|
358
|
-
// Stop at first error (default)
|
|
359
|
-
const failFast = Effect.all(validations)
|
|
360
|
-
|
|
361
|
-
// Collect all errors
|
|
362
|
-
const allErrors = Effect.all(validations, { mode: "validate" })
|
|
363
|
-
// Effect<[A, B, C], [E, E, E], R>
|
|
364
|
-
```
|
|
365
|
-
|
|
366
|
-
### Custom Error Accumulation
|
|
367
|
-
|
|
368
|
-
```typescript
|
|
369
|
-
import { Effect, Either } from "effect"
|
|
370
|
-
|
|
371
|
-
const validateForm = Effect.gen(function* () {
|
|
372
|
-
const results = yield* Effect.all([
|
|
373
|
-
Effect.either(validateEmail(email)),
|
|
374
|
-
Effect.either(validatePassword(password)),
|
|
375
|
-
Effect.either(validateAge(age))
|
|
376
|
-
])
|
|
377
|
-
|
|
378
|
-
const errors = results
|
|
379
|
-
.filter(Either.isLeft)
|
|
380
|
-
.map(r => r.left)
|
|
381
|
-
|
|
382
|
-
if (errors.length > 0) {
|
|
383
|
-
return yield* Effect.fail(new ValidationErrors(errors))
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
return {
|
|
387
|
-
email: (results[0] as Either.Right<string>).right,
|
|
388
|
-
password: (results[1] as Either.Right<string>).right,
|
|
389
|
-
age: (results[2] as Either.Right<number>).right
|
|
390
|
-
}
|
|
391
|
-
})
|
|
392
|
-
```
|
|
393
|
-
|
|
394
|
-
## Best Practices
|
|
395
|
-
|
|
396
|
-
### 1. Use Tagged Error Classes
|
|
397
|
-
|
|
398
|
-
```typescript
|
|
399
|
-
// Good - tagged for catchTag
|
|
400
|
-
class UserNotFound extends Error {
|
|
401
|
-
readonly _tag = "UserNotFound" as const
|
|
402
|
-
constructor(readonly userId: string) {
|
|
403
|
-
super(`User not found: ${userId}`)
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
// Avoid - generic errors
|
|
408
|
-
const bad = Effect.fail(new Error("User not found"))
|
|
409
|
-
```
|
|
410
|
-
|
|
411
|
-
### 2. Be Specific with Error Types
|
|
412
|
-
|
|
413
|
-
```typescript
|
|
414
|
-
// Good - specific error types
|
|
415
|
-
type GetUserError = UserNotFound | DatabaseError | ValidationError
|
|
416
|
-
|
|
417
|
-
const getUser = (id: string): Effect.Effect<User, GetUserError> => // ...
|
|
418
|
-
|
|
419
|
-
// Avoid - union with string/Error
|
|
420
|
-
const bad = (id: string): Effect.Effect<User, string | Error> => // ...
|
|
421
|
-
```
|
|
422
|
-
|
|
423
|
-
### 3. Handle Errors at Appropriate Level
|
|
424
|
-
|
|
425
|
-
```typescript
|
|
426
|
-
// Low-level: expose specific errors
|
|
427
|
-
const queryDb = (sql: string): Effect.Effect<Row[], DbError> => // ...
|
|
428
|
-
|
|
429
|
-
// Mid-level: wrap and add context
|
|
430
|
-
const getUser = (id: string): Effect.Effect<User, UserError> =>
|
|
431
|
-
queryDb(`SELECT * FROM users WHERE id = '${id}'`).pipe(
|
|
432
|
-
Effect.mapError(e => new UserError("Failed to fetch user", e))
|
|
433
|
-
)
|
|
434
|
-
|
|
435
|
-
// High-level: handle or provide fallback
|
|
436
|
-
const getUserOrGuest = (id: string): Effect.Effect<User> =>
|
|
437
|
-
getUser(id).pipe(
|
|
438
|
-
Effect.catchAll(() => Effect.succeed(guestUser))
|
|
439
|
-
)
|
|
440
|
-
```
|
|
441
|
-
|
|
442
|
-
### 4. Use Cause for Debugging
|
|
443
|
-
|
|
444
|
-
```typescript
|
|
445
|
-
const program = effect.pipe(
|
|
446
|
-
Effect.catchAllCause((cause) => {
|
|
447
|
-
// Log full cause for debugging
|
|
448
|
-
console.error(Cause.pretty(cause))
|
|
449
|
-
|
|
450
|
-
// But expose clean error to caller
|
|
451
|
-
return Effect.fail(new ServiceUnavailable())
|
|
452
|
-
})
|
|
453
|
-
)
|
|
454
|
-
```
|
|
455
|
-
|
|
456
|
-
### 5. Don't Swallow Defects
|
|
457
|
-
|
|
458
|
-
```typescript
|
|
459
|
-
// Good - handle expected errors, let defects propagate
|
|
460
|
-
const safe = effect.pipe(
|
|
461
|
-
Effect.catchAll((error) => Effect.succeed(fallback))
|
|
462
|
-
)
|
|
463
|
-
|
|
464
|
-
// Careful - this catches defects too
|
|
465
|
-
const dangerous = effect.pipe(
|
|
466
|
-
Effect.catchAllCause((cause) => Effect.succeed(fallback))
|
|
467
|
-
)
|
|
468
|
-
```
|