@effect-vfs/core 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/BytePath.d.ts +33 -2
  2. package/dist/BytePath.d.ts.map +1 -1
  3. package/dist/BytePath.js +1 -1
  4. package/dist/Snapshot.d.ts +58 -2
  5. package/dist/Snapshot.d.ts.map +1 -1
  6. package/dist/Snapshot.js +57 -1
  7. package/dist/SnapshotDelta.d.ts +113 -3
  8. package/dist/SnapshotDelta.d.ts.map +1 -1
  9. package/dist/SnapshotDelta.js +112 -2
  10. package/dist/VirtualFileSystem.d.ts +919 -10
  11. package/dist/VirtualFileSystem.d.ts.map +1 -1
  12. package/dist/VirtualFileSystem.js +575 -7
  13. package/dist/index.d.ts +1 -1
  14. package/dist/index.js +1 -1
  15. package/dist/internal/bytePath.d.ts.map +1 -1
  16. package/dist/internal/bytePath.js +5 -10
  17. package/dist/internal/bytes.d.ts +6 -0
  18. package/dist/internal/bytes.d.ts.map +1 -0
  19. package/dist/internal/bytes.js +17 -0
  20. package/dist/internal/canonicalBase64.d.ts +13 -5
  21. package/dist/internal/canonicalBase64.d.ts.map +1 -1
  22. package/dist/internal/canonicalBase64.js +28 -6
  23. package/dist/internal/image.d.ts +76 -90
  24. package/dist/internal/image.d.ts.map +1 -1
  25. package/dist/internal/image.js +98 -89
  26. package/dist/internal/metadata.d.ts +28 -0
  27. package/dist/internal/metadata.d.ts.map +1 -0
  28. package/dist/internal/metadata.js +29 -0
  29. package/dist/internal/snapshotDelta.d.ts.map +1 -1
  30. package/dist/internal/snapshotDelta.js +346 -300
  31. package/dist/internal/snapshotDeltaModel.d.ts +2 -2
  32. package/dist/internal/snapshotDeltaModel.d.ts.map +1 -1
  33. package/dist/internal/snapshotDeltaModel.js +3 -1
  34. package/dist/internal/virtualFileSystem/errors.d.ts +5 -0
  35. package/dist/internal/virtualFileSystem/errors.d.ts.map +1 -1
  36. package/dist/internal/virtualFileSystem/errors.js +19 -0
  37. package/dist/internal/virtualFileSystem/fixture.d.ts.map +1 -1
  38. package/dist/internal/virtualFileSystem/fixture.js +74 -74
  39. package/dist/internal/virtualFileSystem/overlayContent.d.ts.map +1 -1
  40. package/dist/internal/virtualFileSystem/overlayContent.js +9 -2
  41. package/dist/internal/virtualFileSystem/overlayDiff.d.ts +2 -11
  42. package/dist/internal/virtualFileSystem/overlayDiff.d.ts.map +1 -1
  43. package/dist/internal/virtualFileSystem/overlayDiff.js +45 -71
  44. package/dist/internal/virtualFileSystem/path.d.ts +15 -14
  45. package/dist/internal/virtualFileSystem/path.d.ts.map +1 -1
  46. package/dist/internal/virtualFileSystem/path.js +57 -54
  47. package/dist/internal/virtualFileSystem/testHooks.d.ts.map +1 -1
  48. package/dist/internal/virtualFileSystem/watchHub.d.ts.map +1 -1
  49. package/dist/internal/virtualFileSystem/watchHub.js +13 -13
  50. package/dist/internal/virtualFileSystem.d.ts +91 -15
  51. package/dist/internal/virtualFileSystem.d.ts.map +1 -1
  52. package/dist/internal/virtualFileSystem.js +470 -392
  53. package/package.json +15 -5
@@ -13,13 +13,12 @@ import * as Result from "effect/Result";
13
13
  import * as Schema from "effect/Schema";
14
14
  import * as SchemaIssue from "effect/SchemaIssue";
15
15
  import * as SchemaTransformation from "effect/SchemaTransformation";
16
- import { BytePath } from "./BytePath.js";
17
16
  export { BytePath } from "./BytePath.js";
18
- import { DecodeLimits, ImageError } from "./Snapshot.js";
19
17
  export { DecodeLimits, ImageError, SnapshotTypeId } from "./Snapshot.js";
20
18
  import * as Image from "./internal/image.js";
21
19
  import * as SnapshotDeltaInternal from "./internal/snapshotDelta.js";
