@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
@@ -1,25 +1,15 @@
1
- /**
2
- * Runtime-neutral virtual filesystem contracts and constructors.
3
- *
4
- * **Details**
5
- *
6
- * Volumes own isolated namespaces. Callers carry path context and credentials,
7
- * while file and directory handles use `Scope` for deterministic release.
8
- *
9
- * @since 0.1.0
10
- */
1
+ import * as Context from "effect/Context";
11
2
  import * as Effect from "effect/Effect";
12
3
  import * as Result from "effect/Result";
13
4
  import * as Schema from "effect/Schema";
14
5
  import * as SchemaIssue from "effect/SchemaIssue";
15
6
  import * as SchemaTransformation from "effect/SchemaTransformation";
16
- import { BytePath } from "./BytePath.js";
17
7
  export { BytePath } from "./BytePath.js";
18
- import { DecodeLimits, ImageError } from "./Snapshot.js";
19
8
  export { DecodeLimits, ImageError, SnapshotTypeId } from "./Snapshot.js";
20
9
  import * as Image from "./internal/image.js";
21
10
  import * as SnapshotDeltaInternal from "./internal/snapshotDelta.js";
22
11
  import * as VfsModel from "./internal/virtualFileSystem.js";
12
+ import { decodeConfiguration } from "./internal/virtualFileSystem/errors.js";
23
13
  import * as FixtureInternal from "./internal/virtualFileSystem/fixture.js";
24
14
  import * as Path from "./internal/virtualFileSystem/path.js";
25
15
  import * as SnapshotDeltaModel from "./SnapshotDelta.js";
@@ -32,6 +22,30 @@ const ObjectReferenceId = VfsModel.ObjectReferenceId;
32
22
  /**
33
23
  * Schema for portable virtual filesystem error codes.
34
24
  *
25
+ * @example
26
+ * ```ts
27
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
28
+ * import { Effect, Schema } from "effect"
29
+ *
30
+ * const asCode = Schema.decodeUnknownEffect(Vfs.FsCode)
31
+ *
32
+ * // Useful when a code has crossed a process boundary and arrives back as data.
33
+ * const program = Effect.gen(function*() {
34
+ * const payload = JSON.parse(`{"code":"NotFound","other":"Nonsense"}`)
35
+ *
36
+ * const known = yield* asCode(payload.code)
37
+ *
38
+ * const unknown = yield* asCode(payload.other).pipe(
39
+ * Effect.orElseSucceed(() => "not a code")
40
+ * )
41
+ *
42
+ * return [known, unknown]
43
+ * })
44
+ *
45
+ * Effect.runPromise(program).then(console.log)
46
+ * // [ 'NotFound', 'not a code' ]
47
+ * ```
48
+ *
35
49
  * @category schemas
36
50
  * @since 0.1.0
37
51
  */
@@ -39,17 +53,65 @@ export const FsCode = VfsModel.FsCode;
39
53
  /**
40
54
  * Describes an expected filesystem operation failure.
41
55
  *
56
+ * Durable providers distinguish `StorageRejected` (the operation definitely did not commit),
57
+ * `OutcomeUnknown` (it may have committed), and `VolumeUnavailable` (the provider cannot safely
58
+ * serve further operations until recovery). Built-in memory volumes never emit these codes.
59
+ *
60
+ * @example
61
+ * ```ts
62
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
63
+ * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
64
+ * import { Effect } from "effect"
65
+ *
66
+ * const program = Effect.gen(function*() {
67
+ * const caller = yield* (yield* Vfs.make()).caller()
68
+ *
69
+ * // Every filesystem operation fails with this one tagged error; `code`
70
+ * // distinguishes the cases, so match on it rather than on the tag alone.
71
+ * return yield* caller.readFile("/missing").pipe(
72
+ * Effect.catchTag("FsError", (error) =>
73
+ * error.code === "NotFound"
74
+ * ? Effect.succeed(new Uint8Array())
75
+ * : Effect.fail(error))
76
+ * )
77
+ * })
78
+ *
79
+ * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
80
+ * // Uint8Array(0) []
81
+ * ```
82
+ *
42
83
  * @category errors
43
84
  * @since 0.1.0
44
85
  */
