@effect-vfs/core 0.5.0 → 0.7.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 (130) hide show
  1. package/README.md +29 -21
  2. package/dist/BytePath.d.ts +115 -4
  3. package/dist/BytePath.d.ts.map +1 -1
  4. package/dist/BytePath.js +155 -0
  5. package/dist/Caller.d.ts +391 -0
  6. package/dist/Caller.d.ts.map +1 -0
  7. package/dist/Caller.js +199 -0
  8. package/dist/FileHandle.d.ts +50 -0
  9. package/dist/FileHandle.d.ts.map +1 -0
  10. package/dist/FileHandle.js +28 -0
  11. package/dist/Fixture.d.ts +155 -0
  12. package/dist/Fixture.d.ts.map +1 -0
  13. package/dist/Fixture.js +65 -0
  14. package/dist/LiveVolume.d.ts +13 -20
  15. package/dist/LiveVolume.d.ts.map +1 -1
  16. package/dist/LiveVolume.js +19 -14
  17. package/dist/Metadata.d.ts +186 -0
  18. package/dist/Metadata.d.ts.map +1 -0
  19. package/dist/Metadata.js +125 -0
  20. package/dist/Snapshot.d.ts +32 -53
  21. package/dist/Snapshot.d.ts.map +1 -1
  22. package/dist/Snapshot.js +27 -49
  23. package/dist/SnapshotDelta.d.ts +24 -39
  24. package/dist/SnapshotDelta.d.ts.map +1 -1
  25. package/dist/SnapshotDelta.js +14 -35
  26. package/dist/Target.d.ts +225 -0
  27. package/dist/Target.d.ts.map +1 -0
  28. package/dist/Target.js +92 -0
  29. package/dist/Testing.d.ts +132 -0
  30. package/dist/Testing.d.ts.map +1 -0
  31. package/dist/Testing.js +100 -0
  32. package/dist/VfsError.d.ts +200 -0
  33. package/dist/VfsError.d.ts.map +1 -0
  34. package/dist/VfsError.js +165 -0
  35. package/dist/VirtualFileSystem.d.ts +676 -353
  36. package/dist/VirtualFileSystem.d.ts.map +1 -1
  37. package/dist/VirtualFileSystem.js +487 -121
  38. package/dist/Volume.d.ts +211 -0
  39. package/dist/Volume.d.ts.map +1 -0
  40. package/dist/Volume.js +172 -0
  41. package/dist/Watch.d.ts +68 -0
  42. package/dist/Watch.d.ts.map +1 -0
  43. package/dist/Watch.js +46 -0
  44. package/dist/index.d.ts +56 -1
  45. package/dist/index.d.ts.map +1 -1
  46. package/dist/index.js +56 -1
  47. package/dist/internal/budget.d.ts +90 -0
  48. package/dist/internal/budget.d.ts.map +1 -0
  49. package/dist/internal/budget.js +70 -0
  50. package/dist/internal/bytePath.d.ts +2 -0
  51. package/dist/internal/bytePath.d.ts.map +1 -1
  52. package/dist/internal/bytePath.js +3 -2
  53. package/dist/internal/bytes.d.ts +3 -0
  54. package/dist/internal/bytes.d.ts.map +1 -1
  55. package/dist/internal/bytes.js +4 -2
  56. package/dist/internal/canonicalBase64.d.ts +1 -4
  57. package/dist/internal/canonicalBase64.d.ts.map +1 -1
  58. package/dist/internal/canonicalBase64.js +9 -13
  59. package/dist/internal/errors.d.ts +47 -5
  60. package/dist/internal/errors.d.ts.map +1 -1
  61. package/dist/internal/errors.js +80 -36
  62. package/dist/internal/fixture.d.ts +2 -2
  63. package/dist/internal/fixture.d.ts.map +1 -1
  64. package/dist/internal/fixture.js +57 -74
  65. package/dist/internal/hex128.d.ts +12 -0
  66. package/dist/internal/hex128.d.ts.map +1 -0
  67. package/dist/internal/hex128.js +7 -0
  68. package/dist/internal/hmac.d.ts +5 -0
  69. package/dist/internal/hmac.d.ts.map +1 -0
  70. package/dist/internal/hmac.js +150 -0
  71. package/dist/internal/image.d.ts +14 -117
  72. package/dist/internal/image.d.ts.map +1 -1
  73. package/dist/internal/image.js +137 -167
  74. package/dist/internal/inodeTable.d.ts +20 -0
  75. package/dist/internal/inodeTable.d.ts.map +1 -0
  76. package/dist/internal/inodeTable.js +48 -0
  77. package/dist/internal/lines.d.ts +36 -0
  78. package/dist/internal/lines.d.ts.map +1 -0
  79. package/dist/internal/lines.js +115 -0
  80. package/dist/internal/liveImage.d.ts +19 -278
  81. package/dist/internal/liveImage.d.ts.map +1 -1
  82. package/dist/internal/liveImage.js +75 -166
  83. package/dist/internal/merkle.d.ts +45 -0
  84. package/dist/internal/merkle.d.ts.map +1 -0
  85. package/dist/internal/merkle.js +179 -0
  86. package/dist/internal/metadata.d.ts +2 -13
  87. package/dist/internal/metadata.d.ts.map +1 -1
  88. package/dist/internal/metadata.js +5 -15
  89. package/dist/internal/overlayDiff.d.ts +2 -2
  90. package/dist/internal/overlayDiff.d.ts.map +1 -1
  91. package/dist/internal/overlayDiff.js +4 -15
  92. package/dist/internal/path.d.ts +17 -5
  93. package/dist/internal/path.d.ts.map +1 -1
  94. package/dist/internal/path.js +38 -24
  95. package/dist/internal/snapshotDelta.d.ts +61 -60
  96. package/dist/internal/snapshotDelta.d.ts.map +1 -1
  97. package/dist/internal/snapshotDelta.js +605 -503
  98. package/dist/internal/snapshotDeltaModel.d.ts.map +1 -1
  99. package/dist/internal/snapshotDeltaModel.js +0 -1
  100. package/dist/internal/testSeams.d.ts +17 -0
  101. package/dist/internal/testSeams.d.ts.map +1 -0
  102. package/dist/internal/testSeams.js +20 -0
  103. package/dist/internal/tree.d.ts +225 -0
  104. package/dist/internal/tree.d.ts.map +1 -0
  105. package/dist/internal/tree.js +385 -0
  106. package/dist/internal/turnstile.d.ts +8 -0
  107. package/dist/internal/turnstile.d.ts.map +1 -0
  108. package/dist/internal/turnstile.js +43 -0
  109. package/dist/internal/virtualFileSystem.d.ts +44 -488
  110. package/dist/internal/virtualFileSystem.d.ts.map +1 -1
  111. package/dist/internal/virtualFileSystem.js +2111 -2466
  112. package/dist/internal/volumeState.d.ts +111 -0
  113. package/dist/internal/volumeState.d.ts.map +1 -0
  114. package/dist/internal/volumeState.js +202 -0
  115. package/dist/internal/watchHub.d.ts +15 -6
  116. package/dist/internal/watchHub.d.ts.map +1 -1
  117. package/dist/internal/watchHub.js +56 -42
  118. package/package.json +54 -10
  119. package/dist/VirtualFileSystemError.d.ts +0 -54
  120. package/dist/VirtualFileSystemError.d.ts.map +0 -1
  121. package/dist/VirtualFileSystemError.js +0 -49
  122. package/dist/internal/overlayContent.d.ts +0 -15
  123. package/dist/internal/overlayContent.d.ts.map +0 -1
  124. package/dist/internal/overlayContent.js +0 -29
  125. package/dist/internal/stagedState.d.ts +0 -22
  126. package/dist/internal/stagedState.d.ts.map +0 -1
  127. package/dist/internal/stagedState.js +0 -62
  128. package/dist/internal/testHooks.d.ts +0 -20
  129. package/dist/internal/testHooks.d.ts.map +0 -1
  130. package/dist/internal/testHooks.js +0 -23
@@ -1,29 +1,38 @@
1
1
  import * as Context from "effect/Context";
2
2
  import * as Effect from "effect/Effect";
3
+ import * as Layer from "effect/Layer";
3
4
  import * as Result from "effect/Result";
4
5
  import * as Schema from "effect/Schema";