22
20
  import * as VfsModel from "./internal/virtualFileSystem.js";
21
+ import { decodeConfiguration } from "./internal/virtualFileSystem/errors.js";
23
22
  import * as FixtureInternal from "./internal/virtualFileSystem/fixture.js";
24
23
  import * as Path from "./internal/virtualFileSystem/path.js";
25
24
  import * as SnapshotDeltaModel from "./SnapshotDelta.js";
@@ -32,6 +31,30 @@ const ObjectReferenceId = VfsModel.ObjectReferenceId;
32
31
  /**
33
32
  * Schema for portable virtual filesystem error codes.
34
33
  *
34
+ * @example
35
+ * ```ts
36
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
37
+ * import { Effect, Schema } from "effect"
38
+ *
39
+ * const asCode = Schema.decodeUnknownEffect(Vfs.FsCode)
40
+ *
41
+ * // Useful when a code has crossed a process boundary and arrives back as data.
42
+ * const program = Effect.gen(function*() {
43
+ * const payload = JSON.parse(`{"code":"NotFound","other":"Nonsense"}`)
44
+ *
45
+ * const known = yield* asCode(payload.code)
46
+ *
47
+ * const unknown = yield* asCode(payload.other).pipe(
48
+ * Effect.orElseSucceed(() => "not a code")
49
+ * )
50
+ *
51
+ * return [known, unknown]
52
+ * })
53
+ *
54
+ * Effect.runPromise(program).then(console.log)
55
+ * // [ 'NotFound', 'not a code' ]
56
+ * ```
57
+ *
35
58
  * @category schemas
36
59
  * @since 0.1.0
37
60
  */
@@ -39,6 +62,28 @@ export const FsCode = VfsModel.FsCode;
39
62
  /**
40
63
  * Describes an expected filesystem operation failure.
41
64
  *
65
+ * @example
66
+ * ```ts
67
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
68
+ * import { Effect } from "effect"
69
+ *
70
+ * const program = Effect.gen(function*() {
71
+ * const caller = yield* (yield* Vfs.make()).caller()
72
+ *
73
+ * // Every filesystem operation fails with this one tagged error; `code`
74
+ * // distinguishes the cases, so match on it rather than on the tag alone.
75
+ * return yield* caller.readFile("/missing").pipe(
76
+ * Effect.catchTag("FsError", (error) =>
77
+ * error.code === "NotFound"
78
+ * ? Effect.succeed(new Uint8Array())
79
+ * : Effect.fail(error))
80
+ * )
81
+ * })
82
+ *
83
+ * Effect.runPromise(program).then(console.log)
84
+ * // Uint8Array(0) []
85
+ * ```
86
+ *
42
87
  * @category errors
43
88
  * @since 0.1.0
44
89
  */
@@ -46,6 +91,26 @@ export const FsError = VfsModel.FsError;
46
91
  /**
47
92
  * Describes an invalid volume or caller option and names the rejected field.
48
93
  *
94
+ * @example
95
+ * ```ts
96
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
97
+ * import { Effect } from "effect"
98
+ *
99
+ * // `field` names the rejected option, so the caller learns which key was wrong.
100
+ * const program = Effect.gen(function*() {
101
+ * // Options that arrived from a config file or CLI flag, not a literal.
102
+ * const options = JSON.parse(`{"maxEntries":-1}`) as Vfs.VolumeOptions
103
+ *
104
+ * return yield* Vfs.make(options).pipe(
105
+ * Effect.as("ok"),
106
+ * Effect.catchTag("ConfigurationError", (error) => Effect.succeed(`rejected ${error.field}`))
107
+ * )
108
+ * })
109
+ *
110
+ * Effect.runPromise(program).then(console.log)
111
+ * // rejected maxEntries
112
+ * ```
113
+ *
49
114
  * @category errors
50
115
  * @since 0.1.0
51
116
  */
@@ -60,6 +125,33 @@ export const Identity = VfsModel.Identity;
60
125
  /**
61
126
  * Schema for root caller credentials and creation mask.
62
127
  *
128
+ * @example
129
+ * ```ts
130
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
131
+ * import { Effect } from "effect"
132
+ *
133
+ * const program = Effect.gen(function*() {
134
+ * const volume = yield* Vfs.make()
135
+ *
136
+ * // Omitting `identity` yields a privileged root caller.
137
+ * const root = yield* volume.caller({ umask: 0 })
138
+ * const user = yield* volume.caller({
139
+ * identity: { uid: 1000, gid: 1000, groups: [], privileged: false },
140
+ * umask: 0o022
141
+ * })
142
+ *
143
+ * yield* root.mkdir("/home", { mode: 0o777 })
144
+ *
145
+ * // The umask clears its bits from the requested mode.
146
+ * yield* user.mkdir("/home/user", { mode: 0o777 })
147
+ *
148
+ * return ((yield* user.stat("/home/user")).mode & 0o777).toString(8)
149
+ * })
150
+ *
151
+ * Effect.runPromise(program).then(console.log)
152
+ * // 755
153
+ * ```
154
+ *
63
155
  * @category schemas
64
156
  * @since 0.1.0
65
157
  */