45
- export const FsError = VfsModel.FsError;
86
+ export { FsError } from "./VirtualFileSystemError.js";
46
87
  /**
47
88
  * Describes an invalid volume or caller option and names the rejected field.
48
89
  *
90
+ * @example
91
+ * ```ts
92
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
93
+ * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
94
+ * import { Effect } from "effect"
95
+ *
96
+ * // `field` names the rejected option, so the caller learns which key was wrong.
97
+ * const program = Effect.gen(function*() {
98
+ * // Options that arrived from a config file or CLI flag, not a literal.
99
+ * const options = JSON.parse(`{"maxEntries":-1}`) as Vfs.VolumeOptions
100
+ *
101
+ * return yield* Vfs.make(options).pipe(
102
+ * Effect.as("ok"),
103
+ * Effect.catchTag("ConfigurationError", (error) => Effect.succeed(`rejected ${error.field}`))
104
+ * )
105
+ * })
106
+ *
107
+ * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
108
+ * // rejected maxEntries
109
+ * ```
110
+ *
49
111
  * @category errors
50
112
  * @since 0.1.0
51
113
  */
52
- export const ConfigurationError = VfsModel.ConfigurationError;
114
+ export { ConfigurationError } from "./VirtualFileSystemError.js";
53
115
  /**
54
116
  * Schema for a caller's numeric identity, supplementary groups, and explicit privilege.
55
117
  *
@@ -60,13 +122,100 @@ export const Identity = VfsModel.Identity;
60
122
  /**
61
123
  * Schema for root caller credentials and creation mask.
62
124
  *
125
+ * @example
126
+ * ```ts
127
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
128
+ * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
129
+ * import { Effect } from "effect"
130
+ *
131
+ * const program = Effect.gen(function*() {
132
+ * const volume = yield* Vfs.make()
133
+ *
134
+ * // Omitting `identity` yields a privileged root caller.
135
+ * const root = yield* volume.caller({ umask: 0 })
136
+ * const user = yield* volume.caller({
137
+ * identity: { uid: 1000, gid: 1000, groups: [], privileged: false },
138
+ * umask: 0o022
139
+ * })
140
+ *
141
+ * yield* root.mkdir("/home", { mode: 0o777 })
142
+ *
143
+ * // The umask clears its bits from the requested mode.
144
+ * yield* user.mkdir("/home/user", { mode: 0o777 })
145
+ *
146
+ * return ((yield* user.stat("/home/user")).mode & 0o777).toString(8)
147
+ * })
148
+ *
149
+ * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
150
+ * // 755
151
+ * ```
152
+ *
63
153
  * @category schemas
64
154
  * @since 0.1.0
65
155
  */
66
156
  export const RootCallerOptions = VfsModel.RootCallerOptions;
157
+ /**
158
+ * Schema for the failure boundary survived by an acknowledged volume write.
159
+ *
160
+ * @category schemas
161
+ * @since 0.1.0
162
+ */
163
+ export const VolumeDurability = VfsModel.VolumeDurability;
164
+ /**
165
+ * Weakest-to-strongest ordering for volume durability guarantees.
166
+ *
167
+ * @category ordering
168
+ * @since 0.1.0
169
+ */
170
+ export const VolumeDurabilityOrder = VfsModel.VolumeDurabilityOrder;
171
+ /**
172
+ * Returns whether `actual` survives at least the failure boundary required by `required`.
173
+ *
174
+ * @category predicates
175
+ * @since 0.1.0
176
+ */
177
+ export const isVolumeDurabilityAtLeast = VfsModel.isVolumeDurabilityAtLeast;
178
+ /**
179
+ * Schema for the stable logical identity of a volume.
180
+ *
181
+ * @category schemas
182
+ * @since 0.1.0
183
+ */
184
+ export const VolumeIdentity = VfsModel.VolumeIdentity;
185
+ /**
186
+ * Schema for one uninterrupted storage lifetime of a volume.
187
+ *
188
+ * @category schemas
189
+ * @since 0.1.0
190
+ */
191
+ export const VolumeIncarnation = VfsModel.VolumeIncarnation;
67
192
  /**
68
193
  * Schema for optional volume capacity and path limits.
69
194
  *
195
+ * @example
196
+ * ```ts
197
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
198
+ * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
199
+ * import { ByteSize, Effect } from "effect"
200
+ *
201
+ * // Every limit is optional, and each one is enforced once set.
202
+ * const program = Effect.gen(function*() {
203
+ * const volume = yield* Vfs.make({ maxFileBytes: ByteSize.bytes(4) })
204
+ * const caller = yield* volume.caller()
205
+ *
206
+ * return yield* caller.writeFile("/big.bin", new Uint8Array(16), {
207
+ * access: "write",
208
+ * create: "exclusive"
209
+ * }).pipe(
210
+ * Effect.as("written"),
211
+ * Effect.catchTag("FsError", (error) => Effect.succeed(error.code))
212
+ * )
213
+ * })
214
+ *
215
+ * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
216
+ * // FileTooLarge
217
+ * ```
218
+ *
70
219
  * @category schemas
71
220
  * @since 0.1.0
72
221
  */
