@martinffx/atelier 0.1.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/LICENSE +21 -0
- package/README.md +285 -0
- package/agents/architect.md +38 -0
- package/agents/oracle.md +40 -0
- package/agents/recon.md +39 -0
- package/dist/atelier.js +33082 -0
- package/package.json +44 -0
- package/skills/code-conventional-commit/SKILL.md +134 -0
- package/skills/code-conventional-commit/evals/evals.json +23 -0
- package/skills/code-conventional-commit/workspace/iteration-1/eval-0/with_skill/outputs/commit-message.txt +4 -0
- package/skills/code-conventional-commit/workspace/iteration-1/eval-0/with_skill/outputs/eval_metadata.json +6 -0
- package/skills/code-conventional-commit/workspace/iteration-1/eval-1/with_skill/outputs/eval_metadata.json +6 -0
- package/skills/code-conventional-commit/workspace/iteration-1/eval-1/with_skill/outputs/validation-result.txt +16 -0
- package/skills/code-conventional-commit/workspace/iteration-1/eval-2/with_skill/outputs/commit-message.txt +8 -0
- package/skills/code-conventional-commit/workspace/iteration-1/eval-2/with_skill/outputs/eval_metadata.json +6 -0
- package/skills/code-conventional-commit/workspace/iteration-1/report.html +1325 -0
- package/skills/code-debug/SKILL.md +271 -0
- package/skills/code-debug/references/language-tools.md +139 -0
- package/skills/code-debug/references/techniques.md +117 -0
- package/skills/code-docs/SKILL.md +171 -0
- package/skills/code-docs/references/api-docs.md +116 -0
- package/skills/code-docs/references/changelog.md +64 -0
- package/skills/code-docs/references/readme-structure.md +94 -0
- package/skills/code-git-worktrees/SKILL.md +218 -0
- package/skills/code-perf/SKILL.md +163 -0
- package/skills/code-perf/references/common-bottlenecks.md +124 -0
- package/skills/code-perf/references/optimization-patterns.md +167 -0
- package/skills/code-perf/references/profiling-tools.md +126 -0
- package/skills/code-review/SKILL.md +78 -0
- package/skills/code-review/references/output.md +200 -0
- package/skills/code-review/references/reviewers.md +719 -0
- package/skills/code-review/references/rq.md +357 -0
- package/skills/code-review/references/rs.md +30 -0
- package/skills/code-security/SKILL.md +183 -0
- package/skills/code-security/references/owasp-top-10.md +129 -0
- package/skills/code-security/references/security-tools.md +102 -0
- package/skills/code-security/references/vulnerability-patterns.md +129 -0
- package/skills/code-stacked-commit/SKILL.md +155 -0
- package/skills/code-subagents/SKILL.md +184 -0
- package/skills/code-subagents/references/code-quality-reviewer-prompt.md +48 -0
- package/skills/code-subagents/references/implementor-prompt.md +79 -0
- package/skills/code-subagents/references/spec-reviewer-prompt.md +62 -0
- package/skills/oracle-architect/SKILL.md +246 -0
- package/skills/oracle-architect/references/api-design.md +345 -0
- package/skills/oracle-architect/references/data-modeling.md +318 -0
- package/skills/oracle-architect/references/ddd-patterns.md +247 -0
- package/skills/oracle-challenge/SKILL.md +126 -0
- package/skills/oracle-testing/SKILL.md +237 -0
- package/skills/oracle-testing/references/anti-patterns.md +618 -0
- package/skills/oracle-testing/references/boundaries.md +579 -0
- package/skills/oracle-testing/references/core-testing.md +691 -0
- package/skills/oracle-testing/references/edge-testing.md +794 -0
- package/skills/oracle-testing/references/stub-driven-tdd.md +322 -0
- package/skills/oracle-thinkdeep/SKILL.md +164 -0
- package/skills/python-architecture/SKILL.md +148 -0
- package/skills/python-architecture/references/data-modeling.md +558 -0
- package/skills/python-architecture/references/ddd.md +569 -0
- package/skills/python-architecture/references/functional-core.md +337 -0
- package/skills/python-build-tools/SKILL.md +153 -0
- package/skills/python-build-tools/references/basedpyright.md +321 -0
- package/skills/python-build-tools/references/ruff.md +225 -0
- package/skills/python-build-tools/references/uv.md +257 -0
- package/skills/python-fastapi/SKILL.md +184 -0
- package/skills/python-fastapi/references/api-design.md +563 -0
- package/skills/python-fastapi/references/dependencies.md +479 -0
- package/skills/python-fastapi/references/middleware.md +475 -0
- package/skills/python-fastapi/references/validation.md +497 -0
- package/skills/python-modern-python/SKILL.md +201 -0
- package/skills/python-modern-python/references/async.md +76 -0
- package/skills/python-modern-python/references/pattern-matching.md +89 -0
- package/skills/python-modern-python/references/typing.md +96 -0
- package/skills/python-monorepo/SKILL.md +146 -0
- package/skills/python-monorepo/references/docker.md +515 -0
- package/skills/python-monorepo/references/namespace-packages.md +426 -0
- package/skills/python-monorepo/references/workspace-config.md +341 -0
- package/skills/python-sqlalchemy/SKILL.md +119 -0
- package/skills/python-sqlalchemy/references/async.md +51 -0
- package/skills/python-sqlalchemy/references/models.md +67 -0
- package/skills/python-sqlalchemy/references/queries.md +57 -0
- package/skills/python-temporal/SKILL.md +128 -0
- package/skills/python-temporal/references/patterns.md +87 -0
- package/skills/python-temporal/references/testing.md +54 -0
- package/skills/python-testing/SKILL.md +161 -0
- package/skills/python-testing/references/boundaries.md +449 -0
- package/skills/python-testing/references/mocking.md +449 -0
- package/skills/python-testing/references/pytest.md +524 -0
- package/skills/spec-brainstorm/SKILL.md +325 -0
- package/skills/spec-finish/SKILL.md +159 -0
- package/skills/spec-implement/SKILL.md +296 -0
- package/skills/spec-orchestrator/SKILL.md +156 -0
- package/skills/spec-plan/SKILL.md +248 -0
- package/skills/typescript-api-design/SKILL.md +396 -0
- package/skills/typescript-api-design/references/error-responses.md +469 -0
- package/skills/typescript-build-tools/SKILL.md +447 -0
- package/skills/typescript-build-tools/references/biome.md +436 -0
- package/skills/typescript-build-tools/references/bun.md +432 -0
- package/skills/typescript-build-tools/references/turborepo.md +426 -0
- package/skills/typescript-build-tools/references/vitest.md +518 -0
- package/skills/typescript-drizzle-orm/SKILL.md +398 -0
- package/skills/typescript-drizzle-orm/references/cloudflare.md +392 -0
- package/skills/typescript-drizzle-orm/references/entity-pattern.md +412 -0
- package/skills/typescript-drizzle-orm/references/postgresql.md +191 -0
- package/skills/typescript-drizzle-orm/references/repository-pattern.md +479 -0
- package/skills/typescript-drizzle-orm/references/sqlite.md +291 -0
- package/skills/typescript-dynamodb-toolbox/SKILL.md +370 -0
- package/skills/typescript-dynamodb-toolbox/references/entity-layer.md +574 -0
- package/skills/typescript-dynamodb-toolbox/references/error-handling.md +495 -0
- package/skills/typescript-dynamodb-toolbox/references/modeling.md +332 -0
- package/skills/typescript-dynamodb-toolbox/references/repository-layer.md +631 -0
- package/skills/typescript-dynamodb-toolbox/references/testing.md +718 -0
- package/skills/typescript-dynamodb-toolbox/references/transactions.md +423 -0
- package/skills/typescript-effect-ts/SKILL.md +341 -0
- package/skills/typescript-effect-ts/references/core.md +412 -0
- package/skills/typescript-effect-ts/references/error-handling.md +468 -0
- package/skills/typescript-effect-ts/references/resources.md +476 -0
- package/skills/typescript-effect-ts/references/schema.md +536 -0
- package/skills/typescript-effect-ts/references/services.md +461 -0
- package/skills/typescript-fastify/SKILL.md +405 -0
- package/skills/typescript-fastify/references/plugins.md +329 -0
- package/skills/typescript-fastify/references/typeid.md +315 -0
- package/skills/typescript-functional-patterns/SKILL.md +402 -0
- package/skills/typescript-functional-patterns/references/adts.md +497 -0
- package/skills/typescript-functional-patterns/references/branded-types.md +542 -0
- package/skills/typescript-functional-patterns/references/migration-guide.md +695 -0
- package/skills/typescript-functional-patterns/references/option-result.md +585 -0
- package/skills/typescript-testing/SKILL.md +353 -0
- package/skills/typescript-testing/references/mocking.md +675 -0
- package/skills/typescript-testing/references/msw.md +852 -0
- package/skills/typescript-testing/references/snapshot-testing.md +774 -0
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
# Resource Management in Effect
|
|
2
|
+
|
|
3
|
+
Effect provides powerful primitives for managing resources that need cleanup, ensuring they're always released even on failure or interruption.
|
|
4
|
+
|
|
5
|
+
## acquireRelease - Basic Resource Pattern
|
|
6
|
+
|
|
7
|
+
The core pattern for resource management:
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
import { Effect } from "effect"
|
|
11
|
+
|
|
12
|
+
// Define a resource with acquisition and release
|
|
13
|
+
const withFile = (path: string) =>
|
|
14
|
+
Effect.acquireRelease(
|
|
15
|
+
// Acquire: runs to get the resource
|
|
16
|
+
Effect.sync(() => {
|
|
17
|
+
console.log(`Opening file: ${path}`)
|
|
18
|
+
return { path, handle: fs.openSync(path, "r") }
|
|
19
|
+
}),
|
|
20
|
+
// Release: always runs when scope closes
|
|
21
|
+
(file) => Effect.sync(() => {
|
|
22
|
+
console.log(`Closing file: ${file.path}`)
|
|
23
|
+
fs.closeSync(file.handle)
|
|
24
|
+
})
|
|
25
|
+
)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Using Resources with Scope
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import { Effect } from "effect"
|
|
32
|
+
|
|
33
|
+
// Effect.scoped provides a scope and runs release after use
|
|
34
|
+
const program = Effect.scoped(
|
|
35
|
+
Effect.gen(function* () {
|
|
36
|
+
const file = yield* withFile("/path/to/file")
|
|
37
|
+
const content = yield* readFile(file)
|
|
38
|
+
return content
|
|
39
|
+
})
|
|
40
|
+
)
|
|
41
|
+
// File is automatically closed after reading
|
|
42
|
+
|
|
43
|
+
Effect.runPromise(program)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Resource Guarantees
|
|
47
|
+
|
|
48
|
+
- **Release always runs** - Even if the using code fails or throws
|
|
49
|
+
- **Release runs on interruption** - If the fiber is interrupted
|
|
50
|
+
- **Typed errors** - Release errors are tracked in the type system
|
|
51
|
+
|
|
52
|
+
## acquireUseRelease - Inline Pattern
|
|
53
|
+
|
|
54
|
+
For one-off resource usage:
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
import { Effect } from "effect"
|
|
58
|
+
|
|
59
|
+
const readConfig = Effect.acquireUseRelease(
|
|
60
|
+
// Acquire
|
|
61
|
+
Effect.sync(() => fs.openSync("config.json", "r")),
|
|
62
|
+
// Use
|
|
63
|
+
(handle) => Effect.sync(() => {
|
|
64
|
+
const content = fs.readFileSync(handle, "utf-8")
|
|
65
|
+
return JSON.parse(content)
|
|
66
|
+
}),
|
|
67
|
+
// Release
|
|
68
|
+
(handle) => Effect.sync(() => fs.closeSync(handle))
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
// No explicit scoping needed - handled internally
|
|
72
|
+
const config = await Effect.runPromise(readConfig)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Multiple Resources
|
|
76
|
+
|
|
77
|
+
### Sequential Acquisition
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
import { Effect } from "effect"
|
|
81
|
+
|
|
82
|
+
const program = Effect.scoped(
|
|
83
|
+
Effect.gen(function* () {
|
|
84
|
+
// Resources acquired in order
|
|
85
|
+
const db = yield* withDatabaseConnection()
|
|
86
|
+
const cache = yield* withCacheConnection()
|
|
87
|
+
const queue = yield* withMessageQueue()
|
|
88
|
+
|
|
89
|
+
// Use all three...
|
|
90
|
+
yield* doWork(db, cache, queue)
|
|
91
|
+
|
|
92
|
+
// Released in reverse order: queue → cache → db
|
|
93
|
+
})
|
|
94
|
+
)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Parallel Acquisition
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
import { Effect } from "effect"
|
|
101
|
+
|
|
102
|
+
const program = Effect.scoped(
|
|
103
|
+
Effect.gen(function* () {
|
|
104
|
+
// Acquire resources in parallel
|
|
105
|
+
const [db, cache, queue] = yield* Effect.all([
|
|
106
|
+
withDatabaseConnection(),
|
|
107
|
+
withCacheConnection(),
|
|
108
|
+
withMessageQueue()
|
|
109
|
+
], { concurrency: "unbounded" })
|
|
110
|
+
|
|
111
|
+
yield* doWork(db, cache, queue)
|
|
112
|
+
})
|
|
113
|
+
)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Scope - Manual Control
|
|
117
|
+
|
|
118
|
+
For more control over resource lifecycle:
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
import { Effect, Scope } from "effect"
|
|
122
|
+
|
|
123
|
+
const program = Effect.gen(function* () {
|
|
124
|
+
// Create a manual scope
|
|
125
|
+
const scope = yield* Scope.make()
|
|
126
|
+
|
|
127
|
+
// Add resources to scope
|
|
128
|
+
const db = yield* Scope.extend(scope)(withDatabaseConnection())
|
|
129
|
+
const cache = yield* Scope.extend(scope)(withCacheConnection())
|
|
130
|
+
|
|
131
|
+
// Use resources...
|
|
132
|
+
yield* doWork(db, cache)
|
|
133
|
+
|
|
134
|
+
// Manually close scope when done
|
|
135
|
+
yield* Scope.close(scope, Exit.succeed(void 0))
|
|
136
|
+
})
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Scope Forking
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
import { Effect, Scope } from "effect"
|
|
143
|
+
|
|
144
|
+
const program = Effect.gen(function* () {
|
|
145
|
+
// Fork scope to create child scope
|
|
146
|
+
const parentScope = yield* Effect.scope
|
|
147
|
+
const childScope = yield* Scope.fork(parentScope)
|
|
148
|
+
|
|
149
|
+
// Resources in child scope
|
|
150
|
+
const tempFile = yield* Scope.extend(childScope)(withTempFile())
|
|
151
|
+
|
|
152
|
+
// Close child scope (releases tempFile)
|
|
153
|
+
yield* Scope.close(childScope, Exit.succeed(void 0))
|
|
154
|
+
|
|
155
|
+
// Parent scope continues...
|
|
156
|
+
})
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Resource Finalization
|
|
160
|
+
|
|
161
|
+
### Ensuring Finalization
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
import { Effect } from "effect"
|
|
165
|
+
|
|
166
|
+
const program = Effect.gen(function* () {
|
|
167
|
+
yield* doWork()
|
|
168
|
+
}).pipe(
|
|
169
|
+
// Runs regardless of success/failure
|
|
170
|
+
Effect.ensuring(
|
|
171
|
+
Effect.sync(() => console.log("Cleanup complete"))
|
|
172
|
+
)
|
|
173
|
+
)
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### onExit - Conditional Finalization
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
import { Effect, Exit } from "effect"
|
|
180
|
+
|
|
181
|
+
const program = effect.pipe(
|
|
182
|
+
Effect.onExit((exit) => {
|
|
183
|
+
if (Exit.isSuccess(exit)) {
|
|
184
|
+
return Effect.sync(() => console.log("Success:", exit.value))
|
|
185
|
+
} else {
|
|
186
|
+
return Effect.sync(() => console.log("Failed:", exit.cause))
|
|
187
|
+
}
|
|
188
|
+
})
|
|
189
|
+
)
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### addFinalizer - Add to Current Scope
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
import { Effect } from "effect"
|
|
196
|
+
|
|
197
|
+
const program = Effect.scoped(
|
|
198
|
+
Effect.gen(function* () {
|
|
199
|
+
// Add finalizer to current scope
|
|
200
|
+
yield* Effect.addFinalizer((exit) =>
|
|
201
|
+
Effect.sync(() => {
|
|
202
|
+
console.log("Cleaning up, exit was:", Exit.isSuccess(exit))
|
|
203
|
+
})
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
yield* doWork()
|
|
207
|
+
})
|
|
208
|
+
)
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Common Resource Patterns
|
|
212
|
+
|
|
213
|
+
### Database Connection Pool
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
import { Effect, Layer, Scope } from "effect"
|
|
217
|
+
|
|
218
|
+
interface ConnectionPool {
|
|
219
|
+
getConnection: () => Effect.Effect<Connection, PoolError>
|
|
220
|
+
release: (conn: Connection) => Effect.Effect<void>
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const makeConnectionPool = (config: PoolConfig) =>
|
|
224
|
+
Effect.acquireRelease(
|
|
225
|
+
// Create pool
|
|
226
|
+
Effect.sync(() => {
|
|
227
|
+
console.log("Creating connection pool...")
|
|
228
|
+
return {
|
|
229
|
+
connections: [] as Connection[],
|
|
230
|
+
getConnection: () => Effect.sync(() => createConnection()),
|
|
231
|
+
release: (conn) => Effect.sync(() => releaseToPool(conn))
|
|
232
|
+
}
|
|
233
|
+
}),
|
|
234
|
+
// Close all connections
|
|
235
|
+
(pool) => Effect.sync(() => {
|
|
236
|
+
console.log("Closing all pool connections...")
|
|
237
|
+
pool.connections.forEach(conn => conn.close())
|
|
238
|
+
})
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
const ConnectionPoolLive = Layer.scoped(
|
|
242
|
+
ConnectionPool,
|
|
243
|
+
makeConnectionPool({ size: 10 })
|
|
244
|
+
)
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### File Handle
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
import { Effect } from "effect"
|
|
251
|
+
import * as fs from "fs/promises"
|
|
252
|
+
|
|
253
|
+
const withFile = (path: string, flags: string) =>
|
|
254
|
+
Effect.acquireRelease(
|
|
255
|
+
Effect.tryPromise({
|
|
256
|
+
try: () => fs.open(path, flags),
|
|
257
|
+
catch: (e) => new FileOpenError(path, e)
|
|
258
|
+
}),
|
|
259
|
+
(handle) => Effect.promise(() => handle.close())
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
const readFile = (path: string) =>
|
|
263
|
+
Effect.scoped(
|
|
264
|
+
Effect.gen(function* () {
|
|
265
|
+
const handle = yield* withFile(path, "r")
|
|
266
|
+
const content = yield* Effect.tryPromise({
|
|
267
|
+
try: () => handle.readFile("utf-8"),
|
|
268
|
+
catch: (e) => new FileReadError(path, e)
|
|
269
|
+
})
|
|
270
|
+
return content
|
|
271
|
+
})
|
|
272
|
+
)
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### HTTP Client with Cleanup
|
|
276
|
+
|
|
277
|
+
```typescript
|
|
278
|
+
import { Effect } from "effect"
|
|
279
|
+
|
|
280
|
+
const withHttpClient = Effect.acquireRelease(
|
|
281
|
+
Effect.sync(() => {
|
|
282
|
+
console.log("Creating HTTP client...")
|
|
283
|
+
return new HttpClient({
|
|
284
|
+
timeout: 30000,
|
|
285
|
+
keepAlive: true
|
|
286
|
+
})
|
|
287
|
+
}),
|
|
288
|
+
(client) => Effect.sync(() => {
|
|
289
|
+
console.log("Destroying HTTP client...")
|
|
290
|
+
client.destroy()
|
|
291
|
+
})
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
const fetchData = (url: string) =>
|
|
295
|
+
Effect.scoped(
|
|
296
|
+
Effect.gen(function* () {
|
|
297
|
+
const client = yield* withHttpClient
|
|
298
|
+
const response = yield* Effect.tryPromise({
|
|
299
|
+
try: () => client.get(url),
|
|
300
|
+
catch: (e) => new FetchError(url, e)
|
|
301
|
+
})
|
|
302
|
+
return response.data
|
|
303
|
+
})
|
|
304
|
+
)
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### Temporary Directory
|
|
308
|
+
|
|
309
|
+
```typescript
|
|
310
|
+
import { Effect } from "effect"
|
|
311
|
+
import * as fs from "fs/promises"
|
|
312
|
+
import * as os from "os"
|
|
313
|
+
import * as path from "path"
|
|
314
|
+
|
|
315
|
+
const withTempDir = Effect.acquireRelease(
|
|
316
|
+
Effect.tryPromise({
|
|
317
|
+
try: async () => {
|
|
318
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "app-"))
|
|
319
|
+
console.log(`Created temp dir: ${dir}`)
|
|
320
|
+
return dir
|
|
321
|
+
},
|
|
322
|
+
catch: (e) => new TempDirError(e)
|
|
323
|
+
}),
|
|
324
|
+
(dir) => Effect.tryPromise({
|
|
325
|
+
try: async () => {
|
|
326
|
+
console.log(`Removing temp dir: ${dir}`)
|
|
327
|
+
await fs.rm(dir, { recursive: true, force: true })
|
|
328
|
+
},
|
|
329
|
+
catch: () => void 0 // Ignore cleanup errors
|
|
330
|
+
})
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
const processWithTempFiles = Effect.scoped(
|
|
334
|
+
Effect.gen(function* () {
|
|
335
|
+
const tempDir = yield* withTempDir
|
|
336
|
+
|
|
337
|
+
// Create temp files in directory
|
|
338
|
+
const tempFile = path.join(tempDir, "data.json")
|
|
339
|
+
yield* Effect.tryPromise({
|
|
340
|
+
try: () => fs.writeFile(tempFile, JSON.stringify(data)),
|
|
341
|
+
catch: (e) => new WriteError(e)
|
|
342
|
+
})
|
|
343
|
+
|
|
344
|
+
// Process...
|
|
345
|
+
const result = yield* processFile(tempFile)
|
|
346
|
+
|
|
347
|
+
return result
|
|
348
|
+
// Temp dir and all contents deleted automatically
|
|
349
|
+
})
|
|
350
|
+
)
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### Lock/Mutex
|
|
354
|
+
|
|
355
|
+
```typescript
|
|
356
|
+
import { Effect, Ref, Deferred } from "effect"
|
|
357
|
+
|
|
358
|
+
const withLock = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
|
|
359
|
+
Effect.gen(function* () {
|
|
360
|
+
const lock = yield* Ref.make(false)
|
|
361
|
+
|
|
362
|
+
// Acquire lock
|
|
363
|
+
yield* Effect.repeatUntil(
|
|
364
|
+
Ref.getAndSet(lock, true),
|
|
365
|
+
(acquired) => acquired === false
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
// Use with guaranteed release
|
|
369
|
+
return yield* effect.pipe(
|
|
370
|
+
Effect.ensuring(Ref.set(lock, false))
|
|
371
|
+
)
|
|
372
|
+
})
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
## Interruption Safety
|
|
376
|
+
|
|
377
|
+
### Making Resources Interruption-Safe
|
|
378
|
+
|
|
379
|
+
```typescript
|
|
380
|
+
import { Effect } from "effect"
|
|
381
|
+
|
|
382
|
+
const criticalOperation = Effect.gen(function* () {
|
|
383
|
+
const resource = yield* acquireResource()
|
|
384
|
+
|
|
385
|
+
// This section cannot be interrupted
|
|
386
|
+
yield* Effect.uninterruptible(
|
|
387
|
+
Effect.gen(function* () {
|
|
388
|
+
yield* step1(resource)
|
|
389
|
+
yield* step2(resource)
|
|
390
|
+
yield* step3(resource)
|
|
391
|
+
})
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
yield* releaseResource(resource)
|
|
395
|
+
})
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
### Interruptible Regions
|
|
399
|
+
|
|
400
|
+
```typescript
|
|
401
|
+
import { Effect } from "effect"
|
|
402
|
+
|
|
403
|
+
const program = Effect.uninterruptible(
|
|
404
|
+
Effect.gen(function* () {
|
|
405
|
+
yield* criticalSetup()
|
|
406
|
+
|
|
407
|
+
// Allow interruption in this region
|
|
408
|
+
yield* Effect.interruptible(
|
|
409
|
+
longRunningTask()
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
yield* criticalCleanup()
|
|
413
|
+
})
|
|
414
|
+
)
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
## Best Practices
|
|
418
|
+
|
|
419
|
+
1. **Use acquireRelease for resources** - Never rely on manual cleanup
|
|
420
|
+
2. **Scope resources appropriately** - Keep scopes as small as practical
|
|
421
|
+
3. **Order matters** - Resources release in reverse acquisition order
|
|
422
|
+
4. **Handle release errors** - Release should not throw; log and continue
|
|
423
|
+
5. **Use Layer.scoped for services** - Service lifecycle tied to app lifecycle
|
|
424
|
+
6. **Test resource cleanup** - Verify resources release on failure paths
|
|
425
|
+
7. **Avoid nested scopes when possible** - Flatten for clarity
|
|
426
|
+
8. **Make critical sections uninterruptible** - Protect invariants
|
|
427
|
+
|
|
428
|
+
## Common Mistakes
|
|
429
|
+
|
|
430
|
+
### Forgetting to Scope
|
|
431
|
+
|
|
432
|
+
```typescript
|
|
433
|
+
// Bad: resource never released
|
|
434
|
+
const bad = Effect.gen(function* () {
|
|
435
|
+
const file = yield* withFile("data.txt")
|
|
436
|
+
return yield* readContent(file)
|
|
437
|
+
})
|
|
438
|
+
|
|
439
|
+
// Good: scoped ensures release
|
|
440
|
+
const good = Effect.scoped(
|
|
441
|
+
Effect.gen(function* () {
|
|
442
|
+
const file = yield* withFile("data.txt")
|
|
443
|
+
return yield* readContent(file)
|
|
444
|
+
})
|
|
445
|
+
)
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
### Releasing in Acquire
|
|
449
|
+
|
|
450
|
+
```typescript
|
|
451
|
+
// Bad: release in acquire, not as finalizer
|
|
452
|
+
const bad = Effect.sync(() => {
|
|
453
|
+
const conn = createConnection()
|
|
454
|
+
return { conn, close: () => conn.close() } // Caller must remember
|
|
455
|
+
})
|
|
456
|
+
|
|
457
|
+
// Good: automatic release
|
|
458
|
+
const good = Effect.acquireRelease(
|
|
459
|
+
Effect.sync(() => createConnection()),
|
|
460
|
+
(conn) => Effect.sync(() => conn.close())
|
|
461
|
+
)
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
### Swallowing Release Errors
|
|
465
|
+
|
|
466
|
+
```typescript
|
|
467
|
+
// Acceptable: log but don't fail
|
|
468
|
+
const resource = Effect.acquireRelease(
|
|
469
|
+
acquire,
|
|
470
|
+
(r) => release(r).pipe(
|
|
471
|
+
Effect.catchAll((error) =>
|
|
472
|
+
Effect.sync(() => console.error("Release error:", error))
|
|
473
|
+
)
|
|
474
|
+
)
|
|
475
|
+
)
|
|
476
|
+
```
|