@@ -67,6 +159,29 @@ export const RootCallerOptions = VfsModel.RootCallerOptions;
67
159
  /**
68
160
  * Schema for optional volume capacity and path limits.
69
161
  *
162
+ * @example
163
+ * ```ts
164
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
165
+ * import { ByteSize, Effect } from "effect"
166
+ *
167
+ * // Every limit is optional, and each one is enforced once set.
168
+ * const program = Effect.gen(function*() {
169
+ * const volume = yield* Vfs.make({ maxFileBytes: ByteSize.bytes(4) })
170
+ * const caller = yield* volume.caller()
171
+ *
172
+ * return yield* caller.writeFile("/big.bin", new Uint8Array(16), {
173
+ * access: "write",
174
+ * create: "exclusive"
175
+ * }).pipe(
176
+ * Effect.as("written"),
177
+ * Effect.catchTag("FsError", (error) => Effect.succeed(error.code))
178
+ * )
179
+ * })
180
+ *
181
+ * Effect.runPromise(program).then(console.log)
182
+ * // FileTooLarge
183
+ * ```
184
+ *
70
185
  * @category schemas
71
186
  * @since 0.1.0
72
187
  */
@@ -74,6 +189,29 @@ export const VolumeOptions = VfsModel.VolumeOptions;
74
189
  /**
75
190
  * Schema for filesystem node metadata with bigint inode, size, and nanosecond fields.
76
191
  *
192
+ * @example
193
+ * ```ts
194
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
195
+ * import { Effect } from "effect"
196
+ *
197
+ * // Sizes and inode numbers are bigint; times are nanosecond timestamps.
198
+ * const program = Effect.gen(function*() {
199
+ * const caller = yield* (yield* Vfs.make()).caller()
200
+ *
201
+ * yield* caller.writeFile("/f", new Uint8Array([1, 2, 3]), {
202
+ * access: "write",
203
+ * create: "exclusive"
204
+ * })
205
+ *
206
+ * const metadata = yield* caller.stat("/f")
207
+ *
208
+ * return [metadata.kind, metadata.size, metadata.nlink]
209
+ * })
210
+ *
211
+ * Effect.runPromise(program).then(console.log)
212
+ * // [ 'file', 3n, 1 ]
213
+ * ```
214
+ *
77
215
  * @category schemas
78
216
  * @since 0.1.0
79
217
  */
@@ -95,6 +233,34 @@ export const TimeUpdate = VfsModel.TimeUpdate;
95
233
  /**
96
234
  * Schema for independent access and modification time updates.
97
235
  *
236
+ * @example
237
+ * ```ts
238
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
239
+ * import { Effect } from "effect"
240
+ *
241
+ * const program = Effect.gen(function*() {
242
+ * const caller = yield* (yield* Vfs.make()).caller()
243
+ *
244
+ * yield* caller.writeFile("/f", new Uint8Array([1]), {
245
+ * access: "write",
246
+ * create: "exclusive"
247
+ * })
248
+ *
249
+ * // Each field is set to a value, to the current clock, or left alone.
250
+ * yield* caller.utimes("/f", {
251
+ * access: { kind: "value", nanoseconds: 1_000n },
252
+ * modification: { kind: "omit" }
253
+ * })
254
+ *
255
+ * const metadata = yield* caller.stat("/f")
256
+ *
257
+ * return [metadata.atimeNs, metadata.mtimeNs === 1_000n]
258
+ * })
259
+ *
260
+ * Effect.runPromise(program).then(console.log)
261
+ * // [ 1000n, false ]
262
+ * ```
263
+ *
98
264
  * @category schemas
99
265
  * @since 0.1.0
100
266
  */