@@ -74,10 +223,48 @@ export const VolumeOptions = VfsModel.VolumeOptions;
74
223
  /**
75
224
  * Schema for filesystem node metadata with bigint inode, size, and nanosecond fields.
76
225
  *
226
+ * @example
227
+ * ```ts
228
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
229
+ * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
230
+ * import { Effect } from "effect"
231
+ *
232
+ * // Sizes and inode numbers are bigint; times are nanosecond timestamps.
233
+ * const program = Effect.gen(function*() {
234
+ * const caller = yield* (yield* Vfs.make()).caller()
235
+ *
236
+ * yield* caller.writeFile("/f", new Uint8Array([1, 2, 3]), {
237
+ * access: "write",
238
+ * create: "exclusive"
239
+ * })
240
+ *
241
+ * const metadata = yield* caller.stat("/f")
242
+ *
243
+ * return [metadata.kind, metadata.size, metadata.nlink]
244
+ * })
245
+ *
246
+ * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
247
+ * // [ 'file', 3n, 1 ]
248
+ * ```
249
+ *
77
250
  * @category schemas
78
251
  * @since 0.1.0
79
252
  */
80
253
  export const Metadata = VfsModel.Metadata;
254
+ /**
255
+ * A directory revision transition captured inside one coordinated operation.
256
+ *
257
+ * @category schemas
258
+ * @since 0.4.0
259
+ */
260
+ export const DirectoryChange = VfsModel.DirectoryChange;
261
+ /**
262
+ * The directory transitions produced by a same-directory or cross-directory rename.
263
+ *
264
+ * @category schemas
265
+ * @since 0.4.0
266
+ */
267
+ export const RenameReferenceResult = VfsModel.RenameReferenceResult;
81
268
  /**
82
269
  * Schema for an owner update. Omitted fields retain their existing values.
83
270
  *
@@ -95,6 +282,35 @@ export const TimeUpdate = VfsModel.TimeUpdate;
95
282
  /**
96
283
  * Schema for independent access and modification time updates.
97
284
  *
285
+ * @example
286
+ * ```ts
287
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
288
+ * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
289
+ * import { Effect } from "effect"
290
+ *
291
+ * const program = Effect.gen(function*() {
292
+ * const caller = yield* (yield* Vfs.make()).caller()
293
+ *
294
+ * yield* caller.writeFile("/f", new Uint8Array([1]), {
295
+ * access: "write",
296
+ * create: "exclusive"
297
+ * })
298
+ *
299
+ * // Each field is set to a value, to the current clock, or left alone.
300
+ * yield* caller.utimes("/f", {
301
+ * access: { kind: "value", nanoseconds: 1_000n },
302
+ * modification: { kind: "omit" }
303
+ * })
304
+ *
305
+ * const metadata = yield* caller.stat("/f")
306
+ *
307
+ * return [metadata.atimeNs, metadata.mtimeNs === 1_000n]
308
+ * })
309
+ *
310
+ * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
311
+ * // [ 1000n, false ]
312
+ * ```
313
+ *
98
314
  * @category schemas
99
315
  * @since 0.1.0
100
316
  */
@@ -109,10 +325,65 @@ export const SeekMode = VfsModel.SeekMode;
109
325
  /**
110
326
  * Schema for file access, creation, append, truncate, and symlink behavior.
111
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
+ * // `access` is required; `create` decides whether a missing file is an error.
335
+ * const program = Effect.gen(function*() {
336
+ * const caller = yield* (yield* Vfs.make()).caller()
337
+ *
338
+ * const handle = yield* caller.open("/log.txt", {
339
+ * access: "readWrite",
340
+ * create: "ifMissing",
341
+ * append: true
342
+ * })
343
+ *
344
+ * yield* handle.write(new Uint8Array([1, 2]))
345
+ *
346
+ * return (yield* handle.stat).size
347
+ * }).pipe(Effect.scoped)
348
+ *
349
+ * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
350
+ * // 2n
351
+ * ```
352
+ *
112
353
  * @category schemas
113
354
  * @since 0.1.0
114
355
  */
115
356
  export const OpenSettings = VfsModel.OpenSettings;
