@effect-vfs/core 0.3.0 → 0.3.1

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 (53) hide show
  1. package/dist/BytePath.d.ts +33 -2
  2. package/dist/BytePath.d.ts.map +1 -1
  3. package/dist/BytePath.js +1 -1
  4. package/dist/Snapshot.d.ts +58 -2
  5. package/dist/Snapshot.d.ts.map +1 -1
  6. package/dist/Snapshot.js +57 -1
  7. package/dist/SnapshotDelta.d.ts +113 -3
  8. package/dist/SnapshotDelta.d.ts.map +1 -1
  9. package/dist/SnapshotDelta.js +112 -2
  10. package/dist/VirtualFileSystem.d.ts +919 -10
  11. package/dist/VirtualFileSystem.d.ts.map +1 -1
  12. package/dist/VirtualFileSystem.js +575 -7
  13. package/dist/index.d.ts +1 -1
  14. package/dist/index.js +1 -1
  15. package/dist/internal/bytePath.d.ts.map +1 -1
  16. package/dist/internal/bytePath.js +5 -10
  17. package/dist/internal/bytes.d.ts +6 -0
  18. package/dist/internal/bytes.d.ts.map +1 -0
  19. package/dist/internal/bytes.js +17 -0
  20. package/dist/internal/canonicalBase64.d.ts +13 -5
  21. package/dist/internal/canonicalBase64.d.ts.map +1 -1
  22. package/dist/internal/canonicalBase64.js +28 -6
  23. package/dist/internal/image.d.ts +76 -90
  24. package/dist/internal/image.d.ts.map +1 -1
  25. package/dist/internal/image.js +98 -89
  26. package/dist/internal/metadata.d.ts +28 -0
  27. package/dist/internal/metadata.d.ts.map +1 -0
  28. package/dist/internal/metadata.js +29 -0
  29. package/dist/internal/snapshotDelta.d.ts.map +1 -1
  30. package/dist/internal/snapshotDelta.js +346 -300
  31. package/dist/internal/snapshotDeltaModel.d.ts +2 -2
  32. package/dist/internal/snapshotDeltaModel.d.ts.map +1 -1
  33. package/dist/internal/snapshotDeltaModel.js +3 -1
  34. package/dist/internal/virtualFileSystem/errors.d.ts +5 -0
  35. package/dist/internal/virtualFileSystem/errors.d.ts.map +1 -1
  36. package/dist/internal/virtualFileSystem/errors.js +19 -0
  37. package/dist/internal/virtualFileSystem/fixture.d.ts.map +1 -1
  38. package/dist/internal/virtualFileSystem/fixture.js +74 -74
  39. package/dist/internal/virtualFileSystem/overlayContent.d.ts.map +1 -1
  40. package/dist/internal/virtualFileSystem/overlayContent.js +9 -2
  41. package/dist/internal/virtualFileSystem/overlayDiff.d.ts +2 -11
  42. package/dist/internal/virtualFileSystem/overlayDiff.d.ts.map +1 -1
  43. package/dist/internal/virtualFileSystem/overlayDiff.js +45 -71
  44. package/dist/internal/virtualFileSystem/path.d.ts +15 -14
  45. package/dist/internal/virtualFileSystem/path.d.ts.map +1 -1
  46. package/dist/internal/virtualFileSystem/path.js +57 -54
  47. package/dist/internal/virtualFileSystem/testHooks.d.ts.map +1 -1
  48. package/dist/internal/virtualFileSystem/watchHub.d.ts.map +1 -1
  49. package/dist/internal/virtualFileSystem/watchHub.js +13 -13
  50. package/dist/internal/virtualFileSystem.d.ts +91 -15
  51. package/dist/internal/virtualFileSystem.d.ts.map +1 -1
  52. package/dist/internal/virtualFileSystem.js +470 -392
  53. package/package.json +15 -5
@@ -12,9 +12,9 @@ import * as Effect from "effect/Effect";
12
12
  import * as Schema from "effect/Schema";
13
13
  import type * as Scope from "effect/Scope";
14
14
  import type * as Stream from "effect/Stream";
15
- import { BytePath } from "./BytePath.js";
15
+ import type { BytePath } from "./BytePath.js";
16
16
  export { BytePath } from "./BytePath.js";
17
- import { DecodeLimits, ImageError, type Snapshot } from "./Snapshot.js";
17
+ import type { DecodeLimits, ImageError, Snapshot } from "./Snapshot.js";
18
18
  export { DecodeLimits, ImageError, type Snapshot, SnapshotTypeId } from "./Snapshot.js";
19
19
  import * as VfsModel from "./internal/virtualFileSystem.js";
20
20
  import * as SnapshotDeltaModel from "./SnapshotDelta.js";
@@ -27,6 +27,12 @@ declare const ObjectReferenceId: typeof VfsModel.ObjectReferenceId;
27
27
  /**
28
28
  * A UTF-8 string path or an opaque byte-preserving path.
29
29
  *
30
+ * **Details**
31
+ *
32
+ * Every operation accepts both. Pass a string unless a name is not valid UTF-8;
33
+ * strings are the normal case and byte paths are the escape hatch.
34
+ *
35
+ * @see {@link BytePath} for the byte-preserving form.
30
36
  * @category models
31
37
  * @since 0.1.0
32
38
  */
@@ -34,6 +40,30 @@ export type PathInput = string | BytePath;
34
40
  /**
35
41
  * Schema for portable virtual filesystem error codes.
36
42
  *
43
+ * @example
44
+ * ```ts
45
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
46
+ * import { Effect, Schema } from "effect"
47
+ *
48
+ * const asCode = Schema.decodeUnknownEffect(Vfs.FsCode)
49
+ *
50
+ * // Useful when a code has crossed a process boundary and arrives back as data.
51
+ * const program = Effect.gen(function*() {
52
+ * const payload = JSON.parse(`{"code":"NotFound","other":"Nonsense"}`)
53
+ *
54
+ * const known = yield* asCode(payload.code)
55
+ *
56
+ * const unknown = yield* asCode(payload.other).pipe(
57
+ * Effect.orElseSucceed(() => "not a code")
58
+ * )
59
+ *
60
+ * return [known, unknown]
61
+ * })
62
+ *
63
+ * Effect.runPromise(program).then(console.log)
64
+ * // [ 'NotFound', 'not a code' ]
65
+ * ```
66
+ *
37
67
  * @category schemas
38
68
  * @since 0.1.0
39
69
  */
@@ -48,6 +78,28 @@ export type FsCode = typeof FsCode.Type;
48
78
  /**
49
79
  * Describes an expected filesystem operation failure.
50
80
  *
81
+ * @example
82
+ * ```ts
83
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
84
+ * import { Effect } from "effect"
85
+ *
86
+ * const program = Effect.gen(function*() {
87
+ * const caller = yield* (yield* Vfs.make()).caller()
88
+ *
89
+ * // Every filesystem operation fails with this one tagged error; `code`
90
+ * // distinguishes the cases, so match on it rather than on the tag alone.
91
+ * return yield* caller.readFile("/missing").pipe(
92
+ * Effect.catchTag("FsError", (error) =>
93
+ * error.code === "NotFound"
94
+ * ? Effect.succeed(new Uint8Array())
95
+ * : Effect.fail(error))
96
+ * )
97
+ * })
98
+ *
99
+ * Effect.runPromise(program).then(console.log)
100
+ * // Uint8Array(0) []
101
+ * ```
102
+ *
51
103
  * @category errors
52
104
  * @since 0.1.0
53
105
  */