@@ -109,6 +275,30 @@ export const SeekMode = VfsModel.SeekMode;
109
275
  /**
110
276
  * Schema for file access, creation, append, truncate, and symlink behavior.
111
277
  *
278
+ * @example
279
+ * ```ts
280
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
281
+ * import { Effect } from "effect"
282
+ *
283
+ * // `access` is required; `create` decides whether a missing file is an error.
284
+ * const program = Effect.gen(function*() {
285
+ * const caller = yield* (yield* Vfs.make()).caller()
286
+ *
287
+ * const handle = yield* caller.open("/log.txt", {
288
+ * access: "readWrite",
289
+ * create: "ifMissing",
290
+ * append: true
291
+ * })
292
+ *
293
+ * yield* handle.write(new Uint8Array([1, 2]))
294
+ *
295
+ * return (yield* handle.stat).size
296
+ * }).pipe(Effect.scoped)
297
+ *
298
+ * Effect.runPromise(program).then(console.log)
299
+ * // 2n
300
+ * ```
301
+ *
112
302
  * @category schemas
113
303
  * @since 0.1.0
114
304
  */
@@ -142,6 +332,28 @@ export const OverlayChange = VfsModel.OverlayChange;
142
332
  /**
143
333
  * Schema for overlay summary filtering.
144
334
  *
335
+ * @example
336
+ * ```ts
337
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
338
+ * import { Effect } from "effect"
339
+ *
340
+ * const program = Effect.gen(function*() {
341
+ * const base = yield* Vfs.make()
342
+ * const workspace = yield* Vfs.makeOverlay(yield* base.snapshot)
343
+ *
344
+ * yield* (yield* workspace.caller()).mkdir("/out")
345
+ *
346
+ * // Timestamps are excluded by default, since they change on every write.
347
+ * const plain = yield* workspace.changes()
348
+ * const timed = yield* workspace.changes({ includeTimestamps: true })
349
+ *
350
+ * return [plain.length, timed.length]
351
+ * })
352
+ *
353
+ * Effect.runPromise(program).then(console.log)
354
+ * // [ 1, 2 ]
355
+ * ```
356
+ *
145
357
  * @category schemas
146
358
  * @since 0.1.0
147
359
  */
@@ -149,6 +361,35 @@ export const OverlayChangesOptions = VfsModel.OverlayChangesOptions;
149
361
  /**
150
362
  * Optional Effect service for providing an existing filesystem caller.
151
363
  *
364
+ * @example
365
+ * ```ts
366
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
367
+ * import { Effect } from "effect"
368
+ *
369
+ * // Code depending on the service does not choose the volume it runs against.
370
+ * const readVersion = Effect.gen(function*() {
371
+ * const caller = yield* Vfs.CurrentFileSystem
372
+ *
373
+ * return yield* caller.readFile("/version")
374
+ * })
375
+ *
376
+ * const program = Effect.gen(function*() {
377
+ * const volume = yield* Vfs.make()
378
+ * const caller = yield* volume.caller()
379
+ *
380
+ * yield* caller.writeFile("/version", new Uint8Array([49]), {
381
+ * access: "write",
382
+ * create: "exclusive"
383
+ * })
384
+ *
385
+ * return yield* readVersion.pipe(Effect.provideService(Vfs.CurrentFileSystem, caller))
386
+ * })
387
+ *
388
+ * Effect.runPromise(program).then(console.log)
389
+ * // Uint8Array(1) [ 49 ]
390
+ * ```
391
+ *
392
+ * @see The testing guide at `/guides/testing-with-an-isolated-filesystem`.
152
393
  * @category services
153
394
  * @since 0.1.0
154
395
  */
@@ -156,6 +397,32 @@ export const CurrentFileSystem = VfsModel.CurrentFileSystem;
156
397
  /**
157
398
  * Encodes a snapshot as owned UTF-8 JSON bytes using the version 1 snapshot format.
158
399
  *
400
+ * @example
401
+ * ```ts
402
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
403
+ * import { Effect } from "effect"
404
+ *
405
+ * // Bytes are portable: store or transmit them, then decode under explicit limits.
406
+ * const program = Effect.gen(function*() {
407
+ * const volume = yield* Vfs.make()
408
+ * const caller = yield* volume.caller()
409
+ *
410
+ * yield* caller.writeFile("/f", new Uint8Array([1, 2, 3]), {
411
+ * access: "write",
412
+ * create: "exclusive"
413
+ * })
414
+ *
415
+ * const bytes = yield* Vfs.encodeSnapshot(yield* volume.snapshot)
416
+ *
417
+ * // The bytes are UTF-8 JSON carrying the version 1 snapshot format.
418
+ * return JSON.parse(new TextDecoder().decode(bytes)).version
419
+ * })
420
+ *
421
+ * Effect.runPromise(program).then(console.log)
422
+ * // 1
423
+ * ```
424
+ *
425
+ * @see {@link decodeSnapshot} for reading the bytes back under limits.
159
426
  * @category serialization
160
427
  * @since 0.1.0
161
428
  */