5
6
  import * as SchemaIssue from "effect/SchemaIssue";
6
7
  import * as SchemaTransformation from "effect/SchemaTransformation";
8
+ import * as Sink from "effect/Sink";
9
+ import * as Stream from "effect/Stream";
10
+ import * as CallerModule from "./Caller.js";
11
+ import * as FileHandleModule from "./FileHandle.js";
12
+ import * as FixtureModule from "./Fixture.js";
13
+ import * as MetadataModule from "./Metadata.js";
14
+ import * as VfsErrorModule from "./VfsError.js";
15
+ import * as VolumeModule from "./Volume.js";
16
+ import * as WatchModule from "./Watch.js";
17
+ export { Entry, isEntry, isTarget, Name, Target } from "./Target.js";
7
18
  export { BytePath } from "./BytePath.js";
8
- export { DecodeLimits, ImageError, SnapshotTypeId } from "./Snapshot.js";
9
- import { decodeConfiguration } from "./internal/errors.js";
19
+ export { DecodeLimits, SnapshotTypeId } from "./Snapshot.js";
20
+ import { BudgetFromDecodeLimits, DeltaBudgetFromLimits } from "./internal/budget.js";
21
+ import { decodeConfiguration, retargetFailure } from "./internal/errors.js";
10
22
  import * as FixtureInternal from "./internal/fixture.js";
11
23
  import * as Image from "./internal/image.js";
12
24
  import * as Path from "./internal/path.js";
13
25
  import * as SnapshotDeltaInternal from "./internal/snapshotDelta.js";
14
26
  import * as VfsModel from "./internal/virtualFileSystem.js";
27
+ import { open as openLiveVolume } from "./LiveVolume.js";
15
28
  import * as SnapshotDeltaModel from "./SnapshotDelta.js";
16
- export { SnapshotChange, SnapshotChangesOptions, SnapshotDeltaError, SnapshotDeltaLimits, SnapshotDeltaTypeId, SnapshotDifference, SnapshotNodeKind } from "./SnapshotDelta.js";
17
- const VolumeId = VfsModel.VolumeId;
18
- const CallerId = VfsModel.CallerId;
19
- const FileHandleId = VfsModel.FileHandleId;
20
- const DirectoryHandleId = VfsModel.DirectoryHandleId;
21
- const ObjectReferenceId = VfsModel.ObjectReferenceId;
29
+ export { SnapshotChange, SnapshotChangesOptions, SnapshotDeltaLimits, SnapshotDeltaTypeId, SnapshotDifference, SnapshotNodeKind } from "./SnapshotDelta.js";
22
30
  /**
23
31
  * Schema for portable virtual filesystem error codes.
24
32
  *
25
33
  * @example
26
34
  * ```ts
35
+ * import * as BunCrypto from "@effect/platform-bun/BunCrypto"
27
36
  * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
28
37
  * import { Effect, Schema } from "effect"
29
38
  *
@@ -42,14 +51,14 @@ const ObjectReferenceId = VfsModel.ObjectReferenceId;
42
51
  * return [known, unknown]
43
52
  * })
44
53
  *
45
- * Effect.runPromise(program).then(console.log)
54
+ * Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
46
55
  * // [ 'NotFound', 'not a code' ]
47
56
  * ```
48
57
  *
49
58
  * @category schemas
50
59
  * @since 0.1.0
51
60
  */
52
- export const FsCode = VfsModel.FsCode;
61
+ export const FsCode = VfsErrorModule.FsCode;
53
62
  /**
54
63
  * Describes an expected filesystem operation failure.
55
64
  *
@@ -59,8 +68,8 @@ export const FsCode = VfsModel.FsCode;
59
68
  *
60
69
  * @example
61
70
  * ```ts
71
+ * import * as BunCrypto from "@effect/platform-bun/BunCrypto"
62
72
  * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
63
- * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
64
73
  * import { Effect } from "effect"
65
74
  *
66
75
  * const program = Effect.gen(function*() {
@@ -69,28 +78,27 @@ export const FsCode = VfsModel.FsCode;
69
78
  * // Every filesystem operation fails with this one tagged error; `code`
70
79
  * // distinguishes the cases, so match on it rather than on the tag alone.
71
80
  * return yield* caller.readFile("/missing").pipe(
72
- * Effect.catchTag("FsError", (error) =>
81
+ * Effect.catchTag("VfsError", (error) =>
73
82
  * error.code === "NotFound"
74
83
  * ? Effect.succeed(new Uint8Array())
75
84
  * : Effect.fail(error))
76
85
  * )
77
86
  * })
78
87
  *
79
- * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
88
+ * Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
80
89
  * // Uint8Array(0) []
81
90
  * ```
82
91
  *
83
92
  * @category errors
84
93
  * @since 0.1.0
85
94
  */
86
- export { FsError } from "./VirtualFileSystemError.js";
87
95
  /**
88
96
  * Describes an invalid volume or caller option and names the rejected field.
89
97
  *
90
98
  * @example
91
99
  * ```ts
100
+ * import * as BunCrypto from "@effect/platform-bun/BunCrypto"
92
101
  * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
93
- * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
94
102
  * import { Effect } from "effect"
95
103
  *
96
104
  * // `field` names the rejected option, so the caller learns which key was wrong.
@@ -100,32 +108,32 @@ export { FsError } from "./VirtualFileSystemError.js";
100
108
  *
101
109
  * return yield* Vfs.make(options).pipe(
102
110
  * Effect.as("ok"),
103
- * Effect.catchTag("ConfigurationError", (error) => Effect.succeed(`rejected ${error.field}`))
111
+ * Effect.catchTag("VfsError", (error) => Effect.succeed(`rejected ${error.field}`))
104
112
  * )
105
113
  * })
106
114
  *
107
- * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
115
+ * Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
108
116
  * // rejected maxEntries
109
117
  * ```
110
118
  *
111
119
  * @category errors
112
120
  * @since 0.1.0
113
121
  */
114
- export { ConfigurationError } from "./VirtualFileSystemError.js";
122
+ export { DeltaCode, ImageCode, StoreCode, VfsCode, VfsError } from "./VfsError.js";
115
123
  /**
116
124
  * Schema for a caller's numeric identity, supplementary groups, and explicit privilege.
117
125
  *
118
126
  * @category schemas
119
127
  * @since 0.1.0
120
128
  */
121
- export const Identity = VfsModel.Identity;
129
+ export const Identity = CallerModule.Identity;
122
130
  /**
123
131
  * Schema for root caller credentials and creation mask.
124
132
  *
125
133
  * @example
126
134
  * ```ts
135
+ * import * as BunCrypto from "@effect/platform-bun/BunCrypto"
127
136
  * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
128
- * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
129
137
  * import { Effect } from "effect"
130
138
  *
131
139
  * const program = Effect.gen(function*() {
@@ -146,56 +154,90 @@ export const Identity = VfsModel.Identity;
146
154
  * return ((yield* user.stat("/home/user")).mode & 0o777).toString(8)
147
155
  * })
148
156
  *
149
- * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
157
+ * Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
150
158
  * // 755
151
159
  * ```
152
160
  *
153
161
  * @category schemas
154
162
  * @since 0.1.0
155
163
  */
156
- export const RootCallerOptions = VfsModel.RootCallerOptions;
164
+ export const RootCallerOptions = CallerModule.RootCallerOptions;
157
165
  /**
158
166
  * Schema for the failure boundary survived by an acknowledged volume write.
159
167
  *
160
168
  * @category schemas
161
169
  * @since 0.1.0
162
170
  */
163
- export const VolumeDurability = VfsModel.VolumeDurability;
171
+ export const VolumeDurability = VolumeModule.VolumeDurability;
164
172
  /**
165
173
  * Weakest-to-strongest ordering for volume durability guarantees.
166
174
  *
167
175
  * @category ordering
168
176
  * @since 0.1.0
169
177
  */
170
- export const VolumeDurabilityOrder = VfsModel.VolumeDurabilityOrder;
178
+ export const VolumeDurabilityOrder = VolumeModule.VolumeDurabilityOrder;
171
179
  /**
172
180
  * Returns whether `actual` survives at least the failure boundary required by `required`.
173
181
  *
174
182
  * @category predicates
175
183
  * @since 0.1.0
176
184
  */
