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