357
+ /**
358
+ * Settings for creating a directory through a parent object reference.
359
+ * `exactMode` skips the caller's umask for an explicit creation mode; permission policy still applies.
360
+ *
361
+ * @category schemas
362
+ * @since 0.4.0
363
+ */
364
+ export const MkdirReferenceSettings = VfsModel.MkdirReferenceSettings;
365
+ /**
366
+ * Settings for creating a symbolic link through a parent object reference.
367
+ *
368
+ * @category schemas
369
+ * @since 0.4.0
370
+ */
371
+ export const SymlinkReferenceSettings = VfsModel.SymlinkReferenceSettings;
372
+ /**
373
+ * Settings for opening an existing regular-file reference.
374
+ *
375
+ * @category schemas
376
+ * @since 0.4.0
377
+ */
378
+ export const OpenReferenceSettings = VfsModel.OpenReferenceSettings;
379
+ /**
380
+ * Settings for atomically looking up or creating and opening one referenced child.
381
+ * `exactMode` skips the caller's umask for an explicit creation mode; permission policy still applies.
382
+ *
383
+ * @category schemas
384
+ * @since 0.4.0
385
+ */
386
+ export const OpenChildReferenceSettings = VfsModel.OpenChildReferenceSettings;
116
387
  /**
117
388
  * Schema for the filesystem entry kinds reported by overlay summaries.
118
389
  *
@@ -142,6 +413,29 @@ export const OverlayChange = VfsModel.OverlayChange;
142
413
  /**
143
414
  * Schema for overlay summary filtering.
144
415
  *
416
+ * @example
417
+ * ```ts
418
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
419
+ * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
420
+ * import { Effect } from "effect"
421
+ *
422
+ * const program = Effect.gen(function*() {
423
+ * const base = yield* Vfs.make()
424
+ * const workspace = yield* Vfs.makeOverlay(yield* base.snapshot)
425
+ *
426
+ * yield* (yield* workspace.caller()).mkdir("/out")
427
+ *
428
+ * // Timestamps are excluded by default, since they change on every write.
429
+ * const plain = yield* workspace.changes()
430
+ * const timed = yield* workspace.changes({ includeTimestamps: true })
431
+ *
432
+ * return [plain.length, timed.length]
433
+ * })
434
+ *
435
+ * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
436
+ * // [ 1, 2 ]
437
+ * ```
438
+ *
145
439
  * @category schemas
146
440
  * @since 0.1.0
147
441
  */
@@ -149,13 +443,70 @@ export const OverlayChangesOptions = VfsModel.OverlayChangesOptions;
149
443
  /**
150
444
  * Optional Effect service for providing an existing filesystem caller.
151
445
  *
446
+ * @example
447
+ * ```ts
448
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
449
+ * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
450
+ * import { Effect } from "effect"
451
+ *
452
+ * // Code depending on the service does not choose the volume it runs against.
453
+ * const readVersion = Effect.gen(function*() {
454
+ * const caller = yield* Vfs.CurrentFileSystem
455
+ *
456
+ * return yield* caller.readFile("/version")
457
+ * })
458
+ *
459
+ * const program = Effect.gen(function*() {
460
+ * const volume = yield* Vfs.make()
461
+ * const caller = yield* volume.caller()
462
+ *
463
+ * yield* caller.writeFile("/version", new Uint8Array([49]), {
464
+ * access: "write",
465
+ * create: "exclusive"
466
+ * })
467
+ *
468
+ * return yield* readVersion.pipe(Effect.provideService(Vfs.CurrentFileSystem, caller))
469
+ * })
470
+ *
471
+ * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
472
+ * // Uint8Array(1) [ 49 ]
473
+ * ```
474
+ *
475
+ * @see The testing guide at `/guides/testing-with-an-isolated-filesystem`.
152
476
  * @category services
153
477
  * @since 0.1.0
154
478
  */
155
- export const CurrentFileSystem = VfsModel.CurrentFileSystem;
479
+ export const CurrentFileSystem = Context.Service("@effect-vfs/core/CurrentFileSystem");
156
480
  /**
157
481
  * Encodes a snapshot as owned UTF-8 JSON bytes using the version 1 snapshot format.
158
482
  *
483
+ * @example
484
+ * ```ts
485
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
486
+ * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
487
+ * import { Effect } from "effect"
488
+ *
489
+ * // Bytes are portable: store or transmit them, then decode under explicit limits.
490
+ * const program = Effect.gen(function*() {
491
+ * const volume = yield* Vfs.make()
492
+ * const caller = yield* volume.caller()
493
+ *
494
+ * yield* caller.writeFile("/f", new Uint8Array([1, 2, 3]), {
495
+ * access: "write",
496
+ * create: "exclusive"
497
+ * })
498
+ *
499
+ * const bytes = yield* Vfs.encodeSnapshot(yield* volume.snapshot)
500
+ *
501
+ * // The bytes are UTF-8 JSON carrying the version 1 snapshot format.
502
+ * return JSON.parse(new TextDecoder().decode(bytes)).version
503
+ * })
504
+ *
505
+ * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
506
+ * // 1
507
+ * ```
508
+ *
509
+ * @see {@link decodeSnapshot} for reading the bytes back under limits.
159
510
  * @category serialization
160
511
  * @since 0.1.0
161
512
  */