177
- export const isVolumeDurabilityAtLeast = VfsModel.isVolumeDurabilityAtLeast;
185
+ export const isVolumeDurabilityAtLeast = VolumeModule.isVolumeDurabilityAtLeast;
178
186
  /**
179
187
  * Schema for the stable logical identity of a volume.
180
188
  *
181
189
  * @category schemas
182
190
  * @since 0.1.0
183
191
  */
184
- export const VolumeIdentity = VfsModel.VolumeIdentity;
192
+ export const VolumeIdentity = VolumeModule.VolumeIdentity;
185
193
  /**
186
194
  * Schema for one uninterrupted storage lifetime of a volume.
187
195
  *
188
196
  * @category schemas
189
197
  * @since 0.1.0
190
198
  */
191
- export const VolumeIncarnation = VfsModel.VolumeIncarnation;
199
+ export const VolumeIncarnation = VolumeModule.VolumeIncarnation;
200
+ /**
201
+ * Schema for the serialisable name of one object in one volume: the volume's
202
+ * identity, the epoch of its inode numbers, the object's inode number, and a
203
+ * tag only the volume can compute, so a key cannot be guessed from another.
204
+ *
205
+ * @example
206
+ * ```ts
207
+ * import * as BunCrypto from "@effect/platform-bun/BunCrypto"
208
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
209
+ * import { Effect, Schema } from "effect"
210
+ *
211
+ * const Json = Schema.fromJsonString(Vfs.ReferenceKey)
212
+ *
213
+ * const program = Effect.gen(function*() {
214
+ * const volume = yield* Vfs.make()
215
+ * const caller = yield* volume.caller()
216
+ * yield* caller.writeFile("/f", new Uint8Array([1]), { access: "write", create: "exclusive" })
217
+ *
218
+ * // The key survives a wire; resolving it returns the same reference.
219
+ * const reference = yield* caller.lookup("/f")
220
+ * const text = yield* Schema.encodeEffect(Json)(yield* volume.referenceKey(reference))
221
+ * const key = yield* Schema.decodeEffect(Json)(text)
222
+ *
223
+ * return (yield* volume.resolveReferenceKey(key)) === reference
224
+ * })
225
+ *
226
+ * Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
227
+ * // true
228
+ * ```
229
+ *
230
+ * @category schemas
231
+ * @since 0.6.0
232
+ */
233
+ export const ReferenceKey = VolumeModule.ReferenceKey;
192
234
  /**
193
235
  * Schema for optional volume capacity and path limits.
194
236
  *
195
237
  * @example
196
238
  * ```ts
239
+ * import * as BunCrypto from "@effect/platform-bun/BunCrypto"
197
240
  * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
198
- * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
199
241
  * import { ByteSize, Effect } from "effect"
200
242
  *
201
243
  * // Every limit is optional, and each one is enforced once set.
@@ -208,25 +250,25 @@ export const VolumeIncarnation = VfsModel.VolumeIncarnation;
208
250
  * create: "exclusive"
209
251
  * }).pipe(
210
252
  * Effect.as("written"),
211
- * Effect.catchTag("FsError", (error) => Effect.succeed(error.code))
253
+ * Effect.catchTag("VfsError", (error) => Effect.succeed(error.code))
212
254
  * )
213
255
  * })
214
256
  *
215
- * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
257
+ * Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
216
258
  * // FileTooLarge
217
259
  * ```
218
260
  *
219
261
  * @category schemas
220
262
  * @since 0.1.0
221
263
  */
222
- export const VolumeOptions = VfsModel.VolumeOptions;
264
+ export const VolumeOptions = VolumeModule.VolumeOptions;
223
265
  /**
224
266
  * Schema for filesystem node metadata with bigint inode, size, and nanosecond fields.
225
267
  *
226
268
  * @example
227
269
  * ```ts
270
+ * import * as BunCrypto from "@effect/platform-bun/BunCrypto"
228
271
  * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
229
- * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
230
272
  * import { Effect } from "effect"
231
273
  *
232
274
  * // Sizes and inode numbers are bigint; times are nanosecond timestamps.
@@ -243,49 +285,57 @@ export const VolumeOptions = VfsModel.VolumeOptions;
243
285
  * return [metadata.kind, metadata.size, metadata.nlink]
244
286
  * })
245
287
  *
246
- * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
288
+ * Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
247
289
  * // [ 'file', 3n, 1 ]
248
290
  * ```
249
291
  *
250
292
  * @category schemas
251
293
  * @since 0.1.0
252
294
  */
253
- export const Metadata = VfsModel.Metadata;
295
+ export const Metadata = MetadataModule.Metadata;
296
+ /**
297
+ * Settings for walking a directory tree: the order and the optional depth,
298
+ * entry, and byte bounds.
299
+ *
300
+ * @category schemas
301
+ * @since 0.6.0
302
+ */
303
+ export const WalkOptions = CallerModule.WalkOptions;
254
304
  /**
255
305
  * A directory revision transition captured inside one coordinated operation.
256
306
  *
257
307
  * @category schemas
258
308
  * @since 0.4.0
259
309
  */
260
- export const DirectoryChange = VfsModel.DirectoryChange;
310
+ export const DirectoryChange = CallerModule.DirectoryChange;
261
311
  /**
262
312
  * The directory transitions produced by a same-directory or cross-directory rename.
263
313
  *
264
314
  * @category schemas
265
315
  * @since 0.4.0
266
316
  */
267
- export const RenameReferenceResult = VfsModel.RenameReferenceResult;
317
+ export const RenameReferenceResult = CallerModule.RenameReferenceResult;
268
318
  /**
269
319
  * Schema for an owner update. Omitted fields retain their existing values.
270
320
  *
271
321
  * @category schemas
272
322
  * @since 0.1.0
273
323
  */
274
- export const OwnerUpdate = VfsModel.OwnerUpdate;
324
+ export const OwnerUpdate = MetadataModule.OwnerUpdate;
275
325
  /**
276
326
  * Schema for setting a timestamp to the clock, retaining it, or supplying nanoseconds.
277
327
  *
278
328
  * @category schemas
279
329
  * @since 0.1.0
280
330
  */
281
- export const TimeUpdate = VfsModel.TimeUpdate;
331
+ export const TimeUpdate = MetadataModule.TimeUpdate;
282
332
  /**
283
333
  * Schema for independent access and modification time updates.
284
334
  *
285
335
  * @example
286
336
  * ```ts
337
+ * import * as BunCrypto from "@effect/platform-bun/BunCrypto"
287
338
  * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
288
- * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
289
339
  * import { Effect } from "effect"
290
340
  *
291
341
  * const program = Effect.gen(function*() {
@@ -307,28 +357,28 @@ export const TimeUpdate = VfsModel.TimeUpdate;
307
357
  * return [metadata.atimeNs, metadata.mtimeNs === 1_000n]
308
358
  * })
309
359
  *
310
- * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
360
+ * Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
311
361
  * // [ 1000n, false ]
312
362
  * ```
313
363
  *
314
364
  * @category schemas
315
365
  * @since 0.1.0
316
366
  */
317
- export const Times = VfsModel.Times;
367
+ export const Times = MetadataModule.Times;
318
368
  /**
319
369
  * Schema for file seek origins, including dense-file data and hole queries.
320
370
  *
321
371
  * @category schemas
322
372
  * @since 0.1.0
323
373
  */
324
- export const SeekMode = VfsModel.SeekMode;
374
+ export const SeekMode = FileHandleModule.SeekMode;
325
375
  /**
326
376
  * Schema for file access, creation, append, truncate, and symlink behavior.
327
377
  *
328
378
  * @example
329
379
  * ```ts
380
+ * import * as BunCrypto from "@effect/platform-bun/BunCrypto"
330
381
  * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
331
- * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
332
382
  * import { Effect } from "effect"
333
383
  *
334
384
  * // `access` is required; `create` decides whether a missing file is an error.
@@ -346,36 +396,49 @@ export const SeekMode = VfsModel.SeekMode;
346
396
  * return (yield* handle.stat).size
347
397
  * }).pipe(Effect.scoped)
348
398
  *
349
- * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
399
+ * Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
350
400
  * // 2n
351
401
  * ```
352
402
  *
353
403
  * @category schemas
354
404
  * @since 0.1.0
355
405
  */
