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