@danieljvdm/dev-kit 0.6.0 → 0.7.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 +123 -56
- package/dev-kit.example.jsonc +7 -3
- package/package.json +19 -16
- package/schema/dev-kit.schema.json +38 -0
- package/skill-sources.jsonc +8 -12
- package/skill-sources.lock.json +3 -9
- package/skills/dev-kit/SKILL.md +52 -17
- package/skills/effect-ts/agents/openai.yaml +0 -1
- package/skills/effect-ts/references/audit-services.md +11 -11
- package/skills/effect-ts/references/guide-effect.md +56 -69
- package/skills/effect-ts/references/guide-error-handling.md +64 -73
- package/skills/effect-ts/references/guide-layers.md +187 -215
- package/skills/effect-ts/references/guide-observability.md +91 -116
- package/skills/effect-ts/references/guide-retries.md +32 -44
- package/skills/effect-ts/references/guide-schedule.md +26 -40
- package/skills/effect-ts/references/guide-schema.md +50 -57
- package/skills/effect-ts/references/guide-sql.md +47 -50
- package/skills/effect-ts/references/guide-testing.md +96 -98
- package/skills/effect-ts/references/guide-type-safety-and-boundaries.md +7 -7
- package/skills/effect-ts/references/version-and-source.md +0 -1
- package/src/bin/dev-kit.ts +61 -28
- package/src/catalog-manager.ts +86 -34
- package/src/catalog.ts +71 -33
- package/src/cli-ui.ts +20 -16
- package/src/effect-source.ts +49 -19
- package/src/effect-tsgo.ts +66 -35
- package/src/gitignore.ts +19 -6
- package/src/index.ts +6 -0
- package/src/manifest.ts +38 -3
- package/src/node-symbolic-link.ts +3 -0
- package/src/oxlint-plugin-effect.js +3 -0
- package/src/oxlint-plugin-style.d.ts +8 -0
- package/src/oxlint-plugin-style.js +8 -0
- package/src/oxlint.js +14 -0
- package/src/oxlint.ts +14 -0
- package/src/package-skill-source.ts +189 -52
- package/src/path-digest.ts +31 -11
- package/src/project-package.ts +44 -19
- package/src/project-process-lock.ts +19 -12
- package/src/project-state.ts +11 -0
- package/src/skill-manager.ts +134 -55
- package/src/skill-selector.ts +8 -2
- package/src/source-manifest.ts +2 -6
- package/src/sync.ts +371 -103
- package/src/vendor.ts +112 -42
- package/src/vite-plus-hooks.ts +174 -0
- package/src/vite-plus-quality.ts +49 -0
- package/templates/vite-plus/github-actions-check.yml +44 -0
- package/templates/vite-plus/vite.config.ts +22 -0
|
@@ -44,17 +44,17 @@ Enumerate source and test files, then find:
|
|
|
44
44
|
|
|
45
45
|
Record one row per discovered service or candidate:
|
|
46
46
|
|
|
47
|
-
| Field
|
|
48
|
-
|
|
|
49
|
-
| Owner
|
|
50
|
-
| Contract
|
|
51
|
-
| Construction
|
|
52
|
-
| Production
|
|
53
|
-
| Tests
|
|
54
|
-
| Consumers
|
|
55
|
-
| Requirements
|
|
56
|
-
| Type boundary | Who owns decoding, narrowing, and error translation?
|
|
57
|
-
| Verdict
|
|
47
|
+
| Field | Question |
|
|
48
|
+
| ------------- | ----------------------------------------------------------- |
|
|
49
|
+
| Owner | Which module owns the capability's meaning? |
|
|
50
|
+
| Contract | Where are its interface and tag? |
|
|
51
|
+
| Construction | Does construction yield every runtime dependency? |
|
|
52
|
+
| Production | Who owns the concrete implementation and Layer choice? |
|
|
53
|
+
| Tests | Does it have an intentional and honest substitute strategy? |
|
|
54
|
+
| Consumers | Are capabilities yielded or drilled as values? |
|
|
55
|
+
| Requirements | Do requirements remain visible to the composition root? |
|
|
56
|
+
| Type boundary | Who owns decoding, narrowing, and error translation? |
|
|
57
|
+
| Verdict | Keep, deepen, relocate, merge, remove, or create? |
|
|
58
58
|
|
|
59
59
|
Build a companion type-safety inventory using
|
|
60
60
|
[`guide-type-safety-and-boundaries.md`](guide-type-safety-and-boundaries.md).
|
|
@@ -47,11 +47,11 @@ The dominant usage pattern is:
|
|
|
47
47
|
For reusable effectful operations, prefer `Effect.fn`.
|
|
48
48
|
|
|
49
49
|
```ts
|
|
50
|
-
import { Effect } from "effect"
|
|
50
|
+
import { Effect } from "effect";
|
|
51
51
|
|
|
52
|
-
const loadUser = Effect.fn("loadUser")(function*(userId: string) {
|
|
53
|
-
return { id: userId, name: "Ada" }
|
|
54
|
-
})
|
|
52
|
+
const loadUser = Effect.fn("loadUser")(function* (userId: string) {
|
|
53
|
+
return { id: userId, name: "Ada" };
|
|
54
|
+
});
|
|
55
55
|
```
|
|
56
56
|
|
|
57
57
|
Use `Effect.fn` when:
|
|
@@ -73,12 +73,12 @@ Repo examples:
|
|
|
73
73
|
Use `Effect.gen` for orchestration and sequential workflows, especially when there are multiple `yield*` steps.
|
|
74
74
|
|
|
75
75
|
```ts
|
|
76
|
-
const program = Effect.gen(function*() {
|
|
77
|
-
const config = yield* Config
|
|
78
|
-
const repo = yield* UserRepo
|
|
79
|
-
const user = yield* repo.getById("u_123")
|
|
80
|
-
return { config, user }
|
|
81
|
-
})
|
|
76
|
+
const program = Effect.gen(function* () {
|
|
77
|
+
const config = yield* Config;
|
|
78
|
+
const repo = yield* UserRepo;
|
|
79
|
+
const user = yield* repo.getById("u_123");
|
|
80
|
+
return { config, user };
|
|
81
|
+
});
|
|
82
82
|
```
|
|
83
83
|
|
|
84
84
|
Use `Effect.gen` when:
|
|
@@ -103,15 +103,15 @@ Use this rule:
|
|
|
103
103
|
Good split:
|
|
104
104
|
|
|
105
105
|
```ts
|
|
106
|
-
const loadUser = Effect.fn("loadUser")(function*(userId: string) {
|
|
107
|
-
const repo = yield* UserRepo
|
|
108
|
-
return yield* repo.getById(userId)
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
const program = Effect.gen(function*() {
|
|
112
|
-
const user = yield* loadUser("u_123")
|
|
113
|
-
yield* Effect.logInfo("loaded user", user)
|
|
114
|
-
})
|
|
106
|
+
const loadUser = Effect.fn("loadUser")(function* (userId: string) {
|
|
107
|
+
const repo = yield* UserRepo;
|
|
108
|
+
return yield* repo.getById(userId);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const program = Effect.gen(function* () {
|
|
112
|
+
const user = yield* loadUser("u_123");
|
|
113
|
+
yield* Effect.logInfo("loaded user", user);
|
|
114
|
+
});
|
|
115
115
|
```
|
|
116
116
|
|
|
117
117
|
## `Effect.fnUntraced` Is An Escape Hatch
|
|
@@ -127,17 +127,17 @@ Use it only when:
|
|
|
127
127
|
If the only goal is to avoid an explicit named span, prefer:
|
|
128
128
|
|
|
129
129
|
```ts
|
|
130
|
-
const normalizeUser = Effect.fn(function*(input: string) {
|
|
131
|
-
return input.trim().toLowerCase()
|
|
132
|
-
})
|
|
130
|
+
const normalizeUser = Effect.fn(function* (input: string) {
|
|
131
|
+
return input.trim().toLowerCase();
|
|
132
|
+
});
|
|
133
133
|
```
|
|
134
134
|
|
|
135
135
|
Instead of:
|
|
136
136
|
|
|
137
137
|
```ts
|
|
138
|
-
const normalizeUser = Effect.fnUntraced(function*(input: string) {
|
|
139
|
-
return input.trim().toLowerCase()
|
|
140
|
-
})
|
|
138
|
+
const normalizeUser = Effect.fnUntraced(function* (input: string) {
|
|
139
|
+
return input.trim().toLowerCase();
|
|
140
|
+
});
|
|
141
141
|
```
|
|
142
142
|
|
|
143
143
|
## Constructor Functions
|
|
@@ -149,7 +149,7 @@ The repo uses constructor functions very deliberately.
|
|
|
149
149
|
Use for pure successful values.
|
|
150
150
|
|
|
151
151
|
```ts
|
|
152
|
-
const ok = Effect.succeed(42)
|
|
152
|
+
const ok = Effect.succeed(42);
|
|
153
153
|
```
|
|
154
154
|
|
|
155
155
|
### `Effect.fail`
|
|
@@ -157,7 +157,7 @@ const ok = Effect.succeed(42)
|
|
|
157
157
|
Use for expected typed failures.
|
|
158
158
|
|
|
159
159
|
```ts
|
|
160
|
-
const notFound = Effect.fail(UserNotFound.make({ userId: "u_123" }))
|
|
160
|
+
const notFound = Effect.fail(UserNotFound.make({ userId: "u_123" }));
|
|
161
161
|
```
|
|
162
162
|
|
|
163
163
|
### `Effect.sync`
|
|
@@ -165,7 +165,7 @@ const notFound = Effect.fail(UserNotFound.make({ userId: "u_123" }))
|
|
|
165
165
|
Use for synchronous side effects or pure synchronous construction that should live inside `Effect`.
|
|
166
166
|
|
|
167
167
|
```ts
|
|
168
|
-
const buildConfig = Effect.sync(() => ({ retries: 3 }))
|
|
168
|
+
const buildConfig = Effect.sync(() => ({ retries: 3 }));
|
|
169
169
|
```
|
|
170
170
|
|
|
171
171
|
### `Effect.try`
|
|
@@ -173,17 +173,17 @@ const buildConfig = Effect.sync(() => ({ retries: 3 }))
|
|
|
173
173
|
Use for synchronous code that may throw.
|
|
174
174
|
|
|
175
175
|
```ts
|
|
176
|
-
import { Effect, Schema } from "effect"
|
|
176
|
+
import { Effect, Schema } from "effect";
|
|
177
177
|
|
|
178
178
|
class ParseError extends Schema.TaggedErrorClass<ParseError>()("ParseError", {
|
|
179
|
-
cause: Schema.Defect()
|
|
179
|
+
cause: Schema.Defect(),
|
|
180
180
|
}) {}
|
|
181
181
|
|
|
182
182
|
const parseJson = (input: string) =>
|
|
183
183
|
Effect.try({
|
|
184
184
|
try: () => JSON.parse(input),
|
|
185
|
-
catch: (cause) => ParseError.make({ cause })
|
|
186
|
-
})
|
|
185
|
+
catch: (cause) => ParseError.make({ cause }),
|
|
186
|
+
});
|
|
187
187
|
```
|
|
188
188
|
|
|
189
189
|
### `Effect.tryPromise`
|
|
@@ -194,17 +194,17 @@ opaque cause when it is diagnostically useful. Compose Effect-native APIs
|
|
|
194
194
|
directly instead of converting them through Promise.
|
|
195
195
|
|
|
196
196
|
```ts
|
|
197
|
-
import { Effect, Schema } from "effect"
|
|
197
|
+
import { Effect, Schema } from "effect";
|
|
198
198
|
|
|
199
199
|
class FetchError extends Schema.TaggedErrorClass<FetchError>()("FetchError", {
|
|
200
|
-
cause: Schema.Defect()
|
|
200
|
+
cause: Schema.Defect(),
|
|
201
201
|
}) {}
|
|
202
202
|
|
|
203
203
|
const fetchText = (url: string) =>
|
|
204
204
|
Effect.tryPromise({
|
|
205
205
|
try: () => fetch(url).then((response) => response.text()),
|
|
206
|
-
catch: (cause) => FetchError.make({ cause })
|
|
207
|
-
})
|
|
206
|
+
catch: (cause) => FetchError.make({ cause }),
|
|
207
|
+
});
|
|
208
208
|
```
|
|
209
209
|
|
|
210
210
|
Preferred rule:
|
|
@@ -224,9 +224,7 @@ The repo uses `map`, `flatMap`, and `tap` constantly for small local transformat
|
|
|
224
224
|
Use to transform successful values.
|
|
225
225
|
|
|
226
226
|
```ts
|
|
227
|
-
const userName = loadUser("u_123").pipe(
|
|
228
|
-
Effect.map((user) => user.name)
|
|
229
|
-
)
|
|
227
|
+
const userName = loadUser("u_123").pipe(Effect.map((user) => user.name));
|
|
230
228
|
```
|
|
231
229
|
|
|
232
230
|
### `Effect.flatMap`
|
|
@@ -234,9 +232,7 @@ const userName = loadUser("u_123").pipe(
|
|
|
234
232
|
Use when the next step returns another `Effect`.
|
|
235
233
|
|
|
236
234
|
```ts
|
|
237
|
-
const result = loadUser("u_123").pipe(
|
|
238
|
-
Effect.flatMap((user) => saveAudit(user.id))
|
|
239
|
-
)
|
|
235
|
+
const result = loadUser("u_123").pipe(Effect.flatMap((user) => saveAudit(user.id)));
|
|
240
236
|
```
|
|
241
237
|
|
|
242
238
|
### `Effect.tap`
|
|
@@ -245,8 +241,8 @@ Use for side effects that should preserve the main value.
|
|
|
245
241
|
|
|
246
242
|
```ts
|
|
247
243
|
const result = loadUser("u_123").pipe(
|
|
248
|
-
Effect.tap((user) => Effect.logDebug("loaded user", { userId: user.id }))
|
|
249
|
-
)
|
|
244
|
+
Effect.tap((user) => Effect.logDebug("loaded user", { userId: user.id })),
|
|
245
|
+
);
|
|
250
246
|
```
|
|
251
247
|
|
|
252
248
|
Preferred rule:
|
|
@@ -264,27 +260,23 @@ Repo style is:
|
|
|
264
260
|
### Access services in implementations
|
|
265
261
|
|
|
266
262
|
```ts
|
|
267
|
-
const loadUser = Effect.fn("loadUser")(function*(userId: string) {
|
|
268
|
-
const repo = yield* UserRepo
|
|
269
|
-
return yield* repo.getById(userId)
|
|
270
|
-
})
|
|
263
|
+
const loadUser = Effect.fn("loadUser")(function* (userId: string) {
|
|
264
|
+
const repo = yield* UserRepo;
|
|
265
|
+
return yield* repo.getById(userId);
|
|
266
|
+
});
|
|
271
267
|
```
|
|
272
268
|
|
|
273
269
|
or:
|
|
274
270
|
|
|
275
271
|
```ts
|
|
276
272
|
const loadUser = (userId: string) =>
|
|
277
|
-
Effect.service(UserRepo).pipe(
|
|
278
|
-
Effect.flatMap((repo) => repo.getById(userId))
|
|
279
|
-
)
|
|
273
|
+
Effect.service(UserRepo).pipe(Effect.flatMap((repo) => repo.getById(userId)));
|
|
280
274
|
```
|
|
281
275
|
|
|
282
276
|
### Provide at the edge
|
|
283
277
|
|
|
284
278
|
```ts
|
|
285
|
-
const program = loadUser("u_123").pipe(
|
|
286
|
-
Effect.provide(AppLayer)
|
|
287
|
-
)
|
|
279
|
+
const program = loadUser("u_123").pipe(Effect.provide(AppLayer));
|
|
288
280
|
```
|
|
289
281
|
|
|
290
282
|
Use `provideService` and `provideServiceEffect` for targeted overrides, especially in tests or framework boundaries.
|
|
@@ -309,9 +301,7 @@ Common repo patterns:
|
|
|
309
301
|
Use for targeted typed recovery.
|
|
310
302
|
|
|
311
303
|
```ts
|
|
312
|
-
const safe = loadUser("u_123").pipe(
|
|
313
|
-
Effect.catchTag("UserNotFound", () => Effect.succeed(null))
|
|
314
|
-
)
|
|
304
|
+
const safe = loadUser("u_123").pipe(Effect.catchTag("UserNotFound", () => Effect.succeed(null)));
|
|
315
305
|
```
|
|
316
306
|
|
|
317
307
|
### `Effect.match`
|
|
@@ -322,9 +312,9 @@ Use when the caller wants a value either way.
|
|
|
322
312
|
const result = loadUser("u_123").pipe(
|
|
323
313
|
Effect.match({
|
|
324
314
|
onFailure: () => null,
|
|
325
|
-
onSuccess: (user) => user
|
|
326
|
-
})
|
|
327
|
-
)
|
|
315
|
+
onSuccess: (user) => user,
|
|
316
|
+
}),
|
|
317
|
+
);
|
|
328
318
|
```
|
|
329
319
|
|
|
330
320
|
For deeper guidance, see `./references/guide-error-handling.md`.
|
|
@@ -338,10 +328,7 @@ One of the strongest repo patterns is explicit resource ownership.
|
|
|
338
328
|
Use for resources that must be cleaned up.
|
|
339
329
|
|
|
340
330
|
```ts
|
|
341
|
-
const connection = Effect.acquireRelease(
|
|
342
|
-
openConnection,
|
|
343
|
-
(conn) => closeConnection(conn)
|
|
344
|
-
)
|
|
331
|
+
const connection = Effect.acquireRelease(openConnection, (conn) => closeConnection(conn));
|
|
345
332
|
```
|
|
346
333
|
|
|
347
334
|
### `Effect.scoped`
|
|
@@ -350,11 +337,11 @@ Use when a workflow consumes scoped resources and should tie cleanup to scope li
|
|
|
350
337
|
|
|
351
338
|
```ts
|
|
352
339
|
const program = Effect.scoped(
|
|
353
|
-
Effect.gen(function*() {
|
|
354
|
-
const conn = yield* connection
|
|
355
|
-
return yield* conn.query("select 1")
|
|
356
|
-
})
|
|
357
|
-
)
|
|
340
|
+
Effect.gen(function* () {
|
|
341
|
+
const conn = yield* connection;
|
|
342
|
+
return yield* conn.query("select 1");
|
|
343
|
+
}),
|
|
344
|
+
);
|
|
358
345
|
```
|
|
359
346
|
|
|
360
347
|
Repo examples:
|
|
@@ -59,34 +59,34 @@ Repo references:
|
|
|
59
59
|
Example:
|
|
60
60
|
|
|
61
61
|
```ts
|
|
62
|
-
import { Effect, Schema } from "effect"
|
|
62
|
+
import { Effect, Schema } from "effect";
|
|
63
63
|
|
|
64
|
-
class InvalidPayload extends Schema.TaggedErrorClass<InvalidPayload>()(
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
reason: Schema.String
|
|
69
|
-
}
|
|
70
|
-
) {}
|
|
64
|
+
class InvalidPayload extends Schema.TaggedErrorClass<InvalidPayload>()("InvalidPayload", {
|
|
65
|
+
field: Schema.String,
|
|
66
|
+
reason: Schema.String,
|
|
67
|
+
}) {}
|
|
71
68
|
|
|
72
69
|
const validate = Effect.fail(
|
|
73
70
|
InvalidPayload.make({
|
|
74
71
|
field: "email",
|
|
75
|
-
reason: "missing"
|
|
76
|
-
})
|
|
77
|
-
)
|
|
72
|
+
reason: "missing",
|
|
73
|
+
}),
|
|
74
|
+
);
|
|
78
75
|
```
|
|
79
76
|
|
|
80
77
|
Expected application and service failures belong in the typed error channel.
|
|
81
78
|
In generators, use an explicit control-flow exit when failing:
|
|
82
79
|
|
|
83
80
|
```ts
|
|
84
|
-
return
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
81
|
+
return (
|
|
82
|
+
yield *
|
|
83
|
+
Effect.fail(
|
|
84
|
+
InvalidPayload.make({
|
|
85
|
+
field: "email",
|
|
86
|
+
reason: "missing",
|
|
87
|
+
}),
|
|
88
|
+
)
|
|
89
|
+
);
|
|
90
90
|
```
|
|
91
91
|
|
|
92
92
|
Use this when:
|
|
@@ -128,18 +128,17 @@ Repo reference:
|
|
|
128
128
|
Example:
|
|
129
129
|
|
|
130
130
|
```ts
|
|
131
|
-
import { Data, Effect } from "effect"
|
|
131
|
+
import { Data, Effect } from "effect";
|
|
132
132
|
|
|
133
133
|
class UserNotFound extends Data.TaggedError("UserNotFound")<{
|
|
134
|
-
readonly userId: string
|
|
134
|
+
readonly userId: string;
|
|
135
135
|
}> {}
|
|
136
136
|
|
|
137
|
-
const loadUser = (userId: string) =>
|
|
138
|
-
Effect.fail(new UserNotFound({ userId }))
|
|
137
|
+
const loadUser = (userId: string) => Effect.fail(new UserNotFound({ userId }));
|
|
139
138
|
|
|
140
|
-
const program = Effect.gen(function*() {
|
|
141
|
-
yield* loadUser("u_123")
|
|
142
|
-
})
|
|
139
|
+
const program = Effect.gen(function* () {
|
|
140
|
+
yield* loadUser("u_123");
|
|
141
|
+
});
|
|
143
142
|
```
|
|
144
143
|
|
|
145
144
|
## When To Prefer `Data.TaggedError` vs `Schema.TaggedErrorClass`
|
|
@@ -173,14 +172,14 @@ Repo references:
|
|
|
173
172
|
Example:
|
|
174
173
|
|
|
175
174
|
```ts
|
|
176
|
-
import { Effect, Schema } from "effect"
|
|
175
|
+
import { Effect, Schema } from "effect";
|
|
177
176
|
|
|
178
177
|
const UserPayload = Schema.Struct({
|
|
179
178
|
id: Schema.String,
|
|
180
|
-
email: Schema.String
|
|
181
|
-
})
|
|
179
|
+
email: Schema.String,
|
|
180
|
+
});
|
|
182
181
|
|
|
183
|
-
const decodeUser = Schema.decodeUnknownEffect(UserPayload)
|
|
182
|
+
const decodeUser = Schema.decodeUnknownEffect(UserPayload);
|
|
184
183
|
```
|
|
185
184
|
|
|
186
185
|
This gives you:
|
|
@@ -195,23 +194,23 @@ For application code, it is often better to convert `SchemaError` into a domain
|
|
|
195
194
|
Example:
|
|
196
195
|
|
|
197
196
|
```ts
|
|
198
|
-
import { Data, Effect, Schema } from "effect"
|
|
197
|
+
import { Data, Effect, Schema } from "effect";
|
|
199
198
|
|
|
200
199
|
class InvalidRequestBody extends Data.TaggedError("InvalidRequestBody")<{
|
|
201
|
-
readonly message: string
|
|
200
|
+
readonly message: string;
|
|
202
201
|
}> {}
|
|
203
202
|
|
|
204
203
|
const UserPayload = Schema.Struct({
|
|
205
204
|
id: Schema.String,
|
|
206
|
-
email: Schema.String
|
|
207
|
-
})
|
|
205
|
+
email: Schema.String,
|
|
206
|
+
});
|
|
208
207
|
|
|
209
208
|
const decodeUser = (input: unknown) =>
|
|
210
209
|
Schema.decodeUnknownEffect(UserPayload)(input).pipe(
|
|
211
210
|
Effect.catchTag("SchemaError", (error) =>
|
|
212
|
-
Effect.fail(new InvalidRequestBody({ message: error.message }))
|
|
213
|
-
)
|
|
214
|
-
)
|
|
211
|
+
Effect.fail(new InvalidRequestBody({ message: error.message })),
|
|
212
|
+
),
|
|
213
|
+
);
|
|
215
214
|
```
|
|
216
215
|
|
|
217
216
|
Why:
|
|
@@ -262,38 +261,32 @@ Prefer using:
|
|
|
262
261
|
Example:
|
|
263
262
|
|
|
264
263
|
```ts
|
|
265
|
-
import { Effect, Schema } from "effect"
|
|
264
|
+
import { Effect, Schema } from "effect";
|
|
266
265
|
|
|
267
|
-
class TodoStorageError extends Schema.TaggedErrorClass<TodoStorageError>()(
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
cause: Schema.Defect()
|
|
272
|
-
}
|
|
273
|
-
) {}
|
|
266
|
+
class TodoStorageError extends Schema.TaggedErrorClass<TodoStorageError>()("TodoStorageError", {
|
|
267
|
+
operation: Schema.String,
|
|
268
|
+
cause: Schema.Defect(),
|
|
269
|
+
}) {}
|
|
274
270
|
|
|
275
271
|
const makeStorageError = (operation: string) => (cause: unknown) =>
|
|
276
272
|
TodoStorageError.make({
|
|
277
273
|
operation,
|
|
278
|
-
cause
|
|
279
|
-
})
|
|
274
|
+
cause,
|
|
275
|
+
});
|
|
280
276
|
|
|
281
277
|
const loadTodo = (id: number) =>
|
|
282
278
|
Effect.try({
|
|
283
279
|
try: () => someLibraryCall(id),
|
|
284
|
-
catch: makeStorageError("loadTodo")
|
|
285
|
-
})
|
|
280
|
+
catch: makeStorageError("loadTodo"),
|
|
281
|
+
});
|
|
286
282
|
```
|
|
287
283
|
|
|
288
284
|
When stack preservation matters in the encoded schema, prefer:
|
|
289
285
|
|
|
290
286
|
```ts
|
|
291
|
-
class WorkerFailure extends Schema.TaggedErrorClass<WorkerFailure>()(
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
cause: Schema.Defect({ includeStack: true })
|
|
295
|
-
}
|
|
296
|
-
) {}
|
|
287
|
+
class WorkerFailure extends Schema.TaggedErrorClass<WorkerFailure>()("WorkerFailure", {
|
|
288
|
+
cause: Schema.Defect({ includeStack: true }),
|
|
289
|
+
}) {}
|
|
297
290
|
```
|
|
298
291
|
|
|
299
292
|
### Why This Is Preferred
|
|
@@ -332,8 +325,8 @@ Bad:
|
|
|
332
325
|
const loadTodo = (id: number) =>
|
|
333
326
|
Effect.try({
|
|
334
327
|
try: () => someLibraryCall(id),
|
|
335
|
-
catch: (cause) => cause as Error
|
|
336
|
-
})
|
|
328
|
+
catch: (cause) => cause as Error,
|
|
329
|
+
});
|
|
337
330
|
```
|
|
338
331
|
|
|
339
332
|
Why this is bad:
|
|
@@ -373,10 +366,8 @@ Example:
|
|
|
373
366
|
|
|
374
367
|
```ts
|
|
375
368
|
const recovered = program.pipe(
|
|
376
|
-
Effect.catchTag("UserNotFound", (error) =>
|
|
377
|
-
|
|
378
|
-
)
|
|
379
|
-
)
|
|
369
|
+
Effect.catchTag("UserNotFound", (error) => Effect.succeed({ id: error.userId, guest: true })),
|
|
370
|
+
);
|
|
380
371
|
```
|
|
381
372
|
|
|
382
373
|
### Handle several tagged errors with `Effect.catchTags`
|
|
@@ -387,9 +378,9 @@ Use `catchTags` when multiple domain errors should be handled together.
|
|
|
387
378
|
const recovered = program.pipe(
|
|
388
379
|
Effect.catchTags({
|
|
389
380
|
UserNotFound: () => Effect.succeed(null),
|
|
390
|
-
InvalidPayload: (error) => Effect.succeed({ error: error.reason })
|
|
391
|
-
})
|
|
392
|
-
)
|
|
381
|
+
InvalidPayload: (error) => Effect.succeed({ error: error.reason }),
|
|
382
|
+
}),
|
|
383
|
+
);
|
|
393
384
|
```
|
|
394
385
|
|
|
395
386
|
### Handle predicate-based subsets with `Effect.catchIf`
|
|
@@ -404,9 +395,9 @@ Use `match` when you want to fully fold the typed error channel into a success v
|
|
|
404
395
|
const outcome = program.pipe(
|
|
405
396
|
Effect.match({
|
|
406
397
|
onFailure: (error) => ({ ok: false as const, error }),
|
|
407
|
-
onSuccess: (value) => ({ ok: true as const, value })
|
|
408
|
-
})
|
|
409
|
-
)
|
|
398
|
+
onSuccess: (value) => ({ ok: true as const, value }),
|
|
399
|
+
}),
|
|
400
|
+
);
|
|
410
401
|
```
|
|
411
402
|
|
|
412
403
|
## Handling Defects
|
|
@@ -439,17 +430,17 @@ Use defects for:
|
|
|
439
430
|
Use `sandbox` to expose `Cause<E>` in the error channel.
|
|
440
431
|
|
|
441
432
|
```ts
|
|
442
|
-
import { Cause, Effect } from "effect"
|
|
433
|
+
import { Cause, Effect } from "effect";
|
|
443
434
|
|
|
444
435
|
const diagnosed = program.pipe(
|
|
445
436
|
Effect.sandbox,
|
|
446
437
|
Effect.catchCause((cause) => {
|
|
447
438
|
if (Cause.hasDies(cause)) {
|
|
448
|
-
return Effect.succeed("defect")
|
|
439
|
+
return Effect.succeed("defect");
|
|
449
440
|
}
|
|
450
|
-
return Effect.failCause(cause)
|
|
451
|
-
})
|
|
452
|
-
)
|
|
441
|
+
return Effect.failCause(cause);
|
|
442
|
+
}),
|
|
443
|
+
);
|
|
453
444
|
```
|
|
454
445
|
|
|
455
446
|
Use `matchCause` or `matchCauseEffect` when you need to distinguish:
|
|
@@ -503,11 +494,11 @@ Interrupts signal that the fiber should stop. They should not usually be transla
|
|
|
503
494
|
If interrupted work needs special cleanup, use `onInterrupt`.
|
|
504
495
|
|
|
505
496
|
```ts
|
|
506
|
-
import { Console, Effect } from "effect"
|
|
497
|
+
import { Console, Effect } from "effect";
|
|
507
498
|
|
|
508
499
|
const program = longRunningTask.pipe(
|
|
509
|
-
Effect.onInterrupt(() => Console.log("cleaning up after interrupt"))
|
|
510
|
-
)
|
|
500
|
+
Effect.onInterrupt(() => Console.log("cleaning up after interrupt")),
|
|
501
|
+
);
|
|
511
502
|
```
|
|
512
503
|
|
|
513
504
|
### Use `Cause` inspection when interrupts must be distinguished
|