356
- export const OpenSettings = VfsModel.OpenSettings;
406
+ export const OpenOptions = CallerModule.OpenOptions;
357
407
  /**
358
408
  * Settings for creating a directory through a parent object reference.
359
409
  * `exactMode` skips the caller's umask for an explicit creation mode; permission policy still applies.
410
+ * `recursive` creates every missing directory on a path in one change, as `mkdir -p` does.
360
411
  *
361
412
  * @category schemas
362
413
  * @since 0.4.0
363
414
  */
364
- export const MkdirReferenceSettings = VfsModel.MkdirReferenceSettings;
415
+ export const MkdirOptions = CallerModule.MkdirOptions;
416
+ /**
417
+ * Settings for removing an entry: whether a directory goes with everything
418
+ * under it, and whether a missing target is success.
419
+ *
420
+ * @category schemas
421
+ * @since 0.6.0
422
+ */
423
+ export const RemoveOptions = CallerModule.RemoveOptions;
365
424
  /**
366
425
  * Settings for creating a symbolic link through a parent object reference.
367
426
  *
368
427
  * @category schemas
369
428
  * @since 0.4.0
370
429
  */
371
- export const SymlinkReferenceSettings = VfsModel.SymlinkReferenceSettings;
430
+ export const SymlinkOptions = CallerModule.SymlinkOptions;
372
431
  /**
373
- * Settings for opening an existing regular-file reference.
432
+ * The attributes `setattr` changes together: a regular file's size, the
433
+ * permission mode, the owner, and the access and modification times. An
434
+ * omitted attribute keeps its current value. `expected` pins the revision the
435
+ * caller last observed and fails as `StaleReference` naming `expected` when
436
+ * the target has moved on.
374
437
  *
375
438
  * @category schemas
376
- * @since 0.4.0
439
+ * @since 0.6.0
377
440
  */
378
- export const OpenReferenceSettings = VfsModel.OpenReferenceSettings;
441
+ export const SetattrOptions = CallerModule.SetattrOptions;
379
442
  /**
380
443
  * Settings for atomically looking up or creating and opening one referenced child.
381
444
  * `exactMode` skips the caller's umask for an explicit creation mode; permission policy still applies.
@@ -383,21 +446,146 @@ export const OpenReferenceSettings = VfsModel.OpenReferenceSettings;
383
446
  * @category schemas
384
447
  * @since 0.4.0
385
448
  */
386
- export const OpenChildReferenceSettings = VfsModel.OpenChildReferenceSettings;
449
+ export const OpenEntryOptions = CallerModule.OpenEntryOptions;
450
+ /**
451
+ * Options for an atomic whole-file write, including replacement and final mode controls.
452
+ *
453
+ * @example
454
+ * ```ts
455
+ * import * as BunCrypto from "@effect/platform-bun/BunCrypto"
456
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
457
+ * import { Effect } from "effect"
458
+ *
459
+ * const program = Effect.gen(function*() {
460
+ * const caller = yield* (yield* Vfs.make()).caller()
461
+ * const bytes = new TextEncoder().encode("v2")
462
+ *
463
+ * // `access` is required. `create: "exclusive"` refuses to replace an entry,
464
+ * // while `truncate` overwrites one that already exists.
465
+ * yield* caller.writeFile("/app.conf", new TextEncoder().encode("v1"), {
466
+ * access: "write",
467
+ * create: "exclusive",
468
+ * mode: 0o640
469
+ * })
470
+ *
471
+ * const refused = yield* caller.writeFile("/app.conf", bytes, {
472
+ * access: "write",
473
+ * create: "exclusive"
474
+ * }).pipe(
475
+ * Effect.as("replaced"),
476
+ * Effect.catchTag("VfsError", (error) => Effect.succeed(error.code))
477
+ * )
478
+ *
479
+ * yield* caller.writeFile("/app.conf", bytes, { access: "write", truncate: true })
480
+ *
481
+ * return [refused, new TextDecoder().decode(yield* caller.readFile("/app.conf"))]
482
+ * })
483
+ *
484
+ * Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
485
+ * // [ 'AlreadyExists', 'v2' ]
486
+ * ```
487
+ *
488
+ * @category models
489
+ * @since 0.1.0
490
+ */
491
+ export const WriteFileOptions = CallerModule.WriteFileOptions;
492
+ /**
493
+ * A committed namespace or content change emitted by a volume watch stream.
494
+ *
495
+ * @example
496
+ * ```ts
497
+ * import * as BunCrypto from "@effect/platform-bun/BunCrypto"
498
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
499
+ * import { Effect, Fiber, Stream } from "effect"
500
+ *
501
+ * const program = Effect.gen(function*() {
502
+ * const volume = yield* Vfs.make()
503
+ * const caller = yield* volume.caller()
504
+ *
505
+ * const watcher = yield* volume.watch().pipe(
506
+ * Effect.flatMap((stream) => Stream.runCollect(Stream.take(stream, 2))),
507
+ * Effect.forkChild({ startImmediately: true })
508
+ * )
509
+ *
510
+ * yield* caller.mkdir("/logs")
511
+ * yield* caller.rmdir("/logs")
512
+ *
513
+ * // Each event pairs the kind of change with the absolute path it happened to.
514
+ * const events = yield* Fiber.join(watcher)
515
+ *
516
+ * return yield* Effect.forEach(events, (change) =>
517
+ * Effect.map(
518
+ * Vfs.pathToBytes(change.path),
519
+ * (bytes) => `${change._tag} ${new TextDecoder().decode(bytes)}`
520
+ * ))
521
+ * }).pipe(Effect.scoped)
522
+ *
523
+ * Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
524
+ * // [ 'Create /logs', 'Remove /logs' ]
525
+ * ```
526
+ *
527
+ * @category schemas
528
+ * @since 0.1.0
529
+ */
530
+ export const Change = WatchModule.Change;
531
+ /**
532
+ * Schema for the options of `Volume.watch`: an object reference to narrow the
533
+ * watch to, and whether its whole subtree is reported.
534
+ *
535
+ * @example
536
+ * ```ts
537
+ * import * as BunCrypto from "@effect/platform-bun/BunCrypto"
538
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
539
+ * import { Effect, Fiber, Stream } from "effect"
540
+ *
541
+ * const program = Effect.gen(function*() {
542
+ * const volume = yield* Vfs.make()
543
+ * const caller = yield* volume.caller()
544
+ * yield* caller.mkdir("/work")
545
+ * const work = yield* caller.lookup("/work")
546
+ *
547
+ * const watcher = yield* volume.watch({ scope: work }).pipe(
548
+ * Effect.flatMap((stream) => Stream.runCollect(Stream.take(stream, 3))),
549
+ * Effect.forkChild({ startImmediately: true })
550
+ * )
551
+ *
552
+ * // Outside the scope, so not reported.
553
+ * yield* caller.mkdir("/elsewhere")
554
+ * // The scope is the directory, not its path: the rename is reported and the watch goes on.
555
+ * yield* caller.rename("/work", "/renamed")
556
+ * yield* caller.mkdir("/renamed/out")
557
+ *
558
+ * const events = yield* Fiber.join(watcher)
559
+ *
560
+ * return yield* Effect.forEach(events, (change) =>
561
+ * Effect.map(
562
+ * Vfs.pathToBytes(change.path),
563
+ * (bytes) => `${change._tag} ${new TextDecoder().decode(bytes)}`
564
+ * ))
565
+ * }).pipe(Effect.scoped)
566
+ *
567
+ * Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
568
+ * // [ 'Remove /work', 'Create /renamed', 'Create /renamed/out' ]
569
+ * ```
570
+ *
571
+ * @category schemas
572
+ * @since 0.6.0
573
+ */
574
+ export const WatchOptions = WatchModule.WatchOptions;
387
575
  /**
388
576
  * Schema for the filesystem entry kinds reported by overlay summaries.
389
577
  *
390
578
  * @category schemas
391
579
  * @since 0.1.0
392
580
  */
393
- export const OverlayNodeKind = VfsModel.OverlayNodeKind;
581
+ export const OverlayNodeKind = VolumeModule.OverlayNodeKind;
394
582
  /**
395
583
  * Schema for observable fields that can differ from an overlay's immutable base.
396
584
  *
397
585
  * @category schemas
398
586
  * @since 0.1.0
399
587
  */
