@effect-vfs/core 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +36 -149
  2. package/dist/BytePath.d.ts +34 -2
  3. package/dist/BytePath.d.ts.map +1 -1
  4. package/dist/BytePath.js +1 -1
  5. package/dist/LiveVolume.d.ts +101 -0
  6. package/dist/LiveVolume.d.ts.map +1 -0
  7. package/dist/LiveVolume.js +72 -0
  8. package/dist/Snapshot.d.ts +60 -2
  9. package/dist/Snapshot.d.ts.map +1 -1
  10. package/dist/Snapshot.js +59 -1
  11. package/dist/SnapshotDelta.d.ts +113 -3
  12. package/dist/SnapshotDelta.d.ts.map +1 -1
  13. package/dist/SnapshotDelta.js +112 -2
  14. package/dist/VirtualFileSystem.d.ts +1224 -90
  15. package/dist/VirtualFileSystem.d.ts.map +1 -1
  16. package/dist/VirtualFileSystem.js +678 -20
  17. package/dist/VirtualFileSystemError.d.ts +54 -0
  18. package/dist/VirtualFileSystemError.d.ts.map +1 -0
  19. package/dist/VirtualFileSystemError.js +49 -0
  20. package/dist/index.d.ts +15 -1
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +15 -1
  23. package/dist/internal/bytePath.d.ts.map +1 -1
  24. package/dist/internal/bytePath.js +5 -10
  25. package/dist/internal/bytes.d.ts +6 -0
  26. package/dist/internal/bytes.d.ts.map +1 -0
  27. package/dist/internal/bytes.js +17 -0
  28. package/dist/internal/canonicalBase64.d.ts +13 -5
  29. package/dist/internal/canonicalBase64.d.ts.map +1 -1
  30. package/dist/internal/canonicalBase64.js +28 -6
  31. package/dist/internal/image.d.ts +76 -90
  32. package/dist/internal/image.d.ts.map +1 -1
  33. package/dist/internal/image.js +98 -89
  34. package/dist/internal/liveImage.d.ts +286 -0
  35. package/dist/internal/liveImage.d.ts.map +1 -0
  36. package/dist/internal/liveImage.js +176 -0
  37. package/dist/internal/metadata.d.ts +28 -0
  38. package/dist/internal/metadata.d.ts.map +1 -0
  39. package/dist/internal/metadata.js +29 -0
  40. package/dist/internal/snapshotDelta.d.ts.map +1 -1
  41. package/dist/internal/snapshotDelta.js +346 -300
  42. package/dist/internal/snapshotDeltaModel.d.ts +2 -2
  43. package/dist/internal/snapshotDeltaModel.d.ts.map +1 -1
  44. package/dist/internal/snapshotDeltaModel.js +3 -1
  45. package/dist/internal/virtualFileSystem/errors.d.ts +6 -20
  46. package/dist/internal/virtualFileSystem/errors.d.ts.map +1 -1
  47. package/dist/internal/virtualFileSystem/errors.js +22 -7
  48. package/dist/internal/virtualFileSystem/fixture.d.ts +4 -1
  49. package/dist/internal/virtualFileSystem/fixture.d.ts.map +1 -1
  50. package/dist/internal/virtualFileSystem/fixture.js +80 -74
  51. package/dist/internal/virtualFileSystem/overlayContent.d.ts.map +1 -1
  52. package/dist/internal/virtualFileSystem/overlayContent.js +9 -2
  53. package/dist/internal/virtualFileSystem/overlayDiff.d.ts +2 -11
  54. package/dist/internal/virtualFileSystem/overlayDiff.d.ts.map +1 -1
  55. package/dist/internal/virtualFileSystem/overlayDiff.js +45 -71
  56. package/dist/internal/virtualFileSystem/path.d.ts +15 -14
  57. package/dist/internal/virtualFileSystem/path.d.ts.map +1 -1
  58. package/dist/internal/virtualFileSystem/path.js +57 -54
  59. package/dist/internal/virtualFileSystem/stagedState.d.ts +22 -0
  60. package/dist/internal/virtualFileSystem/stagedState.d.ts.map +1 -0
  61. package/dist/internal/virtualFileSystem/stagedState.js +62 -0
  62. package/dist/internal/virtualFileSystem/testHooks.d.ts.map +1 -1
  63. package/dist/internal/virtualFileSystem/watchHub.d.ts +3 -3
  64. package/dist/internal/virtualFileSystem/watchHub.d.ts.map +1 -1
  65. package/dist/internal/virtualFileSystem/watchHub.js +45 -24
  66. package/dist/internal/virtualFileSystem.d.ts +307 -23
  67. package/dist/internal/virtualFileSystem.d.ts.map +1 -1
  68. package/dist/internal/virtualFileSystem.js +1738 -525
  69. package/package.json +23 -5
@@ -8,13 +8,19 @@
8
8
  *
9
9
  * @since 0.1.0
10
10
  */
11
+ import type * as ByteSize from "effect/ByteSize";
12
+ import * as Context from "effect/Context";
13
+ import type * as Crypto from "effect/Crypto";
11
14
  import * as Effect from "effect/Effect";
15
+ import type * as Order from "effect/Order";
16
+ import type * as PlatformError from "effect/PlatformError";
12
17
  import * as Schema from "effect/Schema";
13
18
  import type * as Scope from "effect/Scope";
14
19
  import type * as Stream from "effect/Stream";
15
- import { BytePath } from "./BytePath.js";
20
+ import type { BytePath } from "./BytePath.js";
21
+ import type { ConfigurationError, FsError } from "./VirtualFileSystemError.js";
16
22
  export { BytePath } from "./BytePath.js";
17
- import { DecodeLimits, ImageError, type Snapshot } from "./Snapshot.js";
23
+ import type { DecodeLimits, ImageError, Snapshot } from "./Snapshot.js";
18
24
  export { DecodeLimits, ImageError, type Snapshot, SnapshotTypeId } from "./Snapshot.js";
19
25
  import * as VfsModel from "./internal/virtualFileSystem.js";
20
26
  import * as SnapshotDeltaModel from "./SnapshotDelta.js";
@@ -27,6 +33,12 @@ declare const ObjectReferenceId: typeof VfsModel.ObjectReferenceId;
27
33
  /**
28
34
  * A UTF-8 string path or an opaque byte-preserving path.
29
35
  *
36
+ * **Details**
37
+ *
38
+ * Every operation accepts both. Pass a string unless a name is not valid UTF-8;
39
+ * strings are the normal case and byte paths are the escape hatch.
40
+ *
41
+ * @see {@link BytePath} for the byte-preserving form.
30
42
  * @category models
31
43
  * @since 0.1.0
32
44
  */
@@ -34,6 +46,30 @@ export type PathInput = string | BytePath;
34
46
  /**
35
47
  * Schema for portable virtual filesystem error codes.
36
48
  *
49
+ * @example
50
+ * ```ts
51
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
52
+ * import { Effect, Schema } from "effect"
53
+ *
54
+ * const asCode = Schema.decodeUnknownEffect(Vfs.FsCode)
55
+ *
56
+ * // Useful when a code has crossed a process boundary and arrives back as data.
57
+ * const program = Effect.gen(function*() {
58
+ * const payload = JSON.parse(`{"code":"NotFound","other":"Nonsense"}`)
59
+ *
60
+ * const known = yield* asCode(payload.code)
61
+ *
62
+ * const unknown = yield* asCode(payload.other).pipe(
63
+ * Effect.orElseSucceed(() => "not a code")
64
+ * )
65
+ *
66
+ * return [known, unknown]
67
+ * })
68
+ *
69
+ * Effect.runPromise(program).then(console.log)
70
+ * // [ 'NotFound', 'not a code' ]
71
+ * ```
72
+ *
37
73
  * @category schemas
38
74
  * @since 0.1.0
39
75
  */
@@ -48,23 +84,65 @@ export type FsCode = typeof FsCode.Type;
48
84
  /**
49
85
  * Describes an expected filesystem operation failure.
50
86
  *
87
+ * Durable providers distinguish `StorageRejected` (the operation definitely did not commit),
88
+ * `OutcomeUnknown` (it may have committed), and `VolumeUnavailable` (the provider cannot safely
89
+ * serve further operations until recovery). Built-in memory volumes never emit these codes.
90
+ *
91
+ * @example
92
+ * ```ts
93
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
94
+ * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
95
+ * import { Effect } from "effect"
96
+ *
97
+ * const program = Effect.gen(function*() {
98
+ * const caller = yield* (yield* Vfs.make()).caller()
99
+ *
100
+ * // Every filesystem operation fails with this one tagged error; `code`
101
+ * // distinguishes the cases, so match on it rather than on the tag alone.
102
+ * return yield* caller.readFile("/missing").pipe(
103
+ * Effect.catchTag("FsError", (error) =>
104
+ * error.code === "NotFound"
105
+ * ? Effect.succeed(new Uint8Array())
106
+ * : Effect.fail(error))
107
+ * )
108
+ * })
109
+ *
110
+ * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
111
+ * // Uint8Array(0) []
112
+ * ```
113
+ *
51
114
  * @category errors
52
115
  * @since 0.1.0
53
116
  */
54
- export declare const FsError: typeof VfsModel.FsError;
55
- /** @internal */
56
- export interface FsError extends VfsModel.FsError {
57
- }
117
+ export { FsError } from "./VirtualFileSystemError.js";
58
118
  /**
59
119
  * Describes an invalid volume or caller option and names the rejected field.
60
120
  *
121
+ * @example
122
+ * ```ts
123
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
124
+ * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
125
+ * import { Effect } from "effect"
126
+ *
127
+ * // `field` names the rejected option, so the caller learns which key was wrong.
128
+ * const program = Effect.gen(function*() {
129
+ * // Options that arrived from a config file or CLI flag, not a literal.
130
+ * const options = JSON.parse(`{"maxEntries":-1}`) as Vfs.VolumeOptions
131
+ *
132
+ * return yield* Vfs.make(options).pipe(
133
+ * Effect.as("ok"),
134
+ * Effect.catchTag("ConfigurationError", (error) => Effect.succeed(`rejected ${error.field}`))
135
+ * )
136
+ * })
137
+ *
138
+ * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
139
+ * // rejected maxEntries
140
+ * ```
141
+ *
61
142
  * @category errors
62
143
  * @since 0.1.0
63
144
  */