@@ -163,18 +430,74 @@ export const encodeSnapshot = Image.encodeSnapshot;
163
430
  /**
164
431
  * Decodes version 1 snapshot bytes while enforcing explicit input and payload limits.
165
432
  *
433
+ * @example
434
+ * ```ts
435
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
436
+ * import { ByteSize, Effect } from "effect"
437
+ *
438
+ * // Limits are mandatory: decoding is the boundary where untrusted input arrives.
439
+ * const program = Effect.gen(function*() {
440
+ * const volume = yield* Vfs.make()
441
+ * const bytes = yield* Vfs.encodeSnapshot(yield* volume.snapshot)
442
+ *
443
+ * return yield* Vfs.decodeSnapshot(bytes, {
444
+ * maxEncodedBytes: ByteSize.bytes(1),
445
+ * maxRecords: 10_000,
446
+ * maxEntries: 10_000,
447
+ * maxDecodedBytes: ByteSize.megabytes(16)
448
+ * }).pipe(
449
+ * Effect.as("accepted"),
450
+ * Effect.catchTag("ImageError", (error) => Effect.succeed([error.code, error.field]))
451
+ * )
452
+ * })
453
+ *
454
+ * Effect.runPromise(program).then(console.log)
455
+ * // [ 'LimitExceeded', 'encodedBytes' ]
456
+ * ```
457
+ *
458
+ * @see {@link DecodeLimits} for what each bound protects.
166
459
  * @category serialization
167
460
  * @since 0.1.0
168
461
  */
169
462
  export const decodeSnapshot = Image.decodeSnapshot;
170
463
  const deltaLimits = (limits) => {
171
- const decoded = Path.decodeConfiguration(SnapshotDeltaModel.SnapshotDeltaLimits, limits ?? SnapshotDeltaModel.SnapshotDeltaLimits.default);
464
+ const decoded = decodeConfiguration(SnapshotDeltaModel.SnapshotDeltaLimits, limits ?? SnapshotDeltaModel.SnapshotDeltaLimits.default);
172
465
  return Result.isFailure(decoded) ? Effect.fail(decoded.failure) : Effect.succeed(decoded.success);
173
466
  };
174
467
  /**
175
468
  * Computes an exact portable delta between two immutable snapshots.
176
- * Requires the platform-neutral `Crypto.Crypto` service for base identity.
469
+ * Requires the platform-neutral `Crypto.Crypto` service for base identity. Provide
470
+ * `NodeCrypto.layer`, `BunCrypto.layer`, or your own via `Crypto.make`.
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
+ * // Two snapshots in, one opaque delta out. `inspectSnapshotDelta` reads it.
479
+ * const program = Effect.gen(function*() {
480
+ * const volume = yield* Vfs.make()
481
+ * const caller = yield* volume.caller()
482
+ * const base = yield* volume.snapshot
483
+ *
484
+ * yield* caller.writeFile("/added.txt", new Uint8Array([1]), {
485
+ * access: "write",
486
+ * create: "exclusive"
487
+ * })
488
+ *
489
+ * const delta = yield* Vfs.diffSnapshots(base, yield* volume.snapshot)
177
490
  *
491
+ * const changes = yield* Vfs.inspectSnapshotDelta(base, delta)
492
+ *
493
+ * return new TextDecoder().decode(yield* Vfs.pathToBytes(changes[0]!.path))
494
+ * }).pipe(Effect.provide(NodeCrypto.layer))
495
+ *
496
+ * Effect.runPromise(program).then(console.log)
497
+ * // /added.txt
498
+ * ```
499
+ *
500
+ * @see {@link inspectSnapshotDelta} to read a delta, {@link applySnapshotDelta} to apply one.
178
501
  * @category snapshots
179
502
  * @since 0.1.0
180
503
  */
@@ -183,21 +506,80 @@ export const diffSnapshots = Effect.fn("VirtualFileSystem.diffSnapshots")(functi
183
506
  });