400
- export const OverlayDifference = VfsModel.OverlayDifference;
588
+ export const OverlayDifference = VolumeModule.OverlayDifference;
401
589
  /**
402
590
  * Schema for a final-state overlay difference.
403
591
  *
@@ -409,14 +597,14 @@ export const OverlayDifference = VfsModel.OverlayDifference;
409
597
  * @category schemas
410
598
  * @since 0.1.0
411
599
  */
412
- export const OverlayChange = VfsModel.OverlayChange;
600
+ export const OverlayChange = VolumeModule.OverlayChange;
413
601
  /**
414
602
  * Schema for overlay summary filtering.
415
603
  *
416
604
  * @example
417
605
  * ```ts
606
+ * import * as BunCrypto from "@effect/platform-bun/BunCrypto"
418
607
  * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
419
- * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
420
608
  * import { Effect } from "effect"
421
609
  *
422
610
  * const program = Effect.gen(function*() {
@@ -432,58 +620,121 @@ export const OverlayChange = VfsModel.OverlayChange;
432
620
  * return [plain.length, timed.length]
433
621
  * })
434
622
  *
435
- * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
623
+ * Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
436
624
  * // [ 1, 2 ]
437
625
  * ```
438
626
  *
439
627
  * @category schemas
440
628
  * @since 0.1.0
441
629
  */
442
- export const OverlayChangesOptions = VfsModel.OverlayChangesOptions;
630
+ export const OverlayChangesOptions = VolumeModule.OverlayChangesOptions;
443
631
  /**
444
- * Optional Effect service for providing an existing filesystem caller.
632
+ * The volume an application runs against, as an Effect service. The static
633
+ * layers wrap the explicit constructors; each call builds a fresh layer, so
634
+ * share one layer value to share one volume.
445
635
  *
446
636
  * @example
447
637
  * ```ts
638
+ * import * as BunCrypto from "@effect/platform-bun/BunCrypto"
448
639
  * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
449
- * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
450
640
  * import { Effect } from "effect"
451
641
  *
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
642
+ * const program = Effect.gen(function*() {
643
+ * const fs = yield* Vfs.Caller
644
+ * yield* fs.mkdir("/work")
455
645
  *
456
- * return yield* caller.readFile("/version")
646
+ * return (yield* fs.readDirectory("/")).value.map((entry) => new TextDecoder().decode(entry.name))
457
647
  * })
458
648
  *
459
- * const program = Effect.gen(function*() {
460
- * const volume = yield* Vfs.make()
461
- * const caller = yield* volume.caller()
649
+ * Effect.runPromise(program.pipe(Effect.provide(Vfs.Caller.layer()), Effect.provide(Vfs.Volume.layer()), Effect.provide(BunCrypto.layer)))
650
+ * .then(console.log)
651
+ * // [ 'work' ]
652
+ * ```
462
653
  *
463
- * yield* caller.writeFile("/version", new Uint8Array([49]), {
464
- * access: "write",
465
- * create: "exclusive"
654
+ * @category services
655
+ * @since 0.6.0
656
+ */
657
+ export const Volume = Object.assign(Context.Service("@effect-vfs/core/Volume"), {
658
+ layer: (options) => Layer.effect(Volume, VfsModel.make(options)),
659
+ layerFromSnapshot: (snapshot, options) => Layer.effect(Volume, VfsModel.fromSnapshot(snapshot, options)),
660
+ layerFromFixture: (fixture, options) => Layer.effect(Volume, FixtureInternal.fromFixture(fixture, options)),
661
+ layerOverlay: (base, options) => Layer.effect(Volume, VfsModel.makeOverlay(base, options)),
662
+ layerLive: (options) => Layer.effect(Volume, openLiveVolume(options))
663
+ });
664
+ /**
665
+ * The caller an application operates through, as an Effect service. A caller
666
+ * supplied through the service keeps its own volume, credentials, umask and
667
+ * working directory; `layer` makes a root caller on the `Volume` in context.
668
+ *
669
+ * @category services
670
+ * @since 0.6.0
671
+ */
672
+ export const Caller = Object.assign(Context.Service("@effect-vfs/core/Caller"), {
673
+ layer: (options) => Layer.effect(Caller, Effect.flatMap(Volume, (volume) => volume.caller(options)))
674
+ });
675
+ const snapshotBudget = (operation, limits) => Effect.fromResult(decodeConfiguration(BudgetFromDecodeLimits, limits, operation));
676
+ const optionalBudget = (operation, limits) => limits === undefined ? Effect.undefined : snapshotBudget(operation, limits);
677
+ /**
678
+ * Streams a snapshot's version 1 encoding as owned UTF-8 chunks: a header line,
679
+ * then one line per node, each ending in a newline.
680
+ *
681
+ * **Details**
682
+ *
683
+ * The encoding is newline-delimited JSON. The first line names the format and
684
+ * version; each later line is one node in inode order, with names and payloads
685
+ * as canonical base64. Nodes are encoded as the stream is pulled, so writing a
686
+ * large snapshot to a file or a socket never holds its whole encoding. A chunk
687
+ * holds at most 128 lines and 64 KiB, so a pull holds at most that and the next
688
+ * line; a line longer than 64 KiB is a chunk of its own.
689
+ *
690
+ * Given `limits`, the stream fails with `LimitExceeded` at the first line that
691
+ * `decodeSnapshot` would refuse under the same limits, naming the same field,
692
+ * so bytes it completes are known to decode without decoding them.
693
+ *
694
+ * @example
695
+ * ```ts
696
+ * import * as BunCrypto from "@effect/platform-bun/BunCrypto"
697
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
698
+ * import { Effect, Stream } from "effect"
699
+ *
700
+ * const program = Effect.gen(function*() {
701
+ * const volume = yield* Vfs.fromFixture({
702
+ * entries: [{ kind: "file", path: "/f", bytes: new Uint8Array([1]) }]
466
703
  * })
467
704
  *
468
- * return yield* readVersion.pipe(Effect.provideService(Vfs.CurrentFileSystem, caller))
705
+ * const text = yield* Vfs.encodeSnapshotStream(yield* volume.snapshot).pipe(
706
+ * Stream.decodeText,
707
+ * Stream.mkString
708
+ * )
709
+ *
710
+ * // A header line, then the root and the file, each line ending in a newline.
711
+ * return text.split("\n").map((line) => (line === "" ? "end" : JSON.parse(line)._tag ?? "header"))
469
712
  * })
470
713
  *
471
- * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
472
- * // Uint8Array(1) [ 49 ]
714
+ * Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
715
+ * // [ 'header', 'directory', 'file', 'end' ]
473
716
  * ```
474
717
  *
475
- * @see The testing guide at `/guides/testing-with-an-isolated-filesystem`.
476
- * @category services
477
- * @since 0.1.0
718
+ * @see {@link encodeSnapshot} for the same bytes collected into one array.
719
+ * @see {@link decodeSnapshotSink} for reading a stream back under limits.
720
+ * @category serialization
721
+ * @since 0.6.0
478
722
  */
479
- export const CurrentFileSystem = Context.Service("@effect-vfs/core/CurrentFileSystem");
723
+ export const encodeSnapshotStream = (snapshot, limits) => Stream.unwrap(Effect.map(optionalBudget("encodeSnapshotStream", limits), (budget) => Image.encodeSnapshotStream(snapshot, budget))).pipe(Stream.mapError((error) => retargetFailure("encodeSnapshotStream", error)));
480
724
  /**
481
- * Encodes a snapshot as owned UTF-8 JSON bytes using the version 1 snapshot format.
725
+ * Encodes a snapshot as owned UTF-8 bytes in the version 1 snapshot format.
726
+ *
727
+ * **Details**
728
+ *
729
+ * The bytes are the chunks of {@link encodeSnapshotStream} collected into one
730
+ * array: newline-delimited JSON, a header line and then one line per node.
731
+ * Given `limits`, encoding fails with `LimitExceeded` wherever `decodeSnapshot`
732
+ * would refuse the bytes under the same limits.
482
733
  *
483
734
  * @example
484
735
  * ```ts
736
+ * import * as BunCrypto from "@effect/platform-bun/BunCrypto"
485
737
  * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
486
- * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
487
738
  * import { Effect } from "effect"
488
739
  *
489
740
  * // Bytes are portable: store or transmit them, then decode under explicit limits.
@@ -498,11 +749,11 @@ export const CurrentFileSystem = Context.Service("@effect-vfs/core/CurrentFileSy
498
749
  *
499
750
  * const bytes = yield* Vfs.encodeSnapshot(yield* volume.snapshot)
500
751
  *
501
- * // The bytes are UTF-8 JSON carrying the version 1 snapshot format.
502
- * return JSON.parse(new TextDecoder().decode(bytes)).version
752
+ * // The first line is the header carrying the version 1 snapshot format.
753
+ * return JSON.parse(new TextDecoder().decode(bytes).split("\n")[0]!).version
503
754
  * })
504
755
  *
505
- * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
756
+ * Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
506
757
  * // 1
507
758
  * ```
508
759
  *
@@ -510,17 +761,77 @@ export const CurrentFileSystem = Context.Service("@effect-vfs/core/CurrentFileSy
510
761
  * @category serialization
511
762
  * @since 0.1.0
512
763
  */