64
- export declare const ConfigurationError: typeof VfsModel.ConfigurationError;
65
- /** @internal */
66
- export interface ConfigurationError extends VfsModel.ConfigurationError {
67
- }
145
+ export { ConfigurationError } from "./VirtualFileSystemError.js";
68
146
  /**
69
147
  * Schema for a caller's numeric identity, supplementary groups, and explicit privilege.
70
148
  *
@@ -82,6 +160,34 @@ export type Identity = typeof Identity.Type;
82
160
  /**
83
161
  * Schema for root caller credentials and creation mask.
84
162
  *
163
+ * @example
164
+ * ```ts
165
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
166
+ * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
167
+ * import { Effect } from "effect"
168
+ *
169
+ * const program = Effect.gen(function*() {
170
+ * const volume = yield* Vfs.make()
171
+ *
172
+ * // Omitting `identity` yields a privileged root caller.
173
+ * const root = yield* volume.caller({ umask: 0 })
174
+ * const user = yield* volume.caller({
175
+ * identity: { uid: 1000, gid: 1000, groups: [], privileged: false },
176
+ * umask: 0o022
177
+ * })
178
+ *
179
+ * yield* root.mkdir("/home", { mode: 0o777 })
180
+ *
181
+ * // The umask clears its bits from the requested mode.
182
+ * yield* user.mkdir("/home/user", { mode: 0o777 })
183
+ *
184
+ * return ((yield* user.stat("/home/user")).mode & 0o777).toString(8)
185
+ * })
186
+ *
187
+ * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
188
+ * // 755
189
+ * ```
190
+ *
85
191
  * @category schemas
86
192
  * @since 0.1.0
87
193
  */
@@ -93,23 +199,156 @@ export declare const RootCallerOptions: typeof VfsModel.RootCallerOptions;
93
199
  * @since 0.1.0
94
200
  */
95
201
  export type RootCallerOptions = typeof RootCallerOptions.Type;
202
+ /**
203
+ * Schema for the failure boundary survived by an acknowledged volume write.
204
+ *
205
+ * @category schemas
206
+ * @since 0.1.0
207
+ */
208
+ export declare const VolumeDurability: typeof VfsModel.VolumeDurability;
209
+ /**
210
+ * The failure boundary survived by an acknowledged volume write.
211
+ *
212
+ * @category models
213
+ * @since 0.1.0
214
+ */
215
+ export type VolumeDurability = typeof VolumeDurability.Type;
216
+ /**
217
+ * Weakest-to-strongest ordering for volume durability guarantees.
218
+ *
219
+ * @category ordering
220
+ * @since 0.1.0
221
+ */
222
+ export declare const VolumeDurabilityOrder: Order.Order<VolumeDurability>;
223
+ /**
224
+ * Returns whether `actual` survives at least the failure boundary required by `required`.
225
+ *
226
+ * @category predicates
227
+ * @since 0.1.0
228
+ */
229
+ export declare const isVolumeDurabilityAtLeast: (actual: VolumeDurability, required: VolumeDurability) => boolean;
230
+ /**
231
+ * Schema for the stable logical identity of a volume.
232
+ *
233
+ * @category schemas
234
+ * @since 0.1.0
235
+ */
236
+ export declare const VolumeIdentity: typeof VfsModel.VolumeIdentity;
237
+ /**
238
+ * Stable logical identity of a volume as a branded 128-bit lowercase hexadecimal string.
239
+ *
240
+ * @category models
241
+ * @since 0.1.0
242
+ */
243
+ export type VolumeIdentity = typeof VolumeIdentity.Type;
244
+ /**
245
+ * Schema for one uninterrupted storage lifetime of a volume.
246
+ *
247
+ * @category schemas
248
+ * @since 0.1.0
249
+ */
250
+ export declare const VolumeIncarnation: typeof VfsModel.VolumeIncarnation;
251
+ /**
252
+ * One uninterrupted storage lifetime as a branded 128-bit lowercase hexadecimal string.
253
+ *
254
+ * @category models
255
+ * @since 0.1.0
256
+ */
257
+ export type VolumeIncarnation = typeof VolumeIncarnation.Type;
96
258
  /**
97
259
  * Schema for optional volume capacity and path limits.
98
260
  *
261
+ * @example
262
+ * ```ts
263
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
264
+ * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
265
+ * import { ByteSize, Effect } from "effect"
266
+ *
267
+ * // Every limit is optional, and each one is enforced once set.
268
+ * const program = Effect.gen(function*() {
269
+ * const volume = yield* Vfs.make({ maxFileBytes: ByteSize.bytes(4) })
270
+ * const caller = yield* volume.caller()
271
+ *
272
+ * return yield* caller.writeFile("/big.bin", new Uint8Array(16), {
273
+ * access: "write",
274
+ * create: "exclusive"
275
+ * }).pipe(
276
+ * Effect.as("written"),
277
+ * Effect.catchTag("FsError", (error) => Effect.succeed(error.code))
278
+ * )
279
+ * })
280
+ *
281
+ * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
282
+ * // FileTooLarge
283
+ * ```
284
+ *
99
285
  * @category schemas
100
286
  * @since 0.1.0
101
287
  */
102
288
  export declare const VolumeOptions: typeof VfsModel.VolumeOptions;
103
289
  /**
104
- * Capacity and path limits for a volume.
290
+ * Capacity, admission, watch retention, and path limits for a volume.
291
+ * `maxPendingOperations` defaults to 64 and `maxWatchEvents` defaults to 256.
105
292
  *
106
293
  * @category models
107
294
  * @since 0.1.0
108
295
  */
109
296
  export type VolumeOptions = typeof VolumeOptions.Type;
297
+ /**
298
+ * Effective capacity and path limits of a live volume. An absent limit is unlimited.
299
+ * `maxFileBytes` always includes the engine's maximum representable file size.
300
+ *
301
+ * @category models
302
+ * @since 0.1.0
303
+ */
304
+ export interface VolumeLimits {
305
+ readonly maxBytes: ByteSize.ByteSize | undefined;
306
+ readonly maxFileBytes: ByteSize.ByteSize;
307
+ readonly maxEntries: number | undefined;
308
+ readonly maxPathBytes: ByteSize.ByteSize | undefined;
309
+ /** Waiting budget; at most this many plus one callers are admitted at once. */
310
+ readonly maxPendingOperations: number;
311
+ /** Maximum retained events per watch subscriber, including the rescan marker. */
312
+ readonly maxWatchEvents: number;
313
+ }
314
+ /**
315
+ * Live logical content and directory-entry usage of a volume.
316
+ * Open unlinked files remain in `usedBytes` until their last handle closes.
317
+ *
318
+ * @category models
319
+ * @since 0.1.0
320
+ */
321
+ export interface VolumeUsage {
322
+ readonly usedBytes: bigint;
323
+ readonly entries: number;
324
+ }
110
325
  /**
111
326
  * Schema for filesystem node metadata with bigint inode, size, and nanosecond fields.
112
327
  *
328
+ * @example
329
+ * ```ts
330
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
331
+ * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
332
+ * import { Effect } from "effect"
333
+ *
334
+ * // Sizes and inode numbers are bigint; times are nanosecond timestamps.
335
+ * const program = Effect.gen(function*() {
336
+ * const caller = yield* (yield* Vfs.make()).caller()
337
+ *
338
+ * yield* caller.writeFile("/f", new Uint8Array([1, 2, 3]), {
339
+ * access: "write",
340
+ * create: "exclusive"
341
+ * })
342
+ *
343
+ * const metadata = yield* caller.stat("/f")
344
+ *
345
+ * return [metadata.kind, metadata.size, metadata.nlink]
346
+ * })
347
+ *
348
+ * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
349
+ * // [ 'file', 3n, 1 ]
350
+ * ```
351
+ *
113
352
  * @category schemas
114
353
  * @since 0.1.0
115
354
  */
