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