513
- export const encodeSnapshot = Image.encodeSnapshot;
764
+ export const encodeSnapshot = (snapshot, limits) => Effect.flatMap(optionalBudget("encodeSnapshot", limits), (budget) => Image.encodeSnapshot(snapshot, budget)).pipe(Effect.mapError((error) => retargetFailure("encodeSnapshot", error)));
765
+ /**
766
+ * A sink that decodes a stream of version 1 snapshot chunks while enforcing
767
+ * explicit input and payload limits.
768
+ *
769
+ * **Details**
770
+ *
771
+ * Chunks may split lines, and UTF-8 sequences, anywhere. Each line is checked
772
+ * as it ends, so input that breaks a limit or a rule is refused before the
773
+ * sink holds more than one line of it: `maxLineBytes` bounds that line, and
774
+ * the other limits bound what the accepted lines add up to. A line is
775
+ * refused at the byte where it first crosses `maxLineBytes` or
776
+ * `maxEncodedBytes`, so the field a refusal names does not depend on how the
777
+ * input is chunked. Every line must end in a newline, the last included; a
778
+ * carriage return, an empty line, a byte-order mark and malformed UTF-8 are
779
+ * refused as `InvalidEncoding`.
780
+ *
781
+ * @example
782
+ * ```ts
783
+ * import * as BunCrypto from "@effect/platform-bun/BunCrypto"
784
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
785
+ * import { ByteSize, Effect, Stream } from "effect"
786
+ *
787
+ * const limits: Vfs.DecodeLimits = {
788
+ * maxEncodedBytes: ByteSize.megabytes(4),
789
+ * maxRecords: 10_000,
790
+ * maxEntries: 10_000,
791
+ * maxDecodedBytes: ByteSize.megabytes(16),
792
+ * maxLineBytes: ByteSize.megabytes(1)
793
+ * }
794
+ *
795
+ * const program = Effect.gen(function*() {
796
+ * const volume = yield* Vfs.fromFixture({
797
+ * entries: [{ kind: "file", path: "/f", bytes: new Uint8Array([1, 2, 3]) }]
798
+ * })
799
+ *
800
+ * // Rechunked to single bytes: the sink joins lines across chunks.
801
+ * const snapshot = yield* Vfs.encodeSnapshotStream(yield* volume.snapshot).pipe(
802
+ * Stream.flattenIterable,
803
+ * Stream.rechunk(1),
804
+ * Stream.map((byte) => new Uint8Array([byte])),
805
+ * Stream.run(Vfs.decodeSnapshotSink(limits))
806
+ * )
807
+ *
808
+ * return yield* (yield* (yield* Vfs.fromSnapshot(snapshot)).caller()).readFile("/f")
809
+ * })
810
+ *
811
+ * Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
812
+ * // Uint8Array(3) [ 1, 2, 3 ]
813
+ * ```
814
+ *
815
+ * @see {@link DecodeLimits} for what each bound protects.
816
+ * @see {@link decodeSnapshot} for decoding bytes already in memory.
817
+ * @category serialization
818
+ * @since 0.6.0
819
+ */
820
+ export const decodeSnapshotSink = (limits) => Sink.unwrap(Effect.map(snapshotBudget("decodeSnapshot", limits), (budget) => Image.decodeSnapshotSink(budget))).pipe(Sink.mapError((error) => retargetFailure("decodeSnapshot", error)));
514
821
  /**
515
822
  * Decodes version 1 snapshot bytes while enforcing explicit input and payload limits.
516
823
  *
824
+ * **Details**
825
+ *
826
+ * The same decoder as {@link decodeSnapshotSink}, run over one chunk.
827
+ *
517
828
  * @example
518
829
  * ```ts
830
+ * import * as BunCrypto from "@effect/platform-bun/BunCrypto"
519
831
  * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
520
- * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
521
832
  * import { ByteSize, Effect } from "effect"
522
833
  *
523
- * // Limits are mandatory: decoding is the boundary where untrusted input arrives.
834
+ * // Limits are mandatory: decoding is the boundary where outside input arrives.
524
835
  * const program = Effect.gen(function*() {
525
836
  * const volume = yield* Vfs.make()
526
837
  * const bytes = yield* Vfs.encodeSnapshot(yield* volume.snapshot)
@@ -532,11 +843,11 @@ export const encodeSnapshot = Image.encodeSnapshot;
532
843
  * maxDecodedBytes: ByteSize.megabytes(16)
533
844
  * }).pipe(
534
845
  * Effect.as("accepted"),
535
- * Effect.catchTag("ImageError", (error) => Effect.succeed([error.code, error.field]))
846
+ * Effect.catchTag("VfsError", (error) => Effect.succeed([error.code, error.field]))
536
847
  * )
537
848
  * })
538
849
  *
539
- * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
850
+ * Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
540
851
  * // [ 'LimitExceeded', 'encodedBytes' ]
541
852
  * ```
542
853
  *
@@ -544,11 +855,8 @@ export const encodeSnapshot = Image.encodeSnapshot;
544
855
  * @category serialization
545
856
  * @since 0.1.0
546
857
  */
547
- export const decodeSnapshot = Image.decodeSnapshot;
548
- const deltaLimits = (limits) => {
549
- const decoded = decodeConfiguration(SnapshotDeltaModel.SnapshotDeltaLimits, limits ?? SnapshotDeltaModel.SnapshotDeltaLimits.default);
550
- return Result.isFailure(decoded) ? Effect.fail(decoded.failure) : Effect.succeed(decoded.success);
551
- };
858
+ export const decodeSnapshot = (input, limits) => Stream.run(Stream.succeed(input), decodeSnapshotSink(limits));
859
+ const deltaLimits = (operation, limits) => Effect.fromResult(decodeConfiguration(DeltaBudgetFromLimits, limits ?? SnapshotDeltaModel.SnapshotDeltaLimits.default, operation));
552
860
  /**
553
861
  * Computes an exact portable delta between two immutable snapshots.
554
862
  * Requires the platform-neutral `Crypto.Crypto` service for base identity. Provide
@@ -556,6 +864,7 @@ const deltaLimits = (limits) => {
556
864
  *
557
865
  * @example
558
866
  * ```ts
867
+ * import * as BunCrypto from "@effect/platform-bun/BunCrypto"
559
868
  * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
560
869
  * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
561
870
  * import { Effect } from "effect"
@@ -578,7 +887,7 @@ const deltaLimits = (limits) => {
578
887
  * return new TextDecoder().decode(yield* Vfs.pathToBytes(changes[0]!.path))
579
888
  * }).pipe(Effect.provide(NodeCrypto.layer))
580
889
  *
581
- * Effect.runPromise(program).then(console.log)
890
+ * Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
582
891
  * // /added.txt
583
892
  * ```
584
893
  *
@@ -587,7 +896,8 @@ const deltaLimits = (limits) => {
587
896
  * @since 0.1.0
588
897
  */
589
898
  export const diffSnapshots = Effect.fn("VirtualFileSystem.diffSnapshots")(function* (base, target, limits) {
590
- return yield* SnapshotDeltaInternal.diffSnapshots(base, target, yield* deltaLimits(limits));
899
+ // The delta codec fails under its own name; the public entry point names itself.
900
+ return yield* SnapshotDeltaInternal.diffSnapshots(base, target, yield* deltaLimits("diffSnapshots", limits)).pipe(Effect.mapError((error) => retargetFailure("diffSnapshots", error)));
591
901
  });