@@ -163,18 +514,75 @@ export const encodeSnapshot = Image.encodeSnapshot;
163
514
  /**
164
515
  * Decodes version 1 snapshot bytes while enforcing explicit input and payload limits.
165
516
  *
517
+ * @example
518
+ * ```ts
519
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
520
+ * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
521
+ * import { ByteSize, Effect } from "effect"
522
+ *
523
+ * // Limits are mandatory: decoding is the boundary where untrusted input arrives.
524
+ * const program = Effect.gen(function*() {
525
+ * const volume = yield* Vfs.make()
526
+ * const bytes = yield* Vfs.encodeSnapshot(yield* volume.snapshot)
527
+ *
528
+ * return yield* Vfs.decodeSnapshot(bytes, {
529
+ * maxEncodedBytes: ByteSize.bytes(1),
530
+ * maxRecords: 10_000,
531
+ * maxEntries: 10_000,
532
+ * maxDecodedBytes: ByteSize.megabytes(16)
533
+ * }).pipe(
534
+ * Effect.as("accepted"),
535
+ * Effect.catchTag("ImageError", (error) => Effect.succeed([error.code, error.field]))
536
+ * )
537
+ * })
538
+ *
539
+ * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
540
+ * // [ 'LimitExceeded', 'encodedBytes' ]
541
+ * ```
542
+ *
543
+ * @see {@link DecodeLimits} for what each bound protects.
166
544
  * @category serialization
167
545
  * @since 0.1.0
168
546
  */
169
547
  export const decodeSnapshot = Image.decodeSnapshot;
170
548
  const deltaLimits = (limits) => {
171
- const decoded = Path.decodeConfiguration(SnapshotDeltaModel.SnapshotDeltaLimits, limits ?? SnapshotDeltaModel.SnapshotDeltaLimits.default);
549
+ const decoded = decodeConfiguration(SnapshotDeltaModel.SnapshotDeltaLimits, limits ?? SnapshotDeltaModel.SnapshotDeltaLimits.default);
172
550
  return Result.isFailure(decoded) ? Effect.fail(decoded.failure) : Effect.succeed(decoded.success);
173
551
  };
174
552
  /**
175
553
  * Computes an exact portable delta between two immutable snapshots.
176
- * Requires the platform-neutral `Crypto.Crypto` service for base identity.
554
+ * Requires the platform-neutral `Crypto.Crypto` service for base identity. Provide
555
+ * `NodeCrypto.layer`, `BunCrypto.layer`, or your own via `Crypto.make`.
556
+ *
557
+ * @example
558
+ * ```ts
559
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
560
+ * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
561
+ * import { Effect } from "effect"
562
+ *
563
+ * // Two snapshots in, one opaque delta out. `inspectSnapshotDelta` reads it.
564
+ * const program = Effect.gen(function*() {
565
+ * const volume = yield* Vfs.make()
566
+ * const caller = yield* volume.caller()
567
+ * const base = yield* volume.snapshot
568
+ *
569
+ * yield* caller.writeFile("/added.txt", new Uint8Array([1]), {
570
+ * access: "write",
571
+ * create: "exclusive"
572
+ * })
573
+ *
574
+ * const delta = yield* Vfs.diffSnapshots(base, yield* volume.snapshot)
575
+ *
576
+ * const changes = yield* Vfs.inspectSnapshotDelta(base, delta)
577
+ *
578
+ * return new TextDecoder().decode(yield* Vfs.pathToBytes(changes[0]!.path))
579
+ * }).pipe(Effect.provide(NodeCrypto.layer))
580
+ *
581
+ * Effect.runPromise(program).then(console.log)
582
+ * // /added.txt
583
+ * ```
177
584
  *
585
+ * @see {@link inspectSnapshotDelta} to read a delta, {@link applySnapshotDelta} to apply one.
178
586
  * @category snapshots
179
587
  * @since 0.1.0
180
588
  */
@@ -183,21 +591,80 @@ export const diffSnapshots = Effect.fn("VirtualFileSystem.diffSnapshots")(functi
183
591
  });