184
507
  /**
185
508
  * 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.
509
+ * Requires the platform-neutral `Crypto.Crypto` service for base identity. Provide
510
+ * `NodeCrypto.layer`, `BunCrypto.layer`, or your own via `Crypto.make`.
511
+ *
512
+ * @example
513
+ * ```ts
514
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
515
+ * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
516
+ * import { Effect } from "effect"
517
+ *
518
+ * // Inspection verifies the delta against its base before describing it.
519
+ * const program = Effect.gen(function*() {
520
+ * const volume = yield* Vfs.make()
521
+ * const caller = yield* volume.caller()
522
+ * const base = yield* volume.snapshot
523
+ *
524
+ * yield* caller.mkdir("/logs")
525
+ *
526
+ * const delta = yield* Vfs.diffSnapshots(base, yield* volume.snapshot)
527
+ * const changes = yield* Vfs.inspectSnapshotDelta(base, delta)
187
528
  *
529
+ * return changes.map((change) =>
530
+ * change._tag === "Updated" ? change.afterKind : change.kind)
531
+ * }).pipe(Effect.provide(NodeCrypto.layer))
532
+ *
533
+ * Effect.runPromise(program).then(console.log)
534
+ * // [ 'directory' ]
535
+ * ```
536
+ *
537
+ * @see {@link SnapshotChangesOptions} for including timestamp differences.
188
538
  * @category snapshots
189
539
  * @since 0.1.0
190
540
  */
191
541
  export const inspectSnapshotDelta = Effect.fn("VirtualFileSystem.inspectSnapshotDelta")(function* (base, delta, options, limits) {
192
- const decoded = Path.decodeConfiguration(SnapshotDeltaModel.SnapshotChangesOptions, options ?? {});
542
+ const decoded = decodeConfiguration(SnapshotDeltaModel.SnapshotChangesOptions, options ?? {});
193
543
  if (Result.isFailure(decoded))
194
544
  return yield* decoded.failure;
195
545
  return yield* SnapshotDeltaInternal.inspectSnapshotDelta(base, delta, decoded.success, yield* deltaLimits(limits));
196
546
  });
197
547
  /**
198
548
  * 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.
549
+ * Requires the platform-neutral `Crypto.Crypto` service for base identity. Provide
550
+ * `NodeCrypto.layer`, `BunCrypto.layer`, or your own via `Crypto.make`.
551
+ *
552
+ * @example
553
+ * ```ts
554
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
555
+ * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
556
+ * import { Effect } from "effect"
557
+ *
558
+ * const program = Effect.gen(function*() {
559
+ * const volume = yield* Vfs.make()
560
+ * const caller = yield* volume.caller()
561
+ * const base = yield* volume.snapshot
562
+ *
563
+ * yield* caller.writeFile("/f", new Uint8Array([7]), {
564
+ * access: "write",
565
+ * create: "exclusive"
566
+ * })
567
+ *
568
+ * const delta = yield* Vfs.diffSnapshots(base, yield* volume.snapshot)
569
+ *
570
+ * // Applying the delta to the same base reproduces the target snapshot.
571
+ * const applied = yield* Vfs.applySnapshotDelta(base, delta)
572
+ * const restored = yield* Vfs.fromSnapshot(applied)
573
+ * const reader = yield* restored.caller()
200
574
  *
575
+ * return yield* reader.readFile("/f")
576
+ * }).pipe(Effect.provide(NodeCrypto.layer))
577
+ *
578
+ * Effect.runPromise(program).then(console.log)
579
+ * // Uint8Array(1) [ 7 ]
580
+ * ```
581
+ *
582
+ * @see {@link SnapshotDeltaFromBytes} for moving a delta across a boundary.
201
583
  * @category snapshots
202
584
  * @since 0.1.0
203
585
  */
@@ -209,6 +591,34 @@ const deltaSchemaIssue = (cause, input, options) => new SchemaIssue.InvalidValue
209
591
  * Creates an Effect Schema codec between owned bytes and opaque snapshot deltas.
210
592
  * Omission uses `SnapshotDeltaLimits.default`.
211
593
  *
594
+ * @example
595
+ * ```ts
596
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
597
+ * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
598
+ * import { Effect, Schema } from "effect"
599
+ *
600
+ * // Omitting limits uses `SnapshotDeltaLimits.default`.
601
+ * const codec = Vfs.SnapshotDeltaFromBytes()
602
+ *
603
+ * const program = Effect.gen(function*() {
604
+ * const volume = yield* Vfs.make()
605
+ * const base = yield* volume.snapshot
606
+ *
607
+ * yield* (yield* volume.caller()).mkdir("/out")
608
+ *
609
+ * const delta = yield* Vfs.diffSnapshots(base, yield* volume.snapshot)
610
+ * const bytes = yield* Schema.encodeEffect(codec)(delta)
611
+ *
612
+ * // The delta survives the trip through bytes and still applies to its base.
613
+ * const decoded = yield* Schema.decodeEffect(codec)(bytes)
614
+ *
615
+ * return (yield* Vfs.inspectSnapshotDelta(base, decoded)).map((change) => change._tag)
616
+ * }).pipe(Effect.provide(NodeCrypto.layer))
617
+ *
618
+ * Effect.runPromise(program).then(console.log)
619
+ * // [ 'Added' ]
620
+ * ```
621
+ *
212
622
  * @category schemas