@@ -133,6 +372,35 @@ export interface ObjectReference {
133
372
  /**
134
373
  * A value and the revision of the object from the same coordinated observation.
135
374
  *
375
+ * @example
376
+ * ```ts
377
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
378
+ * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
379
+ * import { Effect } from "effect"
380
+ *
381
+ * // References survive renames, and each observation carries the revision it
382
+ * // matched, so a caller can tell whether what it read is still current.
383
+ * const program = Effect.gen(function*() {
384
+ * const caller = yield* (yield* Vfs.make()).caller()
385
+ *
386
+ * yield* caller.mkdir("/work")
387
+ *
388
+ * const root = yield* caller.rootReference
389
+ * const work = yield* caller.lookupReference(root, new TextEncoder().encode("work"))
390
+ *
391
+ * const before = yield* caller.observeMetadata(work)
392
+ *
393
+ * yield* caller.rename("/work", "/moved")
394
+ *
395
+ * const after = yield* caller.observeMetadata(work)
396
+ *
397
+ * return [before.value.kind, after.value.kind]
398
+ * })
399
+ *
400
+ * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
401
+ * // [ 'directory', 'directory' ]
402
+ * ```
403
+ *
136
404
  * @category models
137
405
  * @since 0.1.0
138
406
  */
@@ -150,6 +418,44 @@ export interface DirectoryEntry {
150
418
  readonly name: Uint8Array;
151
419
  readonly reference: ObjectReference;
152
420
  }
421
+ /**
422
+ * A directory revision transition captured inside one coordinated operation.
423
+ *
424
+ * @category schemas
425
+ * @since 0.4.0
426
+ */
427
+ export declare const DirectoryChange: typeof VfsModel.DirectoryChange;
428
+ /**
429
+ * A directory revision transition captured inside one coordinated operation.
430
+ *
431
+ * @category models
432
+ * @since 0.4.0
433
+ */
434
+ export type DirectoryChange = typeof DirectoryChange.Type;
435
+ /**
436
+ * The identity and parent-directory transition produced by a named entry mutation.
437
+ *
438
+ * @category models
439
+ * @since 0.4.0
440
+ */
441
+ export interface ReferenceEntryResult {
442
+ readonly reference: ObjectReference;
443
+ readonly directory: DirectoryChange;
444
+ }
445
+ /**
446
+ * The directory transitions produced by a same-directory or cross-directory rename.
447
+ *
448
+ * @category schemas
449
+ * @since 0.4.0
450
+ */
451
+ export declare const RenameReferenceResult: typeof VfsModel.RenameReferenceResult;
452
+ /**
453
+ * The directory transitions produced by a same-directory or cross-directory rename.
454
+ *
455
+ * @category models
456
+ * @since 0.4.0
457
+ */
458
+ export type RenameReferenceResult = typeof RenameReferenceResult.Type;
153
459
  /**
154
460
  * Resolves a relative path from a live directory handle instead of the caller's directory.
155
461
  *
@@ -163,6 +469,37 @@ export interface RelativeOptions {
163
469
  /**
164
470
  * Controls the base directory and whether metadata operations follow the final symbolic link.
165
471
  *
472
+ * @example
473
+ * ```ts
474
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
475
+ * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
476
+ * import { Effect } from "effect"
477
+ *
478
+ * const program = Effect.gen(function*() {
479
+ * const caller = yield* (yield* Vfs.make()).caller()
480
+ *
481
+ * yield* caller.writeFile("/target", new Uint8Array([1]), {
482
+ * access: "write",
483
+ * create: "exclusive",
484
+ * mode: 0o644
485
+ * })
486
+ * yield* caller.symlink("/target", "/link")
487
+ *
488
+ * // Metadata operations follow the final link by default, so this reaches the
489
+ * // target; opting out changes the link itself and leaves the target alone.
490
+ * yield* caller.chmod("/link", 0o600)
491
+ * const followed = (yield* caller.stat("/target")).mode & 0o777
492
+ *
493
+ * yield* caller.chmod("/link", 0o777, { followFinalSymlink: false })
494
+ * const unchanged = (yield* caller.stat("/target")).mode & 0o777
495
+ *
496
+ * return [followed.toString(8), unchanged.toString(8)]
497
+ * })
498
+ *
499
+ * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
500
+ * // [ '600', '600' ]
501
+ * ```
502
+ *
166
503
  * @category models
167
504
  * @since 0.1.0
168
505
  */
@@ -194,6 +531,35 @@ export declare const TimeUpdate: typeof VfsModel.TimeUpdate;
194
531
  /**
195
532
  * Schema for independent access and modification time updates.
196
533
  *
534
+ * @example
535
+ * ```ts
536
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
537
+ * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
538
+ * import { Effect } from "effect"
539
+ *
540
+ * const program = Effect.gen(function*() {
541
+ * const caller = yield* (yield* Vfs.make()).caller()
542
+ *
543
+ * yield* caller.writeFile("/f", new Uint8Array([1]), {
544
+ * access: "write",
545
+ * create: "exclusive"
546
+ * })
547
+ *
548
+ * // Each field is set to a value, to the current clock, or left alone.
549
+ * yield* caller.utimes("/f", {
550
+ * access: { kind: "value", nanoseconds: 1_000n },
551
+ * modification: { kind: "omit" }
552
+ * })
553
+ *
554
+ * const metadata = yield* caller.stat("/f")
555
+ *
556
+ * return [metadata.atimeNs, metadata.mtimeNs === 1_000n]
557
+ * })
558
+ *
559
+ * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
560
+ * // [ 1000n, false ]
561
+ * ```
562
+ *
197
563
  * @category schemas
198
564
  * @since 0.1.0
199
565
  */
@@ -208,6 +574,35 @@ export type Times = typeof Times.Type;
208
574
  /**
209
575
  * A scoped directory capability that can be used for metadata and relative lookup.
210
576
  *
577
+ * @example
578
+ * ```ts
579
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
580
+ * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
581
+ * import { Effect } from "effect"
582
+ *
583
+ * // The handle anchors relative paths to the directory itself, so a rename in
584
+ * // between does not send the write somewhere else.
585
+ * const program = Effect.gen(function*() {
586
+ * const caller = yield* (yield* Vfs.make()).caller()
587
+ *
588
+ * yield* caller.mkdir("/project")
589
+ *
590
+ * const directory = yield* caller.openDirectory("/project")
591
+ *
592
+ * yield* caller.rename("/project", "/renamed")
593
+ * yield* caller.writeFile("notes.txt", new TextEncoder().encode("kept"), {
594
+ * access: "write",
595
+ * create: "exclusive",
596
+ * relativeTo: directory
597
+ * })
598
+ *
599
+ * return yield* caller.readDirectory("/renamed")
600
+ * }).pipe(Effect.scoped)
601
+ *
602
+ * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
603
+ * // [ 'notes.txt' ]
604
+ * ```
605
+ *
211
606
  * @category models
212
607
  * @since 0.1.0
213
608
  */
@@ -233,6 +628,31 @@ export type SeekMode = typeof SeekMode.Type;
233
628
  /**
234
629
  * Schema for file access, creation, append, truncate, and symlink behavior.
235
630
  *
631
+ * @example
632
+ * ```ts
633
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
634
+ * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
635
+ * import { Effect } from "effect"
636
+ *
637
+ * // `access` is required; `create` decides whether a missing file is an error.
638
+ * const program = Effect.gen(function*() {
639
+ * const caller = yield* (yield* Vfs.make()).caller()
640
+ *
641
+ * const handle = yield* caller.open("/log.txt", {
642
+ * access: "readWrite",
643
+ * create: "ifMissing",
644
+ * append: true
645
+ * })
646
+ *
647
+ * yield* handle.write(new Uint8Array([1, 2]))
648
+ *
649
+ * return (yield* handle.stat).size
650
+ * }).pipe(Effect.scoped)
651
+ *
652
+ * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
653
+ * // 2n
654
+ * ```
655
+ *
236
656
  * @category schemas
237
657
  * @since 0.1.0
238
658
  */
@@ -244,9 +664,121 @@ export declare const OpenSettings: typeof VfsModel.OpenSettings;
244
664
  * @since 0.1.0
245
665
  */
246
666
  export type OpenOptions = typeof OpenSettings.Type & RelativeOptions;
667
+ /**
668
+ * Settings for creating a directory through a parent object reference.
669
+ * `exactMode` skips the caller's umask for an explicit creation mode; permission policy still applies.
670
+ *
671
+ * @category schemas
672
+ * @since 0.4.0
673
+ */
674
+ export declare const MkdirReferenceSettings: typeof VfsModel.MkdirReferenceSettings;
675
+ /**
676
+ * Settings for creating a directory through a parent object reference.
677
+ * `exactMode` skips the caller's umask for an explicit creation mode; permission policy still applies.
678
+ *
679
+ * @category models
680
+ * @since 0.4.0
681
+ */
682
+ export type MkdirReferenceSettings = typeof MkdirReferenceSettings.Type;
683
+ /**
684
+ * Settings for creating a symbolic link through a parent object reference.
685
+ *
686
+ * @category schemas
687
+ * @since 0.4.0
688
+ */
689
+ export declare const SymlinkReferenceSettings: typeof VfsModel.SymlinkReferenceSettings;
690
+ /**
691
+ * Settings for creating a symbolic link through a parent object reference.
692
+ *
693
+ * @category models
694
+ * @since 0.4.0
695
+ */
696
+ export type SymlinkReferenceSettings = typeof SymlinkReferenceSettings.Type;
697
+ /**
698
+ * Settings for opening an existing regular-file reference.
699
+ *
700
+ * @category schemas
701
+ * @since 0.4.0
702
+ */
703
+ export declare const OpenReferenceSettings: typeof VfsModel.OpenReferenceSettings;
704
+ /**
705
+ * Settings for opening an existing regular-file reference.
706
+ *
707
+ * @category models
708
+ * @since 0.4.0
709
+ */
710
+ export type OpenReferenceSettings = typeof OpenReferenceSettings.Type;
711
+ /**
712
+ * Settings for atomically looking up or creating and opening one referenced child.
713
+ * `exactMode` skips the caller's umask for an explicit creation mode; permission policy still applies.
714
+ *
715
+ * @category schemas
716
+ * @since 0.4.0
717
+ */
718
+ export declare const OpenChildReferenceSettings: typeof VfsModel.OpenChildReferenceSettings;
719
+ /**
720
+ * Settings for atomically looking up or creating and opening one referenced child.
721
+ * `exactMode` skips the caller's umask for an explicit creation mode; permission policy still applies.
722
+ * Initial mode, times, `initialSize`, and owner apply only to a newly created file.
723
+ * `expectedChild: null` requires an absent direct child. An observation requires
724
+ * the same direct object, revision, and timestamps; a mismatch fails with
725
+ * `StaleReference` before changing the file. Omission skips this observation check.
726
+ * This condition checks the named entry before any final symbolic-link traversal.
727
+ *
728
+ * @category models
729
+ * @since 0.4.0
730
+ */
731
+ export type OpenChildReferenceSettings = typeof OpenChildReferenceSettings.Type;
732
+ /**
733
+ * A scoped child-open result captured inside one coordinated operation.
734
+ *
735
+ * @category models
736
+ * @since 0.4.0
737
+ */
738
+ export interface OpenChildReferenceResult {
739
+ readonly handle: FileHandle;
740
+ readonly reference: ObjectReference;
741
+ readonly created: boolean;
742
+ readonly directory: DirectoryChange;
743
+ }
247
744
  /**
248
745
  * Options for an atomic whole-file write, including replacement and final mode controls.
249
746
  *
747
+ * @example
748
+ * ```ts
749
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
750
+ * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
751
+ * import { Effect } from "effect"
752
+ *
753
+ * const program = Effect.gen(function*() {
754
+ * const caller = yield* (yield* Vfs.make()).caller()
755
+ * const bytes = new TextEncoder().encode("v2")
756
+ *
757
+ * // `access` is required. `create: "exclusive"` refuses to replace an entry,
758
+ * // while `truncate` overwrites one that already exists.
759
+ * yield* caller.writeFile("/app.conf", new TextEncoder().encode("v1"), {
760
+ * access: "write",
761
+ * create: "exclusive",
762
+ * mode: 0o640
763
+ * })
764
+ *
765
+ * const refused = yield* caller.writeFile("/app.conf", bytes, {
766
+ * access: "write",
767
+ * create: "exclusive"
768
+ * }).pipe(
769
+ * Effect.as("replaced"),
770
+ * Effect.catchTag("FsError", (error) => Effect.succeed(error.code))
771
+ * )
772
+ *
773
+ * yield* caller.writeFile("/app.conf", bytes, { access: "write", truncate: true })
774
+ *
775
+ * return [refused, new TextDecoder().decode(yield* caller.readFile("/app.conf"))]
776
+ * })
777
+ *
778
+ * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
779
+ * // [ 'AlreadyExists', 'v2' ]
780
+ * ```
781
+ *
250
782
  * @category models
251
783
  * @since 0.1.0
252
784
  */
@@ -254,6 +786,32 @@ export type WriteFileOptions = VfsModel.WriteFileOptions;
254
786
  /**
255
787
  * A scoped regular-file capability with an independent bigint cursor.
256
788
  *
789
+ * @example
790
+ * ```ts
791
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
792
+ * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
793
+ * import { Effect } from "effect"
794
+ *
795
+ * const program = Effect.gen(function*() {
796
+ * const caller = yield* (yield* Vfs.make()).caller()
797
+ *
798
+ * const handle = yield* caller.open("/data.bin", {
799
+ * access: "readWrite",
800
+ * create: "exclusive"
801
+ * })
802
+ *
803
+ * yield* handle.write(new Uint8Array([1, 2, 3, 4]))
804
+ *
805
+ * // `pread` reads at an offset and leaves the cursor where writing left it.
806
+ * const slice = yield* handle.pread(2, 1n)
807
+ *
808
+ * return [slice, yield* handle.seek(0n, "current")]
809
+ * }).pipe(Effect.scoped)
810
+ *
811
+ * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
812
+ * // [ Uint8Array(2) [ 2, 3 ], 4n ]
813
+ * ```
814
+ *
257
815
  * @category models
258
816
  * @since 0.1.0
259
817
  */
@@ -273,7 +831,7 @@ export interface FileHandle {
273
831
  readonly truncate: (length: bigint) => Effect.Effect<void, FsError>;
274
832
  /** Reads metadata for the open file. */
275
833
  readonly stat: Effect.Effect<Metadata, FsError>;
276
- /** Checks handle liveness. In-memory storage has no host or crash durability to flush. */
834
+ /** Checks handle and provider health. Durable mutations have already committed; memory volumes have nothing to flush. */
277
835
  readonly sync: Effect.Effect<void, FsError>;
278
836
  /** Closes the handle. A repeated explicit close fails; scope cleanup remains safe. */
279
837
  readonly close: Effect.Effect<void, FsError>;
@@ -281,6 +839,87 @@ export interface FileHandle {
281
839
  /**
282
840
  * A filesystem caller with its own identity, creation mask, and current directory.
283
841
  *
842
+ * @example
843
+ * ```ts
844
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
845
+ * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
846
+ * import { Effect } from "effect"
847
+ *
848
+ * // Whole-file reads and writes need no scope. `writeFile` always takes an
849
+ * // options bag; `access` is required and `create` decides what a missing file means.
850
+ * const program = Effect.gen(function*() {
851
+ * const volume = yield* Vfs.make()
852
+ * const caller = yield* volume.caller()
853
+ *
854
+ * yield* caller.mkdir("/work")
855
+ * yield* caller.writeFile("/work/notes.txt", new TextEncoder().encode("hello"), {
856
+ * access: "write",
857
+ * create: "exclusive"
858
+ * })
859
+ *
860
+ * return yield* caller.readDirectory("/work")
861
+ * })
862
+ *
863
+ * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
864
+ * // [ 'notes.txt' ]
865
+ * ```
866
+ *
867
+ * @example
868
+ * ```ts
869
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
870
+ * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
871
+ * import { Effect } from "effect"
872
+ *
873
+ * // Reach for a handle when a whole-file write will not do. A handle carries a
874
+ * // cursor and lives until its scope closes.
875
+ * const program = Effect.gen(function*() {
876
+ * const caller = yield* (yield* Vfs.make()).caller()
877
+ *
878
+ * const handle = yield* caller.open("/log.txt", {
879
+ * access: "readWrite",
880
+ * create: "exclusive"
881
+ * })
882
+ *
883
+ * yield* handle.write(new TextEncoder().encode("first "))
884
+ * yield* handle.write(new TextEncoder().encode("second"))
885
+ * yield* handle.seek(0n, "start")
886
+ *
887
+ * return new TextDecoder().decode(yield* handle.read(64))
888
+ * }).pipe(Effect.scoped)
889
+ *
890
+ * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
891
+ * // first second
892
+ * ```
893
+ *
894
+ * @example
895
+ * ```ts
896
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
897
+ * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
898
+ * import { Effect } from "effect"
899
+ *
900
+ * // A derived caller holds the directory's identity, not its path, so renaming
901
+ * // the directory does not redirect later relative writes.
902
+ * const program = Effect.gen(function*() {
903
+ * const caller = yield* (yield* Vfs.make()).caller()
904
+ *
905
+ * yield* caller.mkdir("/build")
906
+ *
907
+ * const work = yield* caller.withDirectory("/build")
908
+ *
909
+ * yield* caller.rename("/build", "/dist")
910
+ * yield* work.writeFile("out.txt", new TextEncoder().encode("done"), {
911
+ * access: "write",
912
+ * create: "exclusive"
913
+ * })
914
+ *
915
+ * return yield* caller.readDirectory("/dist")
916
+ * }).pipe(Effect.scoped)
917
+ *
918
+ * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
919
+ * // [ 'out.txt' ]
920
+ * ```
921
+ *
922
+ * @see The filesystem model at `/concepts/filesystem-model`.
284
923
  * @category models
285
924
  * @since 0.1.0
286
925
  */
@@ -292,14 +931,40 @@ export interface Caller {
292
931
  readonly lookupReference: (directory: ObjectReference, name: Uint8Array) => Effect.Effect<ObjectReference, FsError>;
293
932
  /** Returns a referenced directory's current parent. The root is its own parent. */
294
933
  readonly parentReference: (directory: ObjectReference) => Effect.Effect<ObjectReference, FsError>;
295
- /** Reads metadata and its matching live revision. */
934
+ /** Reads metadata and its matching live revision. Requires no permission on the object, as `stat` does not. */
296
935
  readonly observeMetadata: (reference: ObjectReference) => Effect.Effect<ObjectObservation<Metadata>, FsError>;
936
+ /** Checks permission bits on the exact referenced object without resolving a path. */
937
+ readonly accessReference: (reference: ObjectReference, bits?: number) => Effect.Effect<void, FsError>;
297
938
  /** Reads owned directory entries and their matching directory revision. */
298
939
  readonly observeDirectory: (reference: ObjectReference) => Effect.Effect<ObjectObservation<ReadonlyArray<DirectoryEntry>>, FsError>;
299
- /** Reads an owned symbolic-link target through a stable reference. */
940
+ /** Reads an owned symbolic-link target through a stable reference. Requires no permission on the link. */
300
941
  readonly readLinkReference: (reference: ObjectReference) => Effect.Effect<Uint8Array, FsError>;
301
- /** Opens a referenced regular file for reading. */
302
- readonly openReference: (reference: ObjectReference) => Effect.Effect<FileHandle, FsError, Scope.Scope>;
942
+ /** Creates one directory from a referenced parent and returns its exact identity and directory transition. */
943
+ readonly mkdirReference: (directory: ObjectReference, name: Uint8Array, settings?: MkdirReferenceSettings) => Effect.Effect<ReferenceEntryResult, FsError>;
944
+ /** Creates one symbolic link from a referenced parent and returns its exact identity and directory transition. */
945
+ readonly symlinkReference: (target: PathInput, directory: ObjectReference, name: Uint8Array, settings?: SymlinkReferenceSettings) => Effect.Effect<ReferenceEntryResult, FsError>;
946
+ /** Creates another name for the exact source object; symbolic links are not followed. */
947
+ readonly linkReference: (source: ObjectReference, destinationDirectory: ObjectReference, destinationName: Uint8Array) => Effect.Effect<ReferenceEntryResult, FsError>;
948
+ /** Removes a non-directory child and returns the parent directory transition. */
949
+ readonly unlinkReference: (directory: ObjectReference, name: Uint8Array) => Effect.Effect<DirectoryChange, FsError>;
950
+ /** Removes an empty directory child and returns the parent directory transition. */
951
+ readonly rmdirReference: (directory: ObjectReference, name: Uint8Array) => Effect.Effect<DirectoryChange, FsError>;
952
+ /** Removes a child of either type in one coordinated mutation and returns the parent directory transition. */
953
+ readonly removeReference: (directory: ObjectReference, name: Uint8Array) => Effect.Effect<DirectoryChange, FsError>;
954
+ /** Renames one referenced child and returns one transition per distinct parent directory. */
955
+ readonly renameReference: (sourceDirectory: ObjectReference, sourceName: Uint8Array, destinationDirectory: ObjectReference, destinationName: Uint8Array) => Effect.Effect<RenameReferenceResult, FsError>;
956
+ /** Changes permission bits on the exact referenced object. */
957
+ readonly chmodReference: (reference: ObjectReference, mode: number) => Effect.Effect<void, FsError>;
958
+ /** Changes ownership on the exact referenced object. */
959
+ readonly chownReference: (reference: ObjectReference, owner: OwnerUpdate) => Effect.Effect<void, FsError>;
960
+ /** Changes timestamps on the exact referenced object. */
961
+ readonly utimesReference: (reference: ObjectReference, times: Times) => Effect.Effect<void, FsError>;
962
+ /** Changes the length of the exact referenced regular file. */
963
+ readonly truncateReference: (reference: ObjectReference, length: bigint) => Effect.Effect<void, FsError>;
964
+ /** Opens an existing referenced regular file. Omitted settings preserve read-only behavior. */
965
+ readonly openReference: (reference: ObjectReference, settings?: OpenReferenceSettings) => Effect.Effect<FileHandle, FsError, Scope.Scope>;
966
+ /** Atomically looks up or creates and opens one child; `expected` guards its current identity, with `null` meaning absent. */
967
+ readonly openChildReference: (directory: ObjectReference, name: Uint8Array, settings: OpenChildReferenceSettings, expected?: ObjectReference | null) => Effect.Effect<OpenChildReferenceResult, FsError, Scope.Scope>;
303
968
  /** Reads metadata, following the final symbolic link by default. */
304
969
  readonly stat: (path: PathInput, options?: RelativeOptions) => Effect.Effect<Metadata, FsError>;
305
970
  /** Atomically moves an entry within this volume without replacing a non-empty directory. */
@@ -337,15 +1002,15 @@ export interface Caller {
337
1002
  }) => Effect.Effect<void, FsError>;
338
1003
  /** Creates a symbolic link. The target bytes are stored without resolving them. */
339
1004
  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. */
1005
+ /** Reads a symbolic-link target as UTF-8, failing `UnrepresentableName` for other bytes. See `readLinkBytes`. */
341
1006
  readonly readLink: (path: PathInput, options?: RelativeOptions) => Effect.Effect<string, FsError>;
342
1007
  /** Reads a symbolic-link target as owned bytes. */
343
1008
  readonly readLinkBytes: (path: PathInput, options?: RelativeOptions) => Effect.Effect<Uint8Array, FsError>;
344
- /** Reads directory names as UTF-8, failing if any name is not representable. */
1009
+ /** Reads directory names as UTF-8, failing `UnrepresentableName` for other bytes. See `readDirectoryBytes`. */
345
1010
  readonly readDirectory: (path: PathInput, options?: RelativeOptions) => Effect.Effect<ReadonlyArray<string>, FsError>;
346
1011
  /** Reads directory names as owned byte arrays. */
347
1012
  readonly readDirectoryBytes: (path: PathInput, options?: RelativeOptions) => Effect.Effect<ReadonlyArray<Uint8Array>, FsError>;
348
- /** Resolves links and normalizes a path as UTF-8. */
1013
+ /** Resolves links and normalizes a path as UTF-8, failing `UnrepresentableName` for other bytes. See `realPathBytes`. */
349
1014
  readonly realPath: (path: PathInput, options?: RelativeOptions) => Effect.Effect<string, FsError>;
350
1015
  /** Resolves links and normalizes a path without requiring UTF-8 names. */
351
1016
  readonly realPathBytes: (path: PathInput, options?: RelativeOptions) => Effect.Effect<BytePath, FsError>;
@@ -367,13 +1032,45 @@ export interface Caller {
367
1032
  /**
368
1033
  * A committed namespace or content change emitted by a volume watch stream.
369
1034
  *
1035
+ * @example
1036
+ * ```ts
1037
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
1038
+ * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
1039
+ * import { Effect, Fiber, Stream } from "effect"
1040
+ *
1041
+ * const program = Effect.gen(function*() {
1042
+ * const volume = yield* Vfs.make()
1043
+ * const caller = yield* volume.caller()
1044
+ *
1045
+ * const watcher = yield* volume.watch.pipe(
1046
+ * Effect.flatMap((stream) => Stream.runCollect(Stream.take(stream, 2))),
1047
+ * Effect.forkChild({ startImmediately: true })
1048
+ * )
1049
+ *
1050
+ * yield* caller.mkdir("/logs")
1051
+ * yield* caller.rmdir("/logs")
1052
+ *
1053
+ * // Each event pairs the kind of change with the absolute path it happened to.
1054
+ * const events = yield* Fiber.join(watcher)
1055
+ *
1056
+ * return yield* Effect.forEach(events, (change) =>
1057
+ * Effect.map(
1058
+ * Vfs.pathToBytes(change.path),
1059
+ * (bytes) => `${change._tag} ${new TextDecoder().decode(bytes)}`
1060
+ * ))
1061
+ * }).pipe(Effect.scoped)
1062
+ *
1063
+ * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
1064
+ * // [ 'Create /logs', 'Remove /logs' ]
1065
+ * ```
1066
+ *
370
1067
  * @category models
371
1068
  * @since 0.1.0
372
1069
  */
373
1070
  export interface Change {
374
- /** Kind of committed namespace or content change. */
375
- readonly _tag: "Create" | "Update" | "Remove";
376
- /** Absolute path of the changed entry. */
1071
+ /** Kind of committed change. `Rescan` means this subscriber lost events. */
1072
+ readonly _tag: "Create" | "Update" | "Remove" | "Rescan";
1073
+ /** Absolute path of the changed entry, or `/` when retained events were lost. */
377
1074
  readonly path: BytePath;
378
1075
  }
379
1076
  /**
@@ -426,6 +1123,29 @@ export type OverlayChange = typeof OverlayChange.Type;
426
1123
  /**
427
1124
  * Schema for overlay summary filtering.
428
1125
  *
1126
+ * @example
1127
+ * ```ts
1128
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
1129
+ * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
1130
+ * import { Effect } from "effect"
1131
+ *
1132
+ * const program = Effect.gen(function*() {
1133
+ * const base = yield* Vfs.make()
1134
+ * const workspace = yield* Vfs.makeOverlay(yield* base.snapshot)
1135
+ *
1136
+ * yield* (yield* workspace.caller()).mkdir("/out")
1137
+ *
1138
+ * // Timestamps are excluded by default, since they change on every write.
1139
+ * const plain = yield* workspace.changes()
1140
+ * const timed = yield* workspace.changes({ includeTimestamps: true })
1141
+ *
1142
+ * return [plain.length, timed.length]
1143
+ * })
1144
+ *
1145
+ * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
1146
+ * // [ 1, 2 ]
1147
+ * ```
1148
+ *
429
1149
  * @category schemas
430
1150
  * @since 0.1.0
431
1151
  */
@@ -440,6 +1160,37 @@ export type OverlayChangesOptions = typeof OverlayChangesOptions.Type;
440
1160
  /**
441
1161
  * A complete snapshot and final-difference summary captured from one committed state.
442
1162
  *
1163
+ * @example
1164
+ * ```ts
1165
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
1166
+ * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
1167
+ * import { Effect } from "effect"
1168
+ *
1169
+ * const program = Effect.gen(function*() {
1170
+ * const base = yield* Vfs.make()
1171
+ * const workspace = yield* Vfs.makeOverlay(yield* base.snapshot)
1172
+ *
1173
+ * yield* (yield* workspace.caller()).writeFile("/out.txt", new TextEncoder().encode("built"), {
1174
+ * access: "write",
1175
+ * create: "exclusive"
1176
+ * })
1177
+ *
1178
+ * // One committed state: the summary describes exactly the snapshot beside it,
1179
+ * // so restoring from it cannot disagree with the change list.
1180
+ * const captured = yield* workspace.capture()
1181
+ * const restored = yield* Vfs.fromSnapshot(captured.snapshot)
1182
+ * const reader = yield* restored.caller()
1183
+ *
1184
+ * return [
1185
+ * captured.changes.map((change) => change._tag),
1186
+ * new TextDecoder().decode(yield* reader.readFile("/out.txt"))
1187
+ * ]
1188
+ * })
1189
+ *
1190
+ * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
1191
+ * // [ [ 'Added' ], 'built' ]
1192
+ * ```
1193
+ *
443
1194
  * @category models
444
1195
  * @since 0.1.0
445
1196
  */
@@ -452,42 +1203,167 @@ export interface OverlayCapture {
452
1203
  /**
453
1204
  * An isolated virtual filesystem namespace that creates callers, snapshots, and watch streams.
454
1205
  *
1206
+ * @example
1207
+ * ```ts
1208
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
1209
+ * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
1210
+ * import { Effect, Fiber, Option, Stream } from "effect"
1211
+ *
1212
+ * const program = Effect.gen(function*() {
1213
+ * const volume = yield* Vfs.make()
1214
+ * const caller = yield* volume.caller()
1215
+ *
1216
+ * // `watch` reports future changes only; nothing is replayed.
1217
+ * const watcher = yield* volume.watch.pipe(
1218
+ * Effect.flatMap(Stream.runHead),
1219
+ * Effect.forkChild({ startImmediately: true })
1220
+ * )
1221
+ *
1222
+ * yield* caller.mkdir("/logs")
1223
+ *
1224
+ * const change = yield* Fiber.join(watcher)
1225
+ *
1226
+ * return Option.getOrElse(Option.map(change, (event) => event._tag), () => "none")
1227
+ * }).pipe(Effect.scoped)
1228
+ *
1229
+ * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
1230
+ * // Create
1231
+ * ```
1232
+ *
455
1233
  * @category models
456
1234
  * @since 0.1.0
457
1235
  */
458
1236
  export interface Volume {
459
- /** Opens a scoped stream of future committed changes. Events are not replayed. */
460
- readonly watch: Effect.Effect<Stream.Stream<Change>, never, Scope.Scope>;
1237
+ /** The strongest failure boundary survived by acknowledged writes. */
1238
+ readonly durability: VolumeDurability;
1239
+ /** Stable logical identity, preserved across reconstruction only when supplied explicitly. */
1240
+ readonly identity: VolumeIdentity;
1241
+ /** Fresh identity for this uninterrupted storage lifetime. */
1242
+ readonly incarnation: VolumeIncarnation;
1243
+ /** Effective static limits, where `undefined` means unlimited. */
1244
+ readonly limits: VolumeLimits;
1245
+ /** Samples content bytes and directory entries together from the current committed state. */
1246
+ readonly usage: Effect.Effect<VolumeUsage, FsError>;
1247
+ /** Opens a scoped stream of future changes. `Rescan` at `/` requires a full rescan; events are not replayed. */
1248
+ readonly watch: Effect.Effect<Stream.Stream<Change>, FsError, Scope.Scope>;
461
1249
  /** Captures an isolated snapshot of the reachable namespace and metadata. */
462
- readonly snapshot: Effect.Effect<Snapshot, ImageError>;
1250
+ readonly snapshot: Effect.Effect<Snapshot, ImageError | FsError>;
463
1251
  readonly [VolumeId]: true;
464
1252
  /** Creates a caller rooted at `/` with independent credentials, umask, and current directory. */
465
- readonly caller: (options?: RootCallerOptions) => Effect.Effect<Caller, ConfigurationError>;
1253
+ readonly caller: (options?: RootCallerOptions) => Effect.Effect<Caller, ConfigurationError | FsError>;
466
1254
  }
467
1255
  /**
468
1256
  * An ordinary volume with final-state inspection relative to one immutable snapshot base.
469
1257
  *
1258
+ * @example
1259
+ * ```ts
1260
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
1261
+ * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
1262
+ * import { Effect } from "effect"
1263
+ *
1264
+ * const program = Effect.gen(function*() {
1265
+ * const base = yield* Vfs.make()
1266
+ * const workspace = yield* Vfs.makeOverlay(yield* base.snapshot)
1267
+ *
1268
+ * yield* (yield* workspace.caller()).writeFile("/added.txt", new Uint8Array([1]), {
1269
+ * access: "write",
1270
+ * create: "exclusive"
1271
+ * })
1272
+ *
1273
+ * // `capture` pairs one committed snapshot with the summary describing it.
1274
+ * const captured = yield* workspace.capture()
1275
+ *
1276
+ * return captured.changes.map((change) => change._tag)
1277
+ * })
1278
+ *
1279
+ * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
1280
+ * // [ 'Added' ]
1281
+ * ```
1282
+ *
470
1283
  * @category models
471
1284
  * @since 0.1.0
472
1285
  */
473
1286
  export interface OverlayVolume extends Volume {
474
1287
  /** Computes final differences from the immutable base when this reusable effect executes. */
475
- readonly changes: (options?: OverlayChangesOptions) => Effect.Effect<ReadonlyArray<OverlayChange>, ConfigurationError | ImageError>;
1288
+ readonly changes: (options?: OverlayChangesOptions) => Effect.Effect<ReadonlyArray<OverlayChange>, ConfigurationError | ImageError | FsError>;
476
1289
  /** Captures one committed state when this reusable effect executes. */
477
- readonly capture: (options?: OverlayChangesOptions) => Effect.Effect<OverlayCapture, ConfigurationError | ImageError>;
1290
+ readonly capture: (options?: OverlayChangesOptions) => Effect.Effect<OverlayCapture, ConfigurationError | ImageError | FsError>;
478
1291
  }
479
1292
  /**
480
1293
  * Optional Effect service for providing an existing filesystem caller.
481
1294
  *
1295
+ * @example
1296
+ * ```ts
1297
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
1298
+ * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
1299
+ * import { Effect } from "effect"
1300
+ *
1301
+ * // Code depending on the service does not choose the volume it runs against.
1302
+ * const readVersion = Effect.gen(function*() {
1303
+ * const caller = yield* Vfs.CurrentFileSystem
1304
+ *
1305
+ * return yield* caller.readFile("/version")
1306
+ * })
1307
+ *
1308
+ * const program = Effect.gen(function*() {
1309
+ * const volume = yield* Vfs.make()
1310
+ * const caller = yield* volume.caller()
1311
+ *
1312
+ * yield* caller.writeFile("/version", new Uint8Array([49]), {
1313
+ * access: "write",
1314
+ * create: "exclusive"
1315
+ * })
1316
+ *
1317
+ * return yield* readVersion.pipe(Effect.provideService(Vfs.CurrentFileSystem, caller))
1318
+ * })
1319
+ *
1320
+ * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
1321
+ * // Uint8Array(1) [ 49 ]
1322
+ * ```
1323
+ *
1324
+ * @see The testing guide at `/guides/testing-with-an-isolated-filesystem`.
482
1325
  * @category services
483
1326
  * @since 0.1.0
484
1327
  */
485
- export declare const CurrentFileSystem: typeof VfsModel.CurrentFileSystem;
486
- /** @internal */
487
- export type CurrentFileSystem = VfsModel.CurrentFileSystem;
1328
+ export declare const CurrentFileSystem: Context.Service<CurrentFileSystem, Caller>;
1329
+ /**
1330
+ * The caller supplied by the optional filesystem service.
1331
+ *
1332
+ * @category services
1333
+ * @since 0.1.0
1334
+ */
1335
+ export interface CurrentFileSystem extends Caller {
1336
+ }
488
1337
  /**
489
1338
  * Encodes a snapshot as owned UTF-8 JSON bytes using the version 1 snapshot format.
490
1339
  *
1340
+ * @example
1341
+ * ```ts
1342
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
1343
+ * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
1344
+ * import { Effect } from "effect"
1345
+ *
1346
+ * // Bytes are portable: store or transmit them, then decode under explicit limits.
1347
+ * const program = Effect.gen(function*() {
1348
+ * const volume = yield* Vfs.make()
1349
+ * const caller = yield* volume.caller()
1350
+ *
1351
+ * yield* caller.writeFile("/f", new Uint8Array([1, 2, 3]), {
1352
+ * access: "write",
1353
+ * create: "exclusive"
1354
+ * })
1355
+ *
1356
+ * const bytes = yield* Vfs.encodeSnapshot(yield* volume.snapshot)
1357
+ *
1358
+ * // The bytes are UTF-8 JSON carrying the version 1 snapshot format.
1359
+ * return JSON.parse(new TextDecoder().decode(bytes)).version
1360
+ * })
1361
+ *
1362
+ * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
1363
+ * // 1
1364
+ * ```
1365
+ *
1366
+ * @see {@link decodeSnapshot} for reading the bytes back under limits.
491
1367
  * @category serialization
492
1368
  * @since 0.1.0
493
1369
  */
@@ -495,87 +1371,182 @@ export declare const encodeSnapshot: (snapshot: Snapshot) => Effect.Effect<Uint8
495
1371
  /**
496
1372
  * Decodes version 1 snapshot bytes while enforcing explicit input and payload limits.
497
1373
  *
1374
+ * @example
1375
+ * ```ts
1376
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
1377
+ * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
1378
+ * import { ByteSize, Effect } from "effect"
1379
+ *
1380
+ * // Limits are mandatory: decoding is the boundary where untrusted input arrives.
1381
+ * const program = Effect.gen(function*() {
1382
+ * const volume = yield* Vfs.make()
1383
+ * const bytes = yield* Vfs.encodeSnapshot(yield* volume.snapshot)
1384
+ *
1385
+ * return yield* Vfs.decodeSnapshot(bytes, {
1386
+ * maxEncodedBytes: ByteSize.bytes(1),
1387
+ * maxRecords: 10_000,
1388
+ * maxEntries: 10_000,
1389
+ * maxDecodedBytes: ByteSize.megabytes(16)
1390
+ * }).pipe(
1391
+ * Effect.as("accepted"),
1392
+ * Effect.catchTag("ImageError", (error) => Effect.succeed([error.code, error.field]))
1393
+ * )
1394
+ * })
1395
+ *
1396
+ * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
1397
+ * // [ 'LimitExceeded', 'encodedBytes' ]
1398
+ * ```
1399
+ *
1400
+ * @see {@link DecodeLimits} for what each bound protects.
498
1401
  * @category serialization
499
1402
  * @since 0.1.0
500
1403
  */
501
1404
  export declare const decodeSnapshot: (input: Uint8Array, limits: DecodeLimits) => Effect.Effect<Snapshot, ImageError>;
502
1405
  /**
503
1406
  * Computes an exact portable delta between two immutable snapshots.
504
- * Requires the platform-neutral `Crypto.Crypto` service for base identity.
1407
+ * Requires the platform-neutral `Crypto.Crypto` service for base identity. Provide
1408
+ * `NodeCrypto.layer`, `BunCrypto.layer`, or your own via `Crypto.make`.
1409
+ *
1410
+ * @example
1411
+ * ```ts
1412
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
1413
+ * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
1414
+ * import { Effect } from "effect"
1415
+ *
1416
+ * // Two snapshots in, one opaque delta out. `inspectSnapshotDelta` reads it.
1417
+ * const program = Effect.gen(function*() {
1418
+ * const volume = yield* Vfs.make()
1419
+ * const caller = yield* volume.caller()
1420
+ * const base = yield* volume.snapshot
1421
+ *
1422
+ * yield* caller.writeFile("/added.txt", new Uint8Array([1]), {
1423
+ * access: "write",
1424
+ * create: "exclusive"
1425
+ * })
1426
+ *
1427
+ * const delta = yield* Vfs.diffSnapshots(base, yield* volume.snapshot)
1428
+ *
1429
+ * const changes = yield* Vfs.inspectSnapshotDelta(base, delta)
1430
+ *
1431
+ * return new TextDecoder().decode(yield* Vfs.pathToBytes(changes[0]!.path))
1432
+ * }).pipe(Effect.provide(NodeCrypto.layer))
505
1433
  *
1434
+ * Effect.runPromise(program).then(console.log)
1435
+ * // /added.txt
1436
+ * ```
1437
+ *
1438
+ * @see {@link inspectSnapshotDelta} to read a delta, {@link applySnapshotDelta} to apply one.
506
1439
  * @category snapshots
507
1440
  * @since 0.1.0
508
1441
  */
509
- export declare const diffSnapshots: (base: Snapshot, target: Snapshot, limits?: {
510
- readonly maxEncodedBytes: import("effect/ByteSize").ByteSize;
511
- readonly maxIdentityBytes: import("effect/ByteSize").ByteSize;
512
- readonly maxDeltaRecords: number;
513
- readonly maxDecodedDeltaBytes: import("effect/ByteSize").ByteSize;
514
- readonly maxBaseRecords: number;
515
- readonly maxTargetRecords: number;
516
- readonly maxEntries: number;
517
- readonly maxOutputRecords: number;
518
- readonly maxOutputBytes: import("effect/ByteSize").ByteSize;
519
- readonly maxInheritedRecords: number;
520
- } | undefined) => Effect.Effect<SnapshotDeltaModel.SnapshotDelta, VfsModel.ConfigurationError | ImageError | import("effect/PlatformError").PlatformError, import("effect/Crypto").Crypto>;
1442
+ export declare const diffSnapshots: (base: Snapshot, target: Snapshot, limits?: SnapshotDeltaModel.SnapshotDeltaLimits) => Effect.Effect<SnapshotDeltaModel.SnapshotDelta, ConfigurationError | ImageError | PlatformError.PlatformError, Crypto.Crypto>;
521
1443
  /**
522
1444
  * 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.
1445
+ * Requires the platform-neutral `Crypto.Crypto` service for base identity. Provide
1446
+ * `NodeCrypto.layer`, `BunCrypto.layer`, or your own via `Crypto.make`.
1447
+ *
1448
+ * @example
1449
+ * ```ts
1450
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
1451
+ * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
1452
+ * import { Effect } from "effect"
1453
+ *
1454
+ * // Inspection verifies the delta against its base before describing it.
1455
+ * const program = Effect.gen(function*() {
1456
+ * const volume = yield* Vfs.make()
1457
+ * const caller = yield* volume.caller()
1458
+ * const base = yield* volume.snapshot
1459
+ *
1460
+ * yield* caller.mkdir("/logs")
524
1461
  *
1462
+ * const delta = yield* Vfs.diffSnapshots(base, yield* volume.snapshot)
1463
+ * const changes = yield* Vfs.inspectSnapshotDelta(base, delta)
1464
+ *
1465
+ * return changes.map((change) =>
1466
+ * change._tag === "Updated" ? change.afterKind : change.kind)
1467
+ * }).pipe(Effect.provide(NodeCrypto.layer))
1468
+ *
1469
+ * Effect.runPromise(program).then(console.log)
1470
+ * // [ 'directory' ]
1471
+ * ```
1472
+ *
1473
+ * @see {@link SnapshotChangesOptions} for including timestamp differences.
525
1474
  * @category snapshots
526
1475
  * @since 0.1.0
527
1476
  */
528
- export declare const inspectSnapshotDelta: (base: Snapshot, delta: SnapshotDeltaModel.SnapshotDelta, options?: {
529
- readonly includeTimestamps?: boolean;
530
- } | undefined, limits?: {
531
- readonly maxEncodedBytes: import("effect/ByteSize").ByteSize;
532
- readonly maxIdentityBytes: import("effect/ByteSize").ByteSize;
533
- readonly maxDeltaRecords: number;
534
- readonly maxDecodedDeltaBytes: import("effect/ByteSize").ByteSize;
535
- readonly maxBaseRecords: number;
536
- readonly maxTargetRecords: number;
537
- readonly maxEntries: number;
538
- readonly maxOutputRecords: number;
539
- readonly maxOutputBytes: import("effect/ByteSize").ByteSize;
540
- readonly maxInheritedRecords: number;
541
- } | undefined) => Effect.Effect<readonly ({
542
- readonly _tag: "Added";
543
- readonly path: BytePath;
544
- readonly kind: "directory" | "file" | "symlink";
545
- } | {
546
- readonly _tag: "Removed";
547
- readonly path: BytePath;
548
- readonly kind: "directory" | "file" | "symlink";
549
- } | {
550
- readonly _tag: "Updated";
551
- readonly path: BytePath;
552
- readonly beforeKind: "directory" | "file" | "symlink";
553
- readonly afterKind: "directory" | "file" | "symlink";
554
- readonly differences: readonly ("atimeNs" | "birthtimeNs" | "content" | "ctimeNs" | "gid" | "hardLinks" | "kind" | "mode" | "mtimeNs" | "target" | "uid")[];
555
- })[], VfsModel.ConfigurationError | ImageError | import("effect/PlatformError").PlatformError | SnapshotDeltaModel.SnapshotDeltaError, import("effect/Crypto").Crypto>;
1477
+ export declare const inspectSnapshotDelta: (base: Snapshot, delta: SnapshotDeltaModel.SnapshotDelta, options?: SnapshotDeltaModel.SnapshotChangesOptions, limits?: SnapshotDeltaModel.SnapshotDeltaLimits) => Effect.Effect<ReadonlyArray<SnapshotDeltaModel.SnapshotChange>, ConfigurationError | ImageError | PlatformError.PlatformError | SnapshotDeltaModel.SnapshotDeltaError, Crypto.Crypto>;
556
1478
  /**
557
1479
  * 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.
1480
+ * Requires the platform-neutral `Crypto.Crypto` service for base identity. Provide
1481
+ * `NodeCrypto.layer`, `BunCrypto.layer`, or your own via `Crypto.make`.
1482
+ *
1483
+ * @example
1484
+ * ```ts
1485
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
1486
+ * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
1487
+ * import { Effect } from "effect"
1488
+ *
1489
+ * const program = Effect.gen(function*() {
1490
+ * const volume = yield* Vfs.make()
1491
+ * const caller = yield* volume.caller()
1492
+ * const base = yield* volume.snapshot
1493
+ *
1494
+ * yield* caller.writeFile("/f", new Uint8Array([7]), {
1495
+ * access: "write",
1496
+ * create: "exclusive"
1497
+ * })
1498
+ *
1499
+ * const delta = yield* Vfs.diffSnapshots(base, yield* volume.snapshot)
1500
+ *
1501
+ * // Applying the delta to the same base reproduces the target snapshot.
1502
+ * const applied = yield* Vfs.applySnapshotDelta(base, delta)
1503
+ * const restored = yield* Vfs.fromSnapshot(applied)
1504
+ * const reader = yield* restored.caller()
1505
+ *
1506
+ * return yield* reader.readFile("/f")
1507
+ * }).pipe(Effect.provide(NodeCrypto.layer))
1508
+ *
1509
+ * Effect.runPromise(program).then(console.log)
1510
+ * // Uint8Array(1) [ 7 ]
1511
+ * ```
559
1512
  *
1513
+ * @see {@link SnapshotDeltaFromBytes} for moving a delta across a boundary.
560
1514
  * @category snapshots
561
1515
  * @since 0.1.0
562
1516
  */
563
- export declare const applySnapshotDelta: (base: Snapshot, delta: SnapshotDeltaModel.SnapshotDelta, limits?: {
564
- readonly maxEncodedBytes: import("effect/ByteSize").ByteSize;
565
- readonly maxIdentityBytes: import("effect/ByteSize").ByteSize;
566
- readonly maxDeltaRecords: number;
567
- readonly maxDecodedDeltaBytes: import("effect/ByteSize").ByteSize;
568
- readonly maxBaseRecords: number;
569
- readonly maxTargetRecords: number;
570
- readonly maxEntries: number;
571
- readonly maxOutputRecords: number;
572
- readonly maxOutputBytes: import("effect/ByteSize").ByteSize;
573
- readonly maxInheritedRecords: number;
574
- } | undefined) => Effect.Effect<Snapshot, VfsModel.ConfigurationError | ImageError | import("effect/PlatformError").PlatformError | SnapshotDeltaModel.SnapshotDeltaError, import("effect/Crypto").Crypto>;
1517
+ export declare const applySnapshotDelta: (base: Snapshot, delta: SnapshotDeltaModel.SnapshotDelta, limits?: SnapshotDeltaModel.SnapshotDeltaLimits) => Effect.Effect<Snapshot, ConfigurationError | ImageError | PlatformError.PlatformError | SnapshotDeltaModel.SnapshotDeltaError, Crypto.Crypto>;
575
1518
  /**
576
1519
  * Creates an Effect Schema codec between owned bytes and opaque snapshot deltas.
577
1520
  * Omission uses `SnapshotDeltaLimits.default`.
578
1521
  *
1522
+ * @example
1523
+ * ```ts
1524
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
1525
+ * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
1526
+ * import { Effect, Schema } from "effect"
1527
+ *
1528
+ * // Omitting limits uses `SnapshotDeltaLimits.default`.
1529
+ * const codec = Vfs.SnapshotDeltaFromBytes()
1530
+ *
1531
+ * const program = Effect.gen(function*() {
1532
+ * const volume = yield* Vfs.make()
1533
+ * const base = yield* volume.snapshot
1534
+ *
1535
+ * yield* (yield* volume.caller()).mkdir("/out")
1536
+ *
1537
+ * const delta = yield* Vfs.diffSnapshots(base, yield* volume.snapshot)
1538
+ * const bytes = yield* Schema.encodeEffect(codec)(delta)
1539
+ *
1540
+ * // The delta survives the trip through bytes and still applies to its base.
1541
+ * const decoded = yield* Schema.decodeEffect(codec)(bytes)
1542
+ *
1543
+ * return (yield* Vfs.inspectSnapshotDelta(base, decoded)).map((change) => change._tag)
1544
+ * }).pipe(Effect.provide(NodeCrypto.layer))
1545
+ *
1546
+ * Effect.runPromise(program).then(console.log)
1547
+ * // [ 'Added' ]
1548
+ * ```
1549
+ *
579
1550
  * @category schemas
580
1551
  * @since 0.1.0
581
1552
  */
@@ -587,6 +1558,28 @@ export declare const SnapshotDeltaFromBytes: (limits?: SnapshotDeltaModel.Snapsh
587
1558
  *
588
1559
  * Shared-memory-backed and detached views fail with `InvalidArgument`.
589
1560
  *
1561
+ * @example
1562
+ * ```ts
1563
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
1564
+ * import { Effect } from "effect"
1565
+ *
1566
+ * // Any byte except NUL is accepted. Empty, NUL-bearing, detached, and shared-memory
1567
+ * // input all fail with `InvalidArgument`.
1568
+ * const program = Effect.gen(function*() {
1569
+ * const path = yield* Vfs.pathFromBytes(new Uint8Array([47, 116, 109, 112]))
1570
+ *
1571
+ * const rejected = yield* Vfs.pathFromBytes(new Uint8Array([47, 0])).pipe(
1572
+ * Effect.as("accepted"),
1573
+ * Effect.catchTag("FsError", (error) => Effect.succeed(error.code))
1574
+ * )
1575
+ *
1576
+ * return [yield* Vfs.pathToBytes(path), rejected]
1577
+ * })
1578
+ *
1579
+ * Effect.runPromise(program).then(console.log)
1580
+ * // [ Uint8Array(4) [ 47, 116, 109, 112 ], 'InvalidArgument' ]
1581
+ * ```
1582
+ *
590
1583
  * @category constructors
591
1584
  * @since 0.1.0
592
1585
  */
@@ -605,6 +1598,27 @@ export declare const pathToBytes: (path: BytePath) => Effect.Effect<Uint8Array,
605
1598
  *
606
1599
  * Fixture paths must be absolute, unique, and explicitly include their parent directories.
607
1600
  *
1601
+ * @example
1602
+ * ```ts
1603
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
1604
+ * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
1605
+ * import { Effect, Schema } from "effect"
1606
+ *
1607
+ * // Fixtures must list every parent directory before its children.
1608
+ * const program = Schema.decodeUnknownEffect(Vfs.Fixture)({
1609
+ * entries: [
1610
+ * { kind: "file", path: "/etc/hosts", bytes: new TextEncoder().encode("127.0.0.1") }
1611
+ * ]
1612
+ * }).pipe(
1613
+ * Effect.flatMap(Vfs.fromFixture),
1614
+ * Effect.as("built"),
1615
+ * Effect.catchTag("ImageError", (error) => Effect.succeed(`rejected: ${error.code}`))
1616
+ * )
1617
+ *
1618
+ * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
1619
+ * // rejected: InvalidStructure
1620
+ * ```
1621
+ *
608
1622
  * @category schemas
609
1623
  * @since 0.1.0
610
1624
  */
@@ -624,17 +1638,73 @@ export type Fixture = typeof Fixture.Type;
624
1638
  * Each execution creates independent storage. Snapshot image failures cannot
625
1639
  * arise because this constructor does not accept persisted input.
626
1640
  *
1641
+ * @example
1642
+ * ```ts
1643
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
1644
+ * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
1645
+ * import { Effect } from "effect"
1646
+ *
1647
+ * const program = Effect.gen(function*() {
1648
+ * const volume = yield* Vfs.make()
1649
+ * const caller = yield* volume.caller()
1650
+ *
1651
+ * yield* caller.mkdir("/work")
1652
+ * yield* caller.writeFile("/work/notes.txt", new Uint8Array([104, 105]), { access: "write", create: "exclusive" })
1653
+ *
1654
+ * return yield* caller.readFile("/work/notes.txt")
1655
+ * })
1656
+ *
1657
+ * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
1658
+ * // Uint8Array(2) [ 104, 105 ]
1659
+ * ```
1660
+ *
1661
+ * @see The testing guide at `/guides/testing-with-an-isolated-filesystem`.
627
1662
  * @category constructors
628
1663
  * @since 0.1.0
629
1664
  */
630
- export declare const make: (options?: VolumeOptions) => Effect.Effect<Volume, ConfigurationError>;
1665
+ export declare const make: (options?: VolumeOptions) => Effect.Effect<Volume, ConfigurationError | PlatformError.PlatformError, Crypto.Crypto>;
631
1666
  /**
632
1667
  * Restores a fresh volume from an opaque snapshot under the supplied destination limits.
633
1668
  *
1669
+ * @example
1670
+ * ```ts
1671
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
1672
+ * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
1673
+ * import { Effect } from "effect"
1674
+ *
1675
+ * const program = Effect.gen(function*() {
1676
+ * const original = yield* Vfs.make()
1677
+ * const source = yield* original.caller()
1678
+ *
1679
+ * yield* source.writeFile("/seed.txt", new TextEncoder().encode("one"), {
1680
+ * access: "write",
1681
+ * create: "exclusive"
1682
+ * })
1683
+ *
1684
+ * const restored = yield* Vfs.fromSnapshot(yield* original.snapshot)
1685
+ * const copy = yield* restored.caller()
1686
+ *
1687
+ * // Writing to the restore leaves the original untouched: the two are independent.
1688
+ * yield* copy.writeFile("/seed.txt", new TextEncoder().encode("two"), {
1689
+ * access: "write",
1690
+ * truncate: true
1691
+ * })
1692
+ *
1693
+ * return [
1694
+ * new TextDecoder().decode(yield* source.readFile("/seed.txt")),
1695
+ * new TextDecoder().decode(yield* copy.readFile("/seed.txt"))
1696
+ * ]
1697
+ * })
1698
+ *
1699
+ * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
1700
+ * // [ 'one', 'two' ]
1701
+ * ```
1702
+ *
1703
+ * @see {@link makeOverlay} for a copy-on-write workspace over the same base.
634
1704
  * @category constructors
635
1705
  * @since 0.1.0
636
1706
  */
637
- export declare const fromSnapshot: (snapshot: Snapshot, options?: VolumeOptions) => Effect.Effect<Volume, ConfigurationError | ImageError>;
1707
+ export declare const fromSnapshot: (snapshot: Snapshot, options?: VolumeOptions) => Effect.Effect<Volume, ConfigurationError | ImageError | PlatformError.PlatformError, Crypto.Crypto>;
638
1708
  /**
639
1709
  * Creates an isolated writable volume relative to one immutable snapshot base.
640
1710
  *
@@ -648,15 +1718,79 @@ export declare const fromSnapshot: (snapshot: Snapshot, options?: VolumeOptions)
648
1718
  * Invalid base snapshots fail with `ImageError`; invalid volume limits fail
649
1719
  * with `ConfigurationError`. Each execution creates a fresh workspace.
650
1720
  *
1721
+ * @example
1722
+ * ```ts
1723
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
1724
+ * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
1725
+ * import { Effect } from "effect"
1726
+ *
1727
+ * const program = Effect.gen(function*() {
1728
+ * const base = yield* Vfs.make()
1729
+ * const origin = yield* base.caller()
1730
+ *
1731
+ * yield* origin.writeFile("/config.json", new TextEncoder().encode("{}"), {
1732
+ * access: "write",
1733
+ * create: "exclusive"
1734
+ * })
1735
+ *
1736
+ * const workspace = yield* Vfs.makeOverlay(yield* base.snapshot)
1737
+ *
1738
+ * yield* (yield* workspace.caller()).writeFile("/config.json", new TextEncoder().encode("[]"), {
1739
+ * access: "write",
1740
+ * truncate: true
1741
+ * })
1742
+ *
1743
+ * // The write landed in the workspace only; the base still reads its own value.
1744
+ * return [
1745
+ * new TextDecoder().decode(yield* origin.readFile("/config.json")),
1746
+ * (yield* workspace.changes()).map((change) => change._tag)
1747
+ * ]
1748
+ * })
1749
+ *
1750
+ * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
1751
+ * // [ '{}', [ 'Updated' ] ]
1752
+ * ```
1753
+ *
1754
+ * @see The overlay guide at `/guides/overlay-filesystems`.
651
1755
  * @category constructors
652
1756
  * @since 0.1.0
653
1757
  */
654
- export declare const makeOverlay: (base: Snapshot, options?: VolumeOptions) => Effect.Effect<OverlayVolume, ConfigurationError | ImageError>;
1758
+ export declare const makeOverlay: (base: Snapshot, options?: VolumeOptions) => Effect.Effect<OverlayVolume, ConfigurationError | ImageError | PlatformError.PlatformError, Crypto.Crypto>;
655
1759
  /**
656
1760
  * Builds a fresh volume from a validated final-state fixture.
657
1761
  *
1762
+ * @example
1763
+ * ```ts
1764
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
1765
+ * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
1766
+ * import { Effect } from "effect"
1767
+ *
1768
+ * const encoder = new TextEncoder()
1769
+ *
1770
+ * const program = Effect.gen(function*() {
1771
+ * // Parent directories must be listed explicitly, before their children.
1772
+ * const volume = yield* Vfs.fromFixture({
1773
+ * entries: [
1774
+ * { kind: "directory", path: "/project" },
1775
+ * {
1776
+ * kind: "file",
1777
+ * path: "/project/package.json",
1778
+ * bytes: encoder.encode(`{"version":"1.2.3"}`)
1779
+ * },
1780
+ * { kind: "symlink", path: "/project/latest", target: "/project/package.json" }
1781
+ * ]
1782
+ * })
1783
+ *
1784
+ * return yield* (yield* volume.caller()).readDirectory("/project")
1785
+ * })
1786
+ *
1787
+ * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
1788
+ * // [ 'package.json', 'latest' ]
1789
+ * ```
1790
+ *
1791
+ * @see The fixtures guide at `/guides/fixtures-and-snapshots`.
658
1792
  * @category constructors
659
1793
  * @since 0.1.0
660
1794
  */
661
- export declare const fromFixture: (fixture: Fixture, options?: VolumeOptions) => Effect.Effect<Volume, ConfigurationError | ImageError>;
1795
+ export declare const fromFixture: (fixture: Fixture, options?: VolumeOptions) => Effect.Effect<Volume, ConfigurationError | ImageError | PlatformError.PlatformError, Crypto.Crypto>;
662
1796
  //# sourceMappingURL=VirtualFileSystem.d.ts.map