@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
package/README.md
CHANGED
|
@@ -16,8 +16,9 @@ for the exact permission, path, timestamp, quota, and atomicity rules.
|
|
|
16
16
|
npm install @effect-vfs/core@latest
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
The package declares its exact Effect version as a peer dependency.
|
|
20
|
-
or Bun, install the matching
|
|
19
|
+
The package declares its exact Effect version as a peer dependency. An in-memory volume needs no platform service.
|
|
20
|
+
The snapshot delta functions hash with SHA-256, so they take a `Crypto` service; on Node or Bun, install the matching
|
|
21
|
+
provider and supply `NodeCrypto.layer` for those:
|
|
21
22
|
|
|
22
23
|
```sh
|
|
23
24
|
npm install "@effect/platform-node-shared@$(npm view @effect-vfs/core peerDependencies.effect)"
|
|
@@ -42,7 +43,6 @@ lifetime are explicit values that can be composed in one Effect program.
|
|
|
42
43
|
|
|
43
44
|
```ts
|
|
44
45
|
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
|
|
45
|
-
import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
|
|
46
46
|
import { ByteSize, Effect } from "effect"
|
|
47
47
|
|
|
48
48
|
const utf8 = new TextEncoder()
|
|
@@ -79,7 +79,7 @@ const program = Effect.scoped(Effect.gen(function*() {
|
|
|
79
79
|
}
|
|
80
80
|
}))
|
|
81
81
|
|
|
82
|
-
const result = await Effect.runPromise(program
|
|
82
|
+
const result = await Effect.runPromise(program)
|
|
83
83
|
console.log(result) // { config: { feature: "preview" }, mode: "640" }
|
|
84
84
|
```
|
|
85
85
|
|
|
@@ -97,7 +97,6 @@ contain names which are not valid UTF-8.
|
|
|
97
97
|
|
|
98
98
|
```ts
|
|
99
99
|
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
|
|
100
|
-
import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
|
|
101
100
|
import { Effect } from "effect"
|
|
102
101
|
|
|
103
102
|
const program = Effect.gen(function*() {
|
|
@@ -111,18 +110,18 @@ const program = Effect.gen(function*() {
|
|
|
111
110
|
create: "exclusive"
|
|
112
111
|
})
|
|
113
112
|
|
|
114
|
-
const
|
|
113
|
+
const listing = yield* fs.readDirectory("/")
|
|
115
114
|
const roundTrip = yield* Vfs.pathToBytes(opaquePath)
|
|
116
|
-
return { names:
|
|
115
|
+
return { names: listing.value.map((entry) => Array.from(entry.name)), roundTrip: Array.from(roundTrip) }
|
|
117
116
|
})
|
|
118
117
|
|
|
119
|
-
console.log(await Effect.runPromise(program
|
|
118
|
+
console.log(await Effect.runPromise(program))
|
|
120
119
|
// { names: [[255]], roundTrip: [47, 255] }
|
|
121
120
|
```
|
|
122
121
|
|
|
123
122
|
The constructors and byte-returning operations copy their buffers, so later mutation cannot change stored paths.
|
|
124
|
-
|
|
125
|
-
|
|
123
|
+
Directory listings, symbolic-link targets, and resolved paths come back as bytes; decoding them to text is the caller's
|
|
124
|
+
choice, and a strict decoder reports a name that is not valid UTF-8.
|
|
126
125
|
|
|
127
126
|
## Build fixtures and restore snapshots
|
|
128
127
|
|
|
@@ -132,7 +131,6 @@ resettable sandboxes.
|
|
|
132
131
|
|
|
133
132
|
```ts
|
|
134
133
|
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
|
|
135
|
-
import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
|
|
136
134
|
import { Effect } from "effect"
|
|
137
135
|
import * as ByteSize from "effect/ByteSize"
|
|
138
136
|
|
|
@@ -170,7 +168,7 @@ const program = Effect.gen(function*() {
|
|
|
170
168
|
return new TextDecoder().decode(yield* b.readFile("/project/settings.json"))
|
|
171
169
|
})
|
|
172
170
|
|
|
173
|
-
console.log(await Effect.runPromise(program
|
|
171
|
+
console.log(await Effect.runPromise(program)) // {"theme":"dark"}
|
|
174
172
|
```
|
|
175
173
|
|
|
176
174
|
Fixture paths must be absolute and unique, and parent directories must be listed explicitly. Fixtures can also contain
|
|
@@ -198,7 +196,6 @@ interruption.
|
|
|
198
196
|
|
|
199
197
|
```ts
|
|
200
198
|
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
|
|
201
|
-
import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
|
|
202
199
|
import { Effect } from "effect"
|
|
203
200
|
|
|
204
201
|
const program = Effect.scoped(Effect.gen(function*() {
|
|
@@ -213,10 +210,13 @@ const program = Effect.scoped(Effect.gen(function*() {
|
|
|
213
210
|
const second = yield* file.read(4)
|
|
214
211
|
const preview = yield* file.pread(3, 0n) // Does not move the cursor.
|
|
215
212
|
|
|
216
|
-
|
|
213
|
+
const decode = (bytes: Uint8Array) => new TextDecoder().decode(bytes)
|
|
214
|
+
|
|
215
|
+
// A positioned read also says whether it reached the end of the file.
|
|
216
|
+
return [decode(first), decode(second), decode(preview.bytes), preview.eof]
|
|
217
217
|
}))
|
|
218
218
|
|
|
219
|
-
console.log(await Effect.runPromise(program
|
|
219
|
+
console.log(await Effect.runPromise(program)) // ["one\n", "two\n", "one", false]
|
|
220
220
|
```
|
|
221
221
|
|
|
222
222
|
Handles also expose an explicit `close` effect when early release matters. Calling explicit close twice fails, while
|
|
@@ -224,57 +224,60 @@ scope cleanup remains safe after an explicit close.
|
|
|
224
224
|
|
|
225
225
|
## Handle expected failures as data
|
|
226
226
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
227
|
+
Every failure is a `VfsError` with a stable `code` and the `operation` that failed. A rejected option names its
|
|
228
|
+
`field`, a path-addressed failure names its `path` as bytes, and a classified failure keeps its `cause`. Codec and
|
|
229
|
+
store failures share the class with their own codes. Interruption and defects remain separate from these expected
|
|
230
|
+
failures.
|
|
230
231
|
|
|
231
232
|
```ts
|
|
232
233
|
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
|
|
233
|
-
import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
|
|
234
234
|
import { Effect } from "effect"
|
|
235
235
|
|
|
236
236
|
const program = Effect.gen(function*() {
|
|
237
237
|
const fs = yield* (yield* Vfs.make()).caller()
|
|
238
238
|
|
|
239
239
|
return yield* fs.readFile("/optional.json").pipe(
|
|
240
|
-
Effect.catchTag("
|
|
240
|
+
Effect.catchTag("VfsError", (error) =>
|
|
241
241
|
error.code === "NotFound"
|
|
242
242
|
? Effect.succeed(new TextEncoder().encode("{}"))
|
|
243
243
|
: Effect.fail(error))
|
|
244
244
|
)
|
|
245
245
|
})
|
|
246
246
|
|
|
247
|
-
const bytes = await Effect.runPromise(program
|
|
247
|
+
const bytes = await Effect.runPromise(program)
|
|
248
248
|
console.log(new TextDecoder().decode(bytes)) // {}
|
|
249
249
|
```
|
|
250
250
|
|
|
251
|
-
## Provide a caller as
|
|
251
|
+
## Provide a volume and a caller as Effect services
|
|
252
252
|
|
|
253
|
-
`
|
|
254
|
-
|
|
255
|
-
|
|
253
|
+
`Volume` and `Caller` are services with static layers. `Volume.layer()` builds a fresh empty volume,
|
|
254
|
+
`Volume.layerFromSnapshot`, `Volume.layerFromFixture` and `Volume.layerOverlay` build one from existing state, and
|
|
255
|
+
`Volume.layerLive` opens a durable one through the `LiveImageStore` in context. `Caller.layer(options)` makes a root
|
|
256
|
+
caller on the volume in context. A caller supplied through the service keeps its original volume, identity, umask and
|
|
257
|
+
working directory.
|
|
256
258
|
|
|
257
259
|
```ts
|
|
258
260
|
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
|
|
259
|
-
import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
|
|
260
261
|
import { Effect } from "effect"
|
|
261
262
|
|
|
262
263
|
const loadConfig = Effect.gen(function*() {
|
|
263
|
-
const fs = yield* Vfs.
|
|
264
|
+
const fs = yield* Vfs.Caller
|
|
264
265
|
return yield* fs.readFile("/app/config.json")
|
|
265
266
|
})
|
|
266
267
|
|
|
267
268
|
const program = Effect.gen(function*() {
|
|
268
|
-
const fs = yield*
|
|
269
|
+
const fs = yield* Vfs.Caller
|
|
269
270
|
yield* fs.mkdir("/app")
|
|
270
271
|
yield* fs.writeFile("/app/config.json", new TextEncoder().encode("{}"), {
|
|
271
272
|
access: "write",
|
|
272
273
|
create: "exclusive"
|
|
273
274
|
})
|
|
274
|
-
return yield* loadConfig
|
|
275
|
+
return yield* loadConfig
|
|
275
276
|
})
|
|
276
277
|
|
|
277
|
-
const bytes = await Effect.runPromise(
|
|
278
|
+
const bytes = await Effect.runPromise(
|
|
279
|
+
program.pipe(Effect.provide(Vfs.Caller.layer()), Effect.provide(Vfs.Volume.layer()))
|
|
280
|
+
)
|
|
278
281
|
console.log(new TextDecoder().decode(bytes)) // {}
|
|
279
282
|
```
|
|
280
283
|
|
|
@@ -295,7 +298,7 @@ candidate.
|
|
|
295
298
|
- Absolute paths ignore a supplied directory base. Relative paths can use a live, same-volume directory handle.
|
|
296
299
|
- Operations coordinate through one permit per volume. At most 65 operations are admitted at once by
|
|
297
300
|
default, including the active operation; set `maxPendingOperations` to change the waiting budget.
|
|
298
|
-
Excess work fails with retryable `
|
|
301
|
+
Excess work fails with retryable `VfsError` code `VolumeBusy`
|
|
299
302
|
before mutation or storage commit. Interruption while waiting makes no change; interruption after a commit
|
|
300
303
|
does not roll it back.
|
|
301
304
|
- Each watch subscriber retains at most 256 events by default; set `maxWatchEvents` to change this bound
|
package/dist/BytePath.d.ts
CHANGED
|
@@ -6,9 +6,14 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @since 0.1.0
|
|
8
8
|
*/
|
|
9
|
+
import * as Effect from "effect/Effect";
|
|
9
10
|
import type * as Equal from "effect/Equal";
|
|
11
|
+
import * as Option from "effect/Option";
|
|
12
|
+
import * as OrderModule from "effect/Order";
|
|
10
13
|
import type { Pipeable } from "effect/Pipeable";
|
|
11
14
|
import * as Schema from "effect/Schema";
|
|
15
|
+
import type { FsFailure } from "./VfsError.js";
|
|
16
|
+
import type { PathInput } from "./VirtualFileSystem.js";
|
|
12
17
|
/**
|
|
13
18
|
* Type identifier for opaque byte-preserving filesystem paths.
|
|
14
19
|
*
|
|
@@ -29,7 +34,6 @@ export type BytePathId = typeof BytePathId;
|
|
|
29
34
|
* @example
|
|
30
35
|
* ```ts
|
|
31
36
|
* import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
|
|
32
|
-
* import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
|
|
33
37
|
* import { Effect } from "effect"
|
|
34
38
|
*
|
|
35
39
|
* // A name that is not valid UTF-8 still round-trips exactly.
|
|
@@ -42,11 +46,11 @@ export type BytePathId = typeof BytePathId;
|
|
|
42
46
|
* create: "exclusive"
|
|
43
47
|
* })
|
|
44
48
|
*
|
|
45
|
-
* //
|
|
46
|
-
* return yield* caller.
|
|
49
|
+
* // A listing carries names as bytes, so nothing is lost to text decoding.
|
|
50
|
+
* return (yield* caller.readDirectory("/")).value.map((entry) => entry.name)
|
|
47
51
|
* })
|
|
48
52
|
*
|
|
49
|
-
* Effect.runPromise(program
|
|
53
|
+
* Effect.runPromise(program).then(console.log)
|
|
50
54
|
* // [ Uint8Array(2) [ 255, 254 ] ]
|
|
51
55
|
* ```
|
|
52
56
|
*
|
|
@@ -63,4 +67,110 @@ export interface BytePath extends Equal.Equal, Pipeable {
|
|
|
63
67
|
* @since 0.1.0
|
|
64
68
|
*/
|
|
65
69
|
export declare const BytePath: Schema.declare<BytePath, BytePath>;
|
|
70
|
+
/**
|
|
71
|
+
* Whether a value is a byte path.
|
|
72
|
+
*
|
|
73
|
+
* @category predicates
|
|
74
|
+
* @since 0.6.0
|
|
75
|
+
*/
|
|
76
|
+
export declare const isBytePath: (value: unknown) => value is BytePath;
|
|
77
|
+
/**
|
|
78
|
+
* Orders raw byte strings, such as entry names, byte by byte and then by length.
|
|
79
|
+
*
|
|
80
|
+
* @category ordering
|
|
81
|
+
* @since 0.6.0
|
|
82
|
+
*/
|
|
83
|
+
export declare const byteOrder: OrderModule.Order<Uint8Array>;
|
|
84
|
+
/**
|
|
85
|
+
* Orders byte paths by their bytes.
|
|
86
|
+
*
|
|
87
|
+
* @category ordering
|
|
88
|
+
* @since 0.6.0
|
|
89
|
+
*/
|
|
90
|
+
export declare const Order: OrderModule.Order<BytePath>;
|
|
91
|
+
/**
|
|
92
|
+
* A byte path holding a copy of `bytes`, which must be non-empty and hold no NUL.
|
|
93
|
+
*
|
|
94
|
+
* @category constructors
|
|
95
|
+
* @since 0.6.0
|
|
96
|
+
*/
|
|
97
|
+
export declare const fromBytes: (bytes: Uint8Array) => Effect.Effect<BytePath, FsFailure>;
|
|
98
|
+
/**
|
|
99
|
+
* A byte path holding the UTF-8 encoding of `text`.
|
|
100
|
+
*
|
|
101
|
+
* @category constructors
|
|
102
|
+
* @since 0.6.0
|
|
103
|
+
*/
|
|
104
|
+
export declare const fromString: (text: string) => Effect.Effect<BytePath, FsFailure>;
|
|
105
|
+
/**
|
|
106
|
+
* A byte path for any path input: a byte path is returned as is, a string is encoded.
|
|
107
|
+
*
|
|
108
|
+
* @category constructors
|
|
109
|
+
* @since 0.6.0
|
|
110
|
+
*/
|
|
111
|
+
export declare const fromInput: (input: PathInput) => Effect.Effect<BytePath, FsFailure>;
|
|
112
|
+
/**
|
|
113
|
+
* A copy of the bytes a byte path holds.
|
|
114
|
+
*
|
|
115
|
+
* @category getters
|
|
116
|
+
* @since 0.6.0
|
|
117
|
+
*/
|
|
118
|
+
export declare const toBytes: (path: BytePath) => Effect.Effect<Uint8Array, FsFailure>;
|
|
119
|
+
/**
|
|
120
|
+
* The text of a byte path, failing as `UnrepresentableName` when its bytes are not valid UTF-8.
|
|
121
|
+
*
|
|
122
|
+
* @category getters
|
|
123
|
+
* @since 0.6.0
|
|
124
|
+
*/
|
|
125
|
+
export declare const toString: (path: BytePath) => Effect.Effect<string, FsFailure>;
|
|
126
|
+
/**
|
|
127
|
+
* The text of a byte path, or none when its bytes are not valid UTF-8.
|
|
128
|
+
*
|
|
129
|
+
* @category getters
|
|
130
|
+
* @since 0.6.0
|
|
131
|
+
*/
|
|
132
|
+
export declare const toStringOption: (path: BytePath) => Option.Option<string>;
|
|
133
|
+
/**
|
|
134
|
+
* The text of raw bytes such as an entry name, or none when they are not valid UTF-8.
|
|
135
|
+
*
|
|
136
|
+
* @category getters
|
|
137
|
+
* @since 0.6.0
|
|
138
|
+
*/
|
|
139
|
+
export declare const decodeOption: (bytes: Uint8Array) => Option.Option<string>;
|
|
140
|
+
/**
|
|
141
|
+
* Whether a byte path is exactly `/`.
|
|
142
|
+
*
|
|
143
|
+
* @category predicates
|
|
144
|
+
* @since 0.6.0
|
|
145
|
+
*/
|
|
146
|
+
export declare const isRoot: (path: BytePath) => boolean;
|
|
147
|
+
/**
|
|
148
|
+
* Whether a byte path starts with a slash.
|
|
149
|
+
*
|
|
150
|
+
* @category predicates
|
|
151
|
+
* @since 0.6.0
|
|
152
|
+
*/
|
|
153
|
+
export declare const isAbsolute: (path: BytePath) => boolean;
|
|
154
|
+
/**
|
|
155
|
+
* The final component of a byte path as bytes; empty for the root and for a path that ends in a slash.
|
|
156
|
+
*
|
|
157
|
+
* @category getters
|
|
158
|
+
* @since 0.6.0
|
|
159
|
+
*/
|
|
160
|
+
export declare const basename: (path: BytePath) => Uint8Array;
|
|
161
|
+
/**
|
|
162
|
+
* The byte path holding everything before the final component, or the path itself when it has no parent.
|
|
163
|
+
*
|
|
164
|
+
* @category getters
|
|
165
|
+
* @since 0.6.0
|
|
166
|
+
*/
|
|
167
|
+
export declare const parent: (path: BytePath) => BytePath;
|
|
168
|
+
/**
|
|
169
|
+
* A byte path naming `name` under `path`. A string name is encoded as UTF-8 and not checked: a slash in it
|
|
170
|
+
* adds further components, and a NUL makes a path that fails where it is used.
|
|
171
|
+
*
|
|
172
|
+
* @category combinators
|
|
173
|
+
* @since 0.6.0
|
|
174
|
+
*/
|
|
175
|
+
export declare const join: (path: BytePath, name: string | Uint8Array) => BytePath;
|
|
66
176
|
//# sourceMappingURL=BytePath.d.ts.map
|
package/dist/BytePath.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BytePath.d.ts","sourceRoot":"","sources":["../src/BytePath.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,KAAK,KAAK,MAAM,cAAc,CAAA;AAC1C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;
|
|
1
|
+
{"version":3,"file":"BytePath.d.ts","sourceRoot":"","sources":["../src/BytePath.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,KAAK,KAAK,MAAM,cAAc,CAAA;AAC1C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,WAAW,MAAM,cAAc,CAAA;AAC3C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AAG/C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAKvC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAC9C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAA;AAEvD;;;;;GAKG;AACH,eAAO,MAAM,UAAU,EAAE,2BAAiD,CAAA;AAE1E;;;;;GAKG;AACH,MAAM,MAAM,UAAU,GAAG,OAAO,UAAU,CAAA;AAE1C;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,WAAW,QAAS,SAAQ,KAAK,CAAC,KAAK,EAAE,QAAQ;IACrD,QAAQ,CAAC,CAAC,UAAU,CAAC,EAAE,UAAU,CAAA;CAClC;AAED;;;;;GAKG;AACH,eAAO,MAAM,QAAQ,oCAAgD,CAAA;AAUrE;;;;;GAKG;AACH,eAAO,MAAM,UAAU,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,KAAK,IAAI,QAA8B,CAAA;AAEpF;;;;;GAKG;AACH,eAAO,MAAM,SAAS,EAAE,WAAW,CAAC,KAAK,CAAC,UAAU,CAAoB,CAAA;AAExE;;;;;GAKG;AACH,eAAO,MAAM,KAAK,EAAE,WAAW,CAAC,KAAK,CAAC,QAAQ,CAAmD,CAAA;AAMjG;;;;;GAKG;AACH,eAAO,MAAM,SAAS,UAAW,UAAU,KAAG,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,SAAS,CACvB,CAAA;AAExD;;;;;GAKG;AACH,eAAO,MAAM,UAAU,SAAU,MAAM,KAAG,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,SAAS,CAM1E,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,SAAS,UAAW,SAAS,KAAG,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,SAAS,CACN,CAAA;AAExE;;;;;GAKG;AACH,eAAO,MAAM,OAAO,SAAU,QAAQ,KAAG,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,SAAS,CACzB,CAAA;AAEnD;;;;;GAKG;AACH,eAAO,MAAM,QAAQ,SAAU,QAAQ,KAAG,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,CAItE,CAAA;AAEH;;;;;GAKG;AACH,eAAO,MAAM,cAAc,SAAU,QAAQ,KAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAgC,CAAA;AAEpG;;;;;GAKG;AACH,eAAO,MAAM,YAAY,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,MAAM,CAAC,MAAM,CAAC,MAAM,CAErE,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,MAAM,SAAU,QAAQ,KAAG,OAIvC,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,UAAU,SAAU,QAAQ,KAAG,OAAqC,CAAA;AAEjF;;;;;GAKG;AACH,eAAO,MAAM,QAAQ,SAAU,QAAQ,KAAG,UAIzC,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,MAAM,SAAU,QAAQ,KAAG,QAOvC,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,IAAI,SAAU,QAAQ,QAAQ,MAAM,GAAG,UAAU,KAAG,QAWhE,CAAA"}
|
package/dist/BytePath.js
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Opaque, byte-preserving filesystem paths.
|
|
3
|
+
*
|
|
4
|
+
* A `BytePath` stores the exact bytes of a path component so that names which
|
|
5
|
+
* are not valid UTF-8 survive a round trip through the virtual filesystem.
|
|
6
|
+
*
|
|
7
|
+
* @since 0.1.0
|
|
8
|
+
*/
|
|
9
|
+
import * as Effect from "effect/Effect";
|
|
10
|
+
import * as Option from "effect/Option";
|
|
11
|
+
import * as OrderModule from "effect/Order";
|
|
12
|
+
import * as Predicate from "effect/Predicate";
|
|
13
|
+
import * as Result from "effect/Result";
|
|
1
14
|
import * as Schema from "effect/Schema";
|
|
2
15
|
import * as Internal from "./internal/bytePath.js";
|
|
16
|
+
import * as Bytes from "./internal/bytes.js";
|
|
17
|
+
import { fsFailure, retargetFailure } from "./internal/errors.js";
|
|
18
|
+
import * as Path from "./internal/path.js";
|
|
3
19
|
/**
|
|
4
20
|
* Type identifier for opaque byte-preserving filesystem paths.
|
|
5
21
|
*
|
|
@@ -14,3 +30,142 @@ export const BytePathId = Internal.BytePathId;
|
|
|
14
30
|
* @since 0.1.0
|
|
15
31
|
*/
|
|
16
32
|
export const BytePath = Schema.declare(Internal.isBytePath);
|
|
33
|
+
const encoder = new TextEncoder();
|
|
34
|
+
const decoder = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
|
|
35
|
+
const SLASH = 0x2f;
|
|
36
|
+
const bytesOf = (path) => Internal.getBytes(path) ?? new Uint8Array();
|
|
37
|
+
/**
|
|
38
|
+
* Whether a value is a byte path.
|
|
39
|
+
*
|
|
40
|
+
* @category predicates
|
|
41
|
+
* @since 0.6.0
|
|
42
|
+
*/
|
|
43
|
+
export const isBytePath = Internal.isBytePath;
|
|
44
|
+
/**
|
|
45
|
+
* Orders raw byte strings, such as entry names, byte by byte and then by length.
|
|
46
|
+
*
|
|
47
|
+
* @category ordering
|
|
48
|
+
* @since 0.6.0
|
|
49
|
+
*/
|
|
50
|
+
export const byteOrder = Bytes.bytesOrder;
|
|
51
|
+
/**
|
|
52
|
+
* Orders byte paths by their bytes.
|
|
53
|
+
*
|
|
54
|
+
* @category ordering
|
|
55
|
+
* @since 0.6.0
|
|
56
|
+
*/
|
|
57
|
+
export const Order = OrderModule.mapInput(Bytes.bytesOrder, bytesOf);
|
|
58
|
+
// The shared conversions fail under their own names; each toolkit function names itself instead.
|
|
59
|
+
const named = (operation, effect) => Effect.mapError(effect, (error) => retargetFailure(operation, error));
|
|
60
|
+
/**
|
|
61
|
+
* A byte path holding a copy of `bytes`, which must be non-empty and hold no NUL.
|
|
62
|
+
*
|
|
63
|
+
* @category constructors
|
|
64
|
+
* @since 0.6.0
|
|
65
|
+
*/
|
|
66
|
+
export const fromBytes = (bytes) => named("BytePath.fromBytes", Path.pathFromBytes(bytes));
|
|
67
|
+
/**
|
|
68
|
+
* A byte path holding the UTF-8 encoding of `text`.
|
|
69
|
+
*
|
|
70
|
+
* @category constructors
|
|
71
|
+
* @since 0.6.0
|
|
72
|
+
*/
|
|
73
|
+
export const fromString = (text) => {
|
|
74
|
+
const bytes = Path.inputBytes(text);
|
|
75
|
+
return Result.isFailure(bytes)
|
|
76
|
+
? Effect.fail(fsFailure(bytes.failure, "BytePath.fromString"))
|
|
77
|
+
: named("BytePath.fromString", Path.pathFromBytes(new Uint8Array(bytes.success)));
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* A byte path for any path input: a byte path is returned as is, a string is encoded.
|
|
81
|
+
*
|
|
82
|
+
* @category constructors
|
|
83
|
+
* @since 0.6.0
|
|
84
|
+
*/
|
|
85
|
+
export const fromInput = (input) => Internal.isBytePath(input) ? Effect.succeed(input) : fromString(input);
|
|
86
|
+
/**
|
|
87
|
+
* A copy of the bytes a byte path holds.
|
|
88
|
+
*
|
|
89
|
+
* @category getters
|
|
90
|
+
* @since 0.6.0
|
|
91
|
+
*/
|
|
92
|
+
export const toBytes = (path) => named("BytePath.toBytes", Path.pathToBytes(path));
|
|
93
|
+
/**
|
|
94
|
+
* The text of a byte path, failing as `UnrepresentableName` when its bytes are not valid UTF-8.
|
|
95
|
+
*
|
|
96
|
+
* @category getters
|
|
97
|
+
* @since 0.6.0
|
|
98
|
+
*/
|
|
99
|
+
export const toString = (path) => Effect.flatMap(named("BytePath.toString", Path.pathToBytes(path)), (bytes) => Path.strictString(bytes, "BytePath.toString"));
|
|
100
|
+
/**
|
|
101
|
+
* The text of a byte path, or none when its bytes are not valid UTF-8.
|
|
102
|
+
*
|
|
103
|
+
* @category getters
|
|
104
|
+
* @since 0.6.0
|
|
105
|
+
*/
|
|
106
|
+
export const toStringOption = (path) => decodeOption(bytesOf(path));
|
|
107
|
+
/**
|
|
108
|
+
* The text of raw bytes such as an entry name, or none when they are not valid UTF-8.
|
|
109
|
+
*
|
|
110
|
+
* @category getters
|
|
111
|
+
* @since 0.6.0
|
|
112
|
+
*/
|
|
113
|
+
export const decodeOption = Option.liftThrowable((bytes) => decoder.decode(bytes));
|
|
114
|
+
/**
|
|
115
|
+
* Whether a byte path is exactly `/`.
|
|
116
|
+
*
|
|
117
|
+
* @category predicates
|
|
118
|
+
* @since 0.6.0
|
|
119
|
+
*/
|
|
120
|
+
export const isRoot = (path) => {
|
|
121
|
+
const bytes = bytesOf(path);
|
|
122
|
+
return bytes.length === 1 && bytes[0] === SLASH;
|
|
123
|
+
};
|
|
124
|
+
/**
|
|
125
|
+
* Whether a byte path starts with a slash.
|
|
126
|
+
*
|
|
127
|
+
* @category predicates
|
|
128
|
+
* @since 0.6.0
|
|
129
|
+
*/
|
|
130
|
+
export const isAbsolute = (path) => bytesOf(path)[0] === SLASH;
|
|
131
|
+
/**
|
|
132
|
+
* The final component of a byte path as bytes; empty for the root and for a path that ends in a slash.
|
|
133
|
+
*
|
|
134
|
+
* @category getters
|
|
135
|
+
* @since 0.6.0
|
|
136
|
+
*/
|
|
137
|
+
export const basename = (path) => {
|
|
138
|
+
const bytes = bytesOf(path);
|
|
139
|
+
return bytes.slice(bytes.lastIndexOf(SLASH) + 1);
|
|
140
|
+
};
|
|
141
|
+
/**
|
|
142
|
+
* The byte path holding everything before the final component, or the path itself when it has no parent.
|
|
143
|
+
*
|
|
144
|
+
* @category getters
|
|
145
|
+
* @since 0.6.0
|
|
146
|
+
*/
|
|
147
|
+
export const parent = (path) => {
|
|
148
|
+
const bytes = bytesOf(path);
|
|
149
|
+
const slash = bytes.lastIndexOf(SLASH);
|
|
150
|
+
if (slash < 0)
|
|
151
|
+
return path;
|
|
152
|
+
return Internal.make(slash === 0 ? bytes.slice(0, 1) : bytes.slice(0, slash));
|
|
153
|
+
};
|
|
154
|
+
/**
|
|
155
|
+
* A byte path naming `name` under `path`. A string name is encoded as UTF-8 and not checked: a slash in it
|
|
156
|
+
* adds further components, and a NUL makes a path that fails where it is used.
|
|
157
|
+
*
|
|
158
|
+
* @category combinators
|
|
159
|
+
* @since 0.6.0
|
|
160
|
+
*/
|
|
161
|
+
export const join = (path, name) => {
|
|
162
|
+
const prefix = bytesOf(path);
|
|
163
|
+
const suffix = Predicate.isString(name) ? encoder.encode(name) : name;
|
|
164
|
+
const separator = prefix.length > 0 && prefix[prefix.length - 1] === SLASH ? 0 : 1;
|
|
165
|
+
const output = new Uint8Array(prefix.length + separator + suffix.length);
|
|
166
|
+
output.set(prefix);
|
|
167
|
+
if (separator === 1)
|
|
168
|
+
output[prefix.length] = SLASH;
|
|
169
|
+
output.set(suffix, prefix.length + separator);
|
|
170
|
+
return Internal.make(output);
|
|
171
|
+
};
|