213
623
  * @since 0.1.0
214
624
  */
@@ -230,6 +640,28 @@ export const SnapshotDeltaFromBytes = (limits) => {
230
640
  *
231
641
  * Shared-memory-backed and detached views fail with `InvalidArgument`.
232
642
  *
643
+ * @example
644
+ * ```ts
645
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
646
+ * import { Effect } from "effect"
647
+ *
648
+ * // Any byte except NUL is accepted. Empty, NUL-bearing, detached, and shared-memory
649
+ * // input all fail with `InvalidArgument`.
650
+ * const program = Effect.gen(function*() {
651
+ * const path = yield* Vfs.pathFromBytes(new Uint8Array([47, 116, 109, 112]))
652
+ *
653
+ * const rejected = yield* Vfs.pathFromBytes(new Uint8Array([47, 0])).pipe(
654
+ * Effect.as("accepted"),
655
+ * Effect.catchTag("FsError", (error) => Effect.succeed(error.code))
656
+ * )
657
+ *
658
+ * return [yield* Vfs.pathToBytes(path), rejected]
659
+ * })
660
+ *
661
+ * Effect.runPromise(program).then(console.log)
662
+ * // [ Uint8Array(4) [ 47, 116, 109, 112 ], 'InvalidArgument' ]
663
+ * ```
664
+ *
233
665
  * @category constructors
234
666
  * @since 0.1.0
235
667
  */
@@ -248,6 +680,26 @@ export const pathToBytes = Path.pathToBytes;
248
680
  *
249
681
  * Fixture paths must be absolute, unique, and explicitly include their parent directories.
250
682
  *
683
+ * @example
684
+ * ```ts
685
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
686
+ * import { Effect, Schema } from "effect"
687
+ *
688
+ * // Fixtures must list every parent directory before its children.
689
+ * const program = Schema.decodeUnknownEffect(Vfs.Fixture)({
690
+ * entries: [
691
+ * { kind: "file", path: "/etc/hosts", bytes: new TextEncoder().encode("127.0.0.1") }
692
+ * ]
693
+ * }).pipe(
694
+ * Effect.flatMap(Vfs.fromFixture),
695
+ * Effect.as("built"),
696
+ * Effect.catchTag("ImageError", (error) => Effect.succeed(`rejected: ${error.code}`))
697
+ * )
698
+ *
699
+ * Effect.runPromise(program).then(console.log)
700
+ * // rejected: InvalidStructure
701
+ * ```
702
+ *
251
703
  * @category schemas
252
704
  * @since 0.1.0
253
705
  */
@@ -260,6 +712,26 @@ export const Fixture = VfsModel.Fixture;
260
712
  * Each execution creates independent storage. Snapshot image failures cannot
261
713
  * arise because this constructor does not accept persisted input.
262
714
  *
715
+ * @example
716
+ * ```ts
717
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
718
+ * import { Effect } from "effect"
719
+ *
720
+ * const program = Effect.gen(function*() {
721
+ * const volume = yield* Vfs.make()
722
+ * const caller = yield* volume.caller()
723
+ *
724
+ * yield* caller.mkdir("/work")
725
+ * yield* caller.writeFile("/work/notes.txt", new Uint8Array([104, 105]), { access: "write", create: "exclusive" })
726
+ *
727
+ * return yield* caller.readFile("/work/notes.txt")
728
+ * })
729
+ *
730
+ * Effect.runPromise(program).then(console.log)
731
+ * // Uint8Array(2) [ 104, 105 ]
732
+ * ```
733
+ *
734
+ * @see The testing guide at `/guides/testing-with-an-isolated-filesystem`.
263
735
  * @category constructors
264
736
  * @since 0.1.0
265
737
  */
@@ -267,6 +739,40 @@ export const make = VfsModel.make;
267
739
  /**
268
740
  * Restores a fresh volume from an opaque snapshot under the supplied destination limits.
269
741
  *
742
+ * @example
743
+ * ```ts
744
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
745
+ * import { Effect } from "effect"
746
+ *
747
+ * const program = Effect.gen(function*() {
748
+ * const original = yield* Vfs.make()
749
+ * const source = yield* original.caller()
750
+ *
751
+ * yield* source.writeFile("/seed.txt", new TextEncoder().encode("one"), {
752
+ * access: "write",
753
+ * create: "exclusive"
754
+ * })
755
+ *
756
+ * const restored = yield* Vfs.fromSnapshot(yield* original.snapshot)
757
+ * const copy = yield* restored.caller()
758
+ *
759
+ * // Writing to the restore leaves the original untouched: the two are independent.
760
+ * yield* copy.writeFile("/seed.txt", new TextEncoder().encode("two"), {
761
+ * access: "write",
762
+ * truncate: true
763
+ * })
764
+ *
765
+ * return [
766
+ * new TextDecoder().decode(yield* source.readFile("/seed.txt")),
767
+ * new TextDecoder().decode(yield* copy.readFile("/seed.txt"))
768
+ * ]
769
+ * })
770
+ *
771
+ * Effect.runPromise(program).then(console.log)
772
+ * // [ 'one', 'two' ]
773
+ * ```
774
+ *
775
+ * @see {@link makeOverlay} for a copy-on-write workspace over the same base.
270
776
  * @category constructors
271
777
  * @since 0.1.0
272
778
  */
@@ -284,6 +790,39 @@ export const fromSnapshot = VfsModel.fromSnapshot;
284
790
  * Invalid base snapshots fail with `ImageError`; invalid volume limits fail
285
791
  * with `ConfigurationError`. Each execution creates a fresh workspace.
286
792
  *
793
+ * @example
794
+ * ```ts
795
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
796
+ * import { Effect } from "effect"
797
+ *
798
+ * const program = Effect.gen(function*() {
799
+ * const base = yield* Vfs.make()
800
+ * const origin = yield* base.caller()
801
+ *
802
+ * yield* origin.writeFile("/config.json", new TextEncoder().encode("{}"), {
803
+ * access: "write",
804
+ * create: "exclusive"
805
+ * })
806
+ *
807
+ * const workspace = yield* Vfs.makeOverlay(yield* base.snapshot)
808
+ *
809
+ * yield* (yield* workspace.caller()).writeFile("/config.json", new TextEncoder().encode("[]"), {
810
+ * access: "write",
811
+ * truncate: true
812
+ * })
813
+ *
814
+ * // The write landed in the workspace only; the base still reads its own value.
815
+ * return [
816
+ * new TextDecoder().decode(yield* origin.readFile("/config.json")),
817
+ * (yield* workspace.changes()).map((change) => change._tag)
818
+ * ]
819
+ * })
820
+ *
821
+ * Effect.runPromise(program).then(console.log)
822
+ * // [ '{}', [ 'Updated' ] ]
823
+ * ```
824
+ *
825
+ * @see The overlay guide at `/guides/overlay-filesystems`.
287
826
  * @category constructors
288
827
  * @since 0.1.0
289
828
  */
@@ -291,6 +830,35 @@ export const makeOverlay = VfsModel.makeOverlay;
291
830
  /**
292
831
  * Builds a fresh volume from a validated final-state fixture.
293
832
  *
833
+ * @example
834
+ * ```ts
835
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
836
+ * import { Effect } from "effect"
837
+ *
838
+ * const encoder = new TextEncoder()
839
+ *
840
+ * const program = Effect.gen(function*() {
841
+ * // Parent directories must be listed explicitly, before their children.
842
+ * const volume = yield* Vfs.fromFixture({
843
+ * entries: [
844
+ * { kind: "directory", path: "/project" },
845
+ * {
846
+ * kind: "file",
847
+ * path: "/project/package.json",
848
+ * bytes: encoder.encode(`{"version":"1.2.3"}`)
849
+ * },
850
+ * { kind: "symlink", path: "/project/latest", target: "/project/package.json" }
851
+ * ]
852
+ * })
853
+ *
854
+ * return yield* (yield* volume.caller()).readDirectory("/project")
855
+ * })
856
+ *
857
+ * Effect.runPromise(program).then(console.log)
858
+ * // [ 'package.json', 'latest' ]
859
+ * ```
860
+ *
861
+ * @see The fixtures guide at `/guides/fixtures-and-snapshots`.
294
862
  * @category constructors
295
863
  * @since 0.1.0
296
864
  */