@effect-vfs/core 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -21
- package/dist/BytePath.d.ts +115 -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 +13 -20
- package/dist/LiveVolume.d.ts.map +1 -1
- package/dist/LiveVolume.js +19 -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 +32 -53
- package/dist/Snapshot.d.ts.map +1 -1
- package/dist/Snapshot.js +27 -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 +132 -0
- package/dist/Testing.d.ts.map +1 -0
- package/dist/Testing.js +100 -0
- package/dist/VfsError.d.ts +200 -0
- package/dist/VfsError.d.ts.map +1 -0
- package/dist/VfsError.js +165 -0
- package/dist/VirtualFileSystem.d.ts +676 -353
- package/dist/VirtualFileSystem.d.ts.map +1 -1
- package/dist/VirtualFileSystem.js +487 -121
- 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 +4 -15
- 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 +605 -503
- package/dist/internal/snapshotDeltaModel.d.ts.map +1 -1
- package/dist/internal/snapshotDeltaModel.js +0 -1
- 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 +44 -488
- package/dist/internal/virtualFileSystem.d.ts.map +1 -1
- package/dist/internal/virtualFileSystem.js +2111 -2466
- package/dist/internal/volumeState.d.ts +111 -0
- package/dist/internal/volumeState.d.ts.map +1 -0
- package/dist/internal/volumeState.js +202 -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 +54 -10
- 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/stagedState.d.ts +0 -22
- package/dist/internal/stagedState.d.ts.map +0 -1
- package/dist/internal/stagedState.js +0 -62
- 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,8 @@ 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
|
-
|
|
19
|
+
The package declares its exact Effect version as a peer dependency. Volume constructors and snapshot delta functions require Effect’s `Crypto` service. On Node, install the matching
|
|
20
|
+
provider and supply `NodeCrypto.layer` when running these effects:
|
|
21
21
|
|
|
22
22
|
```sh
|
|
23
23
|
npm install "@effect/platform-node-shared@$(npm view @effect-vfs/core peerDependencies.effect)"
|
|
@@ -111,9 +111,9 @@ const program = Effect.gen(function*() {
|
|
|
111
111
|
create: "exclusive"
|
|
112
112
|
})
|
|
113
113
|
|
|
114
|
-
const
|
|
114
|
+
const listing = yield* fs.readDirectory("/")
|
|
115
115
|
const roundTrip = yield* Vfs.pathToBytes(opaquePath)
|
|
116
|
-
return { names:
|
|
116
|
+
return { names: listing.value.map((entry) => Array.from(entry.name)), roundTrip: Array.from(roundTrip) }
|
|
117
117
|
})
|
|
118
118
|
|
|
119
119
|
console.log(await Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer))))
|
|
@@ -121,8 +121,8 @@ console.log(await Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer
|
|
|
121
121
|
```
|
|
122
122
|
|
|
123
123
|
The constructors and byte-returning operations copy their buffers, so later mutation cannot change stored paths.
|
|
124
|
-
|
|
125
|
-
|
|
124
|
+
Directory listings, symbolic-link targets, and resolved paths come back as bytes; decoding them to text is the caller's
|
|
125
|
+
choice, and a strict decoder reports a name that is not valid UTF-8.
|
|
126
126
|
|
|
127
127
|
## Build fixtures and restore snapshots
|
|
128
128
|
|
|
@@ -213,10 +213,13 @@ const program = Effect.scoped(Effect.gen(function*() {
|
|
|
213
213
|
const second = yield* file.read(4)
|
|
214
214
|
const preview = yield* file.pread(3, 0n) // Does not move the cursor.
|
|
215
215
|
|
|
216
|
-
|
|
216
|
+
const decode = (bytes: Uint8Array) => new TextDecoder().decode(bytes)
|
|
217
|
+
|
|
218
|
+
// A positioned read also says whether it reached the end of the file.
|
|
219
|
+
return [decode(first), decode(second), decode(preview.bytes), preview.eof]
|
|
217
220
|
}))
|
|
218
221
|
|
|
219
|
-
console.log(await Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer)))) // ["one\n", "two\n", "one"]
|
|
222
|
+
console.log(await Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.layer)))) // ["one\n", "two\n", "one", false]
|
|
220
223
|
```
|
|
221
224
|
|
|
222
225
|
Handles also expose an explicit `close` effect when early release matters. Calling explicit close twice fails, while
|
|
@@ -224,9 +227,10 @@ scope cleanup remains safe after an explicit close.
|
|
|
224
227
|
|
|
225
228
|
## Handle expected failures as data
|
|
226
229
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
+
Every failure is a `VfsError` with a stable `code` and the `operation` that failed. A rejected option names its
|
|
231
|
+
`field`, a path-addressed failure names its `path` as bytes, and a classified failure keeps its `cause`. Codec and
|
|
232
|
+
store failures share the class with their own codes. Interruption and defects remain separate from these expected
|
|
233
|
+
failures.
|
|
230
234
|
|
|
231
235
|
```ts
|
|
232
236
|
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
|
|
@@ -237,7 +241,7 @@ const program = Effect.gen(function*() {
|
|
|
237
241
|
const fs = yield* (yield* Vfs.make()).caller()
|
|
238
242
|
|
|
239
243
|
return yield* fs.readFile("/optional.json").pipe(
|
|
240
|
-
Effect.catchTag("
|
|
244
|
+
Effect.catchTag("VfsError", (error) =>
|
|
241
245
|
error.code === "NotFound"
|
|
242
246
|
? Effect.succeed(new TextEncoder().encode("{}"))
|
|
243
247
|
: Effect.fail(error))
|
|
@@ -248,11 +252,13 @@ const bytes = await Effect.runPromise(program.pipe(Effect.provide(NodeCrypto.lay
|
|
|
248
252
|
console.log(new TextDecoder().decode(bytes)) // {}
|
|
249
253
|
```
|
|
250
254
|
|
|
251
|
-
## Provide a caller as
|
|
255
|
+
## Provide a volume and a caller as Effect services
|
|
252
256
|
|
|
253
|
-
`
|
|
254
|
-
|
|
255
|
-
|
|
257
|
+
`Volume` and `Caller` are services with static layers. `Volume.layer()` builds a fresh empty volume,
|
|
258
|
+
`Volume.layerFromSnapshot`, `Volume.layerFromFixture` and `Volume.layerOverlay` build one from existing state, and
|
|
259
|
+
`Volume.layerLive` opens a durable one through the `LiveImageStore` in context. `Caller.layer(options)` makes a root
|
|
260
|
+
caller on the volume in context. A caller supplied through the service keeps its original volume, identity, umask and
|
|
261
|
+
working directory.
|
|
256
262
|
|
|
257
263
|
```ts
|
|
258
264
|
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
|
|
@@ -260,21 +266,23 @@ import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
|
|
|
260
266
|
import { Effect } from "effect"
|
|
261
267
|
|
|
262
268
|
const loadConfig = Effect.gen(function*() {
|
|
263
|
-
const fs = yield* Vfs.
|
|
269
|
+
const fs = yield* Vfs.Caller
|
|
264
270
|
return yield* fs.readFile("/app/config.json")
|
|
265
271
|
})
|
|
266
272
|
|
|
267
273
|
const program = Effect.gen(function*() {
|
|
268
|
-
const fs = yield*
|
|
274
|
+
const fs = yield* Vfs.Caller
|
|
269
275
|
yield* fs.mkdir("/app")
|
|
270
276
|
yield* fs.writeFile("/app/config.json", new TextEncoder().encode("{}"), {
|
|
271
277
|
access: "write",
|
|
272
278
|
create: "exclusive"
|
|
273
279
|
})
|
|
274
|
-
return yield* loadConfig
|
|
280
|
+
return yield* loadConfig
|
|
275
281
|
})
|
|
276
282
|
|
|
277
|
-
const bytes = await Effect.runPromise(
|
|
283
|
+
const bytes = await Effect.runPromise(
|
|
284
|
+
program.pipe(Effect.provide(Vfs.Caller.layer()), Effect.provide(Vfs.Volume.layer()), Effect.provide(NodeCrypto.layer))
|
|
285
|
+
)
|
|
278
286
|
console.log(new TextDecoder().decode(bytes)) // {}
|
|
279
287
|
```
|
|
280
288
|
|
|
@@ -295,7 +303,7 @@ candidate.
|
|
|
295
303
|
- Absolute paths ignore a supplied directory base. Relative paths can use a live, same-volume directory handle.
|
|
296
304
|
- Operations coordinate through one permit per volume. At most 65 operations are admitted at once by
|
|
297
305
|
default, including the active operation; set `maxPendingOperations` to change the waiting budget.
|
|
298
|
-
Excess work fails with retryable `
|
|
306
|
+
Excess work fails with retryable `VfsError` code `VolumeBusy`
|
|
299
307
|
before mutation or storage commit. Interruption while waiting makes no change; interruption after a commit
|
|
300
308
|
does not roll it back.
|
|
301
309
|
- 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
|
*
|
|
@@ -28,8 +33,8 @@ export type BytePathId = typeof BytePathId;
|
|
|
28
33
|
*
|
|
29
34
|
* @example
|
|
30
35
|
* ```ts
|
|
36
|
+
* import * as BunCrypto from "@effect/platform-bun/BunCrypto"
|
|
31
37
|
* import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
|
|
32
|
-
* import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
|
|
33
38
|
* import { Effect } from "effect"
|
|
34
39
|
*
|
|
35
40
|
* // A name that is not valid UTF-8 still round-trips exactly.
|
|
@@ -42,11 +47,11 @@ export type BytePathId = typeof BytePathId;
|
|
|
42
47
|
* create: "exclusive"
|
|
43
48
|
* })
|
|
44
49
|
*
|
|
45
|
-
* //
|
|
46
|
-
* return yield* caller.
|
|
50
|
+
* // A listing carries names as bytes, so nothing is lost to text decoding.
|
|
51
|
+
* return (yield* caller.readDirectory("/")).value.map((entry) => entry.name)
|
|
47
52
|
* })
|
|
48
53
|
*
|
|
49
|
-
* Effect.runPromise(program.pipe(Effect.provide(
|
|
54
|
+
* Effect.runPromise(program.pipe(Effect.provide(BunCrypto.layer))).then(console.log)
|
|
50
55
|
* // [ Uint8Array(2) [ 255, 254 ] ]
|
|
51
56
|
* ```
|
|
52
57
|
*
|
|
@@ -63,4 +68,110 @@ export interface BytePath extends Equal.Equal, Pipeable {
|
|
|
63
68
|
* @since 0.1.0
|
|
64
69
|
*/
|
|
65
70
|
export declare const BytePath: Schema.declare<BytePath, BytePath>;
|
|
71
|
+
/**
|
|
72
|
+
* Whether a value is a byte path.
|
|
73
|
+
*
|
|
74
|
+
* @category predicates
|
|
75
|
+
* @since 0.6.0
|
|
76
|
+
*/
|
|
77
|
+
export declare const isBytePath: (value: unknown) => value is BytePath;
|
|
78
|
+
/**
|
|
79
|
+
* Orders raw byte strings, such as entry names, byte by byte and then by length.
|
|
80
|
+
*
|
|
81
|
+
* @category ordering
|
|
82
|
+
* @since 0.6.0
|
|
83
|
+
*/
|
|
84
|
+
export declare const byteOrder: OrderModule.Order<Uint8Array>;
|
|
85
|
+
/**
|
|
86
|
+
* Orders byte paths by their bytes.
|
|
87
|
+
*
|
|
88
|
+
* @category ordering
|
|
89
|
+
* @since 0.6.0
|
|
90
|
+
*/
|
|
91
|
+
export declare const Order: OrderModule.Order<BytePath>;
|
|
92
|
+
/**
|
|
93
|
+
* A byte path holding a copy of `bytes`, which must be non-empty and hold no NUL.
|
|
94
|
+
*
|
|
95
|
+
* @category constructors
|
|
96
|
+
* @since 0.6.0
|
|
97
|
+
*/
|
|
98
|
+
export declare const fromBytes: (bytes: Uint8Array) => Effect.Effect<BytePath, FsFailure>;
|
|
99
|
+
/**
|
|
100
|
+
* A byte path holding the UTF-8 encoding of `text`.
|
|
101
|
+
*
|
|
102
|
+
* @category constructors
|
|
103
|
+
* @since 0.6.0
|
|
104
|
+
*/
|
|
105
|
+
export declare const fromString: (text: string) => Effect.Effect<BytePath, FsFailure>;
|
|
106
|
+
/**
|
|
107
|
+
* A byte path for any path input: a byte path is returned as is, a string is encoded.
|
|
108
|
+
*
|
|
109
|
+
* @category constructors
|
|
110
|
+
* @since 0.6.0
|
|
111
|
+
*/
|
|
112
|
+
export declare const fromInput: (input: PathInput) => Effect.Effect<BytePath, FsFailure>;
|
|
113
|
+
/**
|
|
114
|
+
* A copy of the bytes a byte path holds.
|
|
115
|
+
*
|
|
116
|
+
* @category getters
|
|
117
|
+
* @since 0.6.0
|
|
118
|
+
*/
|
|
119
|
+
export declare const toBytes: (path: BytePath) => Effect.Effect<Uint8Array, FsFailure>;
|
|
120
|
+
/**
|
|
121
|
+
* The text of a byte path, failing as `UnrepresentableName` when its bytes are not valid UTF-8.
|
|
122
|
+
*
|
|
123
|
+
* @category getters
|
|
124
|
+
* @since 0.6.0
|
|
125
|
+
*/
|
|
126
|
+
export declare const toString: (path: BytePath) => Effect.Effect<string, FsFailure>;
|
|
127
|
+
/**
|
|
128
|
+
* The text of a byte path, or none when its bytes are not valid UTF-8.
|
|
129
|
+
*
|
|
130
|
+
* @category getters
|
|
131
|
+
* @since 0.6.0
|
|
132
|
+
*/
|
|
133
|
+
export declare const toStringOption: (path: BytePath) => Option.Option<string>;
|
|
134
|
+
/**
|
|
135
|
+
* The text of raw bytes such as an entry name, or none when they are not valid UTF-8.
|
|
136
|
+
*
|
|
137
|
+
* @category getters
|
|
138
|
+
* @since 0.6.0
|
|
139
|
+
*/
|
|
140
|
+
export declare const decodeOption: (bytes: Uint8Array) => Option.Option<string>;
|
|
141
|
+
/**
|
|
142
|
+
* Whether a byte path is exactly `/`.
|
|
143
|
+
*
|
|
144
|
+
* @category predicates
|
|
145
|
+
* @since 0.6.0
|
|
146
|
+
*/
|
|
147
|
+
export declare const isRoot: (path: BytePath) => boolean;
|
|
148
|
+
/**
|
|
149
|
+
* Whether a byte path starts with a slash.
|
|
150
|
+
*
|
|
151
|
+
* @category predicates
|
|
152
|
+
* @since 0.6.0
|
|
153
|
+
*/
|
|
154
|
+
export declare const isAbsolute: (path: BytePath) => boolean;
|
|
155
|
+
/**
|
|
156
|
+
* The final component of a byte path as bytes; empty for the root and for a path that ends in a slash.
|
|
157
|
+
*
|
|
158
|
+
* @category getters
|
|
159
|
+
* @since 0.6.0
|
|
160
|
+
*/
|
|
161
|
+
export declare const basename: (path: BytePath) => Uint8Array;
|
|
162
|
+
/**
|
|
163
|
+
* The byte path holding everything before the final component, or the path itself when it has no parent.
|
|
164
|
+
*
|
|
165
|
+
* @category getters
|
|
166
|
+
* @since 0.6.0
|
|
167
|
+
*/
|
|
168
|
+
export declare const parent: (path: BytePath) => BytePath;
|
|
169
|
+
/**
|
|
170
|
+
* A byte path naming `name` under `path`. A string name is encoded as UTF-8 and not checked: a slash in it
|
|
171
|
+
* adds further components, and a NUL makes a path that fails where it is used.
|
|
172
|
+
*
|
|
173
|
+
* @category combinators
|
|
174
|
+
* @since 0.6.0
|
|
175
|
+
*/
|
|
176
|
+
export declare const join: (path: BytePath, name: string | Uint8Array) => BytePath;
|
|
66
177
|
//# 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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;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
|
+
};
|