184
592
  /**
185
593
  * Verifies an exact snapshot delta against its base and derives an owned path-oriented summary.
186
- * Requires the platform-neutral `Crypto.Crypto` service for base identity.
594
+ * Requires the platform-neutral `Crypto.Crypto` service for base identity. Provide
595
+ * `NodeCrypto.layer`, `BunCrypto.layer`, or your own via `Crypto.make`.
187
596
  *
597
+ * @example
598
+ * ```ts
599
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
600
+ * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
601
+ * import { Effect } from "effect"
602
+ *
603
+ * // Inspection verifies the delta against its base before describing it.
604
+ * const program = Effect.gen(function*() {
605
+ * const volume = yield* Vfs.make()
606
+ * const caller = yield* volume.caller()
607
+ * const base = yield* volume.snapshot
608
+ *
609
+ * yield* caller.mkdir("/logs")
610
+ *
611
+ * const delta = yield* Vfs.diffSnapshots(base, yield* volume.snapshot)
612
+ * const changes = yield* Vfs.inspectSnapshotDelta(base, delta)
613
+ *
614
+ * return changes.map((change) =>
615
+ * change._tag === "Updated" ? change.afterKind : change.kind)
616
+ * }).pipe(Effect.provide(NodeCrypto.layer))
617
+ *
618
+ * Effect.runPromise(program).then(console.log)
619
+ * // [ 'directory' ]
620
+ * ```
621
+ *
622
+ * @see {@link SnapshotChangesOptions} for including timestamp differences.
188
623
  * @category snapshots
189
624
  * @since 0.1.0
190
625
  */
191
626
  export const inspectSnapshotDelta = Effect.fn("VirtualFileSystem.inspectSnapshotDelta")(function* (base, delta, options, limits) {
192
- const decoded = Path.decodeConfiguration(SnapshotDeltaModel.SnapshotChangesOptions, options ?? {});
627
+ const decoded = decodeConfiguration(SnapshotDeltaModel.SnapshotChangesOptions, options ?? {});
193
628
  if (Result.isFailure(decoded))
194
629
  return yield* decoded.failure;
195
630
  return yield* SnapshotDeltaInternal.inspectSnapshotDelta(base, delta, decoded.success, yield* deltaLimits(limits));
196
631
  });
197
632
  /**
198
633
  * Applies an exact delta to its semantically matching base and returns a new snapshot.
199
- * Requires the platform-neutral `Crypto.Crypto` service for base identity.
634
+ * Requires the platform-neutral `Crypto.Crypto` service for base identity. Provide
635
+ * `NodeCrypto.layer`, `BunCrypto.layer`, or your own via `Crypto.make`.
636
+ *
637
+ * @example
638
+ * ```ts
639
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
640
+ * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
641
+ * import { Effect } from "effect"
642
+ *
643
+ * const program = Effect.gen(function*() {
644
+ * const volume = yield* Vfs.make()
645
+ * const caller = yield* volume.caller()
646
+ * const base = yield* volume.snapshot
647
+ *
648
+ * yield* caller.writeFile("/f", new Uint8Array([7]), {
649
+ * access: "write",
650
+ * create: "exclusive"
651
+ * })
652
+ *
653
+ * const delta = yield* Vfs.diffSnapshots(base, yield* volume.snapshot)
200
654
  *
655
+ * // Applying the delta to the same base reproduces the target snapshot.
656
+ * const applied = yield* Vfs.applySnapshotDelta(base, delta)
657
+ * const restored = yield* Vfs.fromSnapshot(applied)
658
+ * const reader = yield* restored.caller()
659
+ *
660
+ * return yield* reader.readFile("/f")
661
+ * }).pipe(Effect.provide(NodeCrypto.layer))
662
+ *
663
+ * Effect.runPromise(program).then(console.log)
664
+ * // Uint8Array(1) [ 7 ]
665
+ * ```
666
+ *
667
+ * @see {@link SnapshotDeltaFromBytes} for moving a delta across a boundary.
201
668
  * @category snapshots
202
669
  * @since 0.1.0
203
670
  */
@@ -209,6 +676,34 @@ const deltaSchemaIssue = (cause, input, options) => new SchemaIssue.InvalidValue
209
676
  * Creates an Effect Schema codec between owned bytes and opaque snapshot deltas.
210
677
  * Omission uses `SnapshotDeltaLimits.default`.
211
678
  *