@@ -58,6 +110,26 @@ export interface FsError extends VfsModel.FsError {
58
110
  /**
59
111
  * Describes an invalid volume or caller option and names the rejected field.
60
112
  *
113
+ * @example
114
+ * ```ts
115
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
116
+ * import { Effect } from "effect"
117
+ *
118
+ * // `field` names the rejected option, so the caller learns which key was wrong.
119
+ * const program = Effect.gen(function*() {
120
+ * // Options that arrived from a config file or CLI flag, not a literal.
121
+ * const options = JSON.parse(`{"maxEntries":-1}`) as Vfs.VolumeOptions
122
+ *
123
+ * return yield* Vfs.make(options).pipe(
124
+ * Effect.as("ok"),
125
+ * Effect.catchTag("ConfigurationError", (error) => Effect.succeed(`rejected ${error.field}`))
126
+ * )
127
+ * })
128
+ *
129
+ * Effect.runPromise(program).then(console.log)
130
+ * // rejected maxEntries
131
+ * ```
132
+ *
61
133
  * @category errors
62
134
  * @since 0.1.0
63
135
  */
@@ -82,6 +154,33 @@ export type Identity = typeof Identity.Type;
82
154
  /**
83
155
  * Schema for root caller credentials and creation mask.
84
156
  *
157
+ * @example
158
+ * ```ts
159
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
160
+ * import { Effect } from "effect"
161
+ *
162
+ * const program = Effect.gen(function*() {
163
+ * const volume = yield* Vfs.make()
164
+ *
165
+ * // Omitting `identity` yields a privileged root caller.
166
+ * const root = yield* volume.caller({ umask: 0 })
167
+ * const user = yield* volume.caller({
168
+ * identity: { uid: 1000, gid: 1000, groups: [], privileged: false },
169
+ * umask: 0o022
170
+ * })
171
+ *
172
+ * yield* root.mkdir("/home", { mode: 0o777 })
173
+ *
174
+ * // The umask clears its bits from the requested mode.
175
+ * yield* user.mkdir("/home/user", { mode: 0o777 })
176
+ *
177
+ * return ((yield* user.stat("/home/user")).mode & 0o777).toString(8)
178
+ * })
179
+ *
180
+ * Effect.runPromise(program).then(console.log)
181
+ * // 755
182
+ * ```
183
+ *
85
184
  * @category schemas
86
185
  * @since 0.1.0
87
186
  */
@@ -96,6 +195,29 @@ export type RootCallerOptions = typeof RootCallerOptions.Type;
96
195
  /**
97
196
  * Schema for optional volume capacity and path limits.
98
197
  *
198
+ * @example
199
+ * ```ts
200
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
201
+ * import { ByteSize, Effect } from "effect"
202
+ *
203
+ * // Every limit is optional, and each one is enforced once set.
204
+ * const program = Effect.gen(function*() {
205
+ * const volume = yield* Vfs.make({ maxFileBytes: ByteSize.bytes(4) })
206
+ * const caller = yield* volume.caller()
207
+ *
208
+ * return yield* caller.writeFile("/big.bin", new Uint8Array(16), {
209
+ * access: "write",
210
+ * create: "exclusive"
211
+ * }).pipe(
212
+ * Effect.as("written"),
213
+ * Effect.catchTag("FsError", (error) => Effect.succeed(error.code))
214
+ * )
215
+ * })
216
+ *
217
+ * Effect.runPromise(program).then(console.log)
218
+ * // FileTooLarge
219
+ * ```
220
+ *
99
221
  * @category schemas
100
222
  * @since 0.1.0
101
223
  */
@@ -110,6 +232,29 @@ export type VolumeOptions = typeof VolumeOptions.Type;
110
232
  /**
111
233
  * Schema for filesystem node metadata with bigint inode, size, and nanosecond fields.
112
234
  *
235
+ * @example
236
+ * ```ts
237
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
238
+ * import { Effect } from "effect"
239
+ *
240
+ * // Sizes and inode numbers are bigint; times are nanosecond timestamps.
241
+ * const program = Effect.gen(function*() {
242
+ * const caller = yield* (yield* Vfs.make()).caller()
243
+ *
244
+ * yield* caller.writeFile("/f", new Uint8Array([1, 2, 3]), {
245
+ * access: "write",
246
+ * create: "exclusive"
247
+ * })
248
+ *
249
+ * const metadata = yield* caller.stat("/f")
250
+ *
251
+ * return [metadata.kind, metadata.size, metadata.nlink]
252
+ * })
253
+ *
254
+ * Effect.runPromise(program).then(console.log)
255
+ * // [ 'file', 3n, 1 ]
256
+ * ```
257
+ *
113
258
  * @category schemas
114
259
  * @since 0.1.0
115
260
  */
@@ -133,6 +278,34 @@ export interface ObjectReference {
133
278
  /**
134
279
  * A value and the revision of the object from the same coordinated observation.
135
280
  *
281
+ * @example
282
+ * ```ts
283
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
284
+ * import { Effect } from "effect"
285
+ *
286
+ * // References survive renames, and each observation carries the revision it
287
+ * // matched, so a caller can tell whether what it read is still current.
288
+ * const program = Effect.gen(function*() {
289
+ * const caller = yield* (yield* Vfs.make()).caller()
290
+ *
291
+ * yield* caller.mkdir("/work")
292
+ *
293
+ * const root = yield* caller.rootReference
294
+ * const work = yield* caller.lookupReference(root, new TextEncoder().encode("work"))
295
+ *
296
+ * const before = yield* caller.observeMetadata(work)
297
+ *
298
+ * yield* caller.rename("/work", "/moved")
299
+ *
300
+ * const after = yield* caller.observeMetadata(work)
301
+ *
302
+ * return [before.value.kind, after.value.kind]
303
+ * })
304
+ *
305
+ * Effect.runPromise(program).then(console.log)
306
+ * // [ 'directory', 'directory' ]
307
+ * ```
308
+ *
136
309
  * @category models
137
310
  * @since 0.1.0
138
311
  */
@@ -163,6 +336,36 @@ export interface RelativeOptions {
163
336
  /**
164
337
  * Controls the base directory and whether metadata operations follow the final symbolic link.
165
338
  *
339
+ * @example
340
+ * ```ts
341
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
342
+ * import { Effect } from "effect"
343
+ *
344
+ * const program = Effect.gen(function*() {
345
+ * const caller = yield* (yield* Vfs.make()).caller()
346
+ *
347
+ * yield* caller.writeFile("/target", new Uint8Array([1]), {
348
+ * access: "write",
349
+ * create: "exclusive",
350
+ * mode: 0o644
351
+ * })
352
+ * yield* caller.symlink("/target", "/link")
353
+ *
354
+ * // Metadata operations follow the final link by default, so this reaches the
355
+ * // target; opting out changes the link itself and leaves the target alone.
356
+ * yield* caller.chmod("/link", 0o600)
357
+ * const followed = (yield* caller.stat("/target")).mode & 0o777
358
+ *
359
+ * yield* caller.chmod("/link", 0o777, { followFinalSymlink: false })
360
+ * const unchanged = (yield* caller.stat("/target")).mode & 0o777
361
+ *
362
+ * return [followed.toString(8), unchanged.toString(8)]
363
+ * })
364
+ *
365
+ * Effect.runPromise(program).then(console.log)
366
+ * // [ '600', '600' ]
367
+ * ```
368
+ *
166
369
  * @category models
167
370
  * @since 0.1.0
168
371
  */
@@ -194,6 +397,34 @@ export declare const TimeUpdate: typeof VfsModel.TimeUpdate;
194
397
  /**
195
398
  * Schema for independent access and modification time updates.
196
399
  *
400
+ * @example
401
+ * ```ts
402
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
403
+ * import { Effect } from "effect"
404
+ *
405
+ * const program = Effect.gen(function*() {
406
+ * const caller = yield* (yield* Vfs.make()).caller()
407
+ *
408
+ * yield* caller.writeFile("/f", new Uint8Array([1]), {
409
+ * access: "write",
410
+ * create: "exclusive"
411
+ * })
412
+ *
413
+ * // Each field is set to a value, to the current clock, or left alone.
414
+ * yield* caller.utimes("/f", {
415
+ * access: { kind: "value", nanoseconds: 1_000n },
416
+ * modification: { kind: "omit" }
417
+ * })
418
+ *
419
+ * const metadata = yield* caller.stat("/f")
420
+ *
421
+ * return [metadata.atimeNs, metadata.mtimeNs === 1_000n]
422
+ * })
423
+ *
424
+ * Effect.runPromise(program).then(console.log)
425
+ * // [ 1000n, false ]
426
+ * ```
427
+ *
197
428
  * @category schemas
198
429
  * @since 0.1.0
199
430
  */
@@ -208,6 +439,34 @@ export type Times = typeof Times.Type;
208
439
  /**
209
440
  * A scoped directory capability that can be used for metadata and relative lookup.
210
441
  *
442
+ * @example
443
+ * ```ts
444
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
445
+ * import { Effect } from "effect"
446
+ *
447
+ * // The handle anchors relative paths to the directory itself, so a rename in
448
+ * // between does not send the write somewhere else.
449
+ * const program = Effect.gen(function*() {
450
+ * const caller = yield* (yield* Vfs.make()).caller()
451
+ *
452
+ * yield* caller.mkdir("/project")
453
+ *
454
+ * const directory = yield* caller.openDirectory("/project")
455
+ *
456
+ * yield* caller.rename("/project", "/renamed")
457
+ * yield* caller.writeFile("notes.txt", new TextEncoder().encode("kept"), {
458
+ * access: "write",
459
+ * create: "exclusive",
460
+ * relativeTo: directory
461
+ * })
462
+ *
463
+ * return yield* caller.readDirectory("/renamed")
464
+ * }).pipe(Effect.scoped)
465
+ *
466
+ * Effect.runPromise(program).then(console.log)
467
+ * // [ 'notes.txt' ]
468
+ * ```
469
+ *
211
470
  * @category models
212
471
  * @since 0.1.0
213
472
  */
@@ -233,6 +492,30 @@ export type SeekMode = typeof SeekMode.Type;
233
492
  /**
234
493
  * Schema for file access, creation, append, truncate, and symlink behavior.
235
494
  *
495
+ * @example
496
+ * ```ts
497
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
498
+ * import { Effect } from "effect"
499
+ *
500
+ * // `access` is required; `create` decides whether a missing file is an error.
501
+ * const program = Effect.gen(function*() {
502
+ * const caller = yield* (yield* Vfs.make()).caller()
503
+ *
504
+ * const handle = yield* caller.open("/log.txt", {
505
+ * access: "readWrite",
506
+ * create: "ifMissing",
507
+ * append: true
508
+ * })
509
+ *
510
+ * yield* handle.write(new Uint8Array([1, 2]))
511
+ *
512
+ * return (yield* handle.stat).size
513
+ * }).pipe(Effect.scoped)
514
+ *
515
+ * Effect.runPromise(program).then(console.log)
516
+ * // 2n
517
+ * ```
518
+ *
236
519
  * @category schemas
237
520
  * @since 0.1.0
238
521
  */
@@ -247,6 +530,40 @@ export type OpenOptions = typeof OpenSettings.Type & RelativeOptions;
247
530
  /**
248
531
  * Options for an atomic whole-file write, including replacement and final mode controls.
249
532
  *
533
+ * @example
534
+ * ```ts
535
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
536
+ * import { Effect } from "effect"
537
+ *
538
+ * const program = Effect.gen(function*() {
539
+ * const caller = yield* (yield* Vfs.make()).caller()
540
+ * const bytes = new TextEncoder().encode("v2")
541
+ *
542
+ * // `access` is required. `create: "exclusive"` refuses to replace an entry,
543
+ * // while `truncate` overwrites one that already exists.
544
+ * yield* caller.writeFile("/app.conf", new TextEncoder().encode("v1"), {
545
+ * access: "write",
546
+ * create: "exclusive",
547
+ * mode: 0o640
548
+ * })
549
+ *
550
+ * const refused = yield* caller.writeFile("/app.conf", bytes, {
551
+ * access: "write",
552
+ * create: "exclusive"
553
+ * }).pipe(
554
+ * Effect.as("replaced"),
555
+ * Effect.catchTag("FsError", (error) => Effect.succeed(error.code))
556
+ * )
557
+ *
558
+ * yield* caller.writeFile("/app.conf", bytes, { access: "write", truncate: true })
559
+ *
560
+ * return [refused, new TextDecoder().decode(yield* caller.readFile("/app.conf"))]
561
+ * })
562
+ *
563
+ * Effect.runPromise(program).then(console.log)
564
+ * // [ 'AlreadyExists', 'v2' ]
565
+ * ```
566
+ *
250
567
  * @category models
251
568
  * @since 0.1.0
252
569
  */
@@ -254,6 +571,31 @@ export type WriteFileOptions = VfsModel.WriteFileOptions;
254
571
  /**
255
572
  * A scoped regular-file capability with an independent bigint cursor.
256
573
  *
574
+ * @example
575
+ * ```ts
576
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
577
+ * import { Effect } from "effect"
578
+ *
579
+ * const program = Effect.gen(function*() {
580
+ * const caller = yield* (yield* Vfs.make()).caller()
581
+ *
582
+ * const handle = yield* caller.open("/data.bin", {
583
+ * access: "readWrite",
584
+ * create: "exclusive"
585
+ * })
586
+ *
587
+ * yield* handle.write(new Uint8Array([1, 2, 3, 4]))
588
+ *
589
+ * // `pread` reads at an offset and leaves the cursor where writing left it.
590
+ * const slice = yield* handle.pread(2, 1n)
591
+ *
592
+ * return [slice, yield* handle.seek(0n, "current")]
593
+ * }).pipe(Effect.scoped)
594
+ *
595
+ * Effect.runPromise(program).then(console.log)
596
+ * // [ Uint8Array(2) [ 2, 3 ], 4n ]
597
+ * ```
598
+ *
257
599
  * @category models
258
600
  * @since 0.1.0
259
601
  */
@@ -281,6 +623,84 @@ export interface FileHandle {
281
623
  /**
282
624
  * A filesystem caller with its own identity, creation mask, and current directory.
283
625
  *
626
+ * @example
627
+ * ```ts
628
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
629
+ * import { Effect } from "effect"
630
+ *
631
+ * // Whole-file reads and writes need no scope. `writeFile` always takes an
632
+ * // options bag; `access` is required and `create` decides what a missing file means.
633
+ * const program = Effect.gen(function*() {
634
+ * const volume = yield* Vfs.make()
635
+ * const caller = yield* volume.caller()
636
+ *
637
+ * yield* caller.mkdir("/work")
638
+ * yield* caller.writeFile("/work/notes.txt", new TextEncoder().encode("hello"), {
639
+ * access: "write",
640
+ * create: "exclusive"
641
+ * })
642
+ *
643
+ * return yield* caller.readDirectory("/work")
644
+ * })
645
+ *
646
+ * Effect.runPromise(program).then(console.log)
647
+ * // [ 'notes.txt' ]
648
+ * ```
649
+ *
650
+ * @example
651
+ * ```ts
652
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
653
+ * import { Effect } from "effect"
654
+ *
655
+ * // Reach for a handle when a whole-file write will not do. A handle carries a
656
+ * // cursor and lives until its scope closes.
657
+ * const program = Effect.gen(function*() {
658
+ * const caller = yield* (yield* Vfs.make()).caller()
659
+ *
660
+ * const handle = yield* caller.open("/log.txt", {
661
+ * access: "readWrite",
662
+ * create: "exclusive"
663
+ * })
664
+ *
665
+ * yield* handle.write(new TextEncoder().encode("first "))
666
+ * yield* handle.write(new TextEncoder().encode("second"))
667
+ * yield* handle.seek(0n, "start")
668
+ *
669
+ * return new TextDecoder().decode(yield* handle.read(64))
670
+ * }).pipe(Effect.scoped)
671
+ *
672
+ * Effect.runPromise(program).then(console.log)
673
+ * // first second
674
+ * ```
675
+ *
676
+ * @example
677
+ * ```ts
678
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
679
+ * import { Effect } from "effect"
680
+ *
681
+ * // A derived caller holds the directory's identity, not its path, so renaming
682
+ * // the directory does not redirect later relative writes.
683
+ * const program = Effect.gen(function*() {
684
+ * const caller = yield* (yield* Vfs.make()).caller()
685
+ *
686
+ * yield* caller.mkdir("/build")
687
+ *
688
+ * const work = yield* caller.withDirectory("/build")
689
+ *
690
+ * yield* caller.rename("/build", "/dist")
691
+ * yield* work.writeFile("out.txt", new TextEncoder().encode("done"), {
692
+ * access: "write",
693
+ * create: "exclusive"
694
+ * })
695
+ *
696
+ * return yield* caller.readDirectory("/dist")
697
+ * }).pipe(Effect.scoped)
698
+ *
699
+ * Effect.runPromise(program).then(console.log)
700
+ * // [ 'out.txt' ]
701
+ * ```
702
+ *
703
+ * @see The filesystem model at `/concepts/filesystem-model`.
284
704
  * @category models
285
705
  * @since 0.1.0
286
706
  */
@@ -292,11 +712,11 @@ export interface Caller {
292
712
  readonly lookupReference: (directory: ObjectReference, name: Uint8Array) => Effect.Effect<ObjectReference, FsError>;
293
713
  /** Returns a referenced directory's current parent. The root is its own parent. */
294
714
  readonly parentReference: (directory: ObjectReference) => Effect.Effect<ObjectReference, FsError>;
295
- /** Reads metadata and its matching live revision. */
715
+ /** Reads metadata and its matching live revision. Requires no permission on the object, as `stat` does not. */
296
716
  readonly observeMetadata: (reference: ObjectReference) => Effect.Effect<ObjectObservation<Metadata>, FsError>;
297
717
  /** Reads owned directory entries and their matching directory revision. */
298
718
  readonly observeDirectory: (reference: ObjectReference) => Effect.Effect<ObjectObservation<ReadonlyArray<DirectoryEntry>>, FsError>;
299
- /** Reads an owned symbolic-link target through a stable reference. */
719
+ /** Reads an owned symbolic-link target through a stable reference. Requires no permission on the link. */
300
720
  readonly readLinkReference: (reference: ObjectReference) => Effect.Effect<Uint8Array, FsError>;
301
721
  /** Opens a referenced regular file for reading. */
302
722
  readonly openReference: (reference: ObjectReference) => Effect.Effect<FileHandle, FsError, Scope.Scope>;
@@ -337,15 +757,15 @@ export interface Caller {
337
757
  }) => Effect.Effect<void, FsError>;
338
758
  /** Creates a symbolic link. The target bytes are stored without resolving them. */
339
759
  readonly symlink: (target: PathInput, path: PathInput, options?: RelativeOptions) => Effect.Effect<void, FsError>;
340
- /** Reads a symbolic-link target as UTF-8, failing with `UnrepresentableName` for other bytes. */
760
+ /** Reads a symbolic-link target as UTF-8, failing `UnrepresentableName` for other bytes. See `readLinkBytes`. */
341
761
  readonly readLink: (path: PathInput, options?: RelativeOptions) => Effect.Effect<string, FsError>;
342
762
  /** Reads a symbolic-link target as owned bytes. */
343
763
  readonly readLinkBytes: (path: PathInput, options?: RelativeOptions) => Effect.Effect<Uint8Array, FsError>;
344
- /** Reads directory names as UTF-8, failing if any name is not representable. */
764
+ /** Reads directory names as UTF-8, failing `UnrepresentableName` for other bytes. See `readDirectoryBytes`. */
345
765
  readonly readDirectory: (path: PathInput, options?: RelativeOptions) => Effect.Effect<ReadonlyArray<string>, FsError>;
346
766
  /** Reads directory names as owned byte arrays. */
347
767
  readonly readDirectoryBytes: (path: PathInput, options?: RelativeOptions) => Effect.Effect<ReadonlyArray<Uint8Array>, FsError>;
348
- /** Resolves links and normalizes a path as UTF-8. */
768
+ /** Resolves links and normalizes a path as UTF-8, failing `UnrepresentableName` for other bytes. See `realPathBytes`. */
349
769
  readonly realPath: (path: PathInput, options?: RelativeOptions) => Effect.Effect<string, FsError>;
350
770
  /** Resolves links and normalizes a path without requiring UTF-8 names. */
351
771
  readonly realPathBytes: (path: PathInput, options?: RelativeOptions) => Effect.Effect<BytePath, FsError>;
@@ -367,6 +787,37 @@ export interface Caller {
367
787
  /**
368
788
  * A committed namespace or content change emitted by a volume watch stream.
369
789
  *
790
+ * @example
791
+ * ```ts
792
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
793
+ * import { Effect, Fiber, Stream } from "effect"
794
+ *
795
+ * const program = Effect.gen(function*() {
796
+ * const volume = yield* Vfs.make()
797
+ * const caller = yield* volume.caller()
798
+ *
799
+ * const watcher = yield* volume.watch.pipe(
800
+ * Effect.flatMap((stream) => Stream.runCollect(Stream.take(stream, 2))),
801
+ * Effect.forkChild({ startImmediately: true })
802
+ * )
803
+ *
804
+ * yield* caller.mkdir("/logs")
805
+ * yield* caller.rmdir("/logs")
806
+ *
807
+ * // Each event pairs the kind of change with the absolute path it happened to.
808
+ * const events = yield* Fiber.join(watcher)
809
+ *
810
+ * return yield* Effect.forEach(events, (change) =>
811
+ * Effect.map(
812
+ * Vfs.pathToBytes(change.path),
813
+ * (bytes) => `${change._tag} ${new TextDecoder().decode(bytes)}`
814
+ * ))
815
+ * }).pipe(Effect.scoped)
816
+ *
817
+ * Effect.runPromise(program).then(console.log)
818
+ * // [ 'Create /logs', 'Remove /logs' ]
819
+ * ```
820
+ *
370
821
  * @category models
371
822
  * @since 0.1.0
372
823
  */
@@ -426,6 +877,28 @@ export type OverlayChange = typeof OverlayChange.Type;
426
877
  /**
427
878
  * Schema for overlay summary filtering.
428
879
  *
880
+ * @example
881
+ * ```ts
882
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
883
+ * import { Effect } from "effect"
884
+ *
885
+ * const program = Effect.gen(function*() {
886
+ * const base = yield* Vfs.make()
887
+ * const workspace = yield* Vfs.makeOverlay(yield* base.snapshot)
888
+ *
889
+ * yield* (yield* workspace.caller()).mkdir("/out")
890
+ *
891
+ * // Timestamps are excluded by default, since they change on every write.
892
+ * const plain = yield* workspace.changes()
893
+ * const timed = yield* workspace.changes({ includeTimestamps: true })
894
+ *
895
+ * return [plain.length, timed.length]
896
+ * })
897
+ *
898
+ * Effect.runPromise(program).then(console.log)
899
+ * // [ 1, 2 ]
900
+ * ```
901
+ *
429
902
  * @category schemas
430
903
  * @since 0.1.0
431
904
  */
@@ -440,6 +913,36 @@ export type OverlayChangesOptions = typeof OverlayChangesOptions.Type;
440
913
  /**
441
914
  * A complete snapshot and final-difference summary captured from one committed state.
442
915
  *
916
+ * @example
917
+ * ```ts
918
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
919
+ * import { Effect } from "effect"
920
+ *
921
+ * const program = Effect.gen(function*() {
922
+ * const base = yield* Vfs.make()
923
+ * const workspace = yield* Vfs.makeOverlay(yield* base.snapshot)
924
+ *
925
+ * yield* (yield* workspace.caller()).writeFile("/out.txt", new TextEncoder().encode("built"), {
926
+ * access: "write",
927
+ * create: "exclusive"
928
+ * })
929
+ *
930
+ * // One committed state: the summary describes exactly the snapshot beside it,
931
+ * // so restoring from it cannot disagree with the change list.
932
+ * const captured = yield* workspace.capture()
933
+ * const restored = yield* Vfs.fromSnapshot(captured.snapshot)
934
+ * const reader = yield* restored.caller()
935
+ *
936
+ * return [
937
+ * captured.changes.map((change) => change._tag),
938
+ * new TextDecoder().decode(yield* reader.readFile("/out.txt"))
939
+ * ]
940
+ * })
941
+ *
942
+ * Effect.runPromise(program).then(console.log)
943
+ * // [ [ 'Added' ], 'built' ]
944
+ * ```
945
+ *
443
946
  * @category models
444
947
  * @since 0.1.0
445
948
  */
@@ -452,6 +955,32 @@ export interface OverlayCapture {
452
955
  /**
453
956
  * An isolated virtual filesystem namespace that creates callers, snapshots, and watch streams.
454
957
  *
958
+ * @example
959
+ * ```ts
960
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
961
+ * import { Effect, Fiber, Option, Stream } from "effect"
962
+ *
963
+ * const program = Effect.gen(function*() {
964
+ * const volume = yield* Vfs.make()
965
+ * const caller = yield* volume.caller()
966
+ *
967
+ * // `watch` reports future changes only; nothing is replayed.
968
+ * const watcher = yield* volume.watch.pipe(
969
+ * Effect.flatMap(Stream.runHead),
970
+ * Effect.forkChild({ startImmediately: true })
971
+ * )
972
+ *
973
+ * yield* caller.mkdir("/logs")
974
+ *
975
+ * const change = yield* Fiber.join(watcher)
976
+ *
977
+ * return Option.getOrElse(Option.map(change, (event) => event._tag), () => "none")
978
+ * }).pipe(Effect.scoped)
979
+ *
980
+ * Effect.runPromise(program).then(console.log)
981
+ * // Create
982
+ * ```
983
+ *
455
984
  * @category models
456
985
  * @since 0.1.0
457
986
  */
@@ -467,6 +996,30 @@ export interface Volume {
467
996
  /**
468
997
  * An ordinary volume with final-state inspection relative to one immutable snapshot base.
469
998
  *
999
+ * @example
1000
+ * ```ts
1001
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
1002
+ * import { Effect } from "effect"
1003
+ *
1004
+ * const program = Effect.gen(function*() {
1005
+ * const base = yield* Vfs.make()
1006
+ * const workspace = yield* Vfs.makeOverlay(yield* base.snapshot)
1007
+ *
1008
+ * yield* (yield* workspace.caller()).writeFile("/added.txt", new Uint8Array([1]), {
1009
+ * access: "write",
1010
+ * create: "exclusive"
1011
+ * })
1012
+ *
1013
+ * // `capture` pairs one committed snapshot with the summary describing it.
1014
+ * const captured = yield* workspace.capture()
1015
+ *
1016
+ * return captured.changes.map((change) => change._tag)
1017
+ * })
1018
+ *
1019
+ * Effect.runPromise(program).then(console.log)
1020
+ * // [ 'Added' ]
1021
+ * ```
1022
+ *
470
1023
  * @category models
471
1024
  * @since 0.1.0
472
1025
  */
@@ -479,6 +1032,35 @@ export interface OverlayVolume extends Volume {
479
1032
  /**
480
1033
  * Optional Effect service for providing an existing filesystem caller.
481
1034
  *
1035
+ * @example
1036
+ * ```ts
1037
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
1038
+ * import { Effect } from "effect"
1039
+ *
1040
+ * // Code depending on the service does not choose the volume it runs against.
1041
+ * const readVersion = Effect.gen(function*() {
1042
+ * const caller = yield* Vfs.CurrentFileSystem
1043
+ *
1044
+ * return yield* caller.readFile("/version")
1045
+ * })
1046
+ *
1047
+ * const program = Effect.gen(function*() {
1048
+ * const volume = yield* Vfs.make()
1049
+ * const caller = yield* volume.caller()
1050
+ *
1051
+ * yield* caller.writeFile("/version", new Uint8Array([49]), {
1052
+ * access: "write",
1053
+ * create: "exclusive"
1054
+ * })
1055
+ *
1056
+ * return yield* readVersion.pipe(Effect.provideService(Vfs.CurrentFileSystem, caller))
1057
+ * })
1058
+ *
1059
+ * Effect.runPromise(program).then(console.log)
1060
+ * // Uint8Array(1) [ 49 ]
1061
+ * ```
1062
+ *
1063
+ * @see The testing guide at `/guides/testing-with-an-isolated-filesystem`.
482
1064
  * @category services
483
1065
  * @since 0.1.0
484
1066
  */
@@ -488,6 +1070,32 @@ export type CurrentFileSystem = VfsModel.CurrentFileSystem;
488
1070
  /**
489
1071
  * Encodes a snapshot as owned UTF-8 JSON bytes using the version 1 snapshot format.
490
1072
  *
1073
+ * @example
1074
+ * ```ts
1075
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
1076
+ * import { Effect } from "effect"
1077
+ *
1078
+ * // Bytes are portable: store or transmit them, then decode under explicit limits.
1079
+ * const program = Effect.gen(function*() {
1080
+ * const volume = yield* Vfs.make()
1081
+ * const caller = yield* volume.caller()
1082
+ *
1083
+ * yield* caller.writeFile("/f", new Uint8Array([1, 2, 3]), {
1084
+ * access: "write",
1085
+ * create: "exclusive"
1086
+ * })
1087
+ *
1088
+ * const bytes = yield* Vfs.encodeSnapshot(yield* volume.snapshot)
1089
+ *
1090
+ * // The bytes are UTF-8 JSON carrying the version 1 snapshot format.
1091
+ * return JSON.parse(new TextDecoder().decode(bytes)).version
1092
+ * })
1093
+ *
1094
+ * Effect.runPromise(program).then(console.log)
1095
+ * // 1
1096
+ * ```
1097
+ *
1098
+ * @see {@link decodeSnapshot} for reading the bytes back under limits.
491
1099
  * @category serialization
492
1100
  * @since 0.1.0
493
1101
  */
@@ -495,14 +1103,70 @@ export declare const encodeSnapshot: (snapshot: Snapshot) => Effect.Effect<Uint8
495
1103
  /**
496
1104
  * Decodes version 1 snapshot bytes while enforcing explicit input and payload limits.
497
1105
  *
1106
+ * @example
1107
+ * ```ts
1108
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
1109
+ * import { ByteSize, Effect } from "effect"
1110
+ *
1111
+ * // Limits are mandatory: decoding is the boundary where untrusted input arrives.
1112
+ * const program = Effect.gen(function*() {
1113
+ * const volume = yield* Vfs.make()
1114
+ * const bytes = yield* Vfs.encodeSnapshot(yield* volume.snapshot)
1115
+ *
1116
+ * return yield* Vfs.decodeSnapshot(bytes, {
1117
+ * maxEncodedBytes: ByteSize.bytes(1),
1118
+ * maxRecords: 10_000,
1119
+ * maxEntries: 10_000,
1120
+ * maxDecodedBytes: ByteSize.megabytes(16)
1121
+ * }).pipe(
1122
+ * Effect.as("accepted"),
1123
+ * Effect.catchTag("ImageError", (error) => Effect.succeed([error.code, error.field]))
1124
+ * )
1125
+ * })
1126
+ *
1127
+ * Effect.runPromise(program).then(console.log)
1128
+ * // [ 'LimitExceeded', 'encodedBytes' ]
1129
+ * ```
1130
+ *
1131
+ * @see {@link DecodeLimits} for what each bound protects.
498
1132
  * @category serialization
499
1133
  * @since 0.1.0
500
1134
  */
501
1135
  export declare const decodeSnapshot: (input: Uint8Array, limits: DecodeLimits) => Effect.Effect<Snapshot, ImageError>;
502
1136
  /**
503
1137
  * Computes an exact portable delta between two immutable snapshots.
504
- * Requires the platform-neutral `Crypto.Crypto` service for base identity.
1138
+ * Requires the platform-neutral `Crypto.Crypto` service for base identity. Provide
1139
+ * `NodeCrypto.layer`, `BunCrypto.layer`, or your own via `Crypto.make`.
1140
+ *
1141
+ * @example
1142
+ * ```ts
1143
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
1144
+ * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
1145
+ * import { Effect } from "effect"
1146
+ *
1147
+ * // Two snapshots in, one opaque delta out. `inspectSnapshotDelta` reads it.
1148
+ * const program = Effect.gen(function*() {
1149
+ * const volume = yield* Vfs.make()
1150
+ * const caller = yield* volume.caller()
1151
+ * const base = yield* volume.snapshot
1152
+ *
1153
+ * yield* caller.writeFile("/added.txt", new Uint8Array([1]), {
1154
+ * access: "write",
1155
+ * create: "exclusive"
1156
+ * })
1157
+ *
1158
+ * const delta = yield* Vfs.diffSnapshots(base, yield* volume.snapshot)
505
1159
  *
1160
+ * const changes = yield* Vfs.inspectSnapshotDelta(base, delta)
1161
+ *
1162
+ * return new TextDecoder().decode(yield* Vfs.pathToBytes(changes[0]!.path))
1163
+ * }).pipe(Effect.provide(NodeCrypto.layer))
1164
+ *
1165
+ * Effect.runPromise(program).then(console.log)
1166
+ * // /added.txt
1167
+ * ```
1168
+ *
1169
+ * @see {@link inspectSnapshotDelta} to read a delta, {@link applySnapshotDelta} to apply one.
506
1170
  * @category snapshots
507
1171
  * @since 0.1.0
508
1172
  */
@@ -520,8 +1184,35 @@ export declare const diffSnapshots: (base: Snapshot, target: Snapshot, limits?:
520
1184
  } | undefined) => Effect.Effect<SnapshotDeltaModel.SnapshotDelta, VfsModel.ConfigurationError | ImageError | import("effect/PlatformError").PlatformError, import("effect/Crypto").Crypto>;
521
1185
  /**
522
1186
  * Verifies an exact snapshot delta against its base and derives an owned path-oriented summary.
523
- * Requires the platform-neutral `Crypto.Crypto` service for base identity.
1187
+ * Requires the platform-neutral `Crypto.Crypto` service for base identity. Provide
1188
+ * `NodeCrypto.layer`, `BunCrypto.layer`, or your own via `Crypto.make`.
1189
+ *
1190
+ * @example
1191
+ * ```ts
1192
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
1193
+ * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
1194
+ * import { Effect } from "effect"
1195
+ *
1196
+ * // Inspection verifies the delta against its base before describing it.
1197
+ * const program = Effect.gen(function*() {
1198
+ * const volume = yield* Vfs.make()
1199
+ * const caller = yield* volume.caller()
1200
+ * const base = yield* volume.snapshot
1201
+ *
1202
+ * yield* caller.mkdir("/logs")
1203
+ *
1204
+ * const delta = yield* Vfs.diffSnapshots(base, yield* volume.snapshot)
1205
+ * const changes = yield* Vfs.inspectSnapshotDelta(base, delta)
524
1206
  *
1207
+ * return changes.map((change) =>
1208
+ * change._tag === "Updated" ? change.afterKind : change.kind)
1209
+ * }).pipe(Effect.provide(NodeCrypto.layer))
1210
+ *
1211
+ * Effect.runPromise(program).then(console.log)
1212
+ * // [ 'directory' ]
1213
+ * ```
1214
+ *
1215
+ * @see {@link SnapshotChangesOptions} for including timestamp differences.
525
1216
  * @category snapshots
526
1217
  * @since 0.1.0
527
1218
  */
@@ -555,8 +1246,40 @@ export declare const inspectSnapshotDelta: (base: Snapshot, delta: SnapshotDelta
555
1246
  })[], VfsModel.ConfigurationError | ImageError | import("effect/PlatformError").PlatformError | SnapshotDeltaModel.SnapshotDeltaError, import("effect/Crypto").Crypto>;
556
1247
  /**
557
1248
  * Applies an exact delta to its semantically matching base and returns a new snapshot.
558
- * Requires the platform-neutral `Crypto.Crypto` service for base identity.
1249
+ * Requires the platform-neutral `Crypto.Crypto` service for base identity. Provide
1250
+ * `NodeCrypto.layer`, `BunCrypto.layer`, or your own via `Crypto.make`.
1251
+ *
1252
+ * @example
1253
+ * ```ts
1254
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
1255
+ * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
1256
+ * import { Effect } from "effect"
1257
+ *
1258
+ * const program = Effect.gen(function*() {
1259
+ * const volume = yield* Vfs.make()
1260
+ * const caller = yield* volume.caller()
1261
+ * const base = yield* volume.snapshot
1262
+ *
1263
+ * yield* caller.writeFile("/f", new Uint8Array([7]), {
1264
+ * access: "write",
1265
+ * create: "exclusive"
1266
+ * })
1267
+ *
1268
+ * const delta = yield* Vfs.diffSnapshots(base, yield* volume.snapshot)
559
1269
  *
1270
+ * // Applying the delta to the same base reproduces the target snapshot.
1271
+ * const applied = yield* Vfs.applySnapshotDelta(base, delta)
1272
+ * const restored = yield* Vfs.fromSnapshot(applied)
1273
+ * const reader = yield* restored.caller()
1274
+ *
1275
+ * return yield* reader.readFile("/f")
1276
+ * }).pipe(Effect.provide(NodeCrypto.layer))
1277
+ *
1278
+ * Effect.runPromise(program).then(console.log)
1279
+ * // Uint8Array(1) [ 7 ]
1280
+ * ```
1281
+ *
1282
+ * @see {@link SnapshotDeltaFromBytes} for moving a delta across a boundary.
560
1283
  * @category snapshots
561
1284
  * @since 0.1.0
562
1285
  */
@@ -576,6 +1299,34 @@ export declare const applySnapshotDelta: (base: Snapshot, delta: SnapshotDeltaMo
576
1299
  * Creates an Effect Schema codec between owned bytes and opaque snapshot deltas.
577
1300
  * Omission uses `SnapshotDeltaLimits.default`.
578
1301
  *
1302
+ * @example
1303
+ * ```ts
1304
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
1305
+ * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
1306
+ * import { Effect, Schema } from "effect"
1307
+ *
1308
+ * // Omitting limits uses `SnapshotDeltaLimits.default`.
1309
+ * const codec = Vfs.SnapshotDeltaFromBytes()
1310
+ *
1311
+ * const program = Effect.gen(function*() {
1312
+ * const volume = yield* Vfs.make()
1313
+ * const base = yield* volume.snapshot
1314
+ *
1315
+ * yield* (yield* volume.caller()).mkdir("/out")
1316
+ *
1317
+ * const delta = yield* Vfs.diffSnapshots(base, yield* volume.snapshot)
1318
+ * const bytes = yield* Schema.encodeEffect(codec)(delta)
1319
+ *
1320
+ * // The delta survives the trip through bytes and still applies to its base.
1321
+ * const decoded = yield* Schema.decodeEffect(codec)(bytes)
1322
+ *
1323
+ * return (yield* Vfs.inspectSnapshotDelta(base, decoded)).map((change) => change._tag)
1324
+ * }).pipe(Effect.provide(NodeCrypto.layer))
1325
+ *
1326
+ * Effect.runPromise(program).then(console.log)
1327
+ * // [ 'Added' ]
1328
+ * ```
1329
+ *
579
1330
  * @category schemas
580
1331
  * @since 0.1.0
581
1332
  */
@@ -587,6 +1338,28 @@ export declare const SnapshotDeltaFromBytes: (limits?: SnapshotDeltaModel.Snapsh
587
1338
  *
588
1339
  * Shared-memory-backed and detached views fail with `InvalidArgument`.
589
1340
  *
1341
+ * @example
1342
+ * ```ts
1343
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
1344
+ * import { Effect } from "effect"
1345
+ *
1346
+ * // Any byte except NUL is accepted. Empty, NUL-bearing, detached, and shared-memory
1347
+ * // input all fail with `InvalidArgument`.
1348
+ * const program = Effect.gen(function*() {
1349
+ * const path = yield* Vfs.pathFromBytes(new Uint8Array([47, 116, 109, 112]))
1350
+ *
1351
+ * const rejected = yield* Vfs.pathFromBytes(new Uint8Array([47, 0])).pipe(
1352
+ * Effect.as("accepted"),
1353
+ * Effect.catchTag("FsError", (error) => Effect.succeed(error.code))
1354
+ * )
1355
+ *
1356
+ * return [yield* Vfs.pathToBytes(path), rejected]
1357
+ * })
1358
+ *
1359
+ * Effect.runPromise(program).then(console.log)
1360
+ * // [ Uint8Array(4) [ 47, 116, 109, 112 ], 'InvalidArgument' ]
1361
+ * ```
1362
+ *
590
1363
  * @category constructors
591
1364
  * @since 0.1.0
592
1365
  */
@@ -605,6 +1378,26 @@ export declare const pathToBytes: (path: BytePath) => Effect.Effect<Uint8Array,
605
1378
  *
606
1379
  * Fixture paths must be absolute, unique, and explicitly include their parent directories.
607
1380
  *
1381
+ * @example
1382
+ * ```ts
1383
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
1384
+ * import { Effect, Schema } from "effect"
1385
+ *
1386
+ * // Fixtures must list every parent directory before its children.
1387
+ * const program = Schema.decodeUnknownEffect(Vfs.Fixture)({
1388
+ * entries: [
1389
+ * { kind: "file", path: "/etc/hosts", bytes: new TextEncoder().encode("127.0.0.1") }
1390
+ * ]
1391
+ * }).pipe(
1392
+ * Effect.flatMap(Vfs.fromFixture),
1393
+ * Effect.as("built"),
1394
+ * Effect.catchTag("ImageError", (error) => Effect.succeed(`rejected: ${error.code}`))
1395
+ * )
1396
+ *
1397
+ * Effect.runPromise(program).then(console.log)
1398
+ * // rejected: InvalidStructure
1399
+ * ```
1400
+ *
608
1401
  * @category schemas
609
1402
  * @since 0.1.0
610
1403
  */
@@ -624,6 +1417,26 @@ export type Fixture = typeof Fixture.Type;
624
1417
  * Each execution creates independent storage. Snapshot image failures cannot
625
1418
  * arise because this constructor does not accept persisted input.
626
1419
  *
1420
+ * @example
1421
+ * ```ts
1422
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
1423
+ * import { Effect } from "effect"
1424
+ *
1425
+ * const program = Effect.gen(function*() {
1426
+ * const volume = yield* Vfs.make()
1427
+ * const caller = yield* volume.caller()
1428
+ *
1429
+ * yield* caller.mkdir("/work")
1430
+ * yield* caller.writeFile("/work/notes.txt", new Uint8Array([104, 105]), { access: "write", create: "exclusive" })
1431
+ *
1432
+ * return yield* caller.readFile("/work/notes.txt")
1433
+ * })
1434
+ *
1435
+ * Effect.runPromise(program).then(console.log)
1436
+ * // Uint8Array(2) [ 104, 105 ]
1437
+ * ```
1438
+ *
1439
+ * @see The testing guide at `/guides/testing-with-an-isolated-filesystem`.
627
1440
  * @category constructors
628
1441
  * @since 0.1.0
629
1442
  */
@@ -631,6 +1444,40 @@ export declare const make: (options?: VolumeOptions) => Effect.Effect<Volume, Co
631
1444
  /**
632
1445
  * Restores a fresh volume from an opaque snapshot under the supplied destination limits.
633
1446
  *
1447
+ * @example
1448
+ * ```ts
1449
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
1450
+ * import { Effect } from "effect"
1451
+ *
1452
+ * const program = Effect.gen(function*() {
1453
+ * const original = yield* Vfs.make()
1454
+ * const source = yield* original.caller()
1455
+ *
1456
+ * yield* source.writeFile("/seed.txt", new TextEncoder().encode("one"), {
1457
+ * access: "write",
1458
+ * create: "exclusive"
1459
+ * })
1460
+ *
1461
+ * const restored = yield* Vfs.fromSnapshot(yield* original.snapshot)
1462
+ * const copy = yield* restored.caller()
1463
+ *
1464
+ * // Writing to the restore leaves the original untouched: the two are independent.
1465
+ * yield* copy.writeFile("/seed.txt", new TextEncoder().encode("two"), {
1466
+ * access: "write",
1467
+ * truncate: true
1468
+ * })
1469
+ *
1470
+ * return [
1471
+ * new TextDecoder().decode(yield* source.readFile("/seed.txt")),
1472
+ * new TextDecoder().decode(yield* copy.readFile("/seed.txt"))
1473
+ * ]
1474
+ * })
1475
+ *
1476
+ * Effect.runPromise(program).then(console.log)
1477
+ * // [ 'one', 'two' ]
1478
+ * ```
1479
+ *
1480
+ * @see {@link makeOverlay} for a copy-on-write workspace over the same base.
634
1481
  * @category constructors
635
1482
  * @since 0.1.0
636
1483
  */
@@ -648,6 +1495,39 @@ export declare const fromSnapshot: (snapshot: Snapshot, options?: VolumeOptions)
648
1495
  * Invalid base snapshots fail with `ImageError`; invalid volume limits fail
649
1496
  * with `ConfigurationError`. Each execution creates a fresh workspace.
650
1497
  *
1498
+ * @example
1499
+ * ```ts
1500
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
1501
+ * import { Effect } from "effect"
1502
+ *
1503
+ * const program = Effect.gen(function*() {
1504
+ * const base = yield* Vfs.make()
1505
+ * const origin = yield* base.caller()
1506
+ *
1507
+ * yield* origin.writeFile("/config.json", new TextEncoder().encode("{}"), {
1508
+ * access: "write",
1509
+ * create: "exclusive"
1510
+ * })
1511
+ *
1512
+ * const workspace = yield* Vfs.makeOverlay(yield* base.snapshot)
1513
+ *
1514
+ * yield* (yield* workspace.caller()).writeFile("/config.json", new TextEncoder().encode("[]"), {
1515
+ * access: "write",
1516
+ * truncate: true
1517
+ * })
1518
+ *
1519
+ * // The write landed in the workspace only; the base still reads its own value.
1520
+ * return [
1521
+ * new TextDecoder().decode(yield* origin.readFile("/config.json")),
1522
+ * (yield* workspace.changes()).map((change) => change._tag)
1523
+ * ]
1524
+ * })
1525
+ *
1526
+ * Effect.runPromise(program).then(console.log)
1527
+ * // [ '{}', [ 'Updated' ] ]
1528
+ * ```
1529
+ *
1530
+ * @see The overlay guide at `/guides/overlay-filesystems`.
651
1531
  * @category constructors
652
1532
  * @since 0.1.0
653
1533
  */
@@ -655,6 +1535,35 @@ export declare const makeOverlay: (base: Snapshot, options?: VolumeOptions) => E
655
1535
  /**
656
1536
  * Builds a fresh volume from a validated final-state fixture.
657
1537
  *
1538
+ * @example
1539
+ * ```ts
1540
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
1541
+ * import { Effect } from "effect"
1542
+ *
1543
+ * const encoder = new TextEncoder()
1544
+ *
1545
+ * const program = Effect.gen(function*() {
1546
+ * // Parent directories must be listed explicitly, before their children.
1547
+ * const volume = yield* Vfs.fromFixture({
1548
+ * entries: [
1549
+ * { kind: "directory", path: "/project" },
1550
+ * {
1551
+ * kind: "file",
1552
+ * path: "/project/package.json",
1553
+ * bytes: encoder.encode(`{"version":"1.2.3"}`)
1554
+ * },
1555
+ * { kind: "symlink", path: "/project/latest", target: "/project/package.json" }
1556
+ * ]
1557
+ * })
1558
+ *
1559
+ * return yield* (yield* volume.caller()).readDirectory("/project")
1560
+ * })
1561
+ *
1562
+ * Effect.runPromise(program).then(console.log)
1563
+ * // [ 'package.json', 'latest' ]
1564
+ * ```
1565
+ *
1566
+ * @see The fixtures guide at `/guides/fixtures-and-snapshots`.
658
1567
  * @category constructors
659
1568
  * @since 0.1.0
660
1569
  */