@effect-vfs/core 0.4.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 +39 -32
- 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 +15 -22
- package/dist/LiveVolume.d.ts.map +1 -1
- package/dist/LiveVolume.js +19 -15
- 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 +450 -117
- 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 +54 -0
- package/dist/internal/errors.d.ts.map +1 -0
- package/dist/internal/errors.js +91 -0
- package/dist/internal/{virtualFileSystem/fixture.d.ts → fixture.d.ts} +8 -8
- package/dist/internal/fixture.d.ts.map +1 -0
- package/dist/internal/fixture.js +133 -0
- 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/{virtualFileSystem/overlayDiff.d.ts → overlayDiff.d.ts} +3 -3
- package/dist/internal/overlayDiff.d.ts.map +1 -0
- package/dist/internal/{virtualFileSystem/overlayDiff.js → overlayDiff.js} +1 -4
- package/dist/internal/{virtualFileSystem/path.d.ts → path.d.ts} +19 -7
- package/dist/internal/path.d.ts.map +1 -0
- package/dist/internal/{virtualFileSystem/path.js → path.js} +39 -25
- 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 +18 -0
- package/dist/internal/stagedState.d.ts.map +1 -0
- package/dist/internal/stagedState.js +18 -0
- 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 +41 -491
- package/dist/internal/virtualFileSystem.d.ts.map +1 -1
- package/dist/internal/virtualFileSystem.js +2118 -2471
- 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 +25 -0
- package/dist/internal/watchHub.d.ts.map +1 -0
- package/dist/internal/watchHub.js +68 -0
- package/package.json +53 -9
- package/dist/VirtualFileSystemError.d.ts +0 -54
- package/dist/VirtualFileSystemError.d.ts.map +0 -1
- package/dist/VirtualFileSystemError.js +0 -49
- package/dist/internal/virtualFileSystem/errors.d.ts +0 -12
- package/dist/internal/virtualFileSystem/errors.d.ts.map +0 -1
- package/dist/internal/virtualFileSystem/errors.js +0 -47
- package/dist/internal/virtualFileSystem/fixture.d.ts.map +0 -1
- package/dist/internal/virtualFileSystem/fixture.js +0 -150
- package/dist/internal/virtualFileSystem/overlayContent.d.ts +0 -15
- package/dist/internal/virtualFileSystem/overlayContent.d.ts.map +0 -1
- package/dist/internal/virtualFileSystem/overlayContent.js +0 -29
- package/dist/internal/virtualFileSystem/overlayDiff.d.ts.map +0 -1
- package/dist/internal/virtualFileSystem/path.d.ts.map +0 -1
- package/dist/internal/virtualFileSystem/stagedState.d.ts +0 -22
- package/dist/internal/virtualFileSystem/stagedState.d.ts.map +0 -1
- package/dist/internal/virtualFileSystem/stagedState.js +0 -62
- package/dist/internal/virtualFileSystem/testHooks.d.ts +0 -20
- package/dist/internal/virtualFileSystem/testHooks.d.ts.map +0 -1
- package/dist/internal/virtualFileSystem/testHooks.js +0 -23
- package/dist/internal/virtualFileSystem/watchHub.d.ts +0 -16
- package/dist/internal/virtualFileSystem/watchHub.d.ts.map +0 -1
- package/dist/internal/virtualFileSystem/watchHub.js +0 -54
package/README.md
CHANGED
|
@@ -13,11 +13,16 @@ for the exact permission, path, timestamp, quota, and atomicity rules.
|
|
|
13
13
|
## Install
|
|
14
14
|
|
|
15
15
|
```sh
|
|
16
|
-
npm install @effect-vfs/core
|
|
16
|
+
npm install @effect-vfs/core@latest
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
The package
|
|
20
|
-
service; on Node or Bun, install
|
|
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:
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
npm install "@effect/platform-node-shared@$(npm view @effect-vfs/core peerDependencies.effect)"
|
|
25
|
+
```
|
|
21
26
|
|
|
22
27
|
## The mental model
|
|
23
28
|
|
|
@@ -38,7 +43,6 @@ lifetime are explicit values that can be composed in one Effect program.
|
|
|
38
43
|
|
|
39
44
|
```ts
|
|
40
45
|
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
|
|
41
|
-
import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
|
|
42
46
|
import { ByteSize, Effect } from "effect"
|
|
43
47
|
|
|
44
48
|
const utf8 = new TextEncoder()
|
|
@@ -75,7 +79,7 @@ const program = Effect.scoped(Effect.gen(function*() {
|
|
|
75
79
|
}
|
|
76
80
|
}))
|
|
77
81
|
|
|
78
|
-
const result = await Effect.runPromise(program
|
|
82
|
+
const result = await Effect.runPromise(program)
|
|
79
83
|
console.log(result) // { config: { feature: "preview" }, mode: "640" }
|
|
80
84
|
```
|
|
81
85
|
|
|
@@ -93,7 +97,6 @@ contain names which are not valid UTF-8.
|
|
|
93
97
|
|
|
94
98
|
```ts
|
|
95
99
|
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
|
|
96
|
-
import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
|
|
97
100
|
import { Effect } from "effect"
|
|
98
101
|
|
|
99
102
|
const program = Effect.gen(function*() {
|
|
@@ -107,18 +110,18 @@ const program = Effect.gen(function*() {
|
|
|
107
110
|
create: "exclusive"
|
|
108
111
|
})
|
|
109
112
|
|
|
110
|
-
const
|
|
113
|
+
const listing = yield* fs.readDirectory("/")
|
|
111
114
|
const roundTrip = yield* Vfs.pathToBytes(opaquePath)
|
|
112
|
-
return { names:
|
|
115
|
+
return { names: listing.value.map((entry) => Array.from(entry.name)), roundTrip: Array.from(roundTrip) }
|
|
113
116
|
})
|
|
114
117
|
|
|
115
|
-
console.log(await Effect.runPromise(program
|
|
118
|
+
console.log(await Effect.runPromise(program))
|
|
116
119
|
// { names: [[255]], roundTrip: [47, 255] }
|
|
117
120
|
```
|
|
118
121
|
|
|
119
122
|
The constructors and byte-returning operations copy their buffers, so later mutation cannot change stored paths.
|
|
120
|
-
|
|
121
|
-
|
|
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.
|
|
122
125
|
|
|
123
126
|
## Build fixtures and restore snapshots
|
|
124
127
|
|
|
@@ -128,7 +131,6 @@ resettable sandboxes.
|
|
|
128
131
|
|
|
129
132
|
```ts
|
|
130
133
|
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
|
|
131
|
-
import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
|
|
132
134
|
import { Effect } from "effect"
|
|
133
135
|
import * as ByteSize from "effect/ByteSize"
|
|
134
136
|
|
|
@@ -166,7 +168,7 @@ const program = Effect.gen(function*() {
|
|
|
166
168
|
return new TextDecoder().decode(yield* b.readFile("/project/settings.json"))
|
|
167
169
|
})
|
|
168
170
|
|
|
169
|
-
console.log(await Effect.runPromise(program
|
|
171
|
+
console.log(await Effect.runPromise(program)) // {"theme":"dark"}
|
|
170
172
|
```
|
|
171
173
|
|
|
172
174
|
Fixture paths must be absolute and unique, and parent directories must be listed explicitly. Fixtures can also contain
|
|
@@ -194,7 +196,6 @@ interruption.
|
|
|
194
196
|
|
|
195
197
|
```ts
|
|
196
198
|
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
|
|
197
|
-
import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
|
|
198
199
|
import { Effect } from "effect"
|
|
199
200
|
|
|
200
201
|
const program = Effect.scoped(Effect.gen(function*() {
|
|
@@ -209,10 +210,13 @@ const program = Effect.scoped(Effect.gen(function*() {
|
|
|
209
210
|
const second = yield* file.read(4)
|
|
210
211
|
const preview = yield* file.pread(3, 0n) // Does not move the cursor.
|
|
211
212
|
|
|
212
|
-
|
|
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]
|
|
213
217
|
}))
|
|
214
218
|
|
|
215
|
-
console.log(await Effect.runPromise(program
|
|
219
|
+
console.log(await Effect.runPromise(program)) // ["one\n", "two\n", "one", false]
|
|
216
220
|
```
|
|
217
221
|
|
|
218
222
|
Handles also expose an explicit `close` effect when early release matters. Calling explicit close twice fails, while
|
|
@@ -220,57 +224,60 @@ scope cleanup remains safe after an explicit close.
|
|
|
220
224
|
|
|
221
225
|
## Handle expected failures as data
|
|
222
226
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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.
|
|
226
231
|
|
|
227
232
|
```ts
|
|
228
233
|
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
|
|
229
|
-
import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
|
|
230
234
|
import { Effect } from "effect"
|
|
231
235
|
|
|
232
236
|
const program = Effect.gen(function*() {
|
|
233
237
|
const fs = yield* (yield* Vfs.make()).caller()
|
|
234
238
|
|
|
235
239
|
return yield* fs.readFile("/optional.json").pipe(
|
|
236
|
-
Effect.catchTag("
|
|
240
|
+
Effect.catchTag("VfsError", (error) =>
|
|
237
241
|
error.code === "NotFound"
|
|
238
242
|
? Effect.succeed(new TextEncoder().encode("{}"))
|
|
239
243
|
: Effect.fail(error))
|
|
240
244
|
)
|
|
241
245
|
})
|
|
242
246
|
|
|
243
|
-
const bytes = await Effect.runPromise(program
|
|
247
|
+
const bytes = await Effect.runPromise(program)
|
|
244
248
|
console.log(new TextDecoder().decode(bytes)) // {}
|
|
245
249
|
```
|
|
246
250
|
|
|
247
|
-
## Provide a caller as
|
|
251
|
+
## Provide a volume and a caller as Effect services
|
|
248
252
|
|
|
249
|
-
`
|
|
250
|
-
|
|
251
|
-
|
|
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.
|
|
252
258
|
|
|
253
259
|
```ts
|
|
254
260
|
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
|
|
255
|
-
import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto"
|
|
256
261
|
import { Effect } from "effect"
|
|
257
262
|
|
|
258
263
|
const loadConfig = Effect.gen(function*() {
|
|
259
|
-
const fs = yield* Vfs.
|
|
264
|
+
const fs = yield* Vfs.Caller
|
|
260
265
|
return yield* fs.readFile("/app/config.json")
|
|
261
266
|
})
|
|
262
267
|
|
|
263
268
|
const program = Effect.gen(function*() {
|
|
264
|
-
const fs = yield*
|
|
269
|
+
const fs = yield* Vfs.Caller
|
|
265
270
|
yield* fs.mkdir("/app")
|
|
266
271
|
yield* fs.writeFile("/app/config.json", new TextEncoder().encode("{}"), {
|
|
267
272
|
access: "write",
|
|
268
273
|
create: "exclusive"
|
|
269
274
|
})
|
|
270
|
-
return yield* loadConfig
|
|
275
|
+
return yield* loadConfig
|
|
271
276
|
})
|
|
272
277
|
|
|
273
|
-
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
|
+
)
|
|
274
281
|
console.log(new TextDecoder().decode(bytes)) // {}
|
|
275
282
|
```
|
|
276
283
|
|
|
@@ -291,7 +298,7 @@ candidate.
|
|
|
291
298
|
- Absolute paths ignore a supplied directory base. Relative paths can use a live, same-volume directory handle.
|
|
292
299
|
- Operations coordinate through one permit per volume. At most 65 operations are admitted at once by
|
|
293
300
|
default, including the active operation; set `maxPendingOperations` to change the waiting budget.
|
|
294
|
-
Excess work fails with retryable `
|
|
301
|
+
Excess work fails with retryable `VfsError` code `VolumeBusy`
|
|
295
302
|
before mutation or storage commit. Interruption while waiting makes no change; interruption after a commit
|
|
296
303
|
does not roll it back.
|
|
297
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
|
+
};
|