679
+ * @example
680
+ * ```ts
681
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
682
+ * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
683
+ * import { Effect, Schema } from "effect"
684
+ *
685
+ * // Omitting limits uses `SnapshotDeltaLimits.default`.
686
+ * const codec = Vfs.SnapshotDeltaFromBytes()
687
+ *
688
+ * const program = Effect.gen(function*() {
689
+ * const volume = yield* Vfs.make()
690
+ * const base = yield* volume.snapshot
691
+ *
692
+ * yield* (yield* volume.caller()).mkdir("/out")
693
+ *
694
+ * const delta = yield* Vfs.diffSnapshots(base, yield* volume.snapshot)
695
+ * const bytes = yield* Schema.encodeEffect(codec)(delta)
696
+ *
697
+ * // The delta survives the trip through bytes and still applies to its base.
698
+ * const decoded = yield* Schema.decodeEffect(codec)(bytes)
699
+ *
700
+ * return (yield* Vfs.inspectSnapshotDelta(base, decoded)).map((change) => change._tag)
701
+ * }).pipe(Effect.provide(NodeCrypto.layer))
702
+ *
703
+ * Effect.runPromise(program).then(console.log)
704
+ * // [ 'Added' ]
705
+ * ```
706
+ *
212
707
  * @category schemas
213
708
  * @since 0.1.0
214
709
  */
