opencode-effect-enforcer 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 (118) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +278 -0
  3. package/guidance/effect-first-development.md +1247 -0
  4. package/guidance/post__effect-and-the-near-inexpressible-majesty-of-layers.md +490 -0
  5. package/guidance/post__parse-dont-validate.md +109 -0
  6. package/guidance/progressive-disclosure-guidance.md +38 -0
  7. package/package.json +63 -0
  8. package/patterns/avoid-any.md +37 -0
  9. package/patterns/avoid-data-tagged-error.md +34 -0
  10. package/patterns/avoid-direct-json.md +51 -0
  11. package/patterns/avoid-direct-tag-checks.md +54 -0
  12. package/patterns/avoid-expect-in-if.md +52 -0
  13. package/patterns/avoid-mutable-state.md +70 -0
  14. package/patterns/avoid-native-fetch.md +61 -0
  15. package/patterns/avoid-node-imports.md +86 -0
  16. package/patterns/avoid-non-null-assertion.md +44 -0
  17. package/patterns/avoid-object-type.md +46 -0
  18. package/patterns/avoid-option-getorthrow.md +39 -0
  19. package/patterns/avoid-platform-coupling.md +43 -0
  20. package/patterns/avoid-process-env.md +43 -0
  21. package/patterns/avoid-react-hooks.md +73 -0
  22. package/patterns/avoid-schema-suffix.md +45 -0
  23. package/patterns/avoid-sync-fs.md +68 -0
  24. package/patterns/avoid-try-catch.md +47 -0
  25. package/patterns/avoid-ts-ignore.md +38 -0
  26. package/patterns/avoid-untagged-errors.md +67 -0
  27. package/patterns/avoid-yield-ref.md +46 -0
  28. package/patterns/casting-awareness.md +46 -0
  29. package/patterns/context-tag-extends.md +84 -0
  30. package/patterns/effect-catchall-default.md +61 -0
  31. package/patterns/effect-promise-vs-trypromise.md +47 -0
  32. package/patterns/effect-run-in-body.md +58 -0
  33. package/patterns/imperative-loops.md +76 -0
  34. package/patterns/prefer-arr-sort.md +52 -0
  35. package/patterns/prefer-duration-values.md +56 -0
  36. package/patterns/prefer-effect-fn.md +161 -0
  37. package/patterns/prefer-match-over-switch.md +48 -0
  38. package/patterns/prefer-option-over-null.md +56 -0
  39. package/patterns/prefer-redacted-config.md +70 -0
  40. package/patterns/prefer-schema-class.md +54 -0
  41. package/patterns/require-effect-concurrency.md +83 -0
  42. package/patterns/stream-large-files.md +63 -0
  43. package/patterns/throw-in-effect-gen.md +62 -0
  44. package/patterns/use-clock-service.md +45 -0
  45. package/patterns/use-command-executor-service.md +54 -0
  46. package/patterns/use-console-service.md +54 -0
  47. package/patterns/use-filesystem-service.md +59 -0
  48. package/patterns/use-http-client-service.md +77 -0
  49. package/patterns/use-path-service.md +53 -0
  50. package/patterns/use-random-service.md +45 -0
  51. package/patterns/use-temp-file-scoped.md +66 -0
  52. package/patterns/vm-in-wrong-file.md +51 -0
  53. package/patterns/yield-in-for-loop.md +61 -0
  54. package/skills/effect-ai-chat/SKILL.md +472 -0
  55. package/skills/effect-ai-language-model/SKILL.md +652 -0
  56. package/skills/effect-ai-prompt/SKILL.md +752 -0
  57. package/skills/effect-ai-provider/SKILL.md +668 -0
  58. package/skills/effect-ai-streaming/SKILL.md +418 -0
  59. package/skills/effect-ai-tool/SKILL.md +1132 -0
  60. package/skills/effect-atom-rpc/SKILL.md +488 -0
  61. package/skills/effect-atom-state/SKILL.md +640 -0
  62. package/skills/effect-batching/SKILL.md +614 -0
  63. package/skills/effect-cache/SKILL.md +570 -0
  64. package/skills/effect-cli/SKILL.md +523 -0
  65. package/skills/effect-command-executor/SKILL.md +675 -0
  66. package/skills/effect-concurrency-testing/SKILL.md +612 -0
  67. package/skills/effect-config/SKILL.md +580 -0
  68. package/skills/effect-context-witness/SKILL.md +274 -0
  69. package/skills/effect-domain-modeling/SKILL.md +1212 -0
  70. package/skills/effect-domain-predicates/SKILL.md +867 -0
  71. package/skills/effect-error-handling/SKILL.md +1581 -0
  72. package/skills/effect-fiber/SKILL.md +731 -0
  73. package/skills/effect-filesystem/SKILL.md +624 -0
  74. package/skills/effect-graph/SKILL.md +571 -0
  75. package/skills/effect-http-api/SKILL.md +1760 -0
  76. package/skills/effect-http-client/SKILL.md +989 -0
  77. package/skills/effect-http-server/SKILL.md +920 -0
  78. package/skills/effect-incremental-migration/SKILL.md +362 -0
  79. package/skills/effect-layer-design/SKILL.md +642 -0
  80. package/skills/effect-managed-runtime/SKILL.md +395 -0
  81. package/skills/effect-mcp-server/SKILL.md +608 -0
  82. package/skills/effect-observability/SKILL.md +719 -0
  83. package/skills/effect-optics/SKILL.md +554 -0
  84. package/skills/effect-parallelization/SKILL.md +668 -0
  85. package/skills/effect-path/SKILL.md +296 -0
  86. package/skills/effect-pattern-matching/SKILL.md +914 -0
  87. package/skills/effect-platform-abstraction/SKILL.md +1175 -0
  88. package/skills/effect-platform-layers/SKILL.md +514 -0
  89. package/skills/effect-pubsub-event-bus/SKILL.md +384 -0
  90. package/skills/effect-react-composition/SKILL.md +986 -0
  91. package/skills/effect-react-vm/SKILL.md +675 -0
  92. package/skills/effect-rpc-api/SKILL.md +624 -0
  93. package/skills/effect-rpc-client/SKILL.md +666 -0
  94. package/skills/effect-rpc-cluster/SKILL.md +1623 -0
  95. package/skills/effect-rpc-server/SKILL.md +767 -0
  96. package/skills/effect-scheduling/SKILL.md +124 -0
  97. package/skills/effect-schema-composition/SKILL.md +975 -0
  98. package/skills/effect-schema-v4/SKILL.md +691 -0
  99. package/skills/effect-scope/SKILL.md +682 -0
  100. package/skills/effect-service-implementation/SKILL.md +656 -0
  101. package/skills/effect-socket/SKILL.md +703 -0
  102. package/skills/effect-sql/SKILL.md +781 -0
  103. package/skills/effect-stream/SKILL.md +765 -0
  104. package/skills/effect-testing/SKILL.md +1331 -0
  105. package/skills/effect-typeclass-design/SKILL.md +161 -0
  106. package/skills/effect-wide-events/Article.md +66 -0
  107. package/skills/effect-wide-events/SKILL.md +95 -0
  108. package/skills/effect-workflow/SKILL.md +810 -0
  109. package/src/agent-policy.ts +22 -0
  110. package/src/enforcer.ts +104 -0
  111. package/src/frontmatter.ts +34 -0
  112. package/src/guidance.ts +66 -0
  113. package/src/index.ts +38 -0
  114. package/src/pattern-catalog.ts +115 -0
  115. package/src/pattern-matcher.ts +178 -0
  116. package/src/pattern.ts +97 -0
  117. package/src/skills.ts +29 -0
  118. package/src/write-projection.ts +66 -0
