@danieljvdm/dev-kit 0.2.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.
Files changed (47) hide show
  1. package/README.md +290 -0
  2. package/bin/dev-kit.mjs +3 -0
  3. package/dev-kit.example.jsonc +13 -0
  4. package/package.json +69 -0
  5. package/schema/dev-kit.schema.json +128 -0
  6. package/schema/skill-sources.schema.json +83 -0
  7. package/skill-sources.jsonc +55 -0
  8. package/skill-sources.lock.json +136 -0
  9. package/skills/dev-kit/SKILL.md +145 -0
  10. package/skills/dev-kit/agents/openai.yaml +4 -0
  11. package/skills/effect-ts/SKILL.md +242 -0
  12. package/skills/effect-ts/UPSTREAM.md +28 -0
  13. package/skills/effect-ts/agents/openai.yaml +5 -0
  14. package/skills/effect-ts/references/audit-services.md +144 -0
  15. package/skills/effect-ts/references/features.md +525 -0
  16. package/skills/effect-ts/references/guide-cli.md +106 -0
  17. package/skills/effect-ts/references/guide-effect.md +453 -0
  18. package/skills/effect-ts/references/guide-error-handling.md +574 -0
  19. package/skills/effect-ts/references/guide-http-boundaries.md +55 -0
  20. package/skills/effect-ts/references/guide-layers.md +1017 -0
  21. package/skills/effect-ts/references/guide-observability.md +771 -0
  22. package/skills/effect-ts/references/guide-retries.md +446 -0
  23. package/skills/effect-ts/references/guide-schedule.md +357 -0
  24. package/skills/effect-ts/references/guide-schema.md +671 -0
  25. package/skills/effect-ts/references/guide-sql.md +539 -0
  26. package/skills/effect-ts/references/guide-testing.md +534 -0
  27. package/skills/effect-ts/references/guide-type-safety-and-boundaries.md +131 -0
  28. package/skills/effect-ts/references/version-and-source.md +87 -0
  29. package/src/bin/dev-kit.ts +372 -0
  30. package/src/catalog-manager.ts +345 -0
  31. package/src/catalog.ts +246 -0
  32. package/src/cli-ui.ts +110 -0
  33. package/src/effect-source.ts +325 -0
  34. package/src/effect-tsgo.ts +256 -0
  35. package/src/gitignore.ts +212 -0
  36. package/src/index.ts +98 -0
  37. package/src/manifest.ts +133 -0
  38. package/src/node-symbolic-link.ts +31 -0
  39. package/src/path-digest.ts +140 -0
  40. package/src/project-process-lock.ts +76 -0
  41. package/src/project-state.ts +67 -0
  42. package/src/skill-manager.ts +326 -0
  43. package/src/source-manifest.ts +51 -0
  44. package/src/sync.ts +900 -0
  45. package/src/tool-metadata.ts +3 -0
  46. package/src/typescript-package-name.ts +5 -0
  47. package/src/vendor.ts +848 -0