@@ -230,6 +725,28 @@ export const SnapshotDeltaFromBytes = (limits) => {
230
725
  *
231
726
  * Shared-memory-backed and detached views fail with `InvalidArgument`.
232
727
  *
728
+ * @example
729
+ * ```ts
730
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
731
+ * import { Effect } from "effect"
732
+ *
733
+ * // Any byte except NUL is accepted. Empty, NUL-bearing, detached, and shared-memory
734
+ * // input all fail with `InvalidArgument`.
735
+ * const program = Effect.gen(function*() {
736
+ * const path = yield* Vfs.pathFromBytes(new Uint8Array([47, 116, 109, 112]))
737
+ *
738
+ * const rejected = yield* Vfs.pathFromBytes(new Uint8Array([47, 0])).pipe(
739
+ * Effect.as("accepted"),
740
+ * Effect.catchTag("FsError", (error) => Effect.succeed(error.code))
741
+ * )
742
+ *
743
+ * return [yield* Vfs.pathToBytes(path), rejected]
744
+ * })
745
+ *
746
+ * Effect.runPromise(program).then(console.log)
747
+ * // [ Uint8Array(4) [ 47, 116, 109, 112 ], 'InvalidArgument' ]
748
+ * ```
749
+ *
233
750
  * @category constructors
234
751
  * @since 0.1.0
235
752
  */
@@ -248,6 +765,27 @@ export const pathToBytes = Path.pathToBytes;
248
765
  *
249
766
  * Fixture paths must be absolute, unique, and explicitly include their parent directories.
250
767
  *
768
+ * @example
769
+ * ```ts
770
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
771
+ * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
772
+ * import { Effect, Schema } from "effect"
773
+ *
774
+ * // Fixtures must list every parent directory before its children.
775
+ * const program = Schema.decodeUnknownEffect(Vfs.Fixture)({
776
+ * entries: [
777
+ * { kind: "file", path: "/etc/hosts", bytes: new TextEncoder().encode("127.0.0.1") }
778
+ * ]
779
+ * }).pipe(
780
+ * Effect.flatMap(Vfs.fromFixture),
781
+ * Effect.as("built"),
782
+ * Effect.catchTag("ImageError", (error) => Effect.succeed(`rejected: ${error.code}`))
783
+ * )
784
+ *
785
+ * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
786
+ * // rejected: InvalidStructure
787
+ * ```
788
+ *
251
789
  * @category schemas
252
790
  * @since 0.1.0
253
791
  */
@@ -260,6 +798,27 @@ export const Fixture = VfsModel.Fixture;
260
798
  * Each execution creates independent storage. Snapshot image failures cannot
261
799
  * arise because this constructor does not accept persisted input.
262
800
  *
801
+ * @example
802
+ * ```ts
803
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
804
+ * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
805
+ * import { Effect } from "effect"
806
+ *
807
+ * const program = Effect.gen(function*() {
808
+ * const volume = yield* Vfs.make()
809
+ * const caller = yield* volume.caller()
810
+ *
811
+ * yield* caller.mkdir("/work")
812
+ * yield* caller.writeFile("/work/notes.txt", new Uint8Array([104, 105]), { access: "write", create: "exclusive" })
813
+ *
814
+ * return yield* caller.readFile("/work/notes.txt")
815
+ * })
816
+ *
817
+ * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
818
+ * // Uint8Array(2) [ 104, 105 ]
819
+ * ```
820
+ *
821
+ * @see The testing guide at `/guides/testing-with-an-isolated-filesystem`.
263
822
  * @category constructors
264
823
  * @since 0.1.0
265
824
  */
@@ -267,6 +826,41 @@ export const make = VfsModel.make;
267
826
  /**
268
827
  * Restores a fresh volume from an opaque snapshot under the supplied destination limits.
269
828
  *
829
+ * @example
830
+ * ```ts
831
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
832
+ * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
833
+ * import { Effect } from "effect"
834
+ *
835
+ * const program = Effect.gen(function*() {
836
+ * const original = yield* Vfs.make()
837
+ * const source = yield* original.caller()
838
+ *
839
+ * yield* source.writeFile("/seed.txt", new TextEncoder().encode("one"), {
840
+ * access: "write",
841
+ * create: "exclusive"
842
+ * })
843
+ *
844
+ * const restored = yield* Vfs.fromSnapshot(yield* original.snapshot)
845
+ * const copy = yield* restored.caller()
846
+ *
847
+ * // Writing to the restore leaves the original untouched: the two are independent.
848
+ * yield* copy.writeFile("/seed.txt", new TextEncoder().encode("two"), {
849
+ * access: "write",
850
+ * truncate: true
851
+ * })
852
+ *
853
+ * return [
854
+ * new TextDecoder().decode(yield* source.readFile("/seed.txt")),
855
+ * new TextDecoder().decode(yield* copy.readFile("/seed.txt"))
856
+ * ]
857
+ * })
858
+ *
859
+ * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
860
+ * // [ 'one', 'two' ]
861
+ * ```
862
+ *
863
+ * @see {@link makeOverlay} for a copy-on-write workspace over the same base.
270
864
  * @category constructors
271
865
  * @since 0.1.0
272
866
  */
@@ -284,6 +878,40 @@ export const fromSnapshot = VfsModel.fromSnapshot;
284
878
  * Invalid base snapshots fail with `ImageError`; invalid volume limits fail
285
879
  * with `ConfigurationError`. Each execution creates a fresh workspace.
286
880
  *
881
+ * @example
882
+ * ```ts
883
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
884
+ * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
885
+ * import { Effect } from "effect"
886
+ *
887
+ * const program = Effect.gen(function*() {
888
+ * const base = yield* Vfs.make()
889
+ * const origin = yield* base.caller()
890
+ *
891
+ * yield* origin.writeFile("/config.json", new TextEncoder().encode("{}"), {
892
+ * access: "write",
893
+ * create: "exclusive"
894
+ * })
895
+ *
896
+ * const workspace = yield* Vfs.makeOverlay(yield* base.snapshot)
897
+ *
898
+ * yield* (yield* workspace.caller()).writeFile("/config.json", new TextEncoder().encode("[]"), {
899
+ * access: "write",
900
+ * truncate: true
901
+ * })
902
+ *
903
+ * // The write landed in the workspace only; the base still reads its own value.
904
+ * return [
905
+ * new TextDecoder().decode(yield* origin.readFile("/config.json")),
906
+ * (yield* workspace.changes()).map((change) => change._tag)
907
+ * ]
908
+ * })
909
+ *
910
+ * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
911
+ * // [ '{}', [ 'Updated' ] ]
912
+ * ```
913
+ *
914
+ * @see The overlay guide at `/guides/overlay-filesystems`.
287
915
  * @category constructors
288
916
  * @since 0.1.0
289
917
  */
@@ -291,6 +919,36 @@ export const makeOverlay = VfsModel.makeOverlay;
291
919
  /**
292
920
  * Builds a fresh volume from a validated final-state fixture.
293
921
  *
922
+ * @example
923
+ * ```ts
924
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
925
+ * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
926
+ * import { Effect } from "effect"
927
+ *
928
+ * const encoder = new TextEncoder()
929
+ *
930
+ * const program = Effect.gen(function*() {
931
+ * // Parent directories must be listed explicitly, before their children.
932
+ * const volume = yield* Vfs.fromFixture({
933
+ * entries: [
934
+ * { kind: "directory", path: "/project" },
935
+ * {
936
+ * kind: "file",
937
+ * path: "/project/package.json",
938
+ * bytes: encoder.encode(`{"version":"1.2.3"}`)
939
+ * },
940
+ * { kind: "symlink", path: "/project/latest", target: "/project/package.json" }
941
+ * ]
942
+ * })
943
+ *
944
+ * return yield* (yield* volume.caller()).readDirectory("/project")
945
+ * })
946
+ *
947
+ * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
948
+ * // [ 'package.json', 'latest' ]
949
+ * ```
950
+ *
951
+ * @see The fixtures guide at `/guides/fixtures-and-snapshots`.
294
952
  * @category constructors
295
953
  * @since 0.1.0
296
954
  */