@martinffx/atelier 1.4.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +38 -0
- package/README.md +95 -220
- package/agents/architect.md +6 -6
- package/agents/oracle.md +1 -1
- package/agents/{recon.md → sentinel.md} +3 -3
- package/dist/atelier.js +167 -26
- package/package.json +2 -1
- package/skills/atelier-orchestrator/SKILL.md +180 -0
- package/skills/atelier-setup/SKILL.md +80 -0
- package/skills/code-review/SKILL.md +2 -2
- package/skills/code-review/references/reviewers.md +3 -3
- package/skills/code-review/references/rq.md +9 -9
- package/skills/code-subagents/SKILL.md +44 -75
- package/skills/code-subagents/references/batch-reviewer-prompt.md +44 -0
- package/skills/code-subagents/references/implementor-prompt.md +10 -11
- package/skills/spec-brainstorm/SKILL.md +21 -16
- package/skills/spec-finish/SKILL.md +14 -14
- package/skills/spec-implement/SKILL.md +42 -42
- package/skills/spec-plan/SKILL.md +72 -26
- package/skills/spec-plan/references/plan_template.md +77 -0
- package/skills/code-subagents/references/code-quality-reviewer-prompt.md +0 -48
- package/skills/code-subagents/references/spec-reviewer-prompt.md +0 -62
- package/skills/python-architecture/SKILL.md +0 -148
- package/skills/python-architecture/references/data-modeling.md +0 -558
- package/skills/python-architecture/references/ddd.md +0 -569
- package/skills/python-architecture/references/functional-core.md +0 -337
- package/skills/python-build-tools/SKILL.md +0 -153
- package/skills/python-build-tools/references/basedpyright.md +0 -321
- package/skills/python-build-tools/references/ruff.md +0 -225
- package/skills/python-build-tools/references/uv.md +0 -257
- package/skills/python-fastapi/SKILL.md +0 -184
- package/skills/python-fastapi/references/api-design.md +0 -563
- package/skills/python-fastapi/references/dependencies.md +0 -479
- package/skills/python-fastapi/references/middleware.md +0 -475
- package/skills/python-fastapi/references/validation.md +0 -497
- package/skills/python-modern-python/SKILL.md +0 -201
- package/skills/python-modern-python/references/async.md +0 -76
- package/skills/python-modern-python/references/pattern-matching.md +0 -89
- package/skills/python-modern-python/references/typing.md +0 -96
- package/skills/python-monorepo/SKILL.md +0 -146
- package/skills/python-monorepo/references/docker.md +0 -515
- package/skills/python-monorepo/references/namespace-packages.md +0 -426
- package/skills/python-monorepo/references/workspace-config.md +0 -341
- package/skills/python-sqlalchemy/SKILL.md +0 -119
- package/skills/python-sqlalchemy/references/async.md +0 -51
- package/skills/python-sqlalchemy/references/models.md +0 -67
- package/skills/python-sqlalchemy/references/queries.md +0 -57
- package/skills/python-temporal/SKILL.md +0 -128
- package/skills/python-temporal/references/patterns.md +0 -87
- package/skills/python-temporal/references/testing.md +0 -54
- package/skills/python-testing/SKILL.md +0 -161
- package/skills/python-testing/references/boundaries.md +0 -449
- package/skills/python-testing/references/mocking.md +0 -449
- package/skills/python-testing/references/pytest.md +0 -524
- package/skills/spec-orchestrator/SKILL.md +0 -145
- package/skills/typescript-api-design/SKILL.md +0 -396
- package/skills/typescript-api-design/references/error-responses.md +0 -469
- package/skills/typescript-build-tools/SKILL.md +0 -447
- package/skills/typescript-build-tools/references/biome.md +0 -436
- package/skills/typescript-build-tools/references/bun.md +0 -432
- package/skills/typescript-build-tools/references/turborepo.md +0 -426
- package/skills/typescript-build-tools/references/vitest.md +0 -518
- package/skills/typescript-drizzle-orm/SKILL.md +0 -398
- package/skills/typescript-drizzle-orm/references/cloudflare.md +0 -392
- package/skills/typescript-drizzle-orm/references/entity-pattern.md +0 -412
- package/skills/typescript-drizzle-orm/references/postgresql.md +0 -191
- package/skills/typescript-drizzle-orm/references/repository-pattern.md +0 -479
- package/skills/typescript-drizzle-orm/references/sqlite.md +0 -291
- package/skills/typescript-dynamodb-toolbox/SKILL.md +0 -370
- package/skills/typescript-dynamodb-toolbox/references/entity-layer.md +0 -574
- package/skills/typescript-dynamodb-toolbox/references/error-handling.md +0 -495
- package/skills/typescript-dynamodb-toolbox/references/modeling.md +0 -332
- package/skills/typescript-dynamodb-toolbox/references/repository-layer.md +0 -631
- package/skills/typescript-dynamodb-toolbox/references/testing.md +0 -718
- package/skills/typescript-dynamodb-toolbox/references/transactions.md +0 -423
- package/skills/typescript-effect-ts/SKILL.md +0 -341
- package/skills/typescript-effect-ts/references/core.md +0 -412
- package/skills/typescript-effect-ts/references/error-handling.md +0 -468
- package/skills/typescript-effect-ts/references/resources.md +0 -476
- package/skills/typescript-effect-ts/references/schema.md +0 -536
- package/skills/typescript-effect-ts/references/services.md +0 -461
- package/skills/typescript-fastify/SKILL.md +0 -405
- package/skills/typescript-fastify/references/plugins.md +0 -329
- package/skills/typescript-fastify/references/typeid.md +0 -315
- package/skills/typescript-functional-patterns/SKILL.md +0 -402
- package/skills/typescript-functional-patterns/references/adts.md +0 -497
- package/skills/typescript-functional-patterns/references/branded-types.md +0 -542
- package/skills/typescript-functional-patterns/references/migration-guide.md +0 -695
- package/skills/typescript-functional-patterns/references/option-result.md +0 -585
- package/skills/typescript-testing/SKILL.md +0 -353
- package/skills/typescript-testing/references/mocking.md +0 -675
- package/skills/typescript-testing/references/msw.md +0 -852
- package/skills/typescript-testing/references/snapshot-testing.md +0 -774
|
@@ -1,461 +0,0 @@
|
|
|
1
|
-
# Services and Dependency Injection in Effect
|
|
2
|
-
|
|
3
|
-
Effect provides a powerful dependency injection system using `Context.Tag` for service definitions and `Layer` for service implementations and composition.
|
|
4
|
-
|
|
5
|
-
## Context.Tag - Defining Services
|
|
6
|
-
|
|
7
|
-
A Tag uniquely identifies a service and its interface:
|
|
8
|
-
|
|
9
|
-
```typescript
|
|
10
|
-
import { Context, Effect } from "effect"
|
|
11
|
-
|
|
12
|
-
// Define service interface and tag together
|
|
13
|
-
class Database extends Context.Tag("Database")<
|
|
14
|
-
Database,
|
|
15
|
-
{
|
|
16
|
-
readonly query: (sql: string) => Effect.Effect<unknown[]>
|
|
17
|
-
readonly execute: (sql: string) => Effect.Effect<void>
|
|
18
|
-
}
|
|
19
|
-
>() {}
|
|
20
|
-
|
|
21
|
-
// Alternative: separate interface
|
|
22
|
-
interface ILogger {
|
|
23
|
-
log: (message: string) => Effect.Effect<void>
|
|
24
|
-
error: (message: string) => Effect.Effect<void>
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
class Logger extends Context.Tag("Logger")<Logger, ILogger>() {}
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
### Tag Naming
|
|
31
|
-
|
|
32
|
-
Always provide a unique string identifier:
|
|
33
|
-
|
|
34
|
-
```typescript
|
|
35
|
-
// Good - unique string key
|
|
36
|
-
class UserService extends Context.Tag("UserService")<...>() {}
|
|
37
|
-
class OrderService extends Context.Tag("OrderService")<...>() {}
|
|
38
|
-
|
|
39
|
-
// Avoid - no identifier (can cause issues with hot reload)
|
|
40
|
-
class BadService extends Context.Tag()<...>() {}
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
## Using Services
|
|
44
|
-
|
|
45
|
-
### Access Service in Effect.gen
|
|
46
|
-
|
|
47
|
-
```typescript
|
|
48
|
-
import { Effect } from "effect"
|
|
49
|
-
|
|
50
|
-
// Yield the tag to get the service
|
|
51
|
-
const getUsers = Effect.gen(function* () {
|
|
52
|
-
const db = yield* Database
|
|
53
|
-
const rows = yield* db.query("SELECT * FROM users")
|
|
54
|
-
return rows
|
|
55
|
-
})
|
|
56
|
-
// Effect<unknown[], never, Database>
|
|
57
|
-
|
|
58
|
-
// Multiple services
|
|
59
|
-
const logAndQuery = Effect.gen(function* () {
|
|
60
|
-
const db = yield* Database
|
|
61
|
-
const logger = yield* Logger
|
|
62
|
-
|
|
63
|
-
yield* logger.log("Querying users...")
|
|
64
|
-
const users = yield* db.query("SELECT * FROM users")
|
|
65
|
-
yield* logger.log(`Found ${users.length} users`)
|
|
66
|
-
|
|
67
|
-
return users
|
|
68
|
-
})
|
|
69
|
-
// Effect<unknown[], never, Database | Logger>
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
### Access Service with pipe
|
|
73
|
-
|
|
74
|
-
```typescript
|
|
75
|
-
import { Effect } from "effect"
|
|
76
|
-
|
|
77
|
-
const getUsers = Database.pipe(
|
|
78
|
-
Effect.flatMap(db => db.query("SELECT * FROM users"))
|
|
79
|
-
)
|
|
80
|
-
|
|
81
|
-
// Or use Effect.serviceWith
|
|
82
|
-
const getUsers2 = Effect.serviceWith(Database, db =>
|
|
83
|
-
db.query("SELECT * FROM users")
|
|
84
|
-
)
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
## Layer - Providing Services
|
|
88
|
-
|
|
89
|
-
Layers are recipes for building services with lifecycle management.
|
|
90
|
-
|
|
91
|
-
### Simple Layer (No Dependencies)
|
|
92
|
-
|
|
93
|
-
```typescript
|
|
94
|
-
import { Layer } from "effect"
|
|
95
|
-
|
|
96
|
-
// Layer.succeed for services with no effects or dependencies
|
|
97
|
-
const DatabaseLive = Layer.succeed(Database, {
|
|
98
|
-
query: (sql) => Effect.sync(() => {
|
|
99
|
-
console.log(`Query: ${sql}`)
|
|
100
|
-
return []
|
|
101
|
-
}),
|
|
102
|
-
execute: (sql) => Effect.sync(() => {
|
|
103
|
-
console.log(`Execute: ${sql}`)
|
|
104
|
-
})
|
|
105
|
-
})
|
|
106
|
-
// Layer<Database, never, never>
|
|
107
|
-
|
|
108
|
-
const LoggerLive = Layer.succeed(Logger, {
|
|
109
|
-
log: (msg) => Effect.sync(() => console.log(msg)),
|
|
110
|
-
error: (msg) => Effect.sync(() => console.error(msg))
|
|
111
|
-
})
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
### Layer with Effects
|
|
115
|
-
|
|
116
|
-
```typescript
|
|
117
|
-
import { Layer, Effect } from "effect"
|
|
118
|
-
|
|
119
|
-
// Layer.effect when construction requires effects
|
|
120
|
-
const DatabaseLive = Layer.effect(
|
|
121
|
-
Database,
|
|
122
|
-
Effect.sync(() => {
|
|
123
|
-
console.log("Initializing database connection...")
|
|
124
|
-
return {
|
|
125
|
-
query: (sql) => Effect.sync(() => []),
|
|
126
|
-
execute: (sql) => Effect.sync(() => {})
|
|
127
|
-
}
|
|
128
|
-
})
|
|
129
|
-
)
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
### Layer with Dependencies
|
|
133
|
-
|
|
134
|
-
```typescript
|
|
135
|
-
import { Layer, Effect } from "effect"
|
|
136
|
-
|
|
137
|
-
// Service that depends on another service
|
|
138
|
-
class UserRepository extends Context.Tag("UserRepository")<
|
|
139
|
-
UserRepository,
|
|
140
|
-
{
|
|
141
|
-
readonly findById: (id: string) => Effect.Effect<User | null>
|
|
142
|
-
readonly save: (user: User) => Effect.Effect<void>
|
|
143
|
-
}
|
|
144
|
-
>() {}
|
|
145
|
-
|
|
146
|
-
// Layer that requires Database
|
|
147
|
-
const UserRepositoryLive = Layer.effect(
|
|
148
|
-
UserRepository,
|
|
149
|
-
Effect.gen(function* () {
|
|
150
|
-
const db = yield* Database // Dependency
|
|
151
|
-
|
|
152
|
-
return {
|
|
153
|
-
findById: (id) => db.query(`SELECT * FROM users WHERE id = '${id}'`).pipe(
|
|
154
|
-
Effect.map(rows => rows[0] as User | null)
|
|
155
|
-
),
|
|
156
|
-
save: (user) => db.execute(`INSERT INTO users ...`)
|
|
157
|
-
}
|
|
158
|
-
})
|
|
159
|
-
)
|
|
160
|
-
// Layer<UserRepository, never, Database>
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
### Layer with Resources
|
|
164
|
-
|
|
165
|
-
```typescript
|
|
166
|
-
import { Layer, Effect } from "effect"
|
|
167
|
-
|
|
168
|
-
// Layer.scoped for services that need cleanup
|
|
169
|
-
const ConnectionPoolLive = Layer.scoped(
|
|
170
|
-
ConnectionPool,
|
|
171
|
-
Effect.acquireRelease(
|
|
172
|
-
// Acquire
|
|
173
|
-
Effect.sync(() => {
|
|
174
|
-
console.log("Creating connection pool...")
|
|
175
|
-
return { getConnection: () => Effect.succeed(conn) }
|
|
176
|
-
}),
|
|
177
|
-
// Release
|
|
178
|
-
(pool) => Effect.sync(() => {
|
|
179
|
-
console.log("Closing connection pool...")
|
|
180
|
-
})
|
|
181
|
-
)
|
|
182
|
-
)
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
## Composing Layers
|
|
186
|
-
|
|
187
|
-
### Merge (Combine Independent Layers)
|
|
188
|
-
|
|
189
|
-
```typescript
|
|
190
|
-
import { Layer } from "effect"
|
|
191
|
-
|
|
192
|
-
// Combine layers that don't depend on each other
|
|
193
|
-
const AppLayer = Layer.merge(DatabaseLive, LoggerLive)
|
|
194
|
-
// Layer<Database | Logger, never, never>
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
### Provide (Layer Dependencies)
|
|
198
|
-
|
|
199
|
-
```typescript
|
|
200
|
-
import { Layer } from "effect"
|
|
201
|
-
|
|
202
|
-
// UserRepositoryLive requires Database
|
|
203
|
-
// Provide it using Layer.provide
|
|
204
|
-
|
|
205
|
-
const UserRepositoryWithDb = UserRepositoryLive.pipe(
|
|
206
|
-
Layer.provide(DatabaseLive)
|
|
207
|
-
)
|
|
208
|
-
// Layer<UserRepository, never, never>
|
|
209
|
-
|
|
210
|
-
// Build full app layer
|
|
211
|
-
const AppLayer = Layer.merge(
|
|
212
|
-
Layer.merge(DatabaseLive, LoggerLive),
|
|
213
|
-
UserRepositoryWithDb
|
|
214
|
-
)
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
### Sequential Composition
|
|
218
|
-
|
|
219
|
-
```typescript
|
|
220
|
-
import { Layer } from "effect"
|
|
221
|
-
|
|
222
|
-
// provideMerge: provide dependencies and include them in output
|
|
223
|
-
const AppLayer = UserRepositoryLive.pipe(
|
|
224
|
-
Layer.provideMerge(DatabaseLive)
|
|
225
|
-
)
|
|
226
|
-
// Layer<UserRepository | Database, never, never>
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
## Running Effects with Layers
|
|
230
|
-
|
|
231
|
-
### Provide Layer to Effect
|
|
232
|
-
|
|
233
|
-
```typescript
|
|
234
|
-
import { Effect, Layer } from "effect"
|
|
235
|
-
|
|
236
|
-
const program = Effect.gen(function* () {
|
|
237
|
-
const db = yield* Database
|
|
238
|
-
const logger = yield* Logger
|
|
239
|
-
yield* logger.log("Starting...")
|
|
240
|
-
return yield* db.query("SELECT 1")
|
|
241
|
-
})
|
|
242
|
-
|
|
243
|
-
const AppLayer = Layer.merge(DatabaseLive, LoggerLive)
|
|
244
|
-
|
|
245
|
-
// Provide layer and run
|
|
246
|
-
const runnable = Effect.provide(program, AppLayer)
|
|
247
|
-
await Effect.runPromise(runnable)
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
### Provide Multiple Layers
|
|
251
|
-
|
|
252
|
-
```typescript
|
|
253
|
-
const program = Effect.gen(function* () {
|
|
254
|
-
const userRepo = yield* UserRepository
|
|
255
|
-
const logger = yield* Logger
|
|
256
|
-
// ...
|
|
257
|
-
})
|
|
258
|
-
|
|
259
|
-
// Provide layers
|
|
260
|
-
const runnable = program.pipe(
|
|
261
|
-
Effect.provide(UserRepositoryLive),
|
|
262
|
-
Effect.provide(DatabaseLive),
|
|
263
|
-
Effect.provide(LoggerLive)
|
|
264
|
-
)
|
|
265
|
-
```
|
|
266
|
-
|
|
267
|
-
## Testing with Layers
|
|
268
|
-
|
|
269
|
-
### Test Implementations
|
|
270
|
-
|
|
271
|
-
```typescript
|
|
272
|
-
import { Layer } from "effect"
|
|
273
|
-
|
|
274
|
-
// Test implementation
|
|
275
|
-
const DatabaseTest = Layer.succeed(Database, {
|
|
276
|
-
query: (sql) => Effect.succeed([
|
|
277
|
-
{ id: "1", name: "Test User" }
|
|
278
|
-
]),
|
|
279
|
-
execute: (sql) => Effect.succeed(undefined)
|
|
280
|
-
})
|
|
281
|
-
|
|
282
|
-
// Use in tests
|
|
283
|
-
const TestLayer = Layer.merge(DatabaseTest, LoggerLive)
|
|
284
|
-
|
|
285
|
-
test("should get users", async () => {
|
|
286
|
-
const result = await Effect.runPromise(
|
|
287
|
-
getUsers.pipe(Effect.provide(TestLayer))
|
|
288
|
-
)
|
|
289
|
-
expect(result).toHaveLength(1)
|
|
290
|
-
})
|
|
291
|
-
```
|
|
292
|
-
|
|
293
|
-
### Partial Mocks
|
|
294
|
-
|
|
295
|
-
```typescript
|
|
296
|
-
import { Layer } from "effect"
|
|
297
|
-
|
|
298
|
-
// Override specific methods
|
|
299
|
-
const DatabasePartialMock = Layer.succeed(Database, {
|
|
300
|
-
...realDatabase, // spread real implementation
|
|
301
|
-
query: (sql) => Effect.succeed([mockData]) // override specific method
|
|
302
|
-
})
|
|
303
|
-
```
|
|
304
|
-
|
|
305
|
-
## Service Patterns
|
|
306
|
-
|
|
307
|
-
### Service with Configuration
|
|
308
|
-
|
|
309
|
-
```typescript
|
|
310
|
-
import { Context, Effect, Layer, Config } from "effect"
|
|
311
|
-
|
|
312
|
-
interface DatabaseConfig {
|
|
313
|
-
host: string
|
|
314
|
-
port: number
|
|
315
|
-
database: string
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
class DatabaseConfigService extends Context.Tag("DatabaseConfig")<
|
|
319
|
-
DatabaseConfigService,
|
|
320
|
-
DatabaseConfig
|
|
321
|
-
>() {}
|
|
322
|
-
|
|
323
|
-
// Layer that reads from config
|
|
324
|
-
const DatabaseConfigLive = Layer.effect(
|
|
325
|
-
DatabaseConfigService,
|
|
326
|
-
Effect.gen(function* () {
|
|
327
|
-
const host = yield* Config.string("DB_HOST")
|
|
328
|
-
const port = yield* Config.number("DB_PORT")
|
|
329
|
-
const database = yield* Config.string("DB_NAME")
|
|
330
|
-
return { host, port, database }
|
|
331
|
-
})
|
|
332
|
-
)
|
|
333
|
-
```
|
|
334
|
-
|
|
335
|
-
### Factory Services
|
|
336
|
-
|
|
337
|
-
```typescript
|
|
338
|
-
import { Context, Effect } from "effect"
|
|
339
|
-
|
|
340
|
-
// Service that creates other things
|
|
341
|
-
class UserFactory extends Context.Tag("UserFactory")<
|
|
342
|
-
UserFactory,
|
|
343
|
-
{
|
|
344
|
-
readonly create: (data: CreateUserInput) => Effect.Effect<User, ValidationError>
|
|
345
|
-
}
|
|
346
|
-
>() {}
|
|
347
|
-
|
|
348
|
-
const UserFactoryLive = Layer.succeed(UserFactory, {
|
|
349
|
-
create: (data) => Effect.gen(function* () {
|
|
350
|
-
// Validate
|
|
351
|
-
if (!data.email.includes("@")) {
|
|
352
|
-
return yield* Effect.fail(new ValidationError("Invalid email"))
|
|
353
|
-
}
|
|
354
|
-
// Create
|
|
355
|
-
return {
|
|
356
|
-
id: crypto.randomUUID(),
|
|
357
|
-
...data,
|
|
358
|
-
createdAt: new Date()
|
|
359
|
-
}
|
|
360
|
-
})
|
|
361
|
-
})
|
|
362
|
-
```
|
|
363
|
-
|
|
364
|
-
### Service Composition
|
|
365
|
-
|
|
366
|
-
```typescript
|
|
367
|
-
import { Context, Effect, Layer } from "effect"
|
|
368
|
-
|
|
369
|
-
// High-level service composed from others
|
|
370
|
-
class UserService extends Context.Tag("UserService")<
|
|
371
|
-
UserService,
|
|
372
|
-
{
|
|
373
|
-
readonly register: (data: RegisterInput) => Effect.Effect<User, RegisterError>
|
|
374
|
-
readonly getProfile: (id: string) => Effect.Effect<UserProfile, NotFoundError>
|
|
375
|
-
}
|
|
376
|
-
>() {}
|
|
377
|
-
|
|
378
|
-
const UserServiceLive = Layer.effect(
|
|
379
|
-
UserService,
|
|
380
|
-
Effect.gen(function* () {
|
|
381
|
-
const userRepo = yield* UserRepository
|
|
382
|
-
const emailService = yield* EmailService
|
|
383
|
-
const logger = yield* Logger
|
|
384
|
-
|
|
385
|
-
return {
|
|
386
|
-
register: (data) => Effect.gen(function* () {
|
|
387
|
-
yield* logger.log(`Registering user: ${data.email}`)
|
|
388
|
-
const user = yield* userRepo.save({
|
|
389
|
-
id: crypto.randomUUID(),
|
|
390
|
-
...data
|
|
391
|
-
})
|
|
392
|
-
yield* emailService.sendWelcome(user.email)
|
|
393
|
-
return user
|
|
394
|
-
}),
|
|
395
|
-
getProfile: (id) => userRepo.findById(id).pipe(
|
|
396
|
-
Effect.flatMap(user =>
|
|
397
|
-
user ? Effect.succeed(toProfile(user)) : Effect.fail(new NotFoundError(id))
|
|
398
|
-
)
|
|
399
|
-
)
|
|
400
|
-
}
|
|
401
|
-
})
|
|
402
|
-
)
|
|
403
|
-
// Layer<UserService, never, UserRepository | EmailService | Logger>
|
|
404
|
-
```
|
|
405
|
-
|
|
406
|
-
## Layer Lifecycle
|
|
407
|
-
|
|
408
|
-
### Scoped Layers with Finalization
|
|
409
|
-
|
|
410
|
-
```typescript
|
|
411
|
-
import { Layer, Effect, Scope } from "effect"
|
|
412
|
-
|
|
413
|
-
const DatabasePoolLive = Layer.scoped(
|
|
414
|
-
DatabasePool,
|
|
415
|
-
Effect.acquireRelease(
|
|
416
|
-
// Acquire: runs when layer is built
|
|
417
|
-
Effect.sync(() => {
|
|
418
|
-
console.log("Creating pool with 10 connections")
|
|
419
|
-
return createPool({ size: 10 })
|
|
420
|
-
}),
|
|
421
|
-
// Release: runs when scope closes
|
|
422
|
-
(pool) => Effect.sync(() => {
|
|
423
|
-
console.log("Closing all connections")
|
|
424
|
-
pool.close()
|
|
425
|
-
})
|
|
426
|
-
)
|
|
427
|
-
)
|
|
428
|
-
|
|
429
|
-
// Resources are cleaned up when program exits
|
|
430
|
-
const program = Effect.scoped(
|
|
431
|
-
Effect.gen(function* () {
|
|
432
|
-
const pool = yield* DatabasePool
|
|
433
|
-
// Use pool...
|
|
434
|
-
})
|
|
435
|
-
)
|
|
436
|
-
```
|
|
437
|
-
|
|
438
|
-
### Layer Memoization
|
|
439
|
-
|
|
440
|
-
```typescript
|
|
441
|
-
import { Layer } from "effect"
|
|
442
|
-
|
|
443
|
-
// Layers are memoized by default - built once, shared
|
|
444
|
-
const AppLayer = Layer.merge(
|
|
445
|
-
DatabaseLive,
|
|
446
|
-
Layer.merge(UserRepoLive, OrderRepoLive) // Both use same Database
|
|
447
|
-
)
|
|
448
|
-
|
|
449
|
-
// To disable memoization:
|
|
450
|
-
const FreshLayer = Layer.fresh(DatabaseLive)
|
|
451
|
-
```
|
|
452
|
-
|
|
453
|
-
## Best Practices
|
|
454
|
-
|
|
455
|
-
1. **Use string identifiers for tags** - Prevents issues with module reloading
|
|
456
|
-
2. **Keep service interfaces small** - Single responsibility per service
|
|
457
|
-
3. **Use Layer.scoped for resources** - Ensures proper cleanup
|
|
458
|
-
4. **Test with mock layers** - Easy to substitute implementations
|
|
459
|
-
5. **Build layers bottom-up** - Satisfy dependencies before composing
|
|
460
|
-
6. **Separate live and test layers** - Keep production/test implementations distinct
|
|
461
|
-
7. **Use Config for external configuration** - Don't hardcode in layers
|