592
902
  /**
593
903
  * Verifies an exact snapshot delta against its base and derives an owned path-oriented summary.
@@ -596,6 +906,7 @@ export const diffSnapshots = Effect.fn("VirtualFileSystem.diffSnapshots")(functi
596
906
  *
597
907
  * @example
598
908
  * ```ts
909
+ * import * as BunCrypto from "@effect/platform-bun/BunCrypto"
599
910
  * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
600
911
  * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
601
912
  * import { Effect } from "effect"
@@ -615,7 +926,7 @@ export const diffSnapshots = Effect.fn("VirtualFileSystem.diffSnapshots")(functi
615
926
  * change._tag === "Updated" ? change.afterKind : change.kind)
616
927
  * }).pipe(Effect.provide(NodeCrypto.layer))
617
928
  *
618
- * Effect.runPromise(program).then(console.log)
929
+ * Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
619
930
  * // [ 'directory' ]
620
931
  * ```
621
932
  *
@@ -624,10 +935,8 @@ export const diffSnapshots = Effect.fn("VirtualFileSystem.diffSnapshots")(functi
624
935
  * @since 0.1.0
625
936
  */
626
937
  export const inspectSnapshotDelta = Effect.fn("VirtualFileSystem.inspectSnapshotDelta")(function* (base, delta, options, limits) {
627
- const decoded = decodeConfiguration(SnapshotDeltaModel.SnapshotChangesOptions, options ?? {});
628
- if (Result.isFailure(decoded))
629
- return yield* decoded.failure;
630
- return yield* SnapshotDeltaInternal.inspectSnapshotDelta(base, delta, decoded.success, yield* deltaLimits(limits));
938
+ const decoded = yield* Effect.fromResult(decodeConfiguration(SnapshotDeltaModel.SnapshotChangesOptions, options ?? {}, "inspectSnapshotDelta"));
939
+ return yield* SnapshotDeltaInternal.inspectSnapshotDelta(base, delta, decoded, yield* deltaLimits("inspectSnapshotDelta", limits)).pipe(Effect.mapError((error) => retargetFailure("inspectSnapshotDelta", error)));
631
940
  });
632
941
  /**
633
942
  * Applies an exact delta to its semantically matching base and returns a new snapshot.
@@ -636,6 +945,7 @@ export const inspectSnapshotDelta = Effect.fn("VirtualFileSystem.inspectSnapshot
636
945
  *
637
946
  * @example
638
947
  * ```ts
948
+ * import * as BunCrypto from "@effect/platform-bun/BunCrypto"
639
949
  * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
640
950
  * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
641
951
  * import { Effect } from "effect"
@@ -660,7 +970,7 @@ export const inspectSnapshotDelta = Effect.fn("VirtualFileSystem.inspectSnapshot
660
970
  * return yield* reader.readFile("/f")
661
971
  * }).pipe(Effect.provide(NodeCrypto.layer))
662
972
  *
663
- * Effect.runPromise(program).then(console.log)
973
+ * Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
664
974
  * // Uint8Array(1) [ 7 ]
665
975
  * ```
666
976
  *
@@ -669,7 +979,7 @@ export const inspectSnapshotDelta = Effect.fn("VirtualFileSystem.inspectSnapshot
669
979
  * @since 0.1.0
670
980
  */
671
981
  export const applySnapshotDelta = Effect.fn("VirtualFileSystem.applySnapshotDelta")(function* (base, delta, limits) {
672
- return yield* SnapshotDeltaInternal.applySnapshotDelta(base, delta, yield* deltaLimits(limits));
982
+ return yield* SnapshotDeltaInternal.applySnapshotDelta(base, delta, yield* deltaLimits("applySnapshotDelta", limits)).pipe(Effect.mapError((error) => retargetFailure("applySnapshotDelta", error)));
673
983
  });
674
984
  const deltaSchemaIssue = (cause, input, options) => new SchemaIssue.InvalidValue({ message: `Snapshot delta ${cause.code}${cause.field === undefined ? "" : ` at ${cause.field}`}` }, input, options);
675
985
  /**
@@ -678,6 +988,7 @@ const deltaSchemaIssue = (cause, input, options) => new SchemaIssue.InvalidValue
678
988
  *
679
989
  * @example
680
990
  * ```ts
991
+ * import * as BunCrypto from "@effect/platform-bun/BunCrypto"
681
992
  * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
682
993
  * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
683
994
  * import { Effect, Schema } from "effect"
@@ -700,7 +1011,7 @@ const deltaSchemaIssue = (cause, input, options) => new SchemaIssue.InvalidValue
700
1011
  * return (yield* Vfs.inspectSnapshotDelta(base, decoded)).map((change) => change._tag)
701
1012
  * }).pipe(Effect.provide(NodeCrypto.layer))
702
1013
  *
703
- * Effect.runPromise(program).then(console.log)
1014
+ * Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
704
1015
  * // [ 'Added' ]
705
1016
  * ```
706
1017
  *
@@ -708,7 +1019,7 @@ const deltaSchemaIssue = (cause, input, options) => new SchemaIssue.InvalidValue
708
1019
  * @since 0.1.0
709
1020
  */
