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