@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
|
@@ -83,18 +83,18 @@ There are two good definition styles:
|
|
|
83
83
|
Example:
|
|
84
84
|
|
|
85
85
|
```ts
|
|
86
|
-
import { Context, Effect, Schema } from "effect"
|
|
86
|
+
import { Context, Effect, Schema } from "effect";
|
|
87
87
|
|
|
88
|
-
class UserRepoError extends Schema.TaggedErrorClass<UserRepoError>()(
|
|
89
|
-
|
|
88
|
+
class UserRepoError extends Schema.TaggedErrorClass<UserRepoError>()("UserRepoError", {
|
|
89
|
+
message: Schema.String,
|
|
90
|
+
}) {}
|
|
91
|
+
|
|
92
|
+
class UserRepo extends Context.Service<
|
|
93
|
+
UserRepo,
|
|
90
94
|
{
|
|
91
|
-
|
|
95
|
+
readonly getById: (id: string) => Effect.Effect<{ id: string; name: string }, UserRepoError>;
|
|
92
96
|
}
|
|
93
|
-
) {}
|
|
94
|
-
|
|
95
|
-
class UserRepo extends Context.Service<UserRepo, {
|
|
96
|
-
readonly getById: (id: string) => Effect.Effect<{ id: string; name: string }, UserRepoError>
|
|
97
|
-
}>()("UserRepo") {}
|
|
97
|
+
>()("UserRepo") {}
|
|
98
98
|
```
|
|
99
99
|
|
|
100
100
|
Why this style is preferred:
|
|
@@ -123,23 +123,18 @@ Repo reference:
|
|
|
123
123
|
When the implementation shape is clearer than the interface declaration, prefer using the `make` argument so the service shape is inferred from the implementation.
|
|
124
124
|
|
|
125
125
|
```ts
|
|
126
|
-
import { Context, Effect, Schema } from "effect"
|
|
126
|
+
import { Context, Effect, Schema } from "effect";
|
|
127
127
|
|
|
128
|
-
class UserRepoError extends Schema.TaggedErrorClass<UserRepoError>()(
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
message: Schema.String
|
|
132
|
-
}
|
|
133
|
-
) {}
|
|
128
|
+
class UserRepoError extends Schema.TaggedErrorClass<UserRepoError>()("UserRepoError", {
|
|
129
|
+
message: Schema.String,
|
|
130
|
+
}) {}
|
|
134
131
|
|
|
135
132
|
class UserRepo extends Context.Service<UserRepo>()("UserRepo", {
|
|
136
133
|
make: Effect.succeed({
|
|
137
|
-
getById: Effect.fn("UserRepo.getById")(function*(id: string) {
|
|
138
|
-
return yield* Effect.fail(
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
})
|
|
142
|
-
})
|
|
134
|
+
getById: Effect.fn("UserRepo.getById")(function* (id: string) {
|
|
135
|
+
return yield* Effect.fail(UserRepoError.make({ message: `User ${id} not found` }));
|
|
136
|
+
}),
|
|
137
|
+
}),
|
|
143
138
|
}) {}
|
|
144
139
|
```
|
|
145
140
|
|
|
@@ -162,24 +157,24 @@ Prefer the explicit generic shape when:
|
|
|
162
157
|
## Service Example
|
|
163
158
|
|
|
164
159
|
```ts
|
|
165
|
-
import { Context, Effect, Schema } from "effect"
|
|
160
|
+
import { Context, Effect, Schema } from "effect";
|
|
161
|
+
|
|
162
|
+
class UserNotFound extends Schema.TaggedErrorClass<UserNotFound>()("UserNotFound", {
|
|
163
|
+
userId: Schema.String,
|
|
164
|
+
}) {}
|
|
166
165
|
|
|
167
|
-
class
|
|
168
|
-
|
|
166
|
+
class UserRepo extends Context.Service<
|
|
167
|
+
UserRepo,
|
|
169
168
|
{
|
|
170
|
-
|
|
169
|
+
readonly getById: (id: string) => Effect.Effect<{ id: string; name: string }, UserNotFound>;
|
|
171
170
|
}
|
|
172
|
-
) {}
|
|
173
|
-
|
|
174
|
-
class UserRepo extends Context.Service<UserRepo, {
|
|
175
|
-
readonly getById: (id: string) => Effect.Effect<{ id: string; name: string }, UserNotFound>
|
|
176
|
-
}>()("UserRepo") {}
|
|
171
|
+
>()("UserRepo") {}
|
|
177
172
|
|
|
178
173
|
const loadUser = (userId: string) =>
|
|
179
|
-
Effect.gen(function*() {
|
|
180
|
-
const repo = yield* UserRepo
|
|
181
|
-
return yield* repo.getById(userId)
|
|
182
|
-
})
|
|
174
|
+
Effect.gen(function* () {
|
|
175
|
+
const repo = yield* UserRepo;
|
|
176
|
+
return yield* repo.getById(userId);
|
|
177
|
+
});
|
|
183
178
|
```
|
|
184
179
|
|
|
185
180
|
Key points:
|
|
@@ -213,18 +208,16 @@ Use a full service instead when:
|
|
|
213
208
|
Common patterns:
|
|
214
209
|
|
|
215
210
|
```ts
|
|
216
|
-
const program = Effect.gen(function*() {
|
|
217
|
-
const repo = yield* UserRepo
|
|
218
|
-
return yield* repo.getById("u_123")
|
|
219
|
-
})
|
|
211
|
+
const program = Effect.gen(function* () {
|
|
212
|
+
const repo = yield* UserRepo;
|
|
213
|
+
return yield* repo.getById("u_123");
|
|
214
|
+
});
|
|
220
215
|
```
|
|
221
216
|
|
|
222
217
|
or:
|
|
223
218
|
|
|
224
219
|
```ts
|
|
225
|
-
const program = Effect.service(UserRepo).pipe(
|
|
226
|
-
Effect.flatMap((repo) => repo.getById("u_123"))
|
|
227
|
-
)
|
|
220
|
+
const program = Effect.service(UserRepo).pipe(Effect.flatMap((repo) => repo.getById("u_123")));
|
|
228
221
|
```
|
|
229
222
|
|
|
230
223
|
Best practice:
|
|
@@ -239,10 +232,10 @@ Prefer keeping service access inside the business operation that needs it rather
|
|
|
239
232
|
Avoid this pattern:
|
|
240
233
|
|
|
241
234
|
```ts
|
|
242
|
-
export const createTodo = Effect.fn(function*(title: string) {
|
|
243
|
-
const todos = yield* TodoService
|
|
244
|
-
return yield* todos.create(title)
|
|
245
|
-
})
|
|
235
|
+
export const createTodo = Effect.fn(function* (title: string) {
|
|
236
|
+
const todos = yield* TodoService;
|
|
237
|
+
return yield* todos.create(title);
|
|
238
|
+
});
|
|
246
239
|
```
|
|
247
240
|
|
|
248
241
|
Why this is usually a bad pattern:
|
|
@@ -261,14 +254,14 @@ Prefer one of these patterns instead:
|
|
|
261
254
|
Good:
|
|
262
255
|
|
|
263
256
|
```ts
|
|
264
|
-
export const completeTodo = Effect.fn("completeTodo")(function*(id: number) {
|
|
265
|
-
const todos = yield* TodoService
|
|
266
|
-
const todo = yield* todos.getById(id)
|
|
257
|
+
export const completeTodo = Effect.fn("completeTodo")(function* (id: number) {
|
|
258
|
+
const todos = yield* TodoService;
|
|
259
|
+
const todo = yield* todos.getById(id);
|
|
267
260
|
if (todo.completed) {
|
|
268
|
-
return todo
|
|
261
|
+
return todo;
|
|
269
262
|
}
|
|
270
|
-
return yield* todos.setCompleted(id, true)
|
|
271
|
-
})
|
|
263
|
+
return yield* todos.setCompleted(id, true);
|
|
264
|
+
});
|
|
272
265
|
```
|
|
273
266
|
|
|
274
267
|
This is good because:
|
|
@@ -315,8 +308,8 @@ Use for pure, already-constructed implementations.
|
|
|
315
308
|
|
|
316
309
|
```ts
|
|
317
310
|
const UserRepoTest = Layer.succeed(UserRepo)({
|
|
318
|
-
getById: (id) => Effect.succeed({ id, name: "Test User" })
|
|
319
|
-
})
|
|
311
|
+
getById: (id) => Effect.succeed({ id, name: "Test User" }),
|
|
312
|
+
});
|
|
320
313
|
```
|
|
321
314
|
|
|
322
315
|
Use this when:
|
|
@@ -330,23 +323,26 @@ Use this when:
|
|
|
330
323
|
Use when constructing a service requires effects, other services, or scoped resource acquisition.
|
|
331
324
|
|
|
332
325
|
```ts
|
|
333
|
-
class Config extends Context.Service<
|
|
334
|
-
|
|
335
|
-
|
|
326
|
+
class Config extends Context.Service<
|
|
327
|
+
Config,
|
|
328
|
+
{
|
|
329
|
+
readonly apiBaseUrl: string;
|
|
330
|
+
}
|
|
331
|
+
>()("Config") {}
|
|
336
332
|
|
|
337
333
|
const UserRepoLayer = Layer.effect(UserRepo)(
|
|
338
|
-
Effect.gen(function*() {
|
|
339
|
-
const config = yield* Config
|
|
334
|
+
Effect.gen(function* () {
|
|
335
|
+
const config = yield* Config;
|
|
340
336
|
|
|
341
337
|
return {
|
|
342
338
|
getById: (id) =>
|
|
343
339
|
Effect.succeed({
|
|
344
340
|
id,
|
|
345
|
-
name: `Fetched from ${config.apiBaseUrl}
|
|
346
|
-
})
|
|
347
|
-
}
|
|
348
|
-
})
|
|
349
|
-
)
|
|
341
|
+
name: `Fetched from ${config.apiBaseUrl}`,
|
|
342
|
+
}),
|
|
343
|
+
};
|
|
344
|
+
}),
|
|
345
|
+
);
|
|
350
346
|
```
|
|
351
347
|
|
|
352
348
|
Use this when:
|
|
@@ -395,42 +391,51 @@ These operators do different things. Do not treat them as interchangeable.
|
|
|
395
391
|
### Example Services
|
|
396
392
|
|
|
397
393
|
```ts
|
|
398
|
-
import { Context, Effect, Layer } from "effect"
|
|
394
|
+
import { Context, Effect, Layer } from "effect";
|
|
399
395
|
|
|
400
|
-
class Config extends Context.Service<
|
|
401
|
-
|
|
402
|
-
|
|
396
|
+
class Config extends Context.Service<
|
|
397
|
+
Config,
|
|
398
|
+
{
|
|
399
|
+
readonly apiBaseUrl: string;
|
|
400
|
+
}
|
|
401
|
+
>()("Config") {}
|
|
403
402
|
|
|
404
|
-
class Logger extends Context.Service<
|
|
405
|
-
|
|
406
|
-
|
|
403
|
+
class Logger extends Context.Service<
|
|
404
|
+
Logger,
|
|
405
|
+
{
|
|
406
|
+
readonly log: (message: string) => Effect.Effect<void>;
|
|
407
|
+
}
|
|
408
|
+
>()("Logger") {}
|
|
407
409
|
|
|
408
|
-
class UserRepo extends Context.Service<
|
|
409
|
-
|
|
410
|
-
|
|
410
|
+
class UserRepo extends Context.Service<
|
|
411
|
+
UserRepo,
|
|
412
|
+
{
|
|
413
|
+
readonly getById: (id: string) => Effect.Effect<{ id: string; name: string }>;
|
|
414
|
+
}
|
|
415
|
+
>()("UserRepo") {}
|
|
411
416
|
|
|
412
417
|
const ConfigLayer = Layer.succeed(Config)({
|
|
413
|
-
apiBaseUrl: "https://api.example.com"
|
|
414
|
-
})
|
|
418
|
+
apiBaseUrl: "https://api.example.com",
|
|
419
|
+
});
|
|
415
420
|
|
|
416
421
|
const LoggerLayer = Layer.succeed(Logger)({
|
|
417
|
-
log: (message) => Effect.sync(() => console.log(message))
|
|
418
|
-
})
|
|
422
|
+
log: (message) => Effect.sync(() => console.log(message)),
|
|
423
|
+
});
|
|
419
424
|
|
|
420
425
|
const UserRepoLayer = Layer.effect(UserRepo)(
|
|
421
|
-
Effect.gen(function*() {
|
|
422
|
-
const config = yield* Config
|
|
423
|
-
const logger = yield* Logger
|
|
426
|
+
Effect.gen(function* () {
|
|
427
|
+
const config = yield* Config;
|
|
428
|
+
const logger = yield* Logger;
|
|
424
429
|
|
|
425
430
|
return {
|
|
426
431
|
getById: (id) =>
|
|
427
|
-
Effect.gen(function*() {
|
|
428
|
-
yield* logger.log(`loading ${id} from ${config.apiBaseUrl}`)
|
|
429
|
-
return { id, name: "Ada" }
|
|
430
|
-
})
|
|
431
|
-
}
|
|
432
|
-
})
|
|
433
|
-
)
|
|
432
|
+
Effect.gen(function* () {
|
|
433
|
+
yield* logger.log(`loading ${id} from ${config.apiBaseUrl}`);
|
|
434
|
+
return { id, name: "Ada" };
|
|
435
|
+
}),
|
|
436
|
+
};
|
|
437
|
+
}),
|
|
438
|
+
);
|
|
434
439
|
```
|
|
435
440
|
|
|
436
441
|
### `Layer.mergeAll`
|
|
@@ -438,10 +443,7 @@ const UserRepoLayer = Layer.effect(UserRepo)(
|
|
|
438
443
|
Use `Layer.mergeAll` to combine outputs of independent layers.
|
|
439
444
|
|
|
440
445
|
```ts
|
|
441
|
-
const Dependencies = Layer.mergeAll(
|
|
442
|
-
ConfigLayer,
|
|
443
|
-
LoggerLayer
|
|
444
|
-
)
|
|
446
|
+
const Dependencies = Layer.mergeAll(ConfigLayer, LoggerLayer);
|
|
445
447
|
```
|
|
446
448
|
|
|
447
449
|
Use this when:
|
|
@@ -464,10 +466,7 @@ Important:
|
|
|
464
466
|
Correct pattern:
|
|
465
467
|
|
|
466
468
|
```ts
|
|
467
|
-
const Dependencies = Layer.mergeAll(
|
|
468
|
-
ConfigLayer,
|
|
469
|
-
LoggerLayer
|
|
470
|
-
)
|
|
469
|
+
const Dependencies = Layer.mergeAll(ConfigLayer, LoggerLayer);
|
|
471
470
|
```
|
|
472
471
|
|
|
473
472
|
### `Layer.provide`
|
|
@@ -475,12 +474,9 @@ const Dependencies = Layer.mergeAll(
|
|
|
475
474
|
Use `Layer.provide` to satisfy a target layer's dependencies with another layer, while keeping only the target layer's outputs.
|
|
476
475
|
|
|
477
476
|
```ts
|
|
478
|
-
const Dependencies = Layer.mergeAll(
|
|
479
|
-
ConfigLayer,
|
|
480
|
-
LoggerLayer
|
|
481
|
-
)
|
|
477
|
+
const Dependencies = Layer.mergeAll(ConfigLayer, LoggerLayer);
|
|
482
478
|
|
|
483
|
-
const UserRepoLayerReady = Layer.provide(UserRepoLayer, Dependencies)
|
|
479
|
+
const UserRepoLayerReady = Layer.provide(UserRepoLayer, Dependencies);
|
|
484
480
|
```
|
|
485
481
|
|
|
486
482
|
Interpretation:
|
|
@@ -495,12 +491,10 @@ This is the operator to use when you want to hide construction dependencies behi
|
|
|
495
491
|
Example program:
|
|
496
492
|
|
|
497
493
|
```ts
|
|
498
|
-
const program = Effect.gen(function*() {
|
|
499
|
-
const repo = yield* UserRepo
|
|
500
|
-
return yield* repo.getById("u_123")
|
|
501
|
-
}).pipe(
|
|
502
|
-
Effect.provide(UserRepoLayerReady)
|
|
503
|
-
)
|
|
494
|
+
const program = Effect.gen(function* () {
|
|
495
|
+
const repo = yield* UserRepo;
|
|
496
|
+
return yield* repo.getById("u_123");
|
|
497
|
+
}).pipe(Effect.provide(UserRepoLayerReady));
|
|
504
498
|
```
|
|
505
499
|
|
|
506
500
|
### `Layer.provideMerge`
|
|
@@ -508,12 +502,9 @@ const program = Effect.gen(function*() {
|
|
|
508
502
|
Use `provideMerge` when you want to satisfy dependencies and retain both the dependency outputs and the target outputs.
|
|
509
503
|
|
|
510
504
|
```ts
|
|
511
|
-
const Dependencies = Layer.mergeAll(
|
|
512
|
-
ConfigLayer,
|
|
513
|
-
LoggerLayer
|
|
514
|
-
)
|
|
505
|
+
const Dependencies = Layer.mergeAll(ConfigLayer, LoggerLayer);
|
|
515
506
|
|
|
516
|
-
const AppLayer = Layer.provideMerge(UserRepoLayer, Dependencies)
|
|
507
|
+
const AppLayer = Layer.provideMerge(UserRepoLayer, Dependencies);
|
|
517
508
|
```
|
|
518
509
|
|
|
519
510
|
Interpretation:
|
|
@@ -526,16 +517,14 @@ This is useful for assembling larger application layers incrementally, especiall
|
|
|
526
517
|
Example program:
|
|
527
518
|
|
|
528
519
|
```ts
|
|
529
|
-
const program = Effect.gen(function*() {
|
|
530
|
-
const repo = yield* UserRepo
|
|
531
|
-
const logger = yield* Logger
|
|
532
|
-
|
|
533
|
-
const user = yield* repo.getById("u_123")
|
|
534
|
-
yield* logger.log(user.name)
|
|
535
|
-
return user
|
|
536
|
-
}).pipe(
|
|
537
|
-
Effect.provide(AppLayer)
|
|
538
|
-
)
|
|
520
|
+
const program = Effect.gen(function* () {
|
|
521
|
+
const repo = yield* UserRepo;
|
|
522
|
+
const logger = yield* Logger;
|
|
523
|
+
|
|
524
|
+
const user = yield* repo.getById("u_123");
|
|
525
|
+
yield* logger.log(user.name);
|
|
526
|
+
return user;
|
|
527
|
+
}).pipe(Effect.provide(AppLayer));
|
|
539
528
|
```
|
|
540
529
|
|
|
541
530
|
Preferred rule:
|
|
@@ -566,29 +555,18 @@ Preferred style:
|
|
|
566
555
|
Good pattern:
|
|
567
556
|
|
|
568
557
|
```ts
|
|
569
|
-
const UserDependencies = Layer.mergeAll(
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
LoggerLayer,
|
|
579
|
-
DatabaseLayer
|
|
580
|
-
)
|
|
581
|
-
|
|
582
|
-
const BillingLayer = Layer.provide(BillingServiceLayer, BillingDependencies)
|
|
583
|
-
|
|
584
|
-
const AppLayer = Layer.mergeAll(
|
|
585
|
-
UserLayer,
|
|
586
|
-
BillingLayer,
|
|
587
|
-
HttpLayer
|
|
588
|
-
).pipe(
|
|
558
|
+
const UserDependencies = Layer.mergeAll(ConfigLayer, LoggerLayer);
|
|
559
|
+
|
|
560
|
+
const UserLayer = Layer.provide(UserRepoLayer, UserDependencies);
|
|
561
|
+
|
|
562
|
+
const BillingDependencies = Layer.mergeAll(ConfigLayer, LoggerLayer, DatabaseLayer);
|
|
563
|
+
|
|
564
|
+
const BillingLayer = Layer.provide(BillingServiceLayer, BillingDependencies);
|
|
565
|
+
|
|
566
|
+
const AppLayer = Layer.mergeAll(UserLayer, BillingLayer, HttpLayer).pipe(
|
|
589
567
|
Layer.provide(Telemetry),
|
|
590
|
-
Layer.provide(NodeSdk)
|
|
591
|
-
)
|
|
568
|
+
Layer.provide(NodeSdk),
|
|
569
|
+
);
|
|
592
570
|
```
|
|
593
571
|
|
|
594
572
|
Why this style is preferred:
|
|
@@ -605,10 +583,10 @@ const AppLayer = Layer.provide(
|
|
|
605
583
|
Layer.mergeAll(
|
|
606
584
|
Layer.provide(UserRepoLayer, Layer.mergeAll(ConfigLayer, LoggerLayer)),
|
|
607
585
|
Layer.provide(BillingServiceLayer, Layer.mergeAll(ConfigLayer, LoggerLayer, DatabaseLayer)),
|
|
608
|
-
HttpLayer
|
|
586
|
+
HttpLayer,
|
|
609
587
|
),
|
|
610
|
-
Telemetry
|
|
611
|
-
).pipe(Layer.provide(NodeSdk))
|
|
588
|
+
Telemetry,
|
|
589
|
+
).pipe(Layer.provide(NodeSdk));
|
|
612
590
|
```
|
|
613
591
|
|
|
614
592
|
That style is harder to read because:
|
|
@@ -626,9 +604,9 @@ Example:
|
|
|
626
604
|
```ts
|
|
627
605
|
const UserRepoLayerFromConfig = Layer.flatMap(ConfigLayer, (config) =>
|
|
628
606
|
Layer.succeed(UserRepo)({
|
|
629
|
-
getById: (id) => Effect.succeed({ id, name: config.apiBaseUrl })
|
|
630
|
-
})
|
|
631
|
-
)
|
|
607
|
+
getById: (id) => Effect.succeed({ id, name: config.apiBaseUrl }),
|
|
608
|
+
}),
|
|
609
|
+
);
|
|
632
610
|
```
|
|
633
611
|
|
|
634
612
|
This is more specialized than `merge` or `provide`.
|
|
@@ -661,12 +639,10 @@ Bad pattern:
|
|
|
661
639
|
|
|
662
640
|
```ts
|
|
663
641
|
const loadUser = (userId: string) =>
|
|
664
|
-
Effect.gen(function*() {
|
|
665
|
-
const repo = yield* UserRepo
|
|
666
|
-
return yield* repo.getById(userId)
|
|
667
|
-
}).pipe(
|
|
668
|
-
Effect.provide(UserRepoLayer)
|
|
669
|
-
)
|
|
642
|
+
Effect.gen(function* () {
|
|
643
|
+
const repo = yield* UserRepo;
|
|
644
|
+
return yield* repo.getById(userId);
|
|
645
|
+
}).pipe(Effect.provide(UserRepoLayer));
|
|
670
646
|
```
|
|
671
647
|
|
|
672
648
|
Why this is an anti-pattern:
|
|
@@ -681,14 +657,12 @@ Preferred pattern:
|
|
|
681
657
|
|
|
682
658
|
```ts
|
|
683
659
|
const loadUser = (userId: string) =>
|
|
684
|
-
Effect.gen(function*() {
|
|
685
|
-
const repo = yield* UserRepo
|
|
686
|
-
return yield* repo.getById(userId)
|
|
687
|
-
})
|
|
660
|
+
Effect.gen(function* () {
|
|
661
|
+
const repo = yield* UserRepo;
|
|
662
|
+
return yield* repo.getById(userId);
|
|
663
|
+
});
|
|
688
664
|
|
|
689
|
-
const program = loadUser("u_123").pipe(
|
|
690
|
-
Effect.provide(AppLayer)
|
|
691
|
-
)
|
|
665
|
+
const program = loadUser("u_123").pipe(Effect.provide(AppLayer));
|
|
692
666
|
```
|
|
693
667
|
|
|
694
668
|
Rule of thumb:
|
|
@@ -712,10 +686,9 @@ Typical examples:
|
|
|
712
686
|
Preferred pattern:
|
|
713
687
|
|
|
714
688
|
```ts
|
|
715
|
-
const runtime = ManagedRuntime.make(AppLayer)
|
|
689
|
+
const runtime = ManagedRuntime.make(AppLayer);
|
|
716
690
|
|
|
717
|
-
const handleRequest = (id: string) =>
|
|
718
|
-
runtime.runPromise(loadUser(id))
|
|
691
|
+
const handleRequest = (id: string) => runtime.runPromise(loadUser(id));
|
|
719
692
|
```
|
|
720
693
|
|
|
721
694
|
Why:
|
|
@@ -734,9 +707,7 @@ Repo reference:
|
|
|
734
707
|
Use `Effect.provide` to satisfy an effect's dependencies with a layer or context.
|
|
735
708
|
|
|
736
709
|
```ts
|
|
737
|
-
const program = loadUser("u_123").pipe(
|
|
738
|
-
Effect.provide(UserRepoLayerReady)
|
|
739
|
-
)
|
|
710
|
+
const program = loadUser("u_123").pipe(Effect.provide(UserRepoLayerReady));
|
|
740
711
|
```
|
|
741
712
|
|
|
742
713
|
This is the main boundary provisioning operator.
|
|
@@ -748,9 +719,9 @@ Use `provideService` for a single ad hoc implementation.
|
|
|
748
719
|
```ts
|
|
749
720
|
const program = loadUser("u_123").pipe(
|
|
750
721
|
Effect.provideService(UserRepo, {
|
|
751
|
-
getById: (id) => Effect.succeed({ id, name: "Inline User" })
|
|
752
|
-
})
|
|
753
|
-
)
|
|
722
|
+
getById: (id) => Effect.succeed({ id, name: "Inline User" }),
|
|
723
|
+
}),
|
|
724
|
+
);
|
|
754
725
|
```
|
|
755
726
|
|
|
756
727
|
Good use cases:
|
|
@@ -812,11 +783,11 @@ Good:
|
|
|
812
783
|
|
|
813
784
|
```ts
|
|
814
785
|
const sendWelcomeEmail = (userId: string) =>
|
|
815
|
-
Effect.gen(function*() {
|
|
816
|
-
const repo = yield* UserRepo
|
|
817
|
-
const user = yield* repo.getById(userId)
|
|
818
|
-
return user
|
|
819
|
-
})
|
|
786
|
+
Effect.gen(function* () {
|
|
787
|
+
const repo = yield* UserRepo;
|
|
788
|
+
const user = yield* repo.getById(userId);
|
|
789
|
+
return user;
|
|
790
|
+
});
|
|
820
791
|
```
|
|
821
792
|
|
|
822
793
|
Avoid constructing `UserRepo` inside `sendWelcomeEmail`.
|
|
@@ -880,7 +851,7 @@ dependencies or configuration:
|
|
|
880
851
|
```ts
|
|
881
852
|
// Avoid: requirements disappear from the Layer input channel.
|
|
882
853
|
const makeUserRepoLayer = (config: Config, client: HttpClient) =>
|
|
883
|
-
Layer.effect(UserRepo)(makeUserRepo(config, client))
|
|
854
|
+
Layer.effect(UserRepo)(makeUserRepo(config, client));
|
|
884
855
|
```
|
|
885
856
|
|
|
886
857
|
Model configuration, clients, databases, clocks, platform bindings, and
|
|
@@ -890,17 +861,14 @@ outer composition boundary:
|
|
|
890
861
|
|
|
891
862
|
```ts
|
|
892
863
|
const UserRepoLayer = Layer.effect(UserRepo)(
|
|
893
|
-
Effect.gen(function*() {
|
|
894
|
-
const config = yield* Config
|
|
895
|
-
const client = yield* HttpClient
|
|
896
|
-
return UserRepo.of(makeUserRepo(config, client))
|
|
897
|
-
})
|
|
898
|
-
)
|
|
899
|
-
|
|
900
|
-
const AppLayer = UserRepoLayer.pipe(
|
|
901
|
-
Layer.provide(ConfigLayer),
|
|
902
|
-
Layer.provide(HttpClientLayer)
|
|
903
|
-
)
|
|
864
|
+
Effect.gen(function* () {
|
|
865
|
+
const config = yield* Config;
|
|
866
|
+
const client = yield* HttpClient;
|
|
867
|
+
return UserRepo.of(makeUserRepo(config, client));
|
|
868
|
+
}),
|
|
869
|
+
);
|
|
870
|
+
|
|
871
|
+
const AppLayer = UserRepoLayer.pipe(Layer.provide(ConfigLayer), Layer.provide(HttpClientLayer));
|
|
904
872
|
```
|
|
905
873
|
|
|
906
874
|
Arguments are acceptable only when they are intrinsic definitions that create
|
|
@@ -944,56 +912,60 @@ This keeps test wiring explicit and close to production composition style.
|
|
|
944
912
|
## Pattern: service definition plus live layer
|
|
945
913
|
|
|
946
914
|
```ts
|
|
947
|
-
import { Context, Effect, Layer } from "effect"
|
|
915
|
+
import { Context, Effect, Layer } from "effect";
|
|
948
916
|
|
|
949
|
-
class Config extends Context.Service<
|
|
950
|
-
|
|
951
|
-
|
|
917
|
+
class Config extends Context.Service<
|
|
918
|
+
Config,
|
|
919
|
+
{
|
|
920
|
+
readonly apiBaseUrl: string;
|
|
921
|
+
}
|
|
922
|
+
>()("Config") {}
|
|
952
923
|
|
|
953
|
-
class UserRepo extends Context.Service<
|
|
954
|
-
|
|
955
|
-
|
|
924
|
+
class UserRepo extends Context.Service<
|
|
925
|
+
UserRepo,
|
|
926
|
+
{
|
|
927
|
+
readonly getById: (id: string) => Effect.Effect<{ id: string; name: string }>;
|
|
928
|
+
}
|
|
929
|
+
>()("UserRepo") {}
|
|
956
930
|
|
|
957
931
|
const ConfigLayer = Layer.succeed(Config)({
|
|
958
|
-
apiBaseUrl: "https://api.example.com"
|
|
959
|
-
})
|
|
932
|
+
apiBaseUrl: "https://api.example.com",
|
|
933
|
+
});
|
|
960
934
|
|
|
961
935
|
const UserRepoLayer = Layer.effect(UserRepo)(
|
|
962
|
-
Effect.gen(function*() {
|
|
963
|
-
const config = yield* Config
|
|
936
|
+
Effect.gen(function* () {
|
|
937
|
+
const config = yield* Config;
|
|
964
938
|
|
|
965
939
|
return {
|
|
966
940
|
getById: (id) =>
|
|
967
941
|
Effect.succeed({
|
|
968
942
|
id,
|
|
969
|
-
name: `Loaded via ${config.apiBaseUrl}
|
|
970
|
-
})
|
|
971
|
-
}
|
|
972
|
-
})
|
|
973
|
-
)
|
|
943
|
+
name: `Loaded via ${config.apiBaseUrl}`,
|
|
944
|
+
}),
|
|
945
|
+
};
|
|
946
|
+
}),
|
|
947
|
+
);
|
|
974
948
|
|
|
975
|
-
const Dependencies = Layer.mergeAll(ConfigLayer)
|
|
949
|
+
const Dependencies = Layer.mergeAll(ConfigLayer);
|
|
976
950
|
|
|
977
|
-
const AppLayer = Layer.provide(UserRepoLayer, Dependencies)
|
|
951
|
+
const AppLayer = Layer.provide(UserRepoLayer, Dependencies);
|
|
978
952
|
```
|
|
979
953
|
|
|
980
954
|
## Pattern: provide at the top level
|
|
981
955
|
|
|
982
956
|
```ts
|
|
983
|
-
const program = Effect.gen(function*() {
|
|
984
|
-
const repo = yield* UserRepo
|
|
985
|
-
return yield* repo.getById("u_123")
|
|
986
|
-
}).pipe(
|
|
987
|
-
Effect.provide(AppLayer)
|
|
988
|
-
)
|
|
957
|
+
const program = Effect.gen(function* () {
|
|
958
|
+
const repo = yield* UserRepo;
|
|
959
|
+
return yield* repo.getById("u_123");
|
|
960
|
+
}).pipe(Effect.provide(AppLayer));
|
|
989
961
|
```
|
|
990
962
|
|
|
991
963
|
## Pattern: single-service override in tests
|
|
992
964
|
|
|
993
965
|
```ts
|
|
994
966
|
const TestRepo = Layer.succeed(UserRepo)({
|
|
995
|
-
getById: (id) => Effect.succeed({ id, name: "Test" })
|
|
996
|
-
})
|
|
967
|
+
getById: (id) => Effect.succeed({ id, name: "Test" }),
|
|
968
|
+
});
|
|
997
969
|
```
|
|
998
970
|
|
|
999
971
|
## Anti-Patterns
|