710
1021
  export const SnapshotDeltaFromBytes = (limits) => {
711
- const selected = Schema.decodeResult(SnapshotDeltaModel.SnapshotDeltaLimits, { onExcessProperty: "error" })(limits ?? SnapshotDeltaModel.SnapshotDeltaLimits.default);
1022
+ const selected = Schema.decodeResult(DeltaBudgetFromLimits, { onExcessProperty: "error" })(limits ?? SnapshotDeltaModel.SnapshotDeltaLimits.default);
712
1023
  return Schema.Uint8Array.pipe(Schema.decodeTo(SnapshotDeltaModel.SnapshotDelta, SchemaTransformation.transformEffect({
713
1024
  decode: (input, options) => Result.isFailure(selected)
714
1025
  ? Effect.fail(new SchemaIssue.InvalidValue({ message: "Invalid snapshot delta limits" }, limits, options))
@@ -727,6 +1038,7 @@ export const SnapshotDeltaFromBytes = (limits) => {
727
1038
  *
728
1039
  * @example
729
1040
  * ```ts
1041
+ * import * as BunCrypto from "@effect/platform-bun/BunCrypto"
730
1042
  * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
731
1043
  * import { Effect } from "effect"
732
1044
  *
@@ -737,13 +1049,13 @@ export const SnapshotDeltaFromBytes = (limits) => {
737
1049
  *
738
1050
  * const rejected = yield* Vfs.pathFromBytes(new Uint8Array([47, 0])).pipe(
739
1051
  * Effect.as("accepted"),
740
- * Effect.catchTag("FsError", (error) => Effect.succeed(error.code))
1052
+ * Effect.catchTag("VfsError", (error) => Effect.succeed(error.code))
741
1053
  * )
742
1054
  *
743
1055
  * return [yield* Vfs.pathToBytes(path), rejected]
744
1056
  * })
745
1057
  *
746
- * Effect.runPromise(program).then(console.log)
1058
+ * Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
747
1059
  * // [ Uint8Array(4) [ 47, 116, 109, 112 ], 'InvalidArgument' ]
748
1060
  * ```
749
1061
  *
@@ -767,8 +1079,8 @@ export const pathToBytes = Path.pathToBytes;
767
1079
  *
768
1080
  * @example
769
1081
  * ```ts
1082
+ * import * as BunCrypto from "@effect/platform-bun/BunCrypto"
770
1083
  * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
771
- * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
772
1084
  * import { Effect, Schema } from "effect"
773
1085
  *
774
1086
  * // Fixtures must list every parent directory before its children.
@@ -779,17 +1091,17 @@ export const pathToBytes = Path.pathToBytes;
779
1091
  * }).pipe(
780
1092
  * Effect.flatMap(Vfs.fromFixture),
781
1093
  * Effect.as("built"),
782
- * Effect.catchTag("ImageError", (error) => Effect.succeed(`rejected: ${error.code}`))
1094
+ * Effect.catchTag("VfsError", (error) => Effect.succeed(`rejected: ${error.code}`))
783
1095
  * )
784
1096
  *
785
- * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
1097
+ * Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
786
1098
  * // rejected: InvalidStructure
787
1099
  * ```
788
1100
  *
789
1101
  * @category schemas
790
1102
  * @since 0.1.0
791
1103
  */
792
- export const Fixture = VfsModel.Fixture;
1104
+ export const Fixture = FixtureModule.Fixture;
793
1105
  /**
794
1106
  * Creates a fresh empty volume and captures the current Effect `Clock`.
795
1107
  *
@@ -797,11 +1109,12 @@ export const Fixture = VfsModel.Fixture;
797
1109
  *
798
1110
  * Each execution creates independent storage. Snapshot image failures cannot
799
1111
  * arise because this constructor does not accept persisted input.
1112
+ * Provide `Crypto.Crypto` to generate the volume identity and reference-key secret.
800
1113
  *
801
1114
  * @example
802
1115
  * ```ts
1116
+ * import * as BunCrypto from "@effect/platform-bun/BunCrypto"
803
1117
  * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
804
- * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
805
1118
  * import { Effect } from "effect"
806
1119
  *
807
1120
  * const program = Effect.gen(function*() {
@@ -814,7 +1127,7 @@ export const Fixture = VfsModel.Fixture;
814
1127
  * return yield* caller.readFile("/work/notes.txt")
815
1128
  * })
816
1129
  *
817
- * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
1130
+ * Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
818
1131
  * // Uint8Array(2) [ 104, 105 ]
819
1132
  * ```
820
1133
  *
@@ -823,13 +1136,60 @@ export const Fixture = VfsModel.Fixture;
823
1136
  * @since 0.1.0
824
1137
  */
825
1138
  export const make = VfsModel.make;
1139
+ /**
1140
+ * Streams the tree at `root` in a snapshot as fixture entries, read from the
1141
+ * snapshot itself without restoring a volume.
1142
+ *
1143
+ * **Details**
1144
+ *
1145
+ * `root` resolves as a privileged caller at `/` of a volume restored from the
1146
+ * snapshot with default options would resolve it, following intermediate
1147
+ * symbolic links but not a final one and applying no path byte limit. Entries come in sorted
1148
+ * pre-order, children in the byte order of their names, with paths rooted at
1149
+ * `root`, so the root entry's path is `/`. A path stays a string while every
1150
+ * name on it is UTF-8 and is a `BytePath` otherwise. A file or symbolic link
1151
+ * that more than one name reaches is an entry at its first path and a
1152
+ * `hardLink` to that path at the others. Each file's bytes are an owned copy,
1153
+ * made when the stream pulls its entry. Every entry after the root's is a
1154
+ * fixture entry for a volume holding the same tree.
1155
+ *
1156
+ * @example
1157
+ * ```ts
1158
+ * import * as BunCrypto from "@effect/platform-bun/BunCrypto"
1159
+ * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
1160
+ * import { Effect, Stream } from "effect"
1161
+ *
1162
+ * const program = Effect.gen(function*() {
1163
+ * const volume = yield* Vfs.fromFixture({
1164
+ * entries: [
1165
+ * { kind: "directory", path: "/src" },
1166
+ * { kind: "file", path: "/src/b.txt", bytes: new Uint8Array([2]) },
1167
+ * { kind: "file", path: "/src/a.txt", bytes: new Uint8Array([1]) },
1168
+ * { kind: "hardLink", path: "/src/c.txt", target: "/src/a.txt" }
1169
+ * ]
1170
+ * })
1171
+ *
1172
+ * const entries = yield* Stream.runCollect(Vfs.snapshotEntries(yield* volume.snapshot, "/src"))
1173
+ *
1174
+ * return entries.map((entry) => `${entry.kind} ${String(entry.path)}`)
1175
+ * })
1176
+ *
1177
+ * Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
1178
+ * // [ 'directory /', 'file /a.txt', 'file /b.txt', 'hardLink /c.txt' ]
1179
+ * ```
1180
+ *
1181
+ * @see {@link fromFixture} for building a volume from the entries.
1182
+ * @category snapshots
1183
+ * @since 0.6.0
1184
+ */
1185
+ export const snapshotEntries = Image.snapshotEntries;
826
1186
  /**
827
1187
  * Restores a fresh volume from an opaque snapshot under the supplied destination limits.
828
1188
  *
829
1189
  * @example
830
1190
  * ```ts
1191
+ * import * as BunCrypto from "@effect/platform-bun/BunCrypto"
831
1192
  * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
832
- * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
833
1193
  * import { Effect } from "effect"
834
1194
  *
835
1195
  * const program = Effect.gen(function*() {
@@ -856,11 +1216,12 @@ export const make = VfsModel.make;
856
1216
  * ]
857
1217
  * })
858
1218
  *
859
- * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
1219
+ * Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
860
1220
  * // [ 'one', 'two' ]
861
1221
  * ```
862
1222
  *
863
1223
  * @see {@link makeOverlay} for a copy-on-write workspace over the same base.
1224
+ * @see `Crypto.Crypto` for the required secure random-byte service.
864
1225
  * @category constructors
865
1226
  * @since 0.1.0
866
1227
  */
@@ -875,13 +1236,13 @@ export const fromSnapshot = VfsModel.fromSnapshot;
875
1236
  * storage. Metadata, namespace state, coordination, handles, and watches are
876
1237
  * always private to the new workspace.
877
1238
  *
878
- * Invalid base snapshots fail with `ImageError`; invalid volume limits fail
879
- * with `ConfigurationError`. Each execution creates a fresh workspace.
1239
+ * Invalid base snapshots and invalid volume limits fail with `VfsError`. Each
1240
+ * execution creates a fresh workspace.
880
1241
  *
881
1242
  * @example
882
1243
  * ```ts
1244
+ * import * as BunCrypto from "@effect/platform-bun/BunCrypto"
883
1245
  * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
884
- * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
885
1246
  * import { Effect } from "effect"
886
1247
  *
887
1248
  * const program = Effect.gen(function*() {
@@ -907,11 +1268,12 @@ export const fromSnapshot = VfsModel.fromSnapshot;
907
1268
  * ]
908
1269
  * })
909
1270
  *
910
- * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
1271
+ * Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
911
1272
  * // [ '{}', [ 'Updated' ] ]
912
1273
  * ```
913
1274
  *
914
1275
  * @see The overlay guide at `/guides/overlay-filesystems`.
1276
+ * @see `Crypto.Crypto` for the required secure random-byte service.
915
1277
  * @category constructors
916
1278
  * @since 0.1.0
917
1279
  */
@@ -921,14 +1283,14 @@ export const makeOverlay = VfsModel.makeOverlay;
921
1283
  *
922
1284
  * @example
923
1285
  * ```ts
1286
+ * import * as BunCrypto from "@effect/platform-bun/BunCrypto"
924
1287
  * import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
925
- * import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
926
1288
  * import { Effect } from "effect"
927
1289
  *
928
1290
  * const encoder = new TextEncoder()
929
1291
  *
930
1292
  * const program = Effect.gen(function*() {
931
- * // Parent directories must be listed explicitly, before their children.
1293
+ * // Parent directories must be listed explicitly, in any order.
932
1294
  * const volume = yield* Vfs.fromFixture({
933
1295
  * entries: [
934
1296
  * { kind: "directory", path: "/project" },
@@ -941,14 +1303,18 @@ export const makeOverlay = VfsModel.makeOverlay;
941
1303
  * ]
942
1304
  * })
943
1305
  *
944
- * return yield* (yield* volume.caller()).readDirectory("/project")
1306
+ * // A fixture's directories list their entries in the byte order of their names.
1307
+ * const listing = yield* (yield* volume.caller()).readDirectory("/project")
1308
+ *
1309
+ * return listing.value.map((entry) => new TextDecoder().decode(entry.name))
945
1310
  * })
946
1311
  *
947
- * Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))).then(console.log)
948
- * // [ 'package.json', 'latest' ]
1312
+ * Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
1313
+ * // [ 'latest', 'package.json' ]
949
1314
  * ```
950
1315
  *
951
1316
  * @see The fixtures guide at `/guides/fixtures-and-snapshots`.
1317
+ * @see `Crypto.Crypto` for the required secure random-byte service.
952
1318
  * @category constructors
953
1319
  * @since 0.1.0
954
1320
  */