@@ -0,0 +1,1017 @@
1
+ # Layers Guide
2
+
3
+ This guide covers service ownership, service design, Layer construction,
4
+ dependency visibility, composition, and provisioning.
5
+
6
+ Key source files:
7
+
8
+ - `packages/effect/src/Context.ts`
9
+ - `packages/effect/src/Layer.ts`
10
+ - `packages/effect/src/Effect.ts`
11
+ - `packages/effect/src/ManagedRuntime.ts`
12
+
13
+ ## Mental Model
14
+
15
+ A service is a typed dependency.
16
+
17
+ A layer is a recipe for building one or more services, possibly using other services as dependencies.
18
+
19
+ Effect's model is:
20
+
21
+ - define service identifiers with `Context.Service` or `Context.Service(...)`
22
+ - require services from effects with `Effect.service` or by yielding the service key directly
23
+ - build implementations with `Layer`
24
+ - provide layers at the program boundary or subsystem boundary
25
+
26
+ `Layer<ROut, E, RIn>` means:
27
+
28
+ - `ROut`: services produced by the layer
29
+ - `E`: possible failures while constructing the layer
30
+ - `RIn`: dependencies required to build it
31
+
32
+ Repo references:
33
+
34
+ - `packages/effect/src/Context.ts`
35
+ - `packages/effect/src/Layer.ts`
36
+
37
+ ## Service Or Value Decision
38
+
39
+ Treat a service as an authority seam: a cohesive capability whose requirements
40
+ should propagate through the Effect environment.
41
+
42
+ Create or retain a service when it owns persistence, credentials, external I/O,
43
+ runtime resources, configuration, time, randomness, lifecycle, reusable
44
+ effectful policy, or state with meaningful production and test variation.
45
+ Prefer an existing Effect capability such as `Clock`, `Config`, `Random`,
46
+ `HttpClient`, `FileSystem`, or `Path` when it already owns that authority.
47
+
48
+ Keep parsed inputs, per-call request data, deterministic calculations,
49
+ adapter-confined framework values, and forwarding wrappers as values or pure
50
+ modules.
51
+
52
+ Use the deletion test: if removing the service merely eliminates indirection,
53
+ it is probably not an authority seam; if removal spreads resource ownership,
54
+ policy, or implementation complexity into callers, it probably is. Test
55
+ convenience by itself does not justify a service.
56
+
57
+ ## Services
58
+
59
+ ## What A Service Is
60
+
61
+ A service is a typed key plus its implementation shape.
62
+
63
+ In Effect, services are values in `Context`, not global singletons.
64
+
65
+ This gives you:
66
+
67
+ - explicit dependencies
68
+ - easy substitution in tests
69
+ - layer-based composition
70
+ - multiple implementations for the same interface
71
+
72
+ ## Preferred Service Definition Style
73
+
74
+ Prefer the class syntax with `Context.Service`.
75
+
76
+ This matches the canonical source's current style.
77
+
78
+ There are two good definition styles:
79
+
80
+ - explicit service shape in the `Context.Service<...>` generic
81
+ - inferred service shape from the `make` argument
82
+
83
+ Example:
84
+
85
+ ```ts
86
+ import { Context, Effect, Schema } from "effect"
87
+
88
+ class UserRepoError extends Schema.TaggedErrorClass<UserRepoError>()(
89
+ "UserRepoError",
90
+ {
91
+ message: Schema.String
92
+ }
93
+ ) {}
94
+
95
+ class UserRepo extends Context.Service<UserRepo, {
96
+ readonly getById: (id: string) => Effect.Effect<{ id: string; name: string }, UserRepoError>
97
+ }>()("UserRepo") {}
98
+ ```
99
+
100
+ Why this style is preferred:
101
+
102
+ - the service identifier and shape live in one place
103
+ - it works naturally with `yield* UserRepo`
104
+ - it matches the patterns used across canonical `Effect-TS/effect` source
105
+
106
+ Keep the tag and implementation layer together when one module owns both. If
107
+ the application owns a port and a technology adapter owns its implementation,
108
+ keep the contract beside the application operation and concrete construction
109
+ in the adapter module.
110
+
111
+ Prefer a static lowercase `layer` for the default implementation unless the
112
+ repository already has a consistent `Live` convention. Use `layerNoDeps` when
113
+ calling out dependency-free construction is useful. Avoid splitting
114
+ `foo-service.ts` and `foo-service-live.ts` before there is a real ownership
115
+ boundary.
116
+
117
+ Repo reference:
118
+
119
+ - `packages/effect/src/Context.ts`
120
+
121
+ ## Service Shape Inference With `make`
122
+
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
+
125
+ ```ts
126
+ import { Context, Effect, Schema } from "effect"
127
+
128
+ class UserRepoError extends Schema.TaggedErrorClass<UserRepoError>()(
129
+ "UserRepoError",
130
+ {
131
+ message: Schema.String
132
+ }
133
+ ) {}
134
+
135
+ class UserRepo extends Context.Service<UserRepo>()("UserRepo", {
136
+ make: Effect.succeed({
137
+ getById: Effect.fn("UserRepo.getById")(function*(id: string) {
138
+ return yield* Effect.fail(
139
+ UserRepoError.make({ message: `User ${id} not found` })
140
+ )
141
+ })
142
+ })
143
+ }) {}
144
+ ```
145
+
146
+ Why this style is useful:
147
+
148
+ - the implementation and inferred API stay together
149
+ - TypeScript derives the service shape automatically
150
+ - it avoids repeating the same method signatures twice
151
+
152
+ Prefer this style when:
153
+
154
+ - the implementation is small and obvious
155
+ - the explicit service shape would only duplicate the implementation
156
+
157
+ Prefer the explicit generic shape when:
158
+
159
+ - you want the contract stated before the implementation
160
+ - the API surface should be emphasized separately from the implementation
161
+
162
+ ## Service Example
163
+
164
+ ```ts
165
+ import { Context, Effect, Schema } from "effect"
166
+
167
+ class UserNotFound extends Schema.TaggedErrorClass<UserNotFound>()(
168
+ "UserNotFound",
169
+ {
170
+ userId: Schema.String
171
+ }
172
+ ) {}
173
+
174
+ class UserRepo extends Context.Service<UserRepo, {
175
+ readonly getById: (id: string) => Effect.Effect<{ id: string; name: string }, UserNotFound>
176
+ }>()("UserRepo") {}
177
+
178
+ const loadUser = (userId: string) =>
179
+ Effect.gen(function*() {
180
+ const repo = yield* UserRepo
181
+ return yield* repo.getById(userId)
182
+ })
183
+ ```
184
+
185
+ Key points:
186
+
187
+ - `UserRepo` is both the identifier and a value you can yield from `Effect.gen`
188
+ - the implementation shape is explicit in the service definition
189
+ - the effect that uses it stays abstract over the implementation
190
+
191
+ ## When To Use `Context.Reference`
192
+
193
+ Use `Context.Reference` for contextual values with defaults, not for full service APIs.
194
+
195
+ Good use cases:
196
+
197
+ - current configuration knobs
198
+ - current request metadata
199
+ - feature flags or tracing flags with defaults
200
+
201
+ Repo reference:
202
+
203
+ - `packages/effect/src/Context.ts`
204
+
205
+ Use a full service instead when:
206
+
207
+ - behavior matters more than data
208
+ - you need multiple methods
209
+ - you want a concrete test double or alternate implementation
210
+
211
+ ## Accessing Services
212
+
213
+ Common patterns:
214
+
215
+ ```ts
216
+ const program = Effect.gen(function*() {
217
+ const repo = yield* UserRepo
218
+ return yield* repo.getById("u_123")
219
+ })
220
+ ```
221
+
222
+ or:
223
+
224
+ ```ts
225
+ const program = Effect.service(UserRepo).pipe(
226
+ Effect.flatMap((repo) => repo.getById("u_123"))
227
+ )
228
+ ```
229
+
230
+ Best practice:
231
+
232
+ - use `yield* Service` or `Effect.service(Service)` inside business logic
233
+ - do not manually thread service implementations through function arguments when they are real application dependencies
234
+
235
+ ## Service Encapsulation
236
+
237
+ Prefer keeping service access inside the business operation that needs it rather than exporting thin accessor wrappers for every method.
238
+
239
+ Avoid this pattern:
240
+
241
+ ```ts
242
+ export const createTodo = Effect.fn(function*(title: string) {
243
+ const todos = yield* TodoService
244
+ return yield* todos.create(title)
245
+ })
246
+ ```
247
+
248
+ Why this is usually a bad pattern:
249
+
250
+ - it leaks the service dependency into a second public API layer
251
+ - it encourages a redundant accessor function per service method
252
+ - it spreads dependency access patterns across the codebase
253
+ - it weakens service encapsulation instead of improving it
254
+
255
+ Prefer one of these patterns instead:
256
+
257
+ 1. Put the real business logic in a function that uses the service internally because it adds behavior beyond simple forwarding.
258
+ 2. Expose the service itself and call its methods from the module that owns the workflow.
259
+ 3. If you need a public operation, make it a real business operation, not a trivial alias of one service method.
260
+
261
+ Good:
262
+
263
+ ```ts
264
+ export const completeTodo = Effect.fn("completeTodo")(function*(id: number) {
265
+ const todos = yield* TodoService
266
+ const todo = yield* todos.getById(id)
267
+ if (todo.completed) {
268
+ return todo
269
+ }
270
+ return yield* todos.setCompleted(id, true)
271
+ })
272
+ ```
273
+
274
+ This is good because:
275
+
276
+ - the exported function represents a business operation
277
+ - the service remains an internal dependency of that operation
278
+ - the function adds behavior rather than just forwarding one method call
279
+
280
+ ## Domain-Specific Service Abstractions
281
+
282
+ When a repository provides a stronger domain-specific `Tag` helper, prefer it
283
+ over plain `Context.Service`. The helper should wrap `Context.Service`, attach
284
+ the canonical construction effect, and expose the default Layer so a named
285
+ domain definition is declared once.
286
+
287
+ Keep intrinsic definitions and canonical options inline in that declaration.
288
+ Extract them only when they are reused, composed, tested independently, or
289
+ selected dynamically.
290
+
291
+ Avoid factories that return nested service namespaces and force callers to
292
+ yield an inner tag or supply the canonical definition repeatedly. A reusable
293
+ domain abstraction should expose its canonical tag, value constructor, and
294
+ Layer directly. Use domain verbs such as `send`, `dispatch`, `snapshot`,
295
+ `execute`, or `close`; reserve `request` for real request/response protocols.
296
+
297
+ ## Layers
298
+
299
+ ## What A Layer Is
300
+
301
+ A layer constructs services from dependencies.
302
+
303
+ Use a layer when:
304
+
305
+ - service construction is effectful
306
+ - the service depends on other services
307
+ - the service owns resources that must be acquired and released safely
308
+ - you want composition and reuse across modules
309
+
310
+ ## Preferred Layer Constructors
311
+
312
+ ### `Layer.succeed`
313
+
314
+ Use for pure, already-constructed implementations.
315
+
316
+ ```ts
317
+ const UserRepoTest = Layer.succeed(UserRepo)({
318
+ getById: (id) => Effect.succeed({ id, name: "Test User" })
319
+ })
320
+ ```
321
+
322
+ Use this when:
323
+
324
+ - construction is pure
325
+ - no dependencies are needed
326
+ - no scoped resources are involved
327
+
328
+ ### `Layer.effect`
329
+
330
+ Use when constructing a service requires effects, other services, or scoped resource acquisition.
331
+
332
+ ```ts
333
+ class Config extends Context.Service<Config, {
334
+ readonly apiBaseUrl: string
335
+ }>()("Config") {}
336
+
337
+ const UserRepoLayer = Layer.effect(UserRepo)(
338
+ Effect.gen(function*() {
339
+ const config = yield* Config
340
+
341
+ return {
342
+ getById: (id) =>
343
+ Effect.succeed({
344
+ id,
345
+ name: `Fetched from ${config.apiBaseUrl}`
346
+ })
347
+ }
348
+ })
349
+ )
350
+ ```
351
+
352
+ Use this when:
353
+
354
+ - construction is effectful
355
+ - construction depends on other services
356
+ - construction needs `Scope` and finalization
357
+ - you want typed construction failure
358
+
359
+ This is also the correct constructor for services that own resources with acquisition and release semantics. In this repo, `Layer.effect` replaces the old `Layer.scoped` API.
360
+
361
+ Typical examples:
362
+
363
+ - database pools
364
+ - sockets
365
+ - background worker processes
366
+ - long-lived subscriptions
367
+
368
+ ### `Layer.effectDiscard`
369
+
370
+ Use `Layer.effectDiscard` for scoped startup effects that do not provide a service.
371
+
372
+ Good use cases:
373
+
374
+ - starting background fibers in a layer
375
+ - one-time scoped initialization side effects
376
+ - subsystem startup hooks
377
+
378
+ ### `Layer.effectContext`
379
+
380
+ Use when one effect constructs a full `Context` containing multiple services.
381
+
382
+ This is useful for subsystem builders that provide several related services together.
383
+
384
+ ## Layer Composition
385
+
386
+ These operators do different things. Do not treat them as interchangeable.
387
+
388
+ ### Composition Cheat Sheet
389
+
390
+ - `Layer.mergeAll(a, b, ...)`: combine outputs of multiple layers
391
+ - `Layer.provide(target, dependencies)`: feed dependency outputs into `target` and keep only `target` outputs
392
+ - `Layer.provideMerge(target, dependencies)`: feed dependency outputs into `target` and keep both dependency outputs and target outputs
393
+ - `Layer.flatMap(layer, f)`: choose the next layer based on the built service value
394
+
395
+ ### Example Services
396
+
397
+ ```ts
398
+ import { Context, Effect, Layer } from "effect"
399
+
400
+ class Config extends Context.Service<Config, {
401
+ readonly apiBaseUrl: string
402
+ }>()("Config") {}
403
+
404
+ class Logger extends Context.Service<Logger, {
405
+ readonly log: (message: string) => Effect.Effect<void>
406
+ }>()("Logger") {}
407
+
408
+ class UserRepo extends Context.Service<UserRepo, {
409
+ readonly getById: (id: string) => Effect.Effect<{ id: string; name: string }>
410
+ }>()("UserRepo") {}
411
+
412
+ const ConfigLayer = Layer.succeed(Config)({
413
+ apiBaseUrl: "https://api.example.com"
414
+ })
415
+
416
+ const LoggerLayer = Layer.succeed(Logger)({
417
+ log: (message) => Effect.sync(() => console.log(message))
418
+ })
419
+
420
+ const UserRepoLayer = Layer.effect(UserRepo)(
421
+ Effect.gen(function*() {
422
+ const config = yield* Config
423
+ const logger = yield* Logger
424
+
425
+ return {
426
+ getById: (id) =>
427
+ Effect.gen(function*() {
428
+ yield* logger.log(`loading ${id} from ${config.apiBaseUrl}`)
429
+ return { id, name: "Ada" }
430
+ })
431
+ }
432
+ })
433
+ )
434
+ ```
435
+
436
+ ### `Layer.mergeAll`
437
+
438
+ Use `Layer.mergeAll` to combine outputs of independent layers.
439
+
440
+ ```ts
441
+ const Dependencies = Layer.mergeAll(
442
+ ConfigLayer,
443
+ LoggerLayer
444
+ )
445
+ ```
446
+
447
+ Use this when:
448
+
449
+ - the layers provide different services
450
+ - neither needs to transform the other directly
451
+
452
+ Semantics:
453
+
454
+ - inputs are combined
455
+ - outputs are combined
456
+ - no dependency feeding happens automatically
457
+
458
+ Important:
459
+
460
+ - `Layer.mergeAll(ConfigLayer, UserRepoLayer)` is wrong if `UserRepoLayer` requires `Config` and `Logger`
461
+ - `mergeAll` does not satisfy `UserRepoLayer`'s requirements
462
+ - it only places both layers side by side in the output graph
463
+
464
+ Correct pattern:
465
+
466
+ ```ts
467
+ const Dependencies = Layer.mergeAll(
468
+ ConfigLayer,
469
+ LoggerLayer
470
+ )
471
+ ```
472
+
473
+ ### `Layer.provide`
474
+
475
+ Use `Layer.provide` to satisfy a target layer's dependencies with another layer, while keeping only the target layer's outputs.
476
+
477
+ ```ts
478
+ const Dependencies = Layer.mergeAll(
479
+ ConfigLayer,
480
+ LoggerLayer
481
+ )
482
+
483
+ const UserRepoLayerReady = Layer.provide(UserRepoLayer, Dependencies)
484
+ ```
485
+
486
+ Interpretation:
487
+
488
+ - `UserRepoLayer` requires `Config` and `Logger`
489
+ - `Dependencies` provides those dependencies
490
+ - the resulting layer provides only `UserRepo`
491
+ - `Config` and `Logger` are used for construction but are not kept in the final output
492
+
493
+ This is the operator to use when you want to hide construction dependencies behind a narrower public layer.
494
+
495
+ Example program:
496
+
497
+ ```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
+ )
504
+ ```
505
+
506
+ ### `Layer.provideMerge`
507
+
508
+ Use `provideMerge` when you want to satisfy dependencies and retain both the dependency outputs and the target outputs.
509
+
510
+ ```ts
511
+ const Dependencies = Layer.mergeAll(
512
+ ConfigLayer,
513
+ LoggerLayer
514
+ )
515
+
516
+ const AppLayer = Layer.provideMerge(UserRepoLayer, Dependencies)
517
+ ```
518
+
519
+ Interpretation:
520
+
521
+ - `UserRepoLayer` still gets `Config` and `Logger`
522
+ - the resulting layer provides `UserRepo`, `Config`, and `Logger`
523
+
524
+ This is useful for assembling larger application layers incrementally, especially when downstream code still needs access to the dependencies.
525
+
526
+ Example program:
527
+
528
+ ```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
+ )
539
+ ```
540
+
541
+ Preferred rule:
542
+
543
+ - use `provide` when you want to hide dependency details
544
+ - use `provideMerge` when you want to keep dependency services available downstream
545
+
546
+ ### `Layer.mergeAll` vs `Layer.provide` vs `Layer.provideMerge`
547
+
548
+ Think of them like this:
549
+
550
+ - `mergeAll`: put layers next to each other
551
+ - `provide`: plug one layer into another, expose only the target
552
+ - `provideMerge`: plug one layer into another, expose both sides
553
+
554
+ ### Composition Style Best Practice
555
+
556
+ Layers should almost always be fully composed locally before they are assembled into the final application layer.
557
+
558
+ Preferred style:
559
+
560
+ - define each service layer separately
561
+ - define local subsystem dependency bundles separately
562
+ - fully compose each subsystem locally with `Layer.provide` or `Layer.provideMerge`
563
+ - assemble the final application layer with `Layer.mergeAll(...)`
564
+ - apply top-level cross-cutting provisioning in a small number of explicit trailing steps
565
+
566
+ Good pattern:
567
+
568
+ ```ts
569
+ const UserDependencies = Layer.mergeAll(
570
+ ConfigLayer,
571
+ LoggerLayer
572
+ )
573
+
574
+ const UserLayer = Layer.provide(UserRepoLayer, UserDependencies)
575
+
576
+ const BillingDependencies = Layer.mergeAll(
577
+ ConfigLayer,
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(
589
+ Layer.provide(Telemetry),
590
+ Layer.provide(NodeSdk)
591
+ )
592
+ ```
593
+
594
+ Why this style is preferred:
595
+
596
+ - subsystem wiring stays local to the subsystem
597
+ - the final application layer reads as a high-level composition map
598
+ - cross-cutting concerns such as telemetry stay visible at the top level
599
+ - it avoids deeply nested inline layer expressions
600
+
601
+ Avoid this style when a clearer local name would help:
602
+
603
+ ```ts
604
+ const AppLayer = Layer.provide(
605
+ Layer.mergeAll(
606
+ Layer.provide(UserRepoLayer, Layer.mergeAll(ConfigLayer, LoggerLayer)),
607
+ Layer.provide(BillingServiceLayer, Layer.mergeAll(ConfigLayer, LoggerLayer, DatabaseLayer)),
608
+ HttpLayer
609
+ ),
610
+ Telemetry
611
+ ).pipe(Layer.provide(NodeSdk))
612
+ ```
613
+
614
+ That style is harder to read because:
615
+
616
+ - subsystem composition is hidden inside the final assembly
617
+ - shared dependencies are harder to spot
618
+ - it is harder to refactor or reuse subsystem layers
619
+
620
+ ### `Layer.flatMap`
621
+
622
+ Use `flatMap` when the next layer depends on the actual constructed service value, not just its type-level requirement.
623
+
624
+ Example:
625
+
626
+ ```ts
627
+ const UserRepoLayerFromConfig = Layer.flatMap(ConfigLayer, (config) =>
628
+ Layer.succeed(UserRepo)({
629
+ getById: (id) => Effect.succeed({ id, name: config.apiBaseUrl })
630
+ })
631
+ )
632
+ ```
633
+
634
+ This is more specialized than `merge` or `provide`.
635
+
636
+ Prefer simpler composition first:
637
+
638
+ - `merge` for combining
639
+ - `provide` for dependency satisfaction
640
+ - `flatMap` only when construction logic truly depends on the built value
641
+
642
+ ## Providing Layers To Effects
643
+
644
+ ## Preferred Rule
645
+
646
+ Provide layers at boundaries.
647
+
648
+ Usually that means:
649
+
650
+ - the application entrypoint
651
+ - a subsystem entrypoint
652
+ - a test boundary
653
+
654
+ Avoid repeatedly providing layers deep inside business logic unless you are deliberately isolating a subsystem.
655
+
656
+ ### Anti-Pattern: Local `Effect.provide`
657
+
658
+ `Effect.provide` should be used only once at the entry of your program in normal application code.
659
+
660
+ Bad pattern:
661
+
662
+ ```ts
663
+ 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
+ )
670
+ ```
671
+
672
+ Why this is an anti-pattern:
673
+
674
+ - it hides dependency wiring inside business logic
675
+ - it makes implementations harder to swap in tests
676
+ - it prevents clean top-level composition
677
+ - it encourages many small local runtimes instead of one coherent application graph
678
+ - it makes shared cross-cutting services harder to reason about
679
+
680
+ Preferred pattern:
681
+
682
+ ```ts
683
+ const loadUser = (userId: string) =>
684
+ Effect.gen(function*() {
685
+ const repo = yield* UserRepo
686
+ return yield* repo.getById(userId)
687
+ })
688
+
689
+ const program = loadUser("u_123").pipe(
690
+ Effect.provide(AppLayer)
691
+ )
692
+ ```
693
+
694
+ Rule of thumb:
695
+
696
+ - business logic should require services
697
+ - composition should happen in layers
698
+ - `Effect.provide` should happen at the outermost entry boundary
699
+
700
+ ### Multiple Entry Points
701
+
702
+ If your code integrates with a framework and has multiple entry points, prefer `ManagedRuntime` instead of repeatedly calling `Effect.provide` at many call sites.
703
+
704
+ Typical examples:
705
+
706
+ - HTTP handlers registered separately
707
+ - queue consumers
708
+ - cron jobs
709
+ - framework lifecycle hooks
710
+ - RPC handlers or worker callbacks
711
+
712
+ Preferred pattern:
713
+
714
+ ```ts
715
+ const runtime = ManagedRuntime.make(AppLayer)
716
+
717
+ const handleRequest = (id: string) =>
718
+ runtime.runPromise(loadUser(id))
719
+ ```
720
+
721
+ Why:
722
+
723
+ - the layer graph is still composed once
724
+ - services remain shared according to layer semantics
725
+ - the framework integration gets a stable runtime boundary
726
+ - resource lifecycle is explicit through `ManagedRuntime`
727
+
728
+ Repo reference:
729
+
730
+ - `packages/effect/src/ManagedRuntime.ts`
731
+
732
+ ## `Effect.provide`
733
+
734
+ Use `Effect.provide` to satisfy an effect's dependencies with a layer or context.
735
+
736
+ ```ts
737
+ const program = loadUser("u_123").pipe(
738
+ Effect.provide(UserRepoLayerReady)
739
+ )
740
+ ```
741
+
742
+ This is the main boundary provisioning operator.
743
+
744
+ ## `Effect.provideService`
745
+
746
+ Use `provideService` for a single ad hoc implementation.
747
+
748
+ ```ts
749
+ const program = loadUser("u_123").pipe(
750
+ Effect.provideService(UserRepo, {
751
+ getById: (id) => Effect.succeed({ id, name: "Inline User" })
752
+ })
753
+ )
754
+ ```
755
+
756
+ Good use cases:
757
+
758
+ - small tests
759
+ - one-off overrides
760
+ - local customization
761
+
762
+ Do not use this as the default replacement for real application layers.
763
+
764
+ ## `Effect.provideServiceEffect`
765
+
766
+ Use `provideServiceEffect` when one service instance must be built effectfully without creating a reusable layer.
767
+
768
+ This is useful for targeted overrides, but if the construction is reusable or part of application wiring, prefer a named `Layer.effect`.
769
+
770
+ ## Best Practices
771
+
772
+ ## 1. Keep service interfaces small and focused
773
+
774
+ Prefer cohesive services over giant "everything" services.
775
+
776
+ Good:
777
+
778
+ - `UserRepo`
779
+ - `Mailer`
780
+ - `Clock`-like configuration or time abstractions
781
+
782
+ Avoid:
783
+
784
+ - large service shapes that mix unrelated responsibilities
785
+
786
+ ## 2. Prefer layers over manual wiring
787
+
788
+ If construction has dependencies or effects, represent it as a layer.
789
+
790
+ Avoid manually grabbing dependencies and assembling concrete objects all over the codebase.
791
+
792
+ ## 2.5 Prefer Effect-native integrations over raw runtime clients
793
+
794
+ When Effect already provides a module for a capability, prefer the Effect-native integration over directly embedding a raw runtime client in service code.
795
+
796
+ Examples:
797
+
798
+ - prefer `effect/unstable/sql` modules over directly coupling business services to native SQL driver APIs
799
+ - prefer Effect HTTP modules over direct ad hoc request clients when the project is already using Effect HTTP abstractions
800
+
801
+ Why:
802
+
803
+ - resource handling, tracing, and errors stay inside the Effect model
804
+ - integrations compose better with layers and services
805
+ - observability and transactions are easier to keep consistent
806
+
807
+ ## 3. Keep business logic abstract over implementations
808
+
809
+ Business functions should require services, not construct them.
810
+
811
+ Good:
812
+
813
+ ```ts
814
+ const sendWelcomeEmail = (userId: string) =>
815
+ Effect.gen(function*() {
816
+ const repo = yield* UserRepo
817
+ const user = yield* repo.getById(userId)
818
+ return user
819
+ })
820
+ ```
821
+
822
+ Avoid constructing `UserRepo` inside `sendWelcomeEmail`.
823
+
824
+ Service methods take domain inputs only. Yield configuration, clients,
825
+ databases, clocks, loggers, request context, platform bindings, and other
826
+ runtime dependencies from the Effect environment. Do not recreate dependency
827
+ injection through method, constructor, options-bag, or implementation-helper
828
+ parameters; yield a dependency while constructing the service and close over
829
+ it in the method implementation.
830
+
831
+ ## 4. Use `Layer.succeed` only for pure values
832
+
833
+ Do not hide effectful initialization inside supposedly pure service objects.
834
+
835
+ If initialization can fail, depends on effects, or needs scoped acquisition, use `Layer.effect`.
836
+
837
+ ## 5. Use `Layer.effect` for owned resources
838
+
839
+ If the service opens something that must later close, model that lifecycle explicitly.
840
+
841
+ This is one of the main reasons layers exist.
842
+
843
+ ## 6. Prefer top-level composition
844
+
845
+ Compose major application layers once near the boundary.
846
+
847
+ Good pattern:
848
+
849
+ - define `ConfigLayer`
850
+ - define `UserRepoLayer`
851
+ - define `Dependencies = Layer.mergeAll(...)`
852
+ - define `AppLayer` separately with `Layer.provide(...)` or `Layer.provideMerge(...)`
853
+ - provide `AppLayer` to the top-level program
854
+
855
+ Let the composition root choose concrete backends and own transport lifecycles.
856
+ Do not hide request, websocket, worker, RPC, serialization, or server adapter
857
+ Layers inside an exported domain service Layer merely because one entrypoint
858
+ needs them.
859
+
860
+ ## 7. Use `Layer.fresh` only when you really need a new instance
861
+
862
+ Layers are shared by default.
863
+
864
+ That is usually what you want.
865
+
866
+ Use `Layer.fresh` only when you intentionally need to bypass sharing and rebuild the layer.
867
+
868
+ ## 7.5 Understand Layer Memoization
869
+
870
+ Layers are memoized by reference.
871
+
872
+ That means:
873
+
874
+ - reusing the same layer value preserves memoization and sharing
875
+ - creating a new layer value creates a new memoization identity
876
+
877
+ Prefer named Layer values. Layer constructors must not accept runtime
878
+ dependencies or configuration:
879
+
880
+ ```ts
881
+ // Avoid: requirements disappear from the Layer input channel.
882
+ const makeUserRepoLayer = (config: Config, client: HttpClient) =>
883
+ Layer.effect(UserRepo)(makeUserRepo(config, client))
884
+ ```
885
+
886
+ Model configuration, clients, databases, clocks, platform bindings, and
887
+ backend selection as focused services. Yield them inside `Layer.effect` so
888
+ requirements remain visible, and provide their concrete Layers once at the
889
+ outer composition boundary:
890
+
891
+ ```ts
892
+ 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
+ )
904
+ ```
905
+
906
+ Arguments are acceptable only when they are intrinsic definitions that create
907
+ a distinct named service instance, not runtime dependencies or configuration.
908
+ If a Layer-producing function is genuinely needed, call it once during
909
+ construction and reuse the resulting Layer value. Use `Layer.fresh` when a
910
+ distinct instance is intentional.
911
+
912
+ ## 8. Treat `Layer.orDie` carefully
913
+
914
+ `Layer.orDie` converts layer construction failures into defects.
915
+
916
+ Only use it when failure is truly unrecoverable at that boundary.
917
+
918
+ Do not use it to hide legitimate configuration or infrastructure failures.
919
+
920
+ ## 9. Use `ManagedRuntime.make` at true runtime boundaries
921
+
922
+ If you need a reusable runtime built from a layer, `ManagedRuntime.make` is the edge tool for that.
923
+
924
+ Good use cases:
925
+
926
+ - embedding Effect into external frameworks
927
+ - scripts or hosts that repeatedly run Effect programs
928
+
929
+ Repo reference:
930
+
931
+ - `packages/effect/src/ManagedRuntime.ts`
932
+
933
+ ## 10. Prefer explicit test layers
934
+
935
+ For tests, prefer:
936
+
937
+ - `Layer.succeed` for simple fakes
938
+ - `Layer.mock` for partial mocks when appropriate
939
+
940
+ This keeps test wiring explicit and close to production composition style.
941
+
942
+ ## Recommended Patterns
943
+
944
+ ## Pattern: service definition plus live layer
945
+
946
+ ```ts
947
+ import { Context, Effect, Layer } from "effect"
948
+
949
+ class Config extends Context.Service<Config, {
950
+ readonly apiBaseUrl: string
951
+ }>()("Config") {}
952
+
953
+ class UserRepo extends Context.Service<UserRepo, {
954
+ readonly getById: (id: string) => Effect.Effect<{ id: string; name: string }>
955
+ }>()("UserRepo") {}
956
+
957
+ const ConfigLayer = Layer.succeed(Config)({
958
+ apiBaseUrl: "https://api.example.com"
959
+ })
960
+
961
+ const UserRepoLayer = Layer.effect(UserRepo)(
962
+ Effect.gen(function*() {
963
+ const config = yield* Config
964
+
965
+ return {
966
+ getById: (id) =>
967
+ Effect.succeed({
968
+ id,
969
+ name: `Loaded via ${config.apiBaseUrl}`
970
+ })
971
+ }
972
+ })
973
+ )
974
+
975
+ const Dependencies = Layer.mergeAll(ConfigLayer)
976
+
977
+ const AppLayer = Layer.provide(UserRepoLayer, Dependencies)
978
+ ```
979
+
980
+ ## Pattern: provide at the top level
981
+
982
+ ```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
+ )
989
+ ```
990
+
991
+ ## Pattern: single-service override in tests
992
+
993
+ ```ts
994
+ const TestRepo = Layer.succeed(UserRepo)({
995
+ getById: (id) => Effect.succeed({ id, name: "Test" })
996
+ })
997
+ ```
998
+
999
+ ## Anti-Patterns
1000
+
1001
+ - constructing live services directly inside business logic
1002
+ - using `Layer.succeed` for values that actually require effectful initialization
1003
+ - providing the same large layer repeatedly throughout the call graph
1004
+ - collapsing unrelated responsibilities into one service
1005
+ - using `Layer.orDie` to hide normal initialization failures
1006
+ - bypassing layers entirely for resource-owning services
1007
+
1008
+ ## Good Repo Examples To Study
1009
+
1010
+ - `packages/effect/src/Context.ts`
1011
+ - `packages/effect/src/Layer.ts`
1012
+ - `packages/effect/src/ManagedRuntime.ts`
1013
+ - `packages/effect/src/Stream.ts`
1014
+ - `packages/effect/src/unstable/sql/SqlClient.ts`
1015
+ - `packages/effect/src/unstable/persistence/Persistence.ts`
1016
+ - `packages/effect/src/unstable/rpc/RpcSerialization.ts`
1017
+ - `packages/effect/src/unstable/reactivity/Reactivity.ts`