@@ -0,0 +1,642 @@
1
+ ---
2
+ name: effect-layer-design
3
+ description: Design and compose Effect layers for clean dependency management
4
+ ---
5
+
6
+ # Layer Design Skill
7
+
8
+ Create layers that construct services while managing their dependencies cleanly.
9
+
10
+ ## Effect Source Reference
11
+
12
+ The Effect v4 source is available at `~/.local/share/opencode/repos/github.com/Effect-TS/effect@main/`.
13
+ Browse and read files there directly to look up APIs, types, and implementations.
14
+
15
+ Reference this for:
16
+
17
+ - Layer source: `packages/effect/src/Layer.ts`
18
+ - Context source: `packages/effect/src/Context.ts`
19
+ - Migration guide: `MIGRATION.md`
20
+ - Effect source: `packages/effect/src/`
21
+
22
+ ## Layer Structure
23
+
24
+ ```typescript
25
+ import { Layer } from 'effect';
26
+
27
+ // Layer<RequirementsOut, Error, RequirementsIn>
28
+ // ▲ ▲ ▲
29
+ // │ │ └─ What this layer needs
30
+ // │ └─ Errors during construction
31
+ // └─ What this layer produces
32
+ ```
33
+
34
+ ## Choose the Constructor by Output
35
+
36
+ ```typescript
37
+ Layer.succeed(Service, implementation); // already built
38
+ Layer.sync(Service, () => implementation); // lazy synchronous construction
39
+ Layer.effect(Service, acquisition); // effectful single-service acquisition
40
+ Layer.effectContext(acquisition); // effectful Context with multiple services
41
+ Layer.effectDiscard(initialization); // acquisition that provides no service
42
+ Layer.unwrap(effectProducingLayer); // config or discovery chooses a layer
43
+ ```
44
+
45
+ Default production services with dependencies or resources to `Layer.effect`. Use `Layer.effectContext` when one acquisition intentionally provides multiple tags, especially when the same controllable test implementation backs both a production service and a test-control service.
46
+
47
+ ## Pattern: Simple Layer (No Dependencies)
48
+
49
+ ```typescript
50
+ import { Context, Effect, Layer } from 'effect';
51
+
52
+ interface ConfigData {
53
+ readonly logLevel: string;
54
+ readonly connection: string;
55
+ }
56
+
57
+ export class Config extends Context.Service<
58
+ Config,
59
+ {
60
+ readonly getConfig: Effect.Effect<ConfigData>;
61
+ }
62
+ >()('Config') {}
63
+
64
+ // Layer<Config, never, never>
65
+ // ▲ ▲ ▲
66
+ // │ │ └─ No dependencies
67
+ // │ └─ Cannot fail
68
+ // └─ Produces Config
69
+ export const ConfigLive = Layer.succeed(
70
+ Config,
71
+ Config.of({
72
+ getConfig: Effect.succeed({
73
+ logLevel: 'INFO',
74
+ connection: 'mysql://localhost/db'
75
+ })
76
+ })
77
+ );
78
+ ```
79
+
80
+ ## Pattern: Layer with Dependencies
81
+
82
+ ```typescript
83
+ import { Context, Effect, Layer, Console } from 'effect';
84
+
85
+ interface ConfigData {
86
+ readonly logLevel: string;
87
+ readonly connection: string;
88
+ }
89
+
90
+ export class Config extends Context.Service<
91
+ Config,
92
+ {
93
+ readonly getConfig: Effect.Effect<ConfigData>;
94
+ }
95
+ >()('Config') {}
96
+
97
+ export class Logger extends Context.Service<
98
+ Logger,
99
+ {
100
+ readonly log: (message: string) => Effect.Effect<void>;
101
+ }
102
+ >()('Logger') {}
103
+
104
+ // Layer<Logger, never, Config>
105
+ // ▲ ▲ ▲
106
+ // │ │ └─ Needs Config
107
+ // │ └─ Cannot fail
108
+ // └─ Produces Logger
109
+ export const LoggerLive = Layer.effect(
110
+ Logger,
111
+ Effect.gen(function* () {
112
+ const config = yield* Config; // Access dependency
113
+ return Logger.of({
114
+ log: (message) =>
115
+ Effect.gen(function* () {
116
+ const { logLevel } = yield* config.getConfig;
117
+ yield* Console.log(`[${logLevel}] ${message}`);
118
+ })
119
+ });
120
+ })
121
+ );
122
+ ```
123
+
124
+ ## Pattern: Layer with Resource Management
125
+
126
+ Use `Layer.effect` for effectfully acquired services, including resources that need cleanup. In Effect v4, `Layer.effect` automatically handles `Scope` lifecycle — `Layer.scoped` is no longer needed.
127
+
128
+ Resources are acquired and released using `Effect.acquireRelease` or `Effect.addFinalizer` inside the `Layer.effect` constructor:
129
+
130
+ ```typescript
131
+ import { Context, Effect, Layer } from 'effect';
132
+
133
+ interface ConfigData {
134
+ readonly logLevel: string;
135
+ readonly connection: string;
136
+ }
137
+
138
+ interface Connection {
139
+ readonly close: () => void;
140
+ }
141
+
142
+ interface DatabaseError {
143
+ readonly _tag: 'DatabaseError';
144
+ }
145
+
146
+ export class Config extends Context.Service<
147
+ Config,
148
+ {
149
+ readonly getConfig: Effect.Effect<ConfigData>;
150
+ }
151
+ >()('Config') {}
152
+
153
+ export class Database extends Context.Service<
154
+ Database,
155
+ {
156
+ readonly query: (sql: string) => Effect.Effect<unknown, DatabaseError>;
157
+ }
158
+ >()('Database') {}
159
+
160
+ declare const connectToDatabase: (
161
+ config: ConfigData
162
+ ) => Effect.Effect<Connection, DatabaseError>;
163
+ declare const executeQuery: (
164
+ connection: Connection,
165
+ sql: string
166
+ ) => Effect.Effect<unknown, DatabaseError>;
167
+
168
+ // Layer<Database, DatabaseError, Config>
169
+ export const DatabaseLive = Layer.effect(
170
+ Database,
171
+ Effect.gen(function* () {
172
+ const config = yield* Config;
173
+ const configData = yield* config.getConfig;
174
+
175
+ // Acquire resource with automatic release — Layer.effect handles Scope
176
+ const connection = yield* Effect.acquireRelease(
177
+ connectToDatabase(configData),
178
+ (conn) => Effect.sync(() => conn.close()) // Cleanup
179
+ );
180
+
181
+ return Database.of({
182
+ query: (sql) => executeQuery(connection, sql)
183
+ });
184
+ })
185
+ );
186
+ ```
187
+
188
+ Layer construction must complete. If acquisition starts a listener, stream, subscription, worker, or forever loop, fork it into the layer scope rather than running it inline:
189
+
190
+ ```typescript
191
+ export const WorkerLive = Layer.effectDiscard(
192
+ Effect.gen(function* () {
193
+ const events = yield* Events.Service;
194
+ yield* events.stream.pipe(
195
+ Stream.runForEach(handleEvent),
196
+ Effect.forkScoped
197
+ );
198
+ })
199
+ );
200
+ ```
201
+
202
+ Use `Effect.forkScoped`, `FiberSet`, or `FiberMap` so closing the layer scope interrupts the background work. `forkScoped` alone is appropriate only for best-effort work that reports its own failures; monitor or supervise failure-significant consumers so their failures remain observable. Never block layer acquisition on a long-lived loop.
203
+
204
+ ## Composing Layers: Merge vs Provide
205
+
206
+ Start from the service graph, not from whichever combinator makes the types compile:
207
+
208
+ - `Layer.merge` / `Layer.mergeAll` expose independent outputs; they do not satisfy dependencies between the merged layers.
209
+ - `Layer.provide` satisfies and hides an implementation dependency.
210
+ - `Layer.provideMerge` satisfies a dependency and deliberately keeps that dependency in the output.
211
+ - Name and reuse shared dependency layer values when acquisition must be shared.
212
+ - Do not blindly merge every layer or use `provideMerge` as a make-it-compile tool; both can expose authority and lifecycle services that should remain private.
213
+
214
+ ### Merge (Parallel Composition)
215
+
216
+ Merge layers when both outputs should remain exposed. This does not wire one output into another layer's requirements:
217
+
218
+ ```typescript
219
+ import { Context, Layer } from 'effect';
220
+
221
+ declare class Config extends Context.Service<Config, {}>()('Config') {}
222
+ declare class Logger extends Context.Service<Logger, {}>()('Logger') {}
223
+
224
+ declare const ConfigLive: Layer.Layer<Config, never, never>;
225
+ declare const LoggerLive: Layer.Layer<Logger, never, Config>;
226
+
227
+ // Layer<Config | Logger, never, Config>
228
+ // ▲ ▲ ▲
229
+ // │ │ └─ LoggerLive needs Config
230
+ // │ └─ No errors
231
+ // └─ Produces both Config and Logger
232
+ const AppConfigLive = Layer.merge(ConfigLive, LoggerLive);
233
+ ```
234
+
235
+ Result combines:
236
+
237
+ - **Requirements**: Union (`never | Config = Config`)
238
+ - **Outputs**: Union (`Config | Logger`)
239
+
240
+ ### Provide (Sequential Composition)
241
+
242
+ Chain dependent layers:
243
+
244
+ ```typescript
245
+ import { Context, Layer } from 'effect';
246
+
247
+ declare class Config extends Context.Service<Config, {}>()('Config') {}
248
+ declare class Logger extends Context.Service<Logger, {}>()('Logger') {}
249
+
250
+ declare const ConfigLive: Layer.Layer<Config, never, never>;
251
+ declare const LoggerLive: Layer.Layer<Logger, never, Config>;
252
+
253
+ // Layer<Logger, never, never>
254
+ // ▲ ▲ ▲
255
+ // │ │ └─ ConfigLive satisfies LoggerLive's requirement
256
+ // │ └─ No errors
257
+ // └─ Only Logger in output
258
+ const FullLoggerLive = Layer.provide(LoggerLive, ConfigLive);
259
+ ```
260
+
261
+ Result:
262
+
263
+ - **Requirements**: Outer layer's requirements (`never`)
264
+ - **Output**: Inner layer's output (`Logger`)
265
+
266
+ ## Pattern: Direct Default Composition
267
+
268
+ Compose `defaultLayer` directly unless you have a real module-evaluation or circular-import problem. Most services do not need deferred composition.
269
+
270
+ ```typescript
271
+ import { Layer } from 'effect';
272
+
273
+ // Raw layer — declares its dependencies in the type
274
+ export const layer: Layer.Layer<MyService, never, DepA | DepB> = Layer.effect(
275
+ MyService,
276
+ Effect.gen(function* () {
277
+ const depA = yield* DepA;
278
+ const depB = yield* DepB;
279
+ return MyService.of({
280
+ /* ... */
281
+ });
282
+ })
283
+ );
284
+
285
+ // Fully-wired layer — compose directly in the normal case
286
+ export const defaultLayer = layer.pipe(
287
+ Layer.provide(DepA.defaultLayer),
288
+ Layer.provide(DepB.defaultLayer)
289
+ );
290
+ ```
291
+
292
+ ### Deferred Composition with Layer.suspend
293
+
294
+ Use `Layer.suspend(() => ...)` when import evaluation order genuinely requires deferral:
295
+
296
+ ```typescript
297
+ import { Layer } from 'effect';
298
+
299
+ export const defaultLayer = Layer.suspend(() =>
300
+ layer.pipe(Layer.provide(Dep.defaultLayer))
301
+ );
302
+ ```
303
+
304
+ `Layer.unwrap(Effect.sync(...))` still works, but it is not the universal default. Reach for deferred composition only when the dependency graph actually needs it.
305
+
306
+ **Naming convention:**
307
+
308
+ - **`layer`** — exposes the service's true dependency graph in its type signature. Tests compose against `layer` directly, providing mock layers.
309
+ - **`defaultLayer`** — the fully-wired production composition with all dependencies satisfied. Only define `defaultLayer` when `layer` has unsatisfied requirements. Self-contained layers (no external dependencies) export just `layer`.
310
+
311
+ ### When to defer
312
+
313
+ Use deferred composition only for:
314
+
315
+ - real circular-import or module-evaluation hazards
316
+ - runtime-selected layer variants that should not be built eagerly
317
+ - recursive layer graphs that must be tied lazily
318
+
319
+ If none of those apply, compose directly.
320
+
321
+ ## Pattern: Layered Architecture
322
+
323
+ Build applications in layers:
324
+
325
+ ```typescript
326
+ import { Context, Layer } from 'effect';
327
+
328
+ declare class Config extends Context.Service<Config, {}>()('Config') {}
329
+ declare class Database extends Context.Service<Database, {}>()('Database') {}
330
+ declare class Cache extends Context.Service<Cache, {}>()('Cache') {}
331
+ declare class PaymentDomain extends Context.Service<PaymentDomain, {}>()(
332
+ 'PaymentDomain'
333
+ ) {}
334
+ declare class OrderDomain extends Context.Service<OrderDomain, {}>()(
335
+ 'OrderDomain'
336
+ ) {}
337
+ declare class PaymentGateway extends Context.Service<PaymentGateway, {}>()(
338
+ 'PaymentGateway'
339
+ ) {}
340
+ declare class NotificationService extends Context.Service<
341
+ NotificationService,
342
+ {}
343
+ >()('NotificationService') {}
344
+
345
+ declare const ConfigLive: Layer.Layer<Config, never, never>;
346
+ declare const DatabaseLive: Layer.Layer<Database, never, Config>;
347
+ declare const CacheLive: Layer.Layer<Cache, never, Config>;
348
+ declare const PaymentDomainLive: Layer.Layer<PaymentDomain, never, Database>;
349
+ declare const OrderDomainLive: Layer.Layer<OrderDomain, never, Database>;
350
+ declare const PaymentGatewayLive: Layer.Layer<
351
+ PaymentGateway,
352
+ never,
353
+ PaymentDomain
354
+ >;
355
+ declare const NotificationServiceLive: Layer.Layer<
356
+ NotificationService,
357
+ never,
358
+ OrderDomain
359
+ >;
360
+
361
+ // Infrastructure: No dependencies
362
+ const InfrastructureLive = Layer.mergeAll(
363
+ ConfigLive, // Layer<Config, never, never>
364
+ DatabaseLive, // Layer<Database, never, Config>
365
+ CacheLive // Layer<Cache, never, Config>
366
+ ).pipe(
367
+ Layer.provide(ConfigLive) // Satisfy Config requirement
368
+ );
369
+
370
+ // Domain: Depends on infrastructure
371
+ const DomainLive = Layer.mergeAll(
372
+ PaymentDomainLive, // Layer<PaymentDomain, never, Database>
373
+ OrderDomainLive // Layer<OrderDomain, never, Database>
374
+ ).pipe(Layer.provide(InfrastructureLive));
375
+
376
+ // Application: Depends on domain
377
+ const ApplicationLive = Layer.mergeAll(
378
+ PaymentGatewayLive,
379
+ NotificationServiceLive
380
+ ).pipe(Layer.provide(DomainLive));
381
+ ```
382
+
383
+ ## Pattern: Multiple Implementations
384
+
385
+ Switch implementations for different environments:
386
+
387
+ ```typescript
388
+ import { Context, Effect, Layer } from 'effect';
389
+
390
+ interface Connection {
391
+ readonly close: () => void;
392
+ }
393
+
394
+ export class Database extends Context.Service<
395
+ Database,
396
+ {
397
+ readonly query: (sql: string) => Effect.Effect<{ rows: unknown[] }>;
398
+ }
399
+ >()('Database') {}
400
+
401
+ declare const connectToProduction: () => Effect.Effect<Connection>;
402
+ declare const createDatabaseService: (connection: Connection) => {
403
+ readonly query: (sql: string) => Effect.Effect<{ rows: unknown[] }>;
404
+ };
405
+
406
+ declare const myProgram: Effect.Effect<void, never, Database>;
407
+
408
+ // Production
409
+ export const DatabaseLive = Layer.effect(
410
+ Database,
411
+ Effect.gen(function* () {
412
+ const connection = yield* connectToProduction();
413
+ return createDatabaseService(connection);
414
+ })
415
+ );
416
+
417
+ // Test
418
+ export const DatabaseTest = Layer.succeed(
419
+ Database,
420
+ Database.of({
421
+ query: () => Effect.succeed({ rows: [] })
422
+ })
423
+ );
424
+
425
+ // Use in application
426
+ const program = Effect.gen(function* () {
427
+ const nodeEnv = yield* Config.string('NODE_ENV').pipe(
428
+ Config.withDefault('production')
429
+ );
430
+ yield* myProgram.pipe(
431
+ Effect.provide(nodeEnv === 'test' ? DatabaseTest : DatabaseLive)
432
+ );
433
+ });
434
+ ```
435
+
436
+ ## Pattern: Layer Sharing
437
+
438
+ Layers are memoized - same instance shared across program:
439
+
440
+ ```typescript
441
+ import { Context, Effect, Layer } from 'effect';
442
+
443
+ declare class Config extends Context.Service<
444
+ Config,
445
+ { readonly value: string }
446
+ >()('Config') {}
447
+ declare const ConfigLive: Layer.Layer<Config, never, never>;
448
+
449
+ // Config is constructed once and shared
450
+ const program = Effect.all([
451
+ Effect.gen(function* () {
452
+ const config = yield* Config;
453
+ // Uses shared instance
454
+ }),
455
+ Effect.gen(function* () {
456
+ const config = yield* Config;
457
+ // Same instance
458
+ })
459
+ ]).pipe(Effect.provide(ConfigLive));
460
+ ```
461
+
462
+ > **Memo-map fork nuance:** Sharing is mediated by a `MemoMap`. A root memo map (`Layer.makeMemoMapUnsafe()`, used implicitly by `Effect.provide`) shares every layer allocation it builds. A _forked_ memo map (`Layer.forkMemoMap` / `Layer.forkMemoMapUnsafe`) can still see allocations its parent already built, but new allocations it builds stay isolated and are not written back to the parent. This is the mechanism `@effect/vitest` uses to reuse parent layers while isolating nested `it.layer` suites.
463
+
464
+ ## Error Handling in Layers
465
+
466
+ Handle construction errors:
467
+
468
+ ```typescript
469
+ import { Context, Effect, Layer, Schema } from 'effect';
470
+
471
+ interface Connection {
472
+ readonly close: () => void;
473
+ }
474
+
475
+ class ConnectionError extends Schema.TaggedError<ConnectionError>()(
476
+ 'ConnectionError',
477
+ {
478
+ message: Schema.String
479
+ }
480
+ ) {}
481
+
482
+ class DatabaseConstructionError extends Schema.TaggedError<DatabaseConstructionError>()(
483
+ 'DatabaseConstructionError',
484
+ { cause: ConnectionError }
485
+ ) {}
486
+
487
+ export class Database extends Context.Service<
488
+ Database,
489
+ {
490
+ readonly query: (sql: string) => Effect.Effect<unknown>;
491
+ }
492
+ >()('Database') {}
493
+
494
+ declare const connectToDatabase: () => Effect.Effect<
495
+ Connection,
496
+ ConnectionError
497
+ >;
498
+ declare const createDatabaseService: (connection: Connection) => {
499
+ readonly query: (sql: string) => Effect.Effect<unknown>;
500
+ };
501
+
502
+ export const DatabaseLive = Layer.effect(
503
+ Database,
504
+ Effect.gen(function* () {
505
+ const connection = yield* connectToDatabase().pipe(
506
+ Effect.catchTag('ConnectionError', (error) =>
507
+ Effect.fail(new DatabaseConstructionError({ cause: error }))
508
+ )
509
+ );
510
+ return createDatabaseService(connection);
511
+ })
512
+ );
513
+ ```
514
+
515
+ ## Composing Layers: Deliberate ProvideMerge
516
+
517
+ `Layer.provideMerge` satisfies dependencies AND passes them through to the output. Use it only when downstream consumers intentionally need both outputs, including carefully designed test stacks.
518
+
519
+ ### Provide vs ProvideMerge
520
+
521
+ ```typescript
522
+ import { Layer } from 'effect';
523
+
524
+ declare const ConfigLayer: Layer.Layer<Config>;
525
+ declare const DatabaseLayer: Layer.Layer<Database, never, Config>;
526
+ declare const UserServiceLayer: Layer.Layer<UserService, never, Database>;
527
+
528
+ // Layer.provide — satisfies requirement, REMOVES it from output
529
+ const db = DatabaseLayer.pipe(Layer.provide(ConfigLayer));
530
+ // db: Layer<Database> — Config is NOT in the output
531
+
532
+ // Layer.provideMerge — satisfies requirement, KEEPS it in output
533
+ const dbWithConfig = DatabaseLayer.pipe(Layer.provideMerge(ConfigLayer));
534
+ // dbWithConfig: Layer<Database | Config> — Config remains available
535
+ ```
536
+
537
+ ### When to use ProvideMerge
538
+
539
+ Use `Layer.provideMerge` when downstream test layers intentionally need the upstream services as outputs as well as dependencies:
540
+
541
+ ```typescript
542
+ import { Layer } from 'effect';
543
+
544
+ // Test layer composition — downstream layers need Config AND Database
545
+ const infra = Layer.mergeAll(ConfigLayer, DatabaseLayer).pipe(
546
+ Layer.provideMerge(ConfigLayer) // Config stays visible for downstream
547
+ );
548
+
549
+ // Both UserService and OrderService can access Config and Database
550
+ const services = Layer.mergeAll(UserServiceLayer, OrderServiceLayer).pipe(
551
+ Layer.provideMerge(infra)
552
+ );
553
+ ```
554
+
555
+ If downstream code does not need the dependency, use `Layer.provide` and keep it hidden. Do not preserve every intermediate service by default.
556
+
557
+ ## Pattern: SynchronizedRef + Deferred State Machine
558
+
559
+ For services that need atomic state transitions with concurrent callers, use `SynchronizedRef.modifyEffect` combined with `Deferred` for result sharing:
560
+
561
+ ```typescript
562
+ import { Deferred, Effect, Fiber, Scope, SynchronizedRef } from 'effect';
563
+
564
+ type State<A, E> =
565
+ | { readonly _tag: 'Idle' }
566
+ | {
567
+ readonly _tag: 'Running';
568
+ readonly done: Deferred.Deferred<A, E>;
569
+ readonly fiber: Fiber.Fiber<A, E>;
570
+ }
571
+ | { readonly _tag: 'Pending'; readonly done: Deferred.Deferred<A, E> };
572
+
573
+ const make = <A, E>(scope: Scope.Scope) => {
574
+ const ref = SynchronizedRef.makeUnsafe<State<A, E>>({ _tag: 'Idle' });
575
+
576
+ const run = (work: Effect.Effect<A, E>) =>
577
+ SynchronizedRef.modifyEffect(
578
+ ref,
579
+ Effect.fnUntraced(function* (state) {
580
+ switch (state._tag) {
581
+ case 'Running':
582
+ // Already running — share the existing result
583
+ return [Deferred.await(state.done), state];
584
+ case 'Idle': {
585
+ // Start new work
586
+ const done = yield* Deferred.make<A, E>();
587
+ const fiber = yield* Effect.forkIn(
588
+ work.pipe(Effect.intoDeferred(done)),
589
+ scope
590
+ );
591
+ return [
592
+ Deferred.await(done),
593
+ { _tag: 'Running' as const, done, fiber }
594
+ ];
595
+ }
596
+ case 'Pending': {
597
+ // Queued — share the pending result
598
+ return [Deferred.await(state.done), state];
599
+ }
600
+ }
601
+ })
602
+ ).pipe(Effect.flatten);
603
+
604
+ return { run };
605
+ };
606
+ ```
607
+
608
+ Key properties:
609
+
610
+ - **`SynchronizedRef.modifyEffect`** — atomically reads state, runs an effect, and updates state in one operation. No other caller can interleave.
611
+ - **`Deferred`** — shares the result of in-flight work with concurrent callers who arrive while it's running.
612
+ - **`Effect.forkIn(work, scope)`** — ties the worker fiber to the service scope, not the calling fiber.
613
+ - The state machine pattern ensures at most one concurrent execution of `work`, with all callers sharing the same result.
614
+
615
+ Use this pattern when:
616
+
617
+ - Multiple concurrent callers may trigger the same expensive operation
618
+ - Only one execution should run at a time
619
+ - All callers should receive the same result
620
+
621
+ ## Naming Convention
622
+
623
+ - `*Live` - Production implementation
624
+ - `*Test` - Test implementation
625
+ - `*Mock` - Mock for testing
626
+ - Descriptive names for specialized implementations
627
+
628
+ ## Quality Checklist
629
+
630
+ - [ ] Layer type accurately reflects dependencies
631
+ - [ ] `Service.of({...})` used when returning from `Layer.effect`, never a plain object
632
+ - [ ] Resource cleanup using `acquireRelease` or `addFinalizer` if needed
633
+ - [ ] Layer can be tested with mock dependencies
634
+ - [ ] No dependency leakage into service interface
635
+ - [ ] Merge/provide/provideMerge follows the intended exposed service graph, not only type errors
636
+ - [ ] Long-lived acquisition completes and forks background work into the layer scope
637
+ - [ ] `defaultLayer` only present when `layer` has unsatisfied requirements
638
+ - [ ] `defaultLayer` composes directly unless deferred evaluation is truly required
639
+ - [ ] Error handling for construction failures
640
+ - [ ] JSDoc with example usage
641
+
642
+ Layers should make dependency management explicit while keeping service interfaces clean and focused.