@danieljvdm/dev-kit 0.11.3 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +70 -76
- package/dev-kit.example.jsonc +0 -4
- package/package.json +10 -6
- package/schema/dev-kit.schema.json +1 -46
- package/skills/build-effect-apis/SKILL.md +77 -0
- package/skills/build-effect-apis/agents/openai.yaml +4 -0
- package/skills/build-effect-apis/references/cloudflare-workers.md +71 -0
- package/skills/build-effect-apis/references/effect-atom-client.md +161 -0
- package/skills/build-effect-apis/references/effect-atom-lifecycle.md +78 -0
- package/skills/build-effect-apis/references/effect-atom-testing.md +74 -0
- package/skills/build-effect-apis/references/runtime-assembly.md +56 -0
- package/skills/build-effect-apis/references/server-and-middleware.md +174 -0
- package/skills/build-effect-apis/references/shared-contracts.md +108 -0
- package/skills/build-effect-apis/references/tanstack-start.md +86 -0
- package/skills/build-effect-apis/references/verification.md +50 -0
- package/skills/dev-kit/SKILL.md +58 -46
- package/skills/effect-architecture-audit/SKILL.md +26 -0
- package/skills/effect-architecture-audit/agents/openai.yaml +4 -0
- package/skills/effect-architecture-audit/references/service-and-boundary-audit.md +150 -0
- package/skills/effect-ts/SKILL.md +21 -256
- package/skills/effect-ts/agents/openai.yaml +3 -3
- package/skills/testing/SKILL.md +5 -0
- package/src/catalog-manager.ts +16 -17
- package/src/catalog.ts +71 -16
- package/src/effect-source.ts +46 -24
- package/src/effect-tsgo.ts +49 -24
- package/src/gitignore.ts +5 -5
- package/src/index.ts +0 -6
- package/src/manifest.ts +0 -34
- package/src/node-symbolic-link.ts +2 -2
- package/src/oxfmt.js +5 -0
- package/src/oxfmt.ts +5 -0
- package/src/oxlint.js +5 -0
- package/src/oxlint.ts +5 -0
- package/src/package-skill-source.ts +51 -59
- package/src/path-digest.ts +7 -7
- package/src/project-package.ts +8 -7
- package/src/project-process-lock.ts +17 -12
- package/src/project-state.ts +1 -1
- package/src/skill-manager.ts +16 -14
- package/src/skill-selector.ts +12 -0
- package/src/sync.ts +170 -120
- package/src/tool-ignore-patterns.js +9 -0
- package/src/tool-ignore-patterns.ts +15 -0
- package/src/vendor.ts +67 -61
- package/src/vite-plus-dependency.ts +10 -11
- package/src/vite-plus-hooks.ts +24 -14
- package/src/vite-plus-quality.ts +21 -172
- package/src/vite-plus.js +81 -0
- package/src/vite-plus.ts +102 -0
- package/templates/AGENTS.md +1 -1
- package/skills/effect-ts/UPSTREAM.md +0 -28
- package/skills/effect-ts/references/atom-cache-lifecycle.md +0 -78
- package/skills/effect-ts/references/atom-http-and-invalidation.md +0 -97
- package/skills/effect-ts/references/atom-tanstack-start.md +0 -69
- package/skills/effect-ts/references/atom-testing.md +0 -67
- package/skills/effect-ts/references/audit-services.md +0 -144
- package/skills/effect-ts/references/features.md +0 -525
- package/skills/effect-ts/references/guide-atom-data-fetching.md +0 -44
- package/skills/effect-ts/references/guide-cli.md +0 -107
- package/skills/effect-ts/references/guide-datetime.md +0 -72
- package/skills/effect-ts/references/guide-effect.md +0 -440
- package/skills/effect-ts/references/guide-error-handling.md +0 -565
- package/skills/effect-ts/references/guide-http-boundaries.md +0 -55
- package/skills/effect-ts/references/guide-layers.md +0 -989
- package/skills/effect-ts/references/guide-observability.md +0 -746
- package/skills/effect-ts/references/guide-retries.md +0 -434
- package/skills/effect-ts/references/guide-schedule.md +0 -343
- package/skills/effect-ts/references/guide-schema.md +0 -664
- package/skills/effect-ts/references/guide-sql.md +0 -536
- package/skills/effect-ts/references/guide-testing.md +0 -532
- package/skills/effect-ts/references/guide-type-safety-and-boundaries.md +0 -131
- package/skills/effect-ts/references/version-and-source.md +0 -86
- package/templates/vite-plus/vite.config.ts +0 -22
|
@@ -1,565 +0,0 @@
|
|
|
1
|
-
# Error Handling Guide
|
|
2
|
-
|
|
3
|
-
This guide covers expected failures, schema-backed errors, foreign failure
|
|
4
|
-
translation, defects, interrupts, and failure placement.
|
|
5
|
-
|
|
6
|
-
Key source files:
|
|
7
|
-
|
|
8
|
-
- `packages/effect/src/Data.ts`
|
|
9
|
-
- `packages/effect/src/Schema.ts`
|
|
10
|
-
- `packages/effect/src/Cause.ts`
|
|
11
|
-
- `packages/effect/src/Effect.ts`
|
|
12
|
-
- `packages/effect/src/unstable/sql/SqlError.ts`
|
|
13
|
-
|
|
14
|
-
## Mental Model
|
|
15
|
-
|
|
16
|
-
Effect distinguishes three failure modes:
|
|
17
|
-
|
|
18
|
-
- failure: expected, typed errors in the `E` channel of `Effect<A, E, R>`
|
|
19
|
-
- defect: unexpected unchecked failures, represented as `Cause.Die`
|
|
20
|
-
- interrupt: cooperative cancellation, represented as `Cause.Interrupt`
|
|
21
|
-
|
|
22
|
-
This distinction is explicit in `Cause`.
|
|
23
|
-
|
|
24
|
-
Repo references:
|
|
25
|
-
|
|
26
|
-
- `packages/effect/src/Cause.ts`
|
|
27
|
-
- `packages/effect/src/Effect.ts`
|
|
28
|
-
|
|
29
|
-
## Preferred Error Definition Styles
|
|
30
|
-
|
|
31
|
-
Preference order:
|
|
32
|
-
|
|
33
|
-
1. use schema-based errors when possible
|
|
34
|
-
2. fall back to `Data.TaggedError` only when the error payload is not meaningfully serializable or schema-shaped
|
|
35
|
-
|
|
36
|
-
Schema-based errors are strictly more powerful because they give you:
|
|
37
|
-
|
|
38
|
-
- typed yieldable errors
|
|
39
|
-
- schema-defined fields
|
|
40
|
-
- encode/decode support
|
|
41
|
-
- better protocol and boundary interoperability
|
|
42
|
-
- stronger documentation and tooling hooks
|
|
43
|
-
|
|
44
|
-
### 1. `Schema.TaggedErrorClass` for schema-backed tagged errors
|
|
45
|
-
|
|
46
|
-
Use `Schema.TaggedErrorClass` by default when the error can be described with schemas.
|
|
47
|
-
|
|
48
|
-
Why:
|
|
49
|
-
|
|
50
|
-
- it creates a yieldable tagged error
|
|
51
|
-
- fields are defined with `Schema`
|
|
52
|
-
- the error shape can participate in schema-based tooling and encode/decode flows
|
|
53
|
-
|
|
54
|
-
Repo references:
|
|
55
|
-
|
|
56
|
-
- `packages/effect/src/Schema.ts`
|
|
57
|
-
- `packages/effect/src/unstable/sql/SqlError.ts`
|
|
58
|
-
|
|
59
|
-
Example:
|
|
60
|
-
|
|
61
|
-
```ts
|
|
62
|
-
import { Effect, Schema } from "effect";
|
|
63
|
-
|
|
64
|
-
class InvalidPayload extends Schema.TaggedErrorClass<InvalidPayload>()("InvalidPayload", {
|
|
65
|
-
field: Schema.String,
|
|
66
|
-
reason: Schema.String,
|
|
67
|
-
}) {}
|
|
68
|
-
|
|
69
|
-
const validate = Effect.fail(
|
|
70
|
-
InvalidPayload.make({
|
|
71
|
-
field: "email",
|
|
72
|
-
reason: "missing",
|
|
73
|
-
}),
|
|
74
|
-
);
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
Expected application and service failures belong in the typed error channel.
|
|
78
|
-
In generators, use an explicit control-flow exit when failing:
|
|
79
|
-
|
|
80
|
-
```ts
|
|
81
|
-
return (
|
|
82
|
-
yield *
|
|
83
|
-
Effect.fail(
|
|
84
|
-
InvalidPayload.make({
|
|
85
|
-
field: "email",
|
|
86
|
-
reason: "missing",
|
|
87
|
-
}),
|
|
88
|
-
)
|
|
89
|
-
);
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
Use this when:
|
|
93
|
-
|
|
94
|
-
- the error is part of a protocol or transport boundary
|
|
95
|
-
- the error needs a precise schema representation
|
|
96
|
-
- the error should be serializable or documented structurally
|
|
97
|
-
|
|
98
|
-
This is also a good default for domain errors when their payload is schema-friendly.
|
|
99
|
-
|
|
100
|
-
### 2. `Schema.ErrorClass` for schema-backed errors without `_tag` routing
|
|
101
|
-
|
|
102
|
-
Use `Schema.ErrorClass` when you want schema-defined error objects but do not specifically need tag-based pattern matching.
|
|
103
|
-
|
|
104
|
-
Repo references:
|
|
105
|
-
|
|
106
|
-
- `packages/effect/src/Schema.ts`
|
|
107
|
-
- examples across `packages/effect/src/unstable/*`
|
|
108
|
-
|
|
109
|
-
Example shape from the canonical source:
|
|
110
|
-
|
|
111
|
-
- `packages/effect/src/unstable/httpapi/HttpApiError.ts`
|
|
112
|
-
- `packages/effect/src/unstable/workers/WorkerError.ts`
|
|
113
|
-
|
|
114
|
-
### 3. `Data.TaggedError` for non-serializable or lightweight domain errors
|
|
115
|
-
|
|
116
|
-
Use `Data.TaggedError` when schema-based errors are not a good fit.
|
|
117
|
-
|
|
118
|
-
This is mainly the fallback for:
|
|
119
|
-
|
|
120
|
-
- non-serializable payloads
|
|
121
|
-
- ad hoc in-memory-only errors
|
|
122
|
-
- cases where schema shape would be artificial or misleading
|
|
123
|
-
|
|
124
|
-
Repo reference:
|
|
125
|
-
|
|
126
|
-
- `packages/effect/src/Data.ts`
|
|
127
|
-
|
|
128
|
-
Example:
|
|
129
|
-
|
|
130
|
-
```ts
|
|
131
|
-
import { Data, Effect } from "effect";
|
|
132
|
-
|
|
133
|
-
class UserNotFound extends Data.TaggedError("UserNotFound")<{
|
|
134
|
-
readonly userId: string;
|
|
135
|
-
}> {}
|
|
136
|
-
|
|
137
|
-
const loadUser = (userId: string) => Effect.fail(new UserNotFound({ userId }));
|
|
138
|
-
|
|
139
|
-
const program = Effect.gen(function* () {
|
|
140
|
-
yield* loadUser("u_123");
|
|
141
|
-
});
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
## When To Prefer `Data.TaggedError` vs `Schema.TaggedErrorClass`
|
|
145
|
-
|
|
146
|
-
Prefer `Schema.TaggedErrorClass` when:
|
|
147
|
-
|
|
148
|
-
- the error can be expressed as a schema
|
|
149
|
-
- the error payload must be described by schemas
|
|
150
|
-
- the error crosses process, protocol, persistence, or serialization boundaries
|
|
151
|
-
- you want the error type to participate in schema tooling
|
|
152
|
-
|
|
153
|
-
Prefer `Data.TaggedError` when:
|
|
154
|
-
|
|
155
|
-
- the payload is not meaningfully serializable
|
|
156
|
-
- the payload cannot reasonably be modeled as a schema
|
|
157
|
-
- the error is intentionally local and in-memory only
|
|
158
|
-
|
|
159
|
-
## Schema-Based Error Workflows
|
|
160
|
-
|
|
161
|
-
### Boundary validation should fail with `SchemaError`
|
|
162
|
-
|
|
163
|
-
When validating external input, Effect's schema APIs return `SchemaError` in the error channel.
|
|
164
|
-
|
|
165
|
-
Repo references:
|
|
166
|
-
|
|
167
|
-
- `packages/effect/src/Schema.ts`
|
|
168
|
-
- `Schema.decodeUnknownEffect`
|
|
169
|
-
- `Schema.decodeUnknownExit`
|
|
170
|
-
- `Schema.encodeUnknownEffect`
|
|
171
|
-
|
|
172
|
-
Example:
|
|
173
|
-
|
|
174
|
-
```ts
|
|
175
|
-
import { Effect, Schema } from "effect";
|
|
176
|
-
|
|
177
|
-
const UserPayload = Schema.Struct({
|
|
178
|
-
id: Schema.String,
|
|
179
|
-
email: Schema.String,
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
const decodeUser = Schema.decodeUnknownEffect(UserPayload);
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
This gives you:
|
|
186
|
-
|
|
187
|
-
- success: validated typed data
|
|
188
|
-
- failure: `Schema.SchemaError`
|
|
189
|
-
|
|
190
|
-
### Normalize `SchemaError` at the boundary
|
|
191
|
-
|
|
192
|
-
For application code, it is often better to convert `SchemaError` into a domain error near the boundary.
|
|
193
|
-
|
|
194
|
-
Example:
|
|
195
|
-
|
|
196
|
-
```ts
|
|
197
|
-
import { Data, Effect, Schema } from "effect";
|
|
198
|
-
|
|
199
|
-
class InvalidRequestBody extends Data.TaggedError("InvalidRequestBody")<{
|
|
200
|
-
readonly message: string;
|
|
201
|
-
}> {}
|
|
202
|
-
|
|
203
|
-
const UserPayload = Schema.Struct({
|
|
204
|
-
id: Schema.String,
|
|
205
|
-
email: Schema.String,
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
const decodeUser = (input: unknown) =>
|
|
209
|
-
Schema.decodeUnknownEffect(UserPayload)(input).pipe(
|
|
210
|
-
Effect.catchTag("SchemaError", (error) =>
|
|
211
|
-
Effect.fail(new InvalidRequestBody({ message: error.message })),
|
|
212
|
-
),
|
|
213
|
-
);
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
Why:
|
|
217
|
-
|
|
218
|
-
- transport validation stays close to the transport layer
|
|
219
|
-
- the rest of the application can work with domain-specific errors
|
|
220
|
-
|
|
221
|
-
### Use schema-backed errors for protocol errors
|
|
222
|
-
|
|
223
|
-
The canonical source uses schema-backed errors in places like SQL, RPC, sockets, and HTTP APIs.
|
|
224
|
-
|
|
225
|
-
Strong examples:
|
|
226
|
-
|
|
227
|
-
- `packages/effect/src/unstable/sql/SqlError.ts`
|
|
228
|
-
- `packages/effect/src/unstable/socket/Socket.ts`
|
|
229
|
-
- `packages/effect/src/unstable/eventlog/EventLogMessage.ts`
|
|
230
|
-
|
|
231
|
-
These are good reference points when the error contract matters externally.
|
|
232
|
-
|
|
233
|
-
## Wrapping Foreign Or Generic Errors
|
|
234
|
-
|
|
235
|
-
When an error comes from a library, runtime API, or generic `Error`, prefer wrapping it in a typed error instead of leaking the foreign error directly through your domain or protocol boundary.
|
|
236
|
-
|
|
237
|
-
Use `Effect.tryPromise` at external Promise boundaries and translate a
|
|
238
|
-
recoverable rejection once, in the service or adapter that owns that boundary.
|
|
239
|
-
Do not convert Effect-native APIs to Promise and immediately wrap them again.
|
|
240
|
-
|
|
241
|
-
This is a very common pattern in the canonical source.
|
|
242
|
-
|
|
243
|
-
Good examples:
|
|
244
|
-
|
|
245
|
-
- `packages/effect/src/unstable/sql/SqlError.ts`
|
|
246
|
-
- `packages/effect/src/unstable/rpc/RpcClientError.ts`
|
|
247
|
-
- `packages/effect/src/unstable/socket/Socket.ts`
|
|
248
|
-
- `packages/effect/src/unstable/workers/WorkerError.ts`
|
|
249
|
-
- `packages/effect/src/unstable/persistence/Redis.ts`
|
|
250
|
-
|
|
251
|
-
### Preferred Pattern
|
|
252
|
-
|
|
253
|
-
Wrap the foreign error in a schema-backed typed error and preserve the original error in a `cause` field.
|
|
254
|
-
|
|
255
|
-
Prefer using:
|
|
256
|
-
|
|
257
|
-
- `Schema.Defect()` when you want to preserve a generic encoded defect
|
|
258
|
-
- `Schema.Defect({ includeStack: true })` when the stack should be preserved in
|
|
259
|
-
the schema contract
|
|
260
|
-
|
|
261
|
-
Example:
|
|
262
|
-
|
|
263
|
-
```ts
|
|
264
|
-
import { Effect, Schema } from "effect";
|
|
265
|
-
|
|
266
|
-
class TodoStorageError extends Schema.TaggedErrorClass<TodoStorageError>()("TodoStorageError", {
|
|
267
|
-
operation: Schema.String,
|
|
268
|
-
cause: Schema.Defect(),
|
|
269
|
-
}) {}
|
|
270
|
-
|
|
271
|
-
const makeStorageError = (operation: string) => (cause: unknown) =>
|
|
272
|
-
TodoStorageError.make({
|
|
273
|
-
operation,
|
|
274
|
-
cause,
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
const loadTodo = (id: number) =>
|
|
278
|
-
Effect.try({
|
|
279
|
-
try: () => someLibraryCall(id),
|
|
280
|
-
catch: makeStorageError("loadTodo"),
|
|
281
|
-
});
|
|
282
|
-
```
|
|
283
|
-
|
|
284
|
-
When stack preservation matters in the encoded schema, prefer:
|
|
285
|
-
|
|
286
|
-
```ts
|
|
287
|
-
class WorkerFailure extends Schema.TaggedErrorClass<WorkerFailure>()("WorkerFailure", {
|
|
288
|
-
cause: Schema.Defect({ includeStack: true }),
|
|
289
|
-
}) {}
|
|
290
|
-
```
|
|
291
|
-
|
|
292
|
-
### Why This Is Preferred
|
|
293
|
-
|
|
294
|
-
- the application still exposes a typed error contract
|
|
295
|
-
- the original foreign failure is preserved for diagnostics
|
|
296
|
-
- schema-aware transports can encode and decode the failure shape
|
|
297
|
-
- business code does not become coupled to a raw library error type
|
|
298
|
-
|
|
299
|
-
### When To Use This Pattern
|
|
300
|
-
|
|
301
|
-
Use it when:
|
|
302
|
-
|
|
303
|
-
- a third-party library throws or rejects with `Error`
|
|
304
|
-
- a runtime API returns generic failures
|
|
305
|
-
- a lower-level subsystem failure should be surfaced through a typed domain or protocol error
|
|
306
|
-
- you need to preserve the underlying failure for debugging without leaking the foreign type as the public error contract
|
|
307
|
-
|
|
308
|
-
### `Schema.Defect()` options
|
|
309
|
-
|
|
310
|
-
Prefer `Schema.Defect()` by default.
|
|
311
|
-
|
|
312
|
-
Use `Schema.Defect({ includeStack: true })` when:
|
|
313
|
-
|
|
314
|
-
- stack information is part of the intended encoded error contract
|
|
315
|
-
- the error is primarily infrastructural or diagnostic
|
|
316
|
-
- the downstream consumer benefits from the preserved stack
|
|
317
|
-
|
|
318
|
-
### Avoid This Anti-Pattern
|
|
319
|
-
|
|
320
|
-
Avoid exposing raw generic errors directly as the application error contract.
|
|
321
|
-
|
|
322
|
-
Bad:
|
|
323
|
-
|
|
324
|
-
```ts
|
|
325
|
-
const loadTodo = (id: number) =>
|
|
326
|
-
Effect.try({
|
|
327
|
-
try: () => someLibraryCall(id),
|
|
328
|
-
catch: (cause) => cause as Error,
|
|
329
|
-
});
|
|
330
|
-
```
|
|
331
|
-
|
|
332
|
-
Why this is bad:
|
|
333
|
-
|
|
334
|
-
- the error channel loses a stable typed contract
|
|
335
|
-
- the code depends on unsafe assertions
|
|
336
|
-
- transport and schema integration become weaker
|
|
337
|
-
- callers must understand foreign error shapes instead of your own typed error model
|
|
338
|
-
|
|
339
|
-
## Failure Placement
|
|
340
|
-
|
|
341
|
-
- Represent expected domain and service failures with `Effect.fail` and stable,
|
|
342
|
-
schema-backed tagged errors in public error unions.
|
|
343
|
-
- Translate external causes once, adding the domain meaning callers need.
|
|
344
|
-
- Keep raw `Error` and `unknown` out of public expected-error unions.
|
|
345
|
-
- Use `Effect.orDie` only at an explicit runtime boundary when failure is
|
|
346
|
-
intentionally fatal.
|
|
347
|
-
- Reserve synchronous throws for framework contracts, impossible invariants,
|
|
348
|
-
defects, and thunks immediately captured by `Effect.try` or
|
|
349
|
-
`Effect.tryPromise`.
|
|
350
|
-
- Apply the boundary classification and audit process in
|
|
351
|
-
[`guide-type-safety-and-boundaries.md`](guide-type-safety-and-boundaries.md)
|
|
352
|
-
whenever catch callbacks, helpers, or public signatures introduce `unknown`,
|
|
353
|
-
assertions, or runtime shape checks.
|
|
354
|
-
|
|
355
|
-
## Handling Failures
|
|
356
|
-
|
|
357
|
-
### Handle specific tagged errors with `Effect.catchTag`
|
|
358
|
-
|
|
359
|
-
Use `catchTag` when your error type has `_tag` and you want focused recovery.
|
|
360
|
-
|
|
361
|
-
Repo reference:
|
|
362
|
-
|
|
363
|
-
- `packages/effect/src/Effect.ts`
|
|
364
|
-
|
|
365
|
-
Example:
|
|
366
|
-
|
|
367
|
-
```ts
|
|
368
|
-
const recovered = program.pipe(
|
|
369
|
-
Effect.catchTag("UserNotFound", (error) => Effect.succeed({ id: error.userId, guest: true })),
|
|
370
|
-
);
|
|
371
|
-
```
|
|
372
|
-
|
|
373
|
-
### Handle several tagged errors with `Effect.catchTags`
|
|
374
|
-
|
|
375
|
-
Use `catchTags` when multiple domain errors should be handled together.
|
|
376
|
-
|
|
377
|
-
```ts
|
|
378
|
-
const recovered = program.pipe(
|
|
379
|
-
Effect.catchTags({
|
|
380
|
-
UserNotFound: () => Effect.succeed(null),
|
|
381
|
-
InvalidPayload: (error) => Effect.succeed({ error: error.reason }),
|
|
382
|
-
}),
|
|
383
|
-
);
|
|
384
|
-
```
|
|
385
|
-
|
|
386
|
-
### Handle predicate-based subsets with `Effect.catchIf`
|
|
387
|
-
|
|
388
|
-
Use `catchIf` when matching on a predicate or refinement, not just `_tag`.
|
|
389
|
-
|
|
390
|
-
### Turn failure into a value with `Effect.match`
|
|
391
|
-
|
|
392
|
-
Use `match` when you want to fully fold the typed error channel into a success value.
|
|
393
|
-
|
|
394
|
-
```ts
|
|
395
|
-
const outcome = program.pipe(
|
|
396
|
-
Effect.match({
|
|
397
|
-
onFailure: (error) => ({ ok: false as const, error }),
|
|
398
|
-
onSuccess: (value) => ({ ok: true as const, value }),
|
|
399
|
-
}),
|
|
400
|
-
);
|
|
401
|
-
```
|
|
402
|
-
|
|
403
|
-
## Handling Defects
|
|
404
|
-
|
|
405
|
-
Defects are not normal domain failures.
|
|
406
|
-
|
|
407
|
-
They come from:
|
|
408
|
-
|
|
409
|
-
- `Effect.die`
|
|
410
|
-
- unchecked exceptions in effectful code
|
|
411
|
-
- invariants that were broken
|
|
412
|
-
|
|
413
|
-
Repo references:
|
|
414
|
-
|
|
415
|
-
- `packages/effect/src/Cause.ts`
|
|
416
|
-
- `packages/effect/src/Effect.ts`
|
|
417
|
-
|
|
418
|
-
### Preferred rule
|
|
419
|
-
|
|
420
|
-
Do not model expected business failures as defects.
|
|
421
|
-
|
|
422
|
-
Use defects for:
|
|
423
|
-
|
|
424
|
-
- impossible states
|
|
425
|
-
- programmer errors
|
|
426
|
-
- unrecoverable infrastructure corruption
|
|
427
|
-
|
|
428
|
-
### Inspect defects with `sandbox`, `catchCause`, or `matchCause`
|
|
429
|
-
|
|
430
|
-
Use `sandbox` to expose `Cause<E>` in the error channel.
|
|
431
|
-
|
|
432
|
-
```ts
|
|
433
|
-
import { Cause, Effect } from "effect";
|
|
434
|
-
|
|
435
|
-
const diagnosed = program.pipe(
|
|
436
|
-
Effect.sandbox,
|
|
437
|
-
Effect.catchCause((cause) => {
|
|
438
|
-
if (Cause.hasDies(cause)) {
|
|
439
|
-
return Effect.succeed("defect");
|
|
440
|
-
}
|
|
441
|
-
return Effect.failCause(cause);
|
|
442
|
-
}),
|
|
443
|
-
);
|
|
444
|
-
```
|
|
445
|
-
|
|
446
|
-
Use `matchCause` or `matchCauseEffect` when you need to distinguish:
|
|
447
|
-
|
|
448
|
-
- typed failures
|
|
449
|
-
- defects
|
|
450
|
-
- interrupts
|
|
451
|
-
|
|
452
|
-
### Boundary-only recovery for defects
|
|
453
|
-
|
|
454
|
-
If you recover from defects at all, do it only at clear boundaries.
|
|
455
|
-
|
|
456
|
-
Examples:
|
|
457
|
-
|
|
458
|
-
- worker or RPC boundary
|
|
459
|
-
- CLI top-level runner
|
|
460
|
-
- HTTP server adapter
|
|
461
|
-
|
|
462
|
-
Typical pattern:
|
|
463
|
-
|
|
464
|
-
- log or report defect details
|
|
465
|
-
- translate to a safe external error
|
|
466
|
-
- avoid continuing as if it were a normal domain failure
|
|
467
|
-
|
|
468
|
-
### `Effect.orDie`
|
|
469
|
-
|
|
470
|
-
Use `orDie` when an error channel should be treated as unrecoverable from this point onward.
|
|
471
|
-
|
|
472
|
-
That is appropriate when:
|
|
473
|
-
|
|
474
|
-
- a failure has already been validated elsewhere as impossible
|
|
475
|
-
- continuing with typed recovery would only obscure a broken invariant
|
|
476
|
-
|
|
477
|
-
Do not use `orDie` just to silence a type you do not want to handle.
|
|
478
|
-
|
|
479
|
-
## Handling Interrupts
|
|
480
|
-
|
|
481
|
-
Interrupts are cancellation, not business failure.
|
|
482
|
-
|
|
483
|
-
Repo references:
|
|
484
|
-
|
|
485
|
-
- `packages/effect/src/Cause.ts`
|
|
486
|
-
- `packages/effect/src/Effect.ts`
|
|
487
|
-
|
|
488
|
-
### Use `Effect.interrupt` to stop work cooperatively
|
|
489
|
-
|
|
490
|
-
Interrupts signal that the fiber should stop. They should not usually be translated into a domain error.
|
|
491
|
-
|
|
492
|
-
### Use `Effect.onInterrupt` for cleanup
|
|
493
|
-
|
|
494
|
-
If interrupted work needs special cleanup, use `onInterrupt`.
|
|
495
|
-
|
|
496
|
-
```ts
|
|
497
|
-
import { Console, Effect } from "effect";
|
|
498
|
-
|
|
499
|
-
const program = longRunningTask.pipe(
|
|
500
|
-
Effect.onInterrupt(() => Console.log("cleaning up after interrupt")),
|
|
501
|
-
);
|
|
502
|
-
```
|
|
503
|
-
|
|
504
|
-
### Use `Cause` inspection when interrupts must be distinguished
|
|
505
|
-
|
|
506
|
-
When handling full causes, use `Cause.isInterruptReason`, `Cause.hasInterrupts`, or filtering over `cause.reasons`.
|
|
507
|
-
|
|
508
|
-
This is useful for:
|
|
509
|
-
|
|
510
|
-
- deciding whether to suppress logs for normal cancellation
|
|
511
|
-
- keeping retries for failure but not for cancellation
|
|
512
|
-
- distinguishing timeout/cancel flows from real errors
|
|
513
|
-
|
|
514
|
-
### Do not treat interrupts as ordinary failures
|
|
515
|
-
|
|
516
|
-
Avoid patterns that collapse all causes into a single error value too early. Interrupts often need different operational behavior.
|
|
517
|
-
|
|
518
|
-
## Recommended Patterns
|
|
519
|
-
|
|
520
|
-
### Pattern: domain errors inside the app, schema errors at the edge
|
|
521
|
-
|
|
522
|
-
- decode external input with `Schema.decodeUnknownEffect`
|
|
523
|
-
- convert `SchemaError` into a domain or transport error near the boundary
|
|
524
|
-
- keep the rest of the application on domain errors
|
|
525
|
-
|
|
526
|
-
### Pattern: tagged errors for recovery
|
|
527
|
-
|
|
528
|
-
- define domain failures with `Data.TaggedError`
|
|
529
|
-
- recover with `catchTag` or `catchTags`
|
|
530
|
-
- keep `_tag` names stable and descriptive
|
|
531
|
-
|
|
532
|
-
### Pattern: schema-backed errors for protocols
|
|
533
|
-
|
|
534
|
-
- use `Schema.TaggedErrorClass` or `Schema.ErrorClass` when the error contract itself matters
|
|
535
|
-
- follow examples in SQL, socket, RPC, and HTTP modules
|
|
536
|
-
|
|
537
|
-
### Pattern: only inspect `Cause` when you really need the full failure structure
|
|
538
|
-
|
|
539
|
-
Use `catchCause`, `matchCause`, or `sandbox` when you must distinguish:
|
|
540
|
-
|
|
541
|
-
- expected failures
|
|
542
|
-
- defects
|
|
543
|
-
- interrupts
|
|
544
|
-
|
|
545
|
-
Otherwise prefer the simpler typed error operators.
|
|
546
|
-
|
|
547
|
-
## Anti-Patterns
|
|
548
|
-
|
|
549
|
-
- using defects for expected validation or business-rule failures
|
|
550
|
-
- converting every error immediately to `unknown` or `string`
|
|
551
|
-
- using `orDie` to avoid proper handling of expected errors
|
|
552
|
-
- treating interrupts as ordinary business failures
|
|
553
|
-
- leaking `SchemaError` deep into domain code when it should be normalized at the boundary
|
|
554
|
-
|
|
555
|
-
## Good Repo Examples To Study
|
|
556
|
-
|
|
557
|
-
- `packages/effect/src/Data.ts`
|
|
558
|
-
- `packages/effect/src/Cause.ts`
|
|
559
|
-
- `packages/effect/src/Effect.ts`
|
|
560
|
-
- `packages/effect/src/Schema.ts`
|
|
561
|
-
- `packages/effect/src/unstable/sql/SqlError.ts`
|
|
562
|
-
- `packages/effect/src/unstable/http/HttpClientError.ts`
|
|
563
|
-
- `packages/effect/src/unstable/http/HttpServerError.ts`
|
|
564
|
-
- `packages/effect/src/unstable/socket/Socket.ts`
|
|
565
|
-
- `packages/effect/src/unstable/httpapi/HttpApiError.ts`
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
# Effect HTTP Boundaries
|
|
2
|
-
|
|
3
|
-
Use this when changing `HttpApi` contracts, handlers, DTOs, or route boundary
|
|
4
|
-
code.
|
|
5
|
-
|
|
6
|
-
## Contracts
|
|
7
|
-
|
|
8
|
-
Prefer one endpoint per file.
|
|
9
|
-
|
|
10
|
-
The endpoint file declares route params, query, payload, success, and error
|
|
11
|
-
schemas inline. Group and index files compose or re-export only.
|
|
12
|
-
|
|
13
|
-
Do not create endpoint DTO bundle files unless multiple endpoints share a real
|
|
14
|
-
transport type.
|
|
15
|
-
|
|
16
|
-
## Handlers
|
|
17
|
-
|
|
18
|
-
Handlers are thin request-boundary adapters.
|
|
19
|
-
|
|
20
|
-
A handler should decode transport inputs, call one service workflow, and map the
|
|
21
|
-
result to the response DTO.
|
|
22
|
-
|
|
23
|
-
Business orchestration, persistence, rollback, and cross-service coordination
|
|
24
|
-
belong in services, not endpoint files.
|
|
25
|
-
|
|
26
|
-
Do not provide app service layers inside endpoint files unless there is a
|
|
27
|
-
specific transport-only dependency.
|
|
28
|
-
|
|
29
|
-
## DTOs
|
|
30
|
-
|
|
31
|
-
Request DTOs describe the route contract explicitly.
|
|
32
|
-
|
|
33
|
-
Response DTOs describe the wire shape explicitly. If clients see `id`, the DTO
|
|
34
|
-
uses `id`; service models can keep `publicId`.
|
|
35
|
-
|
|
36
|
-
Use small route-layer mappers for service model to wire DTO conversion.
|
|
37
|
-
|
|
38
|
-
Prefer named nested DTOs when fields are reused or transport-sensitive.
|
|
39
|
-
|
|
40
|
-
## Errors
|
|
41
|
-
|
|
42
|
-
Expected HTTP failures belong in endpoint `error:` schemas.
|
|
43
|
-
|
|
44
|
-
Prefer tagged or schema errors with HTTP status metadata.
|
|
45
|
-
|
|
46
|
-
Success schemas describe success only. Do not encode error bodies in `success`.
|
|
47
|
-
|
|
48
|
-
## Transport APIs
|
|
49
|
-
|
|
50
|
-
Use `HttpServerResponse` for upstream response passthrough, redirects, cookies,
|
|
51
|
-
or non-default success status/body behavior.
|
|
52
|
-
|
|
53
|
-
Do not use `HttpServerResponse.jsonUnsafe` for ordinary typed 4xx errors.
|
|
54
|
-
|
|
55
|
-
Prefer Effect request and cookie APIs over manual header or cookie parsing.
|