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.
- package/LICENSE +21 -0
- package/README.md +278 -0
- package/guidance/effect-first-development.md +1247 -0
- package/guidance/post__effect-and-the-near-inexpressible-majesty-of-layers.md +490 -0
- package/guidance/post__parse-dont-validate.md +109 -0
- package/guidance/progressive-disclosure-guidance.md +38 -0
- package/package.json +63 -0
- package/patterns/avoid-any.md +37 -0
- package/patterns/avoid-data-tagged-error.md +34 -0
- package/patterns/avoid-direct-json.md +51 -0
- package/patterns/avoid-direct-tag-checks.md +54 -0
- package/patterns/avoid-expect-in-if.md +52 -0
- package/patterns/avoid-mutable-state.md +70 -0
- package/patterns/avoid-native-fetch.md +61 -0
- package/patterns/avoid-node-imports.md +86 -0
- package/patterns/avoid-non-null-assertion.md +44 -0
- package/patterns/avoid-object-type.md +46 -0
- package/patterns/avoid-option-getorthrow.md +39 -0
- package/patterns/avoid-platform-coupling.md +43 -0
- package/patterns/avoid-process-env.md +43 -0
- package/patterns/avoid-react-hooks.md +73 -0
- package/patterns/avoid-schema-suffix.md +45 -0
- package/patterns/avoid-sync-fs.md +68 -0
- package/patterns/avoid-try-catch.md +47 -0
- package/patterns/avoid-ts-ignore.md +38 -0
- package/patterns/avoid-untagged-errors.md +67 -0
- package/patterns/avoid-yield-ref.md +46 -0
- package/patterns/casting-awareness.md +46 -0
- package/patterns/context-tag-extends.md +84 -0
- package/patterns/effect-catchall-default.md +61 -0
- package/patterns/effect-promise-vs-trypromise.md +47 -0
- package/patterns/effect-run-in-body.md +58 -0
- package/patterns/imperative-loops.md +76 -0
- package/patterns/prefer-arr-sort.md +52 -0
- package/patterns/prefer-duration-values.md +56 -0
- package/patterns/prefer-effect-fn.md +161 -0
- package/patterns/prefer-match-over-switch.md +48 -0
- package/patterns/prefer-option-over-null.md +56 -0
- package/patterns/prefer-redacted-config.md +70 -0
- package/patterns/prefer-schema-class.md +54 -0
- package/patterns/require-effect-concurrency.md +83 -0
- package/patterns/stream-large-files.md +63 -0
- package/patterns/throw-in-effect-gen.md +62 -0
- package/patterns/use-clock-service.md +45 -0
- package/patterns/use-command-executor-service.md +54 -0
- package/patterns/use-console-service.md +54 -0
- package/patterns/use-filesystem-service.md +59 -0
- package/patterns/use-http-client-service.md +77 -0
- package/patterns/use-path-service.md +53 -0
- package/patterns/use-random-service.md +45 -0
- package/patterns/use-temp-file-scoped.md +66 -0
- package/patterns/vm-in-wrong-file.md +51 -0
- package/patterns/yield-in-for-loop.md +61 -0
- package/skills/effect-ai-chat/SKILL.md +472 -0
- package/skills/effect-ai-language-model/SKILL.md +652 -0
- package/skills/effect-ai-prompt/SKILL.md +752 -0
- package/skills/effect-ai-provider/SKILL.md +668 -0
- package/skills/effect-ai-streaming/SKILL.md +418 -0
- package/skills/effect-ai-tool/SKILL.md +1132 -0
- package/skills/effect-atom-rpc/SKILL.md +488 -0
- package/skills/effect-atom-state/SKILL.md +640 -0
- package/skills/effect-batching/SKILL.md +614 -0
- package/skills/effect-cache/SKILL.md +570 -0
- package/skills/effect-cli/SKILL.md +523 -0
- package/skills/effect-command-executor/SKILL.md +675 -0
- package/skills/effect-concurrency-testing/SKILL.md +612 -0
- package/skills/effect-config/SKILL.md +580 -0
- package/skills/effect-context-witness/SKILL.md +274 -0
- package/skills/effect-domain-modeling/SKILL.md +1212 -0
- package/skills/effect-domain-predicates/SKILL.md +867 -0
- package/skills/effect-error-handling/SKILL.md +1581 -0
- package/skills/effect-fiber/SKILL.md +731 -0
- package/skills/effect-filesystem/SKILL.md +624 -0
- package/skills/effect-graph/SKILL.md +571 -0
- package/skills/effect-http-api/SKILL.md +1760 -0
- package/skills/effect-http-client/SKILL.md +989 -0
- package/skills/effect-http-server/SKILL.md +920 -0
- package/skills/effect-incremental-migration/SKILL.md +362 -0
- package/skills/effect-layer-design/SKILL.md +642 -0
- package/skills/effect-managed-runtime/SKILL.md +395 -0
- package/skills/effect-mcp-server/SKILL.md +608 -0
- package/skills/effect-observability/SKILL.md +719 -0
- package/skills/effect-optics/SKILL.md +554 -0
- package/skills/effect-parallelization/SKILL.md +668 -0
- package/skills/effect-path/SKILL.md +296 -0
- package/skills/effect-pattern-matching/SKILL.md +914 -0
- package/skills/effect-platform-abstraction/SKILL.md +1175 -0
- package/skills/effect-platform-layers/SKILL.md +514 -0
- package/skills/effect-pubsub-event-bus/SKILL.md +384 -0
- package/skills/effect-react-composition/SKILL.md +986 -0
- package/skills/effect-react-vm/SKILL.md +675 -0
- package/skills/effect-rpc-api/SKILL.md +624 -0
- package/skills/effect-rpc-client/SKILL.md +666 -0
- package/skills/effect-rpc-cluster/SKILL.md +1623 -0
- package/skills/effect-rpc-server/SKILL.md +767 -0
- package/skills/effect-scheduling/SKILL.md +124 -0
- package/skills/effect-schema-composition/SKILL.md +975 -0
- package/skills/effect-schema-v4/SKILL.md +691 -0
- package/skills/effect-scope/SKILL.md +682 -0
- package/skills/effect-service-implementation/SKILL.md +656 -0
- package/skills/effect-socket/SKILL.md +703 -0
- package/skills/effect-sql/SKILL.md +781 -0
- package/skills/effect-stream/SKILL.md +765 -0
- package/skills/effect-testing/SKILL.md +1331 -0
- package/skills/effect-typeclass-design/SKILL.md +161 -0
- package/skills/effect-wide-events/Article.md +66 -0
- package/skills/effect-wide-events/SKILL.md +95 -0
- package/skills/effect-workflow/SKILL.md +810 -0
- package/src/agent-policy.ts +22 -0
- package/src/enforcer.ts +104 -0
- package/src/frontmatter.ts +34 -0
- package/src/guidance.ts +66 -0
- package/src/index.ts +38 -0
- package/src/pattern-catalog.ts +115 -0
- package/src/pattern-matcher.ts +178 -0
- package/src/pattern.ts +97 -0
- package/src/skills.ts +29 -0
- package/src/write-projection.ts +66 -0
|
@@ -0,0 +1,624 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: effect-filesystem
|
|
3
|
+
description: Use Effect FileSystem for platform-abstract file I/O with Node.js/Bun layers or custom implementations.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# FileSystem Platform Abstraction
|
|
7
|
+
|
|
8
|
+
Use `effect` FileSystem for platform-abstract file I/O. Stock layers are provided for Node.js and Bun; `@effect/platform-browser` does not provide a FileSystem layer, so browser code needs a custom/injected implementation.
|
|
9
|
+
|
|
10
|
+
## Basic Pattern
|
|
11
|
+
|
|
12
|
+
```typescript
|
|
13
|
+
import { FileSystem } from 'effect';
|
|
14
|
+
import { Effect } from 'effect';
|
|
15
|
+
|
|
16
|
+
// Service injection via yield*
|
|
17
|
+
const program = Effect.gen(function* () {
|
|
18
|
+
const fs = yield* FileSystem.FileSystem;
|
|
19
|
+
|
|
20
|
+
// Use fs methods here
|
|
21
|
+
const content = yield* fs.readFileString('path/to/file.txt');
|
|
22
|
+
return content;
|
|
23
|
+
});
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Reading Operations
|
|
27
|
+
|
|
28
|
+
### Read File (Binary)
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import { FileSystem } from 'effect';
|
|
32
|
+
import { Effect } from 'effect';
|
|
33
|
+
|
|
34
|
+
const readBinary = Effect.gen(function* () {
|
|
35
|
+
const fs = yield* FileSystem.FileSystem;
|
|
36
|
+
const bytes = yield* fs.readFile('data.bin');
|
|
37
|
+
return bytes; // Uint8Array
|
|
38
|
+
});
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Read File (String)
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
import { FileSystem } from 'effect';
|
|
45
|
+
import { Effect } from 'effect';
|
|
46
|
+
|
|
47
|
+
const readText = Effect.gen(function* () {
|
|
48
|
+
const fs = yield* FileSystem.FileSystem;
|
|
49
|
+
const content = yield* fs.readFileString('config.json');
|
|
50
|
+
return content; // string
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Stream File
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
import { FileSystem } from 'effect';
|
|
58
|
+
import { Effect } from 'effect';
|
|
59
|
+
|
|
60
|
+
const streamFile = Effect.gen(function* () {
|
|
61
|
+
const fs = yield* FileSystem.FileSystem;
|
|
62
|
+
// fs.stream() returns a Stream directly — not an Effect
|
|
63
|
+
const stream = fs.stream('large-file.log');
|
|
64
|
+
return stream; // Stream<Uint8Array, PlatformError>
|
|
65
|
+
});
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Read Directory
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import { FileSystem } from 'effect';
|
|
72
|
+
import { Effect } from 'effect';
|
|
73
|
+
|
|
74
|
+
const listFiles = Effect.gen(function* () {
|
|
75
|
+
const fs = yield* FileSystem.FileSystem;
|
|
76
|
+
const entries = yield* fs.readDirectory('src/');
|
|
77
|
+
return entries; // ReadonlyArray<string>
|
|
78
|
+
});
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Read Symbolic Link
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
import { FileSystem } from 'effect';
|
|
85
|
+
import { Effect } from 'effect';
|
|
86
|
+
|
|
87
|
+
const readLink = Effect.gen(function* () {
|
|
88
|
+
const fs = yield* FileSystem.FileSystem;
|
|
89
|
+
const target = yield* fs.readLink('symlink');
|
|
90
|
+
return target; // string
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Writing Operations
|
|
95
|
+
|
|
96
|
+
### Write File (Binary)
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
import { FileSystem } from 'effect';
|
|
100
|
+
import { Effect } from 'effect';
|
|
101
|
+
|
|
102
|
+
const writeBinary = Effect.gen(function* () {
|
|
103
|
+
const fs = yield* FileSystem.FileSystem;
|
|
104
|
+
const data = new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f]);
|
|
105
|
+
yield* fs.writeFile('output.bin', data);
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Write File (String)
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
import { FileSystem } from 'effect';
|
|
113
|
+
import { Effect } from 'effect';
|
|
114
|
+
|
|
115
|
+
const writeText = Effect.gen(function* () {
|
|
116
|
+
const fs = yield* FileSystem.FileSystem;
|
|
117
|
+
yield* fs.writeFileString('output.txt', 'Hello, World!');
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Sink (Stream Writing)
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
import { FileSystem } from 'effect';
|
|
125
|
+
import { Effect, Stream, pipe } from 'effect';
|
|
126
|
+
|
|
127
|
+
const writeStream = Effect.gen(function* () {
|
|
128
|
+
const fs = yield* FileSystem.FileSystem;
|
|
129
|
+
// fs.sink() returns a Sink directly — not an Effect
|
|
130
|
+
const sink = fs.sink('output.log');
|
|
131
|
+
|
|
132
|
+
yield* pipe(
|
|
133
|
+
Stream.fromIterable(['line 1\n', 'line 2\n', 'line 3\n']),
|
|
134
|
+
Stream.mapEffect((s) => Effect.succeed(new TextEncoder().encode(s))),
|
|
135
|
+
Stream.run(sink)
|
|
136
|
+
);
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## File Operations
|
|
141
|
+
|
|
142
|
+
### Copy File
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
import { FileSystem } from 'effect';
|
|
146
|
+
import { Effect } from 'effect';
|
|
147
|
+
|
|
148
|
+
const copyFile = Effect.gen(function* () {
|
|
149
|
+
const fs = yield* FileSystem.FileSystem;
|
|
150
|
+
yield* fs.copyFile('source.txt', 'dest.txt');
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Copy (Recursive Directory)
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
import { FileSystem } from 'effect';
|
|
158
|
+
import { Effect } from 'effect';
|
|
159
|
+
|
|
160
|
+
const copyDir = Effect.gen(function* () {
|
|
161
|
+
const fs = yield* FileSystem.FileSystem;
|
|
162
|
+
yield* fs.copy('src-dir/', 'dest-dir/');
|
|
163
|
+
});
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Rename/Move
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
import { FileSystem } from 'effect';
|
|
170
|
+
import { Effect } from 'effect';
|
|
171
|
+
|
|
172
|
+
const renameFile = Effect.gen(function* () {
|
|
173
|
+
const fs = yield* FileSystem.FileSystem;
|
|
174
|
+
yield* fs.rename('old-name.txt', 'new-name.txt');
|
|
175
|
+
});
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Remove
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
import { FileSystem } from 'effect';
|
|
182
|
+
import { Effect } from 'effect';
|
|
183
|
+
|
|
184
|
+
const removeFile = Effect.gen(function* () {
|
|
185
|
+
const fs = yield* FileSystem.FileSystem;
|
|
186
|
+
yield* fs.remove('file.txt');
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// Remove directory recursively
|
|
190
|
+
const removeDir = Effect.gen(function* () {
|
|
191
|
+
const fs = yield* FileSystem.FileSystem;
|
|
192
|
+
yield* fs.remove('directory/', { recursive: true });
|
|
193
|
+
});
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Open File Handle
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
import { FileSystem } from 'effect';
|
|
200
|
+
import { Effect } from 'effect';
|
|
201
|
+
|
|
202
|
+
const useFileHandle = Effect.gen(function* () {
|
|
203
|
+
const fs = yield* FileSystem.FileSystem;
|
|
204
|
+
|
|
205
|
+
// File handles are scoped — automatically closed when scope exits
|
|
206
|
+
yield* Effect.scoped(
|
|
207
|
+
Effect.gen(function* () {
|
|
208
|
+
const file = yield* fs.open('data.txt', { flag: 'r' });
|
|
209
|
+
|
|
210
|
+
// File methods return branded Size values for byte counts and offsets.
|
|
211
|
+
const buffer = new Uint8Array(1024);
|
|
212
|
+
const bytesRead = yield* file.read(buffer);
|
|
213
|
+
const offset = yield* file.seek(FileSystem.Size(0), 'start');
|
|
214
|
+
})
|
|
215
|
+
);
|
|
216
|
+
});
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
`file.seek(offset, from)` accepts a `SizeInput`, supports `from: 'start' | 'current'`, and returns the new offset as `FileSystem.Size` (not `void` or a plain number). Open `File` handles no longer expose a `descriptor` property or `File.Descriptor` type as of beta.103; use the scoped handle operations (`read`, `readAlloc`, `write`, `writeAll`, `seek`, `stat`, `sync`, and `truncate`) instead.
|
|
220
|
+
|
|
221
|
+
## Directory Operations
|
|
222
|
+
|
|
223
|
+
### Make Directory
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
import { FileSystem } from 'effect';
|
|
227
|
+
import { Effect } from 'effect';
|
|
228
|
+
|
|
229
|
+
const createDir = Effect.gen(function* () {
|
|
230
|
+
const fs = yield* FileSystem.FileSystem;
|
|
231
|
+
yield* fs.makeDirectory('new-dir/');
|
|
232
|
+
|
|
233
|
+
// Recursive directory creation
|
|
234
|
+
yield* fs.makeDirectory('path/to/nested/dir/', { recursive: true });
|
|
235
|
+
});
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Make Temp Directory
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
import { FileSystem } from 'effect';
|
|
242
|
+
import { Effect } from 'effect';
|
|
243
|
+
|
|
244
|
+
const useTempDir = Effect.gen(function* () {
|
|
245
|
+
const fs = yield* FileSystem.FileSystem;
|
|
246
|
+
const tempPath = yield* fs.makeTempDirectory();
|
|
247
|
+
|
|
248
|
+
// Use tempPath
|
|
249
|
+
yield* fs.writeFileString(`${tempPath}/temp-file.txt`, 'data');
|
|
250
|
+
|
|
251
|
+
// Manual cleanup required
|
|
252
|
+
yield* fs.remove(tempPath, { recursive: true });
|
|
253
|
+
});
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### Make Temp Directory (Scoped)
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
import { FileSystem } from 'effect';
|
|
260
|
+
import { Effect } from 'effect';
|
|
261
|
+
|
|
262
|
+
const useScopedTempDir = Effect.gen(function* () {
|
|
263
|
+
const fs = yield* FileSystem.FileSystem;
|
|
264
|
+
const tempPath = yield* fs.makeTempDirectoryScoped();
|
|
265
|
+
|
|
266
|
+
// Use tempPath within scope
|
|
267
|
+
yield* fs.writeFileString(`${tempPath}/temp-file.txt`, 'data');
|
|
268
|
+
|
|
269
|
+
// Automatically cleaned up when scope exits
|
|
270
|
+
}).pipe(Effect.scoped);
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
## Metadata Operations
|
|
274
|
+
|
|
275
|
+
### Stat (File Info)
|
|
276
|
+
|
|
277
|
+
```typescript
|
|
278
|
+
import { FileSystem } from 'effect';
|
|
279
|
+
import { Effect, Console } from 'effect';
|
|
280
|
+
|
|
281
|
+
const getFileInfo = Effect.gen(function* () {
|
|
282
|
+
const fs = yield* FileSystem.FileSystem;
|
|
283
|
+
const info = yield* fs.stat('file.txt');
|
|
284
|
+
|
|
285
|
+
yield* Console.log(`Type: ${info.type}`);
|
|
286
|
+
// "File" | "Directory" | "SymbolicLink" | "BlockDevice" | "CharacterDevice" | "FIFO" | "Socket" | "Unknown"
|
|
287
|
+
yield* Console.log(`Size: ${info.size}`); // FileSystem.Size (branded bigint)
|
|
288
|
+
yield* Console.log(`Modified: ${info.mtime}`); // Option<Date>
|
|
289
|
+
yield* Console.log(`Accessed: ${info.atime}`); // Option<Date>
|
|
290
|
+
yield* Console.log(`Created: ${info.birthtime}`); // Option<Date>
|
|
291
|
+
});
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### Access (Check Permissions)
|
|
295
|
+
|
|
296
|
+
```typescript
|
|
297
|
+
import { FileSystem } from 'effect';
|
|
298
|
+
import { Effect } from 'effect';
|
|
299
|
+
|
|
300
|
+
const checkAccess = Effect.gen(function* () {
|
|
301
|
+
const fs = yield* FileSystem.FileSystem;
|
|
302
|
+
|
|
303
|
+
// Check if file exists and is readable
|
|
304
|
+
yield* fs.access('file.txt', { readable: true });
|
|
305
|
+
|
|
306
|
+
// Check writable
|
|
307
|
+
yield* fs.access('file.txt', { writable: true });
|
|
308
|
+
|
|
309
|
+
// Check if file exists (ok)
|
|
310
|
+
yield* fs.access('script.sh', { ok: true });
|
|
311
|
+
});
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### Exists
|
|
315
|
+
|
|
316
|
+
```typescript
|
|
317
|
+
import { FileSystem } from 'effect';
|
|
318
|
+
import { Effect } from 'effect';
|
|
319
|
+
|
|
320
|
+
const fileExists = Effect.gen(function* () {
|
|
321
|
+
const fs = yield* FileSystem.FileSystem;
|
|
322
|
+
const exists = yield* fs.exists('file.txt');
|
|
323
|
+
return exists; // boolean
|
|
324
|
+
});
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### Real Path (Resolve Symlinks)
|
|
328
|
+
|
|
329
|
+
```typescript
|
|
330
|
+
import { FileSystem } from 'effect';
|
|
331
|
+
import { Effect } from 'effect';
|
|
332
|
+
|
|
333
|
+
const resolvePath = Effect.gen(function* () {
|
|
334
|
+
const fs = yield* FileSystem.FileSystem;
|
|
335
|
+
const realPath = yield* fs.realPath('symlink-or-relative-path');
|
|
336
|
+
return realPath; // string (absolute path)
|
|
337
|
+
});
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
## Permission Operations
|
|
341
|
+
|
|
342
|
+
### Change Mode (chmod)
|
|
343
|
+
|
|
344
|
+
```typescript
|
|
345
|
+
import { FileSystem } from 'effect';
|
|
346
|
+
import { Effect } from 'effect';
|
|
347
|
+
|
|
348
|
+
const changeMode = Effect.gen(function* () {
|
|
349
|
+
const fs = yield* FileSystem.FileSystem;
|
|
350
|
+
yield* fs.chmod('script.sh', 0o755);
|
|
351
|
+
});
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
### Change Owner (chown)
|
|
355
|
+
|
|
356
|
+
```typescript
|
|
357
|
+
import { FileSystem } from 'effect';
|
|
358
|
+
import { Effect } from 'effect';
|
|
359
|
+
|
|
360
|
+
const changeOwner = Effect.gen(function* () {
|
|
361
|
+
const fs = yield* FileSystem.FileSystem;
|
|
362
|
+
yield* fs.chown('file.txt', 1000, 1000); // uid, gid
|
|
363
|
+
});
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### Update Times (utimes)
|
|
367
|
+
|
|
368
|
+
```typescript
|
|
369
|
+
import { FileSystem } from 'effect';
|
|
370
|
+
import { Effect } from 'effect';
|
|
371
|
+
|
|
372
|
+
const updateTimes = Effect.gen(function* () {
|
|
373
|
+
const fs = yield* FileSystem.FileSystem;
|
|
374
|
+
const now = new Date();
|
|
375
|
+
yield* fs.utimes('file.txt', now, now); // atime, mtime
|
|
376
|
+
});
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
## Links
|
|
380
|
+
|
|
381
|
+
### Hard Link
|
|
382
|
+
|
|
383
|
+
```typescript
|
|
384
|
+
import { FileSystem } from 'effect';
|
|
385
|
+
import { Effect } from 'effect';
|
|
386
|
+
|
|
387
|
+
const createHardLink = Effect.gen(function* () {
|
|
388
|
+
const fs = yield* FileSystem.FileSystem;
|
|
389
|
+
yield* fs.link('original.txt', 'hardlink.txt');
|
|
390
|
+
});
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
### Symbolic Link
|
|
394
|
+
|
|
395
|
+
```typescript
|
|
396
|
+
import { FileSystem } from 'effect';
|
|
397
|
+
import { Effect } from 'effect';
|
|
398
|
+
|
|
399
|
+
const createSymlink = Effect.gen(function* () {
|
|
400
|
+
const fs = yield* FileSystem.FileSystem;
|
|
401
|
+
yield* fs.symlink('target.txt', 'symlink.txt');
|
|
402
|
+
});
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
## Watching
|
|
406
|
+
|
|
407
|
+
### Watch Files/Directories
|
|
408
|
+
|
|
409
|
+
```typescript
|
|
410
|
+
import { FileSystem } from 'effect';
|
|
411
|
+
import { Effect, Stream, Console, pipe } from 'effect';
|
|
412
|
+
|
|
413
|
+
const watchFiles = Effect.gen(function* () {
|
|
414
|
+
const fs = yield* FileSystem.FileSystem;
|
|
415
|
+
// fs.watch() returns a Stream directly — not an Effect
|
|
416
|
+
const events = fs.watch('src/'); // direct children only
|
|
417
|
+
|
|
418
|
+
return events; // Stream<WatchEvent, PlatformError>
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
// Consume watch events
|
|
422
|
+
const consumeWatchEvents = Effect.gen(function* () {
|
|
423
|
+
const fs = yield* FileSystem.FileSystem;
|
|
424
|
+
const events = fs.watch('config/', { recursive: true });
|
|
425
|
+
|
|
426
|
+
yield* pipe(
|
|
427
|
+
events,
|
|
428
|
+
Stream.runForEach((event) =>
|
|
429
|
+
Console.log(`Event: ${event._tag}, Path: ${event.path}`)
|
|
430
|
+
)
|
|
431
|
+
);
|
|
432
|
+
});
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
Directory watching is non-recursive by default. Pass `{ recursive: true }` to include changes in nested subdirectories. Watching a file watches that file; the recursive option matters for directory trees.
|
|
436
|
+
|
|
437
|
+
## Size Helpers
|
|
438
|
+
|
|
439
|
+
```typescript
|
|
440
|
+
import { FileSystem } from 'effect';
|
|
441
|
+
import { Effect } from 'effect';
|
|
442
|
+
|
|
443
|
+
const { Size, KiB, MiB, GiB, TiB, PiB } = FileSystem;
|
|
444
|
+
|
|
445
|
+
// Create size values
|
|
446
|
+
const oneKb = Size(1024);
|
|
447
|
+
const tenKb = KiB(10);
|
|
448
|
+
const oneMb = MiB(1);
|
|
449
|
+
const fiveGb = GiB(5);
|
|
450
|
+
const oneTb = TiB(1);
|
|
451
|
+
const onePb = PiB(1);
|
|
452
|
+
|
|
453
|
+
// Use with file operations
|
|
454
|
+
const checkFileSize = Effect.gen(function* () {
|
|
455
|
+
const fs = yield* FileSystem.FileSystem;
|
|
456
|
+
const info = yield* fs.stat('large-file.bin');
|
|
457
|
+
|
|
458
|
+
const maxSize = MiB(100);
|
|
459
|
+
if (info.size > maxSize) {
|
|
460
|
+
yield* Effect.fail(new Error('File too large'));
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
## Error Handling
|
|
466
|
+
|
|
467
|
+
### SystemErrorTag Values
|
|
468
|
+
|
|
469
|
+
FileSystem operations fail with `PlatformError` containing a `SystemErrorTag`:
|
|
470
|
+
|
|
471
|
+
- `AlreadyExists` - File/directory already exists
|
|
472
|
+
- `BadResource` - Invalid file descriptor or handle
|
|
473
|
+
- `Busy` - Resource is busy
|
|
474
|
+
- `InvalidData` - Invalid data format
|
|
475
|
+
- `NotFound` - File/directory not found
|
|
476
|
+
- `PermissionDenied` - Insufficient permissions
|
|
477
|
+
- `TimedOut` - Operation timed out
|
|
478
|
+
- `UnexpectedEof` - Unexpected end of file
|
|
479
|
+
- `Unknown` - Unknown error
|
|
480
|
+
- `WouldBlock` - Operation would block
|
|
481
|
+
- `WriteZero` - Write operation wrote zero bytes
|
|
482
|
+
|
|
483
|
+
### Error Handling Pattern
|
|
484
|
+
|
|
485
|
+
```typescript
|
|
486
|
+
import { FileSystem } from 'effect';
|
|
487
|
+
import { Effect, pipe } from 'effect';
|
|
488
|
+
|
|
489
|
+
const readConfigWithFallback = pipe(
|
|
490
|
+
Effect.gen(function* () {
|
|
491
|
+
const fs = yield* FileSystem.FileSystem;
|
|
492
|
+
return yield* fs.readFileString('config.json');
|
|
493
|
+
}),
|
|
494
|
+
Effect.catchTag('PlatformError', (error) => {
|
|
495
|
+
if (error.reason._tag === 'NotFound') {
|
|
496
|
+
return Effect.succeed('{}');
|
|
497
|
+
}
|
|
498
|
+
if (error.reason._tag === 'PermissionDenied') {
|
|
499
|
+
return Effect.fail(
|
|
500
|
+
new Error('Cannot read config: permission denied')
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
return Effect.fail(error);
|
|
504
|
+
})
|
|
505
|
+
);
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
### Typed Error Recovery
|
|
509
|
+
|
|
510
|
+
```typescript
|
|
511
|
+
import { FileSystem } from 'effect';
|
|
512
|
+
import { Effect, Schema, pipe } from 'effect';
|
|
513
|
+
|
|
514
|
+
class ConfigNotFound extends Schema.TaggedError<ConfigNotFound>()(
|
|
515
|
+
'ConfigNotFound',
|
|
516
|
+
{
|
|
517
|
+
path: Schema.String
|
|
518
|
+
}
|
|
519
|
+
) {}
|
|
520
|
+
|
|
521
|
+
class ConfigInvalid extends Schema.TaggedError<ConfigInvalid>()(
|
|
522
|
+
'ConfigInvalid',
|
|
523
|
+
{
|
|
524
|
+
path: Schema.String,
|
|
525
|
+
reason: Schema.String
|
|
526
|
+
}
|
|
527
|
+
) {}
|
|
528
|
+
|
|
529
|
+
const readConfig = (path: string) =>
|
|
530
|
+
Effect.gen(function* () {
|
|
531
|
+
const fs = yield* FileSystem.FileSystem;
|
|
532
|
+
|
|
533
|
+
const content = yield* pipe(
|
|
534
|
+
fs.readFileString(path),
|
|
535
|
+
Effect.mapError((error) =>
|
|
536
|
+
error.reason._tag === 'NotFound'
|
|
537
|
+
? new ConfigNotFound({ path })
|
|
538
|
+
: new ConfigInvalid({ path, reason: error.message })
|
|
539
|
+
)
|
|
540
|
+
);
|
|
541
|
+
|
|
542
|
+
return content;
|
|
543
|
+
});
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
## Scoped Resources Pattern
|
|
547
|
+
|
|
548
|
+
```typescript
|
|
549
|
+
import { FileSystem } from 'effect';
|
|
550
|
+
import { Effect } from 'effect';
|
|
551
|
+
|
|
552
|
+
const processInTempDir = Effect.gen(function* () {
|
|
553
|
+
const fs = yield* FileSystem.FileSystem;
|
|
554
|
+
|
|
555
|
+
// Create temp directory with automatic cleanup
|
|
556
|
+
const tempDir = yield* fs.makeTempDirectoryScoped();
|
|
557
|
+
|
|
558
|
+
// Do work in temp directory
|
|
559
|
+
const inputPath = `${tempDir}/input.txt`;
|
|
560
|
+
const outputPath = `${tempDir}/output.txt`;
|
|
561
|
+
|
|
562
|
+
yield* fs.writeFileString(inputPath, 'data');
|
|
563
|
+
const content = yield* fs.readFileString(inputPath);
|
|
564
|
+
yield* fs.writeFileString(outputPath, content.toUpperCase());
|
|
565
|
+
|
|
566
|
+
const result = yield* fs.readFileString(outputPath);
|
|
567
|
+
|
|
568
|
+
// Temp directory is automatically removed when scope exits
|
|
569
|
+
return result;
|
|
570
|
+
}).pipe(Effect.scoped);
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
## Layer Provision
|
|
574
|
+
|
|
575
|
+
### Node.js
|
|
576
|
+
|
|
577
|
+
```typescript
|
|
578
|
+
import { FileSystem } from 'effect';
|
|
579
|
+
import { NodeFileSystem } from '@effect/platform-node';
|
|
580
|
+
import { Effect } from 'effect';
|
|
581
|
+
|
|
582
|
+
const program = Effect.gen(function* () {
|
|
583
|
+
const fs = yield* FileSystem.FileSystem;
|
|
584
|
+
return yield* fs.readFileString('data.txt');
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
// Provide Node.js implementation
|
|
588
|
+
const runnable = program.pipe(Effect.provide(NodeFileSystem.layer));
|
|
589
|
+
|
|
590
|
+
Effect.runPromise(runnable);
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
### Bun
|
|
594
|
+
|
|
595
|
+
```typescript
|
|
596
|
+
import { FileSystem } from 'effect';
|
|
597
|
+
import { BunFileSystem } from '@effect/platform-bun';
|
|
598
|
+
import { Effect } from 'effect';
|
|
599
|
+
|
|
600
|
+
declare const program: Effect.Effect<string, never, FileSystem.FileSystem>;
|
|
601
|
+
|
|
602
|
+
const runnable = program.pipe(Effect.provide(BunFileSystem.layer));
|
|
603
|
+
|
|
604
|
+
Effect.runPromise(runnable);
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
## DO
|
|
608
|
+
|
|
609
|
+
- Import from `effect`
|
|
610
|
+
- Use `yield* FileSystem.FileSystem` for service injection
|
|
611
|
+
- Provide platform layer at entry point only
|
|
612
|
+
- Use scoped temp directories with `makeTempDirectoryScoped`
|
|
613
|
+
- Handle `PlatformError` with `catchTag("PlatformError", ...)`
|
|
614
|
+
- Use size helpers: `Size()`, `KiB()`, `MiB()`, `GiB()`, `TiB()`, `PiB()`
|
|
615
|
+
- Stream large files with `stream()` and `sink()`
|
|
616
|
+
|
|
617
|
+
## DON'T
|
|
618
|
+
|
|
619
|
+
- Import `node:fs`, `fs/promises`, or platform-specific modules in business logic
|
|
620
|
+
- Use synchronous fs operations
|
|
621
|
+
- Forget to cleanup temp directories (use scoped version)
|
|
622
|
+
- Mix platform-specific code with business logic
|
|
623
|
+
- Use `Date.now()` - use `Clock` service instead (see testability requirements)
|
|
624
|
+
- Hardcode platform-specific paths - use `Path` service for path operations
|