@effect-vfs/memory 0.0.1
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/LICENSE +22 -0
- package/README.md +207 -0
- package/dist/MemoryFileSystem.d.ts +64 -0
- package/dist/MemoryFileSystem.d.ts.map +1 -0
- package/dist/MemoryFileSystem.js +10 -0
- package/dist/chunk-GXCMBMOL.js +813 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/internal/glob.d.ts +77 -0
- package/dist/internal/glob.d.ts.map +1 -0
- package/dist/internal/memoryFileSystem.d.ts +27 -0
- package/dist/internal/memoryFileSystem.d.ts.map +1 -0
- package/package.json +61 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Effectful Technologies Inc
|
|
4
|
+
Copyright (c) 2026 Lloyd Richards
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# @effect-vfs/memory
|
|
2
|
+
|
|
3
|
+
`@effect-vfs/memory` provides Effect's `FileSystem` service without touching the
|
|
4
|
+
host filesystem. Existing programs can keep using `effect/FileSystem`; provide
|
|
5
|
+
the memory layer when you want isolated, disposable filesystem state for tests,
|
|
6
|
+
build previews, code generators, or browser tools.
|
|
7
|
+
|
|
8
|
+
The adapter supports regular files, directories, symbolic links, hard links,
|
|
9
|
+
file handles, temporary resources, globbing, and watch streams. Filesystem state
|
|
10
|
+
and POSIX behavior come from `@effect-vfs/core`. This package presents that state
|
|
11
|
+
through Effect's string-based `FileSystem` API.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
npm install @effect-vfs/memory effect@4.0.0-rc.112
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The binding and snapshot examples below import `@effect-vfs/core` directly. Add it as a direct dependency when using
|
|
20
|
+
those APIs:
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
npm install @effect-vfs/core
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Version `0.1.0` targets exactly `effect@4.0.0-rc.112`. Effect v4 is still a
|
|
27
|
+
release candidate, so a later Effect release may require a matching version of
|
|
28
|
+
this package.
|
|
29
|
+
|
|
30
|
+
## Replace the host filesystem
|
|
31
|
+
|
|
32
|
+
Write application code against Effect's `FileSystem` service, then choose the
|
|
33
|
+
implementation when you run it. The same program can use a platform filesystem
|
|
34
|
+
in production and an isolated in-memory filesystem in tests or tools.
|
|
35
|
+
|
|
36
|
+
This complete program creates a small build artifact without writing to disk:
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { MemoryFileSystem } from "@effect-vfs/memory"
|
|
40
|
+
import { Effect, FileSystem } from "effect"
|
|
41
|
+
|
|
42
|
+
const buildManifest = Effect.gen(function*() {
|
|
43
|
+
const fs = yield* FileSystem.FileSystem
|
|
44
|
+
|
|
45
|
+
yield* fs.makeDirectory("/dist", { recursive: true })
|
|
46
|
+
yield* fs.writeFileString(
|
|
47
|
+
"/dist/manifest.json",
|
|
48
|
+
JSON.stringify({ files: ["index.js", "index.css"] }, null, 2)
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
return yield* fs.readFileString("/dist/manifest.json")
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
const manifest = await Effect.runPromise(
|
|
55
|
+
buildManifest.pipe(Effect.provide(MemoryFileSystem.layer))
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
console.log(manifest)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`MemoryFileSystem.layer` creates a fresh volume containing `/tmp` and uses `/`
|
|
62
|
+
as its working directory. `MemoryFileSystem.make` returns the service directly
|
|
63
|
+
when a layer is unnecessary.
|
|
64
|
+
|
|
65
|
+
## Choose isolated or shared state
|
|
66
|
+
|
|
67
|
+
Each execution of `MemoryFileSystem.make` creates independent storage. Use
|
|
68
|
+
`MemoryFileSystem.bind` when several callers need to see the same files through
|
|
69
|
+
an existing `@effect-vfs/core` volume.
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
|
|
73
|
+
import { MemoryFileSystem } from "@effect-vfs/memory"
|
|
74
|
+
import { Effect } from "effect"
|
|
75
|
+
|
|
76
|
+
const program = Effect.gen(function*() {
|
|
77
|
+
const isolatedA = yield* MemoryFileSystem.make
|
|
78
|
+
const isolatedB = yield* MemoryFileSystem.make
|
|
79
|
+
|
|
80
|
+
yield* isolatedA.writeFileString("/private.txt", "only in A")
|
|
81
|
+
|
|
82
|
+
const volume = yield* Vfs.make()
|
|
83
|
+
const sharedA = yield* MemoryFileSystem.bind(volume)
|
|
84
|
+
const sharedB = yield* MemoryFileSystem.bind(volume)
|
|
85
|
+
|
|
86
|
+
yield* sharedA.writeFileString("/shared.txt", "visible to both")
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
isolatedBHasFile: yield* isolatedB.exists("/private.txt"),
|
|
90
|
+
sharedContents: yield* sharedB.readFileString("/shared.txt")
|
|
91
|
+
}
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
console.log(await Effect.runPromise(program))
|
|
95
|
+
// { isolatedBHasFile: false, sharedContents: "visible to both" }
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Bindings share file and directory changes. Each binding still has its own
|
|
99
|
+
caller, descriptor table, file cursors, and resource scopes. A binding does not
|
|
100
|
+
create `/tmp` or otherwise change the supplied volume. Its caller defaults to a
|
|
101
|
+
privileged uid and gid of `0` with umask `0`.
|
|
102
|
+
|
|
103
|
+
Reusing `MemoryFileSystem.layer` within one layer graph shares the service
|
|
104
|
+
because Effect memoizes layers. Wrap it with `Layer.fresh` when separate parts
|
|
105
|
+
of the same graph must receive independent filesystems.
|
|
106
|
+
|
|
107
|
+
## Save and restore a volume
|
|
108
|
+
|
|
109
|
+
Snapshots let a test or tool capture a prepared filesystem and restore clean,
|
|
110
|
+
independent copies. Snapshot operations live in `@effect-vfs/core`, so keep the
|
|
111
|
+
core volume and expose it through `MemoryFileSystem.bind`.
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
import { VirtualFileSystem as Vfs } from "@effect-vfs/core"
|
|
115
|
+
import { MemoryFileSystem } from "@effect-vfs/memory"
|
|
116
|
+
import { Effect } from "effect"
|
|
117
|
+
|
|
118
|
+
const decodeLimits = {
|
|
119
|
+
maxEncodedBytes: 1_000_000,
|
|
120
|
+
maxRecords: 10_000,
|
|
121
|
+
maxEntries: 10_000,
|
|
122
|
+
maxDecodedBytes: 1_000_000
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const program = Effect.gen(function*() {
|
|
126
|
+
const volume = yield* Vfs.make()
|
|
127
|
+
const fs = yield* MemoryFileSystem.bind(volume)
|
|
128
|
+
|
|
129
|
+
yield* fs.writeFileString("/config.json", "version 1")
|
|
130
|
+
const encoded = yield* Vfs.encodeSnapshot(yield* volume.snapshot)
|
|
131
|
+
|
|
132
|
+
yield* fs.writeFileString("/config.json", "version 2")
|
|
133
|
+
|
|
134
|
+
const snapshot = yield* Vfs.decodeSnapshot(encoded, decodeLimits)
|
|
135
|
+
const restoredVolume = yield* Vfs.fromSnapshot(snapshot)
|
|
136
|
+
const restoredFs = yield* MemoryFileSystem.bind(restoredVolume)
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
current: yield* fs.readFileString("/config.json"),
|
|
140
|
+
restored: yield* restoredFs.readFileString("/config.json")
|
|
141
|
+
}
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
console.log(await Effect.runPromise(program))
|
|
145
|
+
// { current: "version 2", restored: "version 1" }
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
A snapshot contains the reachable namespace, file contents, and metadata. It
|
|
149
|
+
does not contain bindings, callers, open handles, file cursors, watches, or
|
|
150
|
+
unlinked content. `/tmp` is restored only when it existed in the snapshot.
|
|
151
|
+
|
|
152
|
+
## Resource lifetimes
|
|
153
|
+
|
|
154
|
+
`FileSystem.open`, temporary resources, and watch subscriptions are scoped.
|
|
155
|
+
Keep the owning `Effect.scoped` workflow alive while the resource is in use.
|
|
156
|
+
Closing the scope closes its handles and subscriptions.
|
|
157
|
+
|
|
158
|
+
Bindings keep their own Effect-compatible file cursors. Watch streams receive
|
|
159
|
+
changes made through any binding and changes made by direct core callers on the
|
|
160
|
+
same volume.
|
|
161
|
+
|
|
162
|
+
## Globbing
|
|
163
|
+
|
|
164
|
+
Glob patterns are relative to the selected root and use `/` separators. The
|
|
165
|
+
adapter supports `*`, `?`, character classes, `**`, and brace alternatives.
|
|
166
|
+
Wildcards do not match a leading `.` unless that segment starts with a literal
|
|
167
|
+
dot. Empty, `.` and `..` path segments are rejected. Brace expansion is limited
|
|
168
|
+
to 256 alternatives.
|
|
169
|
+
|
|
170
|
+
## Limits
|
|
171
|
+
|
|
172
|
+
This package provides Effect's `FileSystem` service. It cannot intercept
|
|
173
|
+
`node:fs`, child processes, native extensions, or any code that accesses the
|
|
174
|
+
host filesystem directly. Relative paths resolve from virtual `/`, not the host
|
|
175
|
+
process working directory.
|
|
176
|
+
|
|
177
|
+
The implementation is ESM-only and does not depend on Node or Bun runtime APIs.
|
|
178
|
+
It can run in a browser when the rest of the application supports Effect and
|
|
179
|
+
ESM.
|
|
180
|
+
|
|
181
|
+
The core implements a bounded POSIX profile, not a mounted or persistent
|
|
182
|
+
filesystem. It does not provide FUSE mounts, host-tree import or export, special
|
|
183
|
+
files, advisory locks, descriptor duplication, crash durability, or
|
|
184
|
+
copy-on-write snapshot optimization. Recursive adapter operations run as a
|
|
185
|
+
sequence of core operations rather than one transaction. See the
|
|
186
|
+
[implemented profile](https://github.com/lloydrichards/effect-virtual-fs/blob/main/.docs/context/implemented-profile.md)
|
|
187
|
+
for the exact behavior and exclusions.
|
|
188
|
+
|
|
189
|
+
## Compatibility
|
|
190
|
+
|
|
191
|
+
The package is experimental. Its public API may change between minor releases
|
|
192
|
+
while Effect v4 remains a release candidate.
|
|
193
|
+
|
|
194
|
+
The test suite checks the portable `FileSystem` contract and the core-backed
|
|
195
|
+
adapter. Release checks also compile the NodeNext import path, bundle a browser
|
|
196
|
+
consumer, and run a Node import smoke test.
|
|
197
|
+
|
|
198
|
+
The API began in [Effect PR #6573](https://github.com/Effect-TS/effect/pull/6573).
|
|
199
|
+
The portable contract suite began in
|
|
200
|
+
[Effect PR #6555](https://github.com/Effect-TS/effect/pull/6555).
|
|
201
|
+
|
|
202
|
+
## Credits
|
|
203
|
+
|
|
204
|
+
The original implementation was based on earlier work in
|
|
205
|
+
[effect-smol PR #456](https://github.com/Effect-TS/effect-smol/pull/456).
|
|
206
|
+
|
|
207
|
+
Licensed under the MIT License.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provides an in-memory implementation of Effect's `FileSystem` service.
|
|
3
|
+
*
|
|
4
|
+
* The service uses `@effect-vfs/core` for filesystem state and exposes that
|
|
5
|
+
* state through Effect's path-based `FileSystem` interface. It is intended for
|
|
6
|
+
* tests, build tools, and programs that need filesystem behavior without host
|
|
7
|
+
* filesystem I/O.
|
|
8
|
+
*
|
|
9
|
+
* @since 0.1.0
|
|
10
|
+
*/
|
|
11
|
+
import type * as Vfs from "@effect-vfs/core/VirtualFileSystem";
|
|
12
|
+
import type * as Effect from "effect/Effect";
|
|
13
|
+
import type * as FileSystem from "effect/FileSystem";
|
|
14
|
+
import type * as Layer from "effect/Layer";
|
|
15
|
+
/**
|
|
16
|
+
* Creates a `FileSystem.FileSystem` service backed by a fresh in-memory volume.
|
|
17
|
+
*
|
|
18
|
+
* **When to use**
|
|
19
|
+
*
|
|
20
|
+
* Use when you need the service value directly. The volume
|
|
21
|
+
* starts with an empty `/tmp` directory and uses `/` as its working directory.
|
|
22
|
+
*
|
|
23
|
+
* @see {@link layer} for providing the service as a Layer.
|
|
24
|
+
* @category constructors
|
|
25
|
+
* @since 0.1.0
|
|
26
|
+
*/
|
|
27
|
+
export declare const make: Effect.Effect<FileSystem.FileSystem>;
|
|
28
|
+
/**
|
|
29
|
+
* Provides a `FileSystem.FileSystem` backed by a fresh in-memory volume.
|
|
30
|
+
*
|
|
31
|
+
* **When to use**
|
|
32
|
+
*
|
|
33
|
+
* Use when you need to replace the host filesystem in an Effect program.
|
|
34
|
+
*
|
|
35
|
+
* **Gotchas**
|
|
36
|
+
*
|
|
37
|
+
* Reusing this layer value in one layer graph shares the volume through layer
|
|
38
|
+
* memoization. Wrap it with `Layer.fresh` when each use needs separate state.
|
|
39
|
+
*
|
|
40
|
+
* @see {@link make} for constructing the service directly.
|
|
41
|
+
* @category layers
|
|
42
|
+
* @since 0.1.0
|
|
43
|
+
*/
|
|
44
|
+
export declare const layer: Layer.Layer<FileSystem.FileSystem>;
|
|
45
|
+
/**
|
|
46
|
+
* Creates a `FileSystem.FileSystem` service backed by an existing core volume.
|
|
47
|
+
*
|
|
48
|
+
* **Details**
|
|
49
|
+
*
|
|
50
|
+
* The binding creates its own root caller and file-descriptor table. It does not
|
|
51
|
+
* add `/tmp` or otherwise modify the volume. Use this when an adapter and direct
|
|
52
|
+
* core callers must share filesystem state. Bindings share namespace and content
|
|
53
|
+
* changes, but keep independent caller state, descriptors, and file cursors.
|
|
54
|
+
*
|
|
55
|
+
* The caller defaults to a privileged uid and gid of `0` with umask `0`. Invalid
|
|
56
|
+
* caller options fail with `VirtualFileSystem.ConfigurationError`. Filesystem
|
|
57
|
+
* operations translate core failures to Effect `PlatformError` values.
|
|
58
|
+
*
|
|
59
|
+
* @see {@link make} for a service backed by a fresh volume with `/tmp`.
|
|
60
|
+
* @category constructors
|
|
61
|
+
* @since 0.1.0
|
|
62
|
+
*/
|
|
63
|
+
export declare const bind: (volume: Vfs.Volume, options?: Vfs.RootCallerOptions) => Effect.Effect<FileSystem.FileSystem, Vfs.ConfigurationError>;
|
|
64
|
+
//# sourceMappingURL=MemoryFileSystem.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MemoryFileSystem.d.ts","sourceRoot":"","sources":["../src/MemoryFileSystem.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,KAAK,GAAG,MAAM,oCAAoC,CAAA;AAC9D,OAAO,KAAK,KAAK,MAAM,MAAM,eAAe,CAAA;AAC5C,OAAO,KAAK,KAAK,UAAU,MAAM,mBAAmB,CAAA;AACpD,OAAO,KAAK,KAAK,KAAK,MAAM,cAAc,CAAA;AAG1C;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,UAAU,CAAiB,CAAA;AAEvE;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC,UAAU,CAAkB,CAAA;AAEvE;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,IAAI,EAAE,CACjB,MAAM,EAAE,GAAG,CAAC,MAAM,EAClB,OAAO,CAAC,EAAE,GAAG,CAAC,iBAAiB,KAC5B,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,UAAU,EAAE,GAAG,CAAC,kBAAkB,CAAiB,CAAA"}
|
|
@@ -0,0 +1,813 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __export = (target, all) => {
|
|
3
|
+
for (var name in all)
|
|
4
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
// src/MemoryFileSystem.ts
|
|
8
|
+
var MemoryFileSystem_exports = {};
|
|
9
|
+
__export(MemoryFileSystem_exports, {
|
|
10
|
+
bind: () => bind2,
|
|
11
|
+
layer: () => layer2,
|
|
12
|
+
make: () => make4
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
// src/internal/memoryFileSystem.ts
|
|
16
|
+
import { VirtualFileSystem as Vfs } from "@effect-vfs/core";
|
|
17
|
+
import * as DateTime from "effect/DateTime";
|
|
18
|
+
import * as Effect2 from "effect/Effect";
|
|
19
|
+
import * as FileSystem from "effect/FileSystem";
|
|
20
|
+
import * as Layer from "effect/Layer";
|
|
21
|
+
import * as Option from "effect/Option";
|
|
22
|
+
import { badArgument as badArgument2, systemError } from "effect/PlatformError";
|
|
23
|
+
import * as Semaphore from "effect/Semaphore";
|
|
24
|
+
import * as Stream from "effect/Stream";
|
|
25
|
+
|
|
26
|
+
// src/internal/glob.ts
|
|
27
|
+
import * as Data from "effect/Data";
|
|
28
|
+
import * as Effect from "effect/Effect";
|
|
29
|
+
import { badArgument } from "effect/PlatformError";
|
|
30
|
+
var argumentError = (method, description) => badArgument({ module: "FileSystem", method, description });
|
|
31
|
+
var MAX_BRACE_EXPANSIONS = 256;
|
|
32
|
+
var GlobToken = Data.taggedEnum();
|
|
33
|
+
var globSyntaxCharacters = /* @__PURE__ */ new Set(["*", "?", "[", "]", "{", "}", ",", "\\"]);
|
|
34
|
+
var findBraceExpansion = (pattern) => {
|
|
35
|
+
for (let start = 0; start < pattern.length; start++) {
|
|
36
|
+
if (pattern[start] === "\\") {
|
|
37
|
+
start += 1;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (pattern[start] !== "{") continue;
|
|
41
|
+
let depth = 1;
|
|
42
|
+
let characterClass = false;
|
|
43
|
+
let closed = false;
|
|
44
|
+
const commas = [];
|
|
45
|
+
for (let end = start + 1; end < pattern.length; end++) {
|
|
46
|
+
if (pattern[end] === "\\") {
|
|
47
|
+
end += 1;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (pattern[end] === "[") {
|
|
51
|
+
characterClass = true;
|
|
52
|
+
} else if (pattern[end] === "]") {
|
|
53
|
+
characterClass = false;
|
|
54
|
+
} else if (!characterClass && pattern[end] === "{") {
|
|
55
|
+
depth += 1;
|
|
56
|
+
} else if (!characterClass && pattern[end] === "}") {
|
|
57
|
+
depth -= 1;
|
|
58
|
+
if (depth === 0) {
|
|
59
|
+
closed = true;
|
|
60
|
+
if (commas.length === 0) {
|
|
61
|
+
const nested = findBraceExpansion(pattern.slice(start + 1, end));
|
|
62
|
+
if (nested !== void 0) {
|
|
63
|
+
return {
|
|
64
|
+
start: start + nested.start + 1,
|
|
65
|
+
end: start + nested.end + 1,
|
|
66
|
+
alternatives: nested.alternatives
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
start = end;
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
const alternatives = [];
|
|
73
|
+
let alternativeStart = start + 1;
|
|
74
|
+
for (const comma of [...commas, end]) {
|
|
75
|
+
alternatives.push(pattern.slice(alternativeStart, comma));
|
|
76
|
+
alternativeStart = comma + 1;
|
|
77
|
+
}
|
|
78
|
+
return { start, end, alternatives };
|
|
79
|
+
}
|
|
80
|
+
} else if (!characterClass && pattern[end] === "," && depth === 1) {
|
|
81
|
+
commas.push(end);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (!closed) return void 0;
|
|
85
|
+
}
|
|
86
|
+
return void 0;
|
|
87
|
+
};
|
|
88
|
+
var expandBraces = (method, pattern) => {
|
|
89
|
+
let patterns = [pattern];
|
|
90
|
+
while (true) {
|
|
91
|
+
const index = patterns.findIndex((pattern2) => findBraceExpansion(pattern2) !== void 0);
|
|
92
|
+
if (index === -1) return Effect.succeed(patterns);
|
|
93
|
+
const current = patterns[index];
|
|
94
|
+
const expansion = findBraceExpansion(current);
|
|
95
|
+
if (expansion === void 0) return Effect.succeed(patterns);
|
|
96
|
+
if (patterns.length - 1 + expansion.alternatives.length > MAX_BRACE_EXPANSIONS) {
|
|
97
|
+
return Effect.fail(argumentError(method, `brace expansion exceeds ${MAX_BRACE_EXPANSIONS} alternatives`));
|
|
98
|
+
}
|
|
99
|
+
patterns = [
|
|
100
|
+
...patterns.slice(0, index),
|
|
101
|
+
...expansion.alternatives.map(
|
|
102
|
+
(alternative) => `${current.slice(0, expansion.start)}${alternative}${current.slice(expansion.end + 1)}`
|
|
103
|
+
),
|
|
104
|
+
...patterns.slice(index + 1)
|
|
105
|
+
];
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
var parseCharacterClass = (method, segment, start) => {
|
|
109
|
+
let index = start + 1;
|
|
110
|
+
const negated = segment[index] === "!";
|
|
111
|
+
if (negated) index += 1;
|
|
112
|
+
const characters = [];
|
|
113
|
+
while (index < segment.length) {
|
|
114
|
+
if (segment[index] === "]" && characters.length > 0) break;
|
|
115
|
+
let escaped = false;
|
|
116
|
+
if (segment[index] === "\\") {
|
|
117
|
+
escaped = true;
|
|
118
|
+
index += 1;
|
|
119
|
+
if (index === segment.length) {
|
|
120
|
+
return argumentError(method, "character classes must not end with an escape");
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
characters.push({ value: segment.charAt(index), escaped });
|
|
124
|
+
index += 1;
|
|
125
|
+
}
|
|
126
|
+
if (index === segment.length || characters.length === 0) {
|
|
127
|
+
return argumentError(method, "character classes must be closed and non-empty");
|
|
128
|
+
}
|
|
129
|
+
const literals = [];
|
|
130
|
+
const ranges = [];
|
|
131
|
+
for (let characterIndex = 0; characterIndex < characters.length; characterIndex++) {
|
|
132
|
+
const character = characters[characterIndex];
|
|
133
|
+
if (characterIndex + 2 < characters.length && characters[characterIndex + 1].value === "-" && !characters[characterIndex + 1].escaped && characters[characterIndex + 2].value !== "-") {
|
|
134
|
+
const end = characters[characterIndex + 2].value;
|
|
135
|
+
if (character.value > end) {
|
|
136
|
+
return argumentError(method, "character class ranges must be ascending");
|
|
137
|
+
}
|
|
138
|
+
ranges.push([character.value, end]);
|
|
139
|
+
characterIndex += 2;
|
|
140
|
+
} else {
|
|
141
|
+
literals.push(character.value);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return Effect.succeed([GlobToken.CharacterClass({ negated, ranges, literals }), index + 1]);
|
|
145
|
+
};
|
|
146
|
+
var parseGlobSegment = Effect.fnUntraced(function* (method, segment) {
|
|
147
|
+
if (segment === "**") return { _tag: "Globstar" };
|
|
148
|
+
const tokens = [];
|
|
149
|
+
let index = 0;
|
|
150
|
+
while (index < segment.length) {
|
|
151
|
+
const character = segment.charAt(index);
|
|
152
|
+
if (character === "\\") {
|
|
153
|
+
index += 1;
|
|
154
|
+
if (index === segment.length) {
|
|
155
|
+
return yield* argumentError(method, "patterns must not end with an escape");
|
|
156
|
+
}
|
|
157
|
+
const value = segment.charAt(index);
|
|
158
|
+
if (globSyntaxCharacters.has(value)) {
|
|
159
|
+
tokens.push(GlobToken.Literal({ value }));
|
|
160
|
+
} else {
|
|
161
|
+
tokens.push(GlobToken.Literal({ value: "\\" }));
|
|
162
|
+
index -= 1;
|
|
163
|
+
}
|
|
164
|
+
} else if (character === "*") {
|
|
165
|
+
tokens.push(GlobToken.Star());
|
|
166
|
+
} else if (character === "?") {
|
|
167
|
+
tokens.push(GlobToken.One());
|
|
168
|
+
} else if (character === "[") {
|
|
169
|
+
const parsed = yield* parseCharacterClass(method, segment, index);
|
|
170
|
+
tokens.push(parsed[0]);
|
|
171
|
+
index = parsed[1] - 1;
|
|
172
|
+
} else {
|
|
173
|
+
tokens.push(GlobToken.Literal({ value: character }));
|
|
174
|
+
}
|
|
175
|
+
index += 1;
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
_tag: "Segment",
|
|
179
|
+
tokens,
|
|
180
|
+
startsWithDot: tokens[0]?._tag === "Literal" && tokens[0].value === "." || tokens[0]?._tag === "CharacterClass" && !tokens[0].negated && (tokens[0].literals.includes(".") || tokens[0].ranges.some(([start, end]) => start <= "." && "." <= end))
|
|
181
|
+
};
|
|
182
|
+
});
|
|
183
|
+
var compileGlobPattern = Effect.fnUntraced(function* (method, pattern) {
|
|
184
|
+
if (pattern.length === 0 || pattern.includes("\0") || pattern.startsWith("/")) {
|
|
185
|
+
return yield* argumentError(method, "pattern must be a root-relative POSIX glob");
|
|
186
|
+
}
|
|
187
|
+
const directoryOnly = pattern.endsWith("/");
|
|
188
|
+
const path = directoryOnly ? pattern.slice(0, -1) : pattern;
|
|
189
|
+
const segments = path.split("/");
|
|
190
|
+
if (segments.includes("") || segments.includes(".") || segments.includes("..")) {
|
|
191
|
+
return yield* argumentError(method, "pattern must not contain empty or dot path segments");
|
|
192
|
+
}
|
|
193
|
+
const compiled = yield* Effect.forEach(segments, (segment) => parseGlobSegment(method, segment));
|
|
194
|
+
return { segments: compiled, directoryOnly };
|
|
195
|
+
});
|
|
196
|
+
var compileGlobPatterns = Effect.fnUntraced(function* (method, pattern) {
|
|
197
|
+
const expanded = yield* expandBraces(method, pattern);
|
|
198
|
+
return yield* Effect.forEach(expanded, (alternative) => compileGlobPattern(method, alternative));
|
|
199
|
+
});
|
|
200
|
+
var matchesGlobToken = (token, value) => GlobToken.$match(token, {
|
|
201
|
+
Literal: (token2) => token2.value === value,
|
|
202
|
+
Star: () => false,
|
|
203
|
+
One: () => true,
|
|
204
|
+
CharacterClass: (token2) => {
|
|
205
|
+
const matches = token2.literals.includes(value) || token2.ranges.some(([start, end]) => start <= value && value <= end);
|
|
206
|
+
return token2.negated ? !matches : matches;
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
var matchesGlobSegment = (pattern, value) => {
|
|
210
|
+
if (value.startsWith(".") && !pattern.startsWithDot) return false;
|
|
211
|
+
let patternIndex = 0;
|
|
212
|
+
let valueIndex = 0;
|
|
213
|
+
let starIndex = -1;
|
|
214
|
+
let starValueIndex = -1;
|
|
215
|
+
while (valueIndex < value.length) {
|
|
216
|
+
const token = pattern.tokens[patternIndex];
|
|
217
|
+
if (token !== void 0 && token._tag !== "Star" && matchesGlobToken(token, value.charAt(valueIndex))) {
|
|
218
|
+
patternIndex += 1;
|
|
219
|
+
valueIndex += 1;
|
|
220
|
+
} else if (token?._tag === "Star") {
|
|
221
|
+
starIndex = patternIndex;
|
|
222
|
+
starValueIndex = valueIndex;
|
|
223
|
+
patternIndex += 1;
|
|
224
|
+
} else if (starIndex !== -1) {
|
|
225
|
+
patternIndex = starIndex + 1;
|
|
226
|
+
starValueIndex += 1;
|
|
227
|
+
valueIndex = starValueIndex;
|
|
228
|
+
} else {
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
while (pattern.tokens[patternIndex]?._tag === "Star") {
|
|
233
|
+
patternIndex += 1;
|
|
234
|
+
}
|
|
235
|
+
return patternIndex === pattern.tokens.length;
|
|
236
|
+
};
|
|
237
|
+
var matchesGlob = (pattern, path, directory) => {
|
|
238
|
+
if (pattern.directoryOnly && !directory) return false;
|
|
239
|
+
let next = Array.from({ length: path.length + 1 }, (_, index) => index === path.length);
|
|
240
|
+
for (let patternIndex = pattern.segments.length - 1; patternIndex >= 0; patternIndex--) {
|
|
241
|
+
const current = Array.from({ length: path.length + 1 }, () => false);
|
|
242
|
+
const segment = pattern.segments[patternIndex];
|
|
243
|
+
if (segment._tag === "Globstar") {
|
|
244
|
+
for (let pathIndex = path.length; pathIndex >= 0; pathIndex--) {
|
|
245
|
+
current[pathIndex] = next[pathIndex] || pathIndex < path.length && !path[pathIndex].startsWith(".") && current[pathIndex + 1];
|
|
246
|
+
}
|
|
247
|
+
} else {
|
|
248
|
+
for (let pathIndex = path.length - 1; pathIndex >= 0; pathIndex--) {
|
|
249
|
+
current[pathIndex] = matchesGlobSegment(segment, path[pathIndex]) && next[pathIndex + 1];
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
next = current;
|
|
253
|
+
}
|
|
254
|
+
return next[0];
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
// src/internal/memoryFileSystem.ts
|
|
258
|
+
var argumentError2 = (method, description) => badArgument2({ module: "FileSystem", method, description });
|
|
259
|
+
var resourceError = (method, pathOrDescriptor, description) => systemError({
|
|
260
|
+
module: "FileSystem",
|
|
261
|
+
method,
|
|
262
|
+
pathOrDescriptor,
|
|
263
|
+
_tag: "BadResource",
|
|
264
|
+
...description === void 0 ? {} : { description }
|
|
265
|
+
});
|
|
266
|
+
var translate = (error, method, pathOrDescriptor) => {
|
|
267
|
+
if (error.code === "InvalidArgument") return argumentError2(method, error.code);
|
|
268
|
+
const tags = {
|
|
269
|
+
NotFound: "NotFound",
|
|
270
|
+
AlreadyExists: "AlreadyExists",
|
|
271
|
+
AccessDenied: "PermissionDenied",
|
|
272
|
+
InvalidPathEncoding: "InvalidData",
|
|
273
|
+
UnrepresentableName: "InvalidData",
|
|
274
|
+
PathTooLong: "InvalidData"
|
|
275
|
+
};
|
|
276
|
+
return systemError({
|
|
277
|
+
module: "FileSystem",
|
|
278
|
+
method,
|
|
279
|
+
pathOrDescriptor,
|
|
280
|
+
_tag: tags[error.code] ?? "BadResource",
|
|
281
|
+
description: error.code
|
|
282
|
+
});
|
|
283
|
+
};
|
|
284
|
+
var mapped = (effect2, method, path) => effect2.pipe(Effect2.mapError((error) => translate(error, method, path)));
|
|
285
|
+
var info = Effect2.fnUntraced(function* (value, pathOrDescriptor) {
|
|
286
|
+
const date = (field) => {
|
|
287
|
+
const result2 = DateTime.make(Number(value[field] / 1000000n));
|
|
288
|
+
return Option.isSome(result2) ? Effect2.succeed(Option.some(DateTime.toDateUtc(result2.value))) : Effect2.fail(systemError({
|
|
289
|
+
module: "FileSystem",
|
|
290
|
+
method: "stat",
|
|
291
|
+
pathOrDescriptor,
|
|
292
|
+
_tag: "InvalidData",
|
|
293
|
+
description: `${field} cannot be represented as a JavaScript Date`
|
|
294
|
+
}));
|
|
295
|
+
};
|
|
296
|
+
return {
|
|
297
|
+
type: value.kind === "file" ? "File" : value.kind === "directory" ? "Directory" : "SymbolicLink",
|
|
298
|
+
ino: Option.some(Number(value.ino)),
|
|
299
|
+
dev: 0,
|
|
300
|
+
mode: value.mode | (value.kind === "file" ? 32768 : value.kind === "directory" ? 16384 : 40960),
|
|
301
|
+
uid: Option.some(value.uid),
|
|
302
|
+
gid: Option.some(value.gid),
|
|
303
|
+
nlink: Option.some(value.nlink),
|
|
304
|
+
rdev: Option.some(0),
|
|
305
|
+
size: FileSystem.Size(value.size),
|
|
306
|
+
blksize: Option.none(),
|
|
307
|
+
blocks: Option.none(),
|
|
308
|
+
atime: yield* date("atimeNs"),
|
|
309
|
+
mtime: yield* date("mtimeNs"),
|
|
310
|
+
birthtime: yield* date("birthtimeNs")
|
|
311
|
+
};
|
|
312
|
+
});
|
|
313
|
+
var validateMode = (mode, method) => mode === void 0 || Number.isInteger(mode) && mode >= 0 && mode <= 4294967295 ? Effect2.void : Effect2.fail(argumentError2(method, "mode must be an unsigned 32-bit integer"));
|
|
314
|
+
var sizeInput = (size, method) => {
|
|
315
|
+
const number = Number(size ?? 0);
|
|
316
|
+
return Number.isSafeInteger(number) && number >= 0 ? Effect2.succeed(BigInt(number)) : Effect2.fail(argumentError2(method, "size must be a non-negative safe integer"));
|
|
317
|
+
};
|
|
318
|
+
var openOptions = Effect2.fnUntraced(function* (flag, mode, method) {
|
|
319
|
+
if (!["r", "r+", "w", "wx", "w+", "wx+", "a", "ax", "a+", "ax+"].includes(flag)) {
|
|
320
|
+
return yield* argumentError2(method, "Unsupported open flag");
|
|
321
|
+
}
|
|
322
|
+
yield* validateMode(mode, method);
|
|
323
|
+
const create = flag.startsWith("w") || flag.startsWith("a");
|
|
324
|
+
return {
|
|
325
|
+
access: flag === "r" ? "read" : flag.endsWith("+") ? "readWrite" : "write",
|
|
326
|
+
create: create ? flag.includes("x") ? "exclusive" : "ifMissing" : "never",
|
|
327
|
+
...create ? { mode: (mode ?? 420) & 4095 } : {},
|
|
328
|
+
append: flag.startsWith("a"),
|
|
329
|
+
truncate: flag.startsWith("w")
|
|
330
|
+
};
|
|
331
|
+
});
|
|
332
|
+
var childPath = (parent, name) => parent === "/" ? `/${name}` : `${parent}/${name}`;
|
|
333
|
+
var textPath = Effect2.fnUntraced(function* (path, method) {
|
|
334
|
+
const bytes = yield* Vfs.pathToBytes(path);
|
|
335
|
+
return yield* Effect2.try({
|
|
336
|
+
try: () => new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes),
|
|
337
|
+
catch: () => new Vfs.FsError({ code: "UnrepresentableName", operation: method })
|
|
338
|
+
});
|
|
339
|
+
});
|
|
340
|
+
var bind = Effect2.fn("MemoryFileSystem.bind")(function* (volume, options) {
|
|
341
|
+
const caller = yield* volume.caller({ ...options, umask: options?.umask ?? 0 });
|
|
342
|
+
let nextDescriptor = 3;
|
|
343
|
+
let nextTemporary = 1;
|
|
344
|
+
const makeDirectory = Effect2.fn("MemoryFileSystem.makeDirectory")(
|
|
345
|
+
function* (path, options2) {
|
|
346
|
+
yield* validateMode(options2?.mode, "makeDirectory");
|
|
347
|
+
const mode = (options2?.mode ?? 493) & 4095;
|
|
348
|
+
if (!options2?.recursive) return yield* mapped(caller.mkdir(path, { mode }), "makeDirectory", path);
|
|
349
|
+
return yield* mapped(
|
|
350
|
+
Effect2.scoped(Effect2.gen(function* () {
|
|
351
|
+
if (path === "") return yield* new Vfs.FsError({ code: "NotFound", operation: "makeDirectory" });
|
|
352
|
+
let base = yield* caller.openDirectory("/");
|
|
353
|
+
const components = path.split("/").filter((part) => part.length > 0);
|
|
354
|
+
for (const [index, name] of components.entries()) {
|
|
355
|
+
const result2 = yield* Effect2.result(caller.mkdir(name, { relativeTo: base, mode }));
|
|
356
|
+
if (result2._tag === "Failure" && result2.failure.code !== "AlreadyExists") return yield* result2.failure;
|
|
357
|
+
const next = yield* caller.openDirectory(name, { relativeTo: base }).pipe(
|
|
358
|
+
Effect2.mapError(
|
|
359
|
+
(error) => error.code === "NotDirectory" && index === components.length - 1 ? new Vfs.FsError({ code: "AlreadyExists", operation: "makeDirectory" }) : error
|
|
360
|
+
)
|
|
361
|
+
);
|
|
362
|
+
yield* base.close;
|
|
363
|
+
base = next;
|
|
364
|
+
}
|
|
365
|
+
})),
|
|
366
|
+
"makeDirectory",
|
|
367
|
+
path
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
);
|
|
371
|
+
const open = Effect2.fn("MemoryFileSystem.open")(function* (path, options2) {
|
|
372
|
+
const chosen = yield* openOptions(options2?.flag ?? "r", options2?.mode, "open");
|
|
373
|
+
const handle = yield* mapped(caller.open(path, chosen), "open", path);
|
|
374
|
+
const fd = nextDescriptor++;
|
|
375
|
+
let position = 0n;
|
|
376
|
+
let closed = false;
|
|
377
|
+
const gate = Semaphore.makeUnsafe(1);
|
|
378
|
+
const locked = (effect2) => gate.withPermit(Effect2.uninterruptible(effect2));
|
|
379
|
+
yield* Effect2.addFinalizer(
|
|
380
|
+
() => locked(Effect2.sync(() => {
|
|
381
|
+
closed = true;
|
|
382
|
+
}))
|
|
383
|
+
);
|
|
384
|
+
const read = Effect2.fnUntraced(function* (length, method) {
|
|
385
|
+
if (closed) return yield* resourceError(method, fd);
|
|
386
|
+
if (length > 0 && (position < 0n || position > BigInt(Number.MAX_SAFE_INTEGER))) {
|
|
387
|
+
return yield* resourceError(method, fd, "Invalid file position");
|
|
388
|
+
}
|
|
389
|
+
const bytes = yield* mapped(handle.pread(length, length === 0 ? 0n : position), method, fd);
|
|
390
|
+
position += BigInt(bytes.length);
|
|
391
|
+
return bytes;
|
|
392
|
+
});
|
|
393
|
+
const write = Effect2.fnUntraced(function* (input, method, all) {
|
|
394
|
+
const bytes = new Uint8Array(input);
|
|
395
|
+
return yield* locked(Effect2.gen(function* () {
|
|
396
|
+
if (closed) return yield* resourceError(method, fd);
|
|
397
|
+
if (bytes.length > 0 && (position < 0n || position > BigInt(Number.MAX_SAFE_INTEGER))) {
|
|
398
|
+
return yield* resourceError(method, fd, "Invalid file position");
|
|
399
|
+
}
|
|
400
|
+
let total = 0;
|
|
401
|
+
do {
|
|
402
|
+
const part = bytes.subarray(total);
|
|
403
|
+
const written = yield* mapped(
|
|
404
|
+
chosen.append ? handle.write(part) : handle.pwrite(part, bytes.length === 0 ? 0n : position),
|
|
405
|
+
method,
|
|
406
|
+
fd
|
|
407
|
+
);
|
|
408
|
+
total += written;
|
|
409
|
+
if (!chosen.append) position += BigInt(written);
|
|
410
|
+
} while (all && total < bytes.length);
|
|
411
|
+
return FileSystem.Size(total);
|
|
412
|
+
}));
|
|
413
|
+
});
|
|
414
|
+
return {
|
|
415
|
+
[FileSystem.FileTypeId]: FileSystem.FileTypeId,
|
|
416
|
+
stat: mapped(handle.stat, "stat", fd).pipe(Effect2.flatMap((value) => info(value, fd))),
|
|
417
|
+
sync: mapped(handle.sync, "sync", fd),
|
|
418
|
+
seek: Effect2.fn("MemoryFile.seek")(function* (offset, from) {
|
|
419
|
+
return yield* locked(Effect2.sync(() => {
|
|
420
|
+
if (closed) return FileSystem.Size(0);
|
|
421
|
+
position = from === "start" ? FileSystem.Size(offset) : position + FileSystem.Size(offset);
|
|
422
|
+
return FileSystem.Size(position);
|
|
423
|
+
}));
|
|
424
|
+
}),
|
|
425
|
+
read: Effect2.fn("MemoryFile.read")(function* (buffer) {
|
|
426
|
+
return yield* locked(Effect2.gen(function* () {
|
|
427
|
+
const bytes = yield* read(buffer.length, "read");
|
|
428
|
+
buffer.set(bytes);
|
|
429
|
+
return FileSystem.Size(bytes.length);
|
|
430
|
+
}));
|
|
431
|
+
}),
|
|
432
|
+
readAlloc: Effect2.fn("MemoryFile.readAlloc")(function* (size) {
|
|
433
|
+
const length = yield* sizeInput(size, "readAlloc");
|
|
434
|
+
return yield* locked(
|
|
435
|
+
Effect2.map(
|
|
436
|
+
read(Number(length), "readAlloc"),
|
|
437
|
+
(bytes) => bytes.length === 0 ? Option.none() : Option.some(bytes)
|
|
438
|
+
)
|
|
439
|
+
);
|
|
440
|
+
}),
|
|
441
|
+
truncate: Effect2.fn("MemoryFile.truncate")(function* (length) {
|
|
442
|
+
const size = yield* sizeInput(length, "truncate");
|
|
443
|
+
return yield* locked(Effect2.gen(function* () {
|
|
444
|
+
yield* mapped(handle.truncate(size), "truncate", fd);
|
|
445
|
+
if (!chosen.append && position > size) position = size;
|
|
446
|
+
}));
|
|
447
|
+
}),
|
|
448
|
+
write: (buffer) => write(buffer, "write", false),
|
|
449
|
+
writeAll: (buffer) => Effect2.asVoid(write(buffer, "writeAll", true))
|
|
450
|
+
};
|
|
451
|
+
});
|
|
452
|
+
const walk = Effect2.fnUntraced(function* (path) {
|
|
453
|
+
const root = yield* caller.openDirectory(path);
|
|
454
|
+
const pending = [{ base: root, prefix: "" }];
|
|
455
|
+
const output = [];
|
|
456
|
+
while (pending.length > 0) {
|
|
457
|
+
const next = pending.pop();
|
|
458
|
+
if (next === void 0) break;
|
|
459
|
+
const names = yield* caller.readDirectory(".", { relativeTo: next.base });
|
|
460
|
+
for (const name of names) {
|
|
461
|
+
const relative = next.prefix === "" ? name : `${next.prefix}/${name}`;
|
|
462
|
+
const metadata = yield* caller.lstat(name, { relativeTo: next.base });
|
|
463
|
+
output.push({ relative, base: next.base, name, metadata });
|
|
464
|
+
if (metadata.kind === "directory") {
|
|
465
|
+
pending.push({ base: yield* caller.openDirectory(name, { relativeTo: next.base }), prefix: relative });
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
return output;
|
|
470
|
+
});
|
|
471
|
+
const remove = Effect2.fn("MemoryFileSystem.remove")(function* (path, options2) {
|
|
472
|
+
const name = path.split("/").filter((part) => part.length > 0).at(-1);
|
|
473
|
+
if (name === void 0 || name === "." || name === "..") {
|
|
474
|
+
return yield* resourceError("remove", path, "Cannot remove root or dot entries");
|
|
475
|
+
}
|
|
476
|
+
const action = Effect2.scoped(Effect2.gen(function* () {
|
|
477
|
+
const node = yield* caller.lstat(path);
|
|
478
|
+
if (node.kind !== "directory") return yield* caller.unlink(path);
|
|
479
|
+
if (options2?.recursive) {
|
|
480
|
+
const entries = yield* walk(path);
|
|
481
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
482
|
+
const entry = entries[i];
|
|
483
|
+
if (entry === void 0) continue;
|
|
484
|
+
const relative = { relativeTo: entry.base };
|
|
485
|
+
yield* entry.metadata.kind === "directory" ? caller.rmdir(entry.name, relative) : caller.unlink(entry.name, relative);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
yield* caller.rmdir(path);
|
|
489
|
+
}));
|
|
490
|
+
return yield* mapped(
|
|
491
|
+
options2?.force ? action.pipe(Effect2.catchIf((error) => error.code === "NotFound", () => Effect2.void)) : action,
|
|
492
|
+
"remove",
|
|
493
|
+
path
|
|
494
|
+
);
|
|
495
|
+
});
|
|
496
|
+
const readDirectory = Effect2.fn("MemoryFileSystem.readDirectory")(
|
|
497
|
+
function* (path, options2) {
|
|
498
|
+
return yield* mapped(
|
|
499
|
+
options2?.recursive ? Effect2.scoped(walk(path)).pipe(Effect2.map((entries) => entries.map((entry) => entry.relative).sort())) : caller.readDirectory(path).pipe(Effect2.map((names) => [...names].sort())),
|
|
500
|
+
"readDirectory",
|
|
501
|
+
path
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
);
|
|
505
|
+
const writeCopiedFile = (destination, bytes, mode, options2) => caller.writeFile(destination, bytes, {
|
|
506
|
+
...options2,
|
|
507
|
+
access: "write",
|
|
508
|
+
truncate: true,
|
|
509
|
+
mode,
|
|
510
|
+
finalMode: mode
|
|
511
|
+
});
|
|
512
|
+
const copy = Effect2.fn("MemoryFileSystem.copy")(
|
|
513
|
+
function* (source, destination, options2) {
|
|
514
|
+
return yield* mapped(
|
|
515
|
+
Effect2.scoped(Effect2.gen(function* () {
|
|
516
|
+
const sourceNode = yield* caller.lstat(source);
|
|
517
|
+
const existing = yield* Effect2.result(caller.lstat(destination));
|
|
518
|
+
if (existing._tag === "Failure" && existing.failure.code !== "NotFound") return yield* existing.failure;
|
|
519
|
+
if (existing._tag === "Success" && existing.success.ino === sourceNode.ino) {
|
|
520
|
+
return yield* new Vfs.FsError({ code: "InvalidArgument", operation: "copy" });
|
|
521
|
+
}
|
|
522
|
+
if (existing._tag === "Success" && !options2?.overwrite) {
|
|
523
|
+
return yield* new Vfs.FsError({ code: "AlreadyExists", operation: "copy" });
|
|
524
|
+
}
|
|
525
|
+
if (sourceNode.kind === "file") {
|
|
526
|
+
const bytes = yield* caller.readFile(source);
|
|
527
|
+
yield* writeCopiedFile(destination, bytes, sourceNode.mode, {
|
|
528
|
+
create: options2?.overwrite ? "ifMissing" : "exclusive",
|
|
529
|
+
replaceFinalSymlink: true
|
|
530
|
+
});
|
|
531
|
+
} else if (sourceNode.kind === "symlink") {
|
|
532
|
+
if (existing._tag === "Success") {
|
|
533
|
+
yield* caller.unlink(destination);
|
|
534
|
+
}
|
|
535
|
+
yield* caller.symlink(yield* caller.readLink(source), destination);
|
|
536
|
+
} else {
|
|
537
|
+
const canonicalSource = yield* caller.realPath(source);
|
|
538
|
+
const trimmed = destination.replace(/\/+$/, "") || "/";
|
|
539
|
+
const slash = trimmed.lastIndexOf("/");
|
|
540
|
+
const parentPath = slash <= 0 ? "/" : trimmed.slice(0, slash);
|
|
541
|
+
const parent = yield* caller.realPath(parentPath);
|
|
542
|
+
if (parent === canonicalSource || parent.startsWith(`${canonicalSource}/`)) {
|
|
543
|
+
return yield* new Vfs.FsError({ code: "InvalidArgument", operation: "copy" });
|
|
544
|
+
}
|
|
545
|
+
const entries = yield* walk(source);
|
|
546
|
+
if (existing._tag === "Failure") {
|
|
547
|
+
yield* caller.mkdir(destination, { mode: sourceNode.mode });
|
|
548
|
+
} else if (existing.success.kind !== "directory") {
|
|
549
|
+
return yield* new Vfs.FsError({ code: "NotDirectory", operation: "copy" });
|
|
550
|
+
}
|
|
551
|
+
const copiedNodes = /* @__PURE__ */ new Map();
|
|
552
|
+
const directoryTimes = [];
|
|
553
|
+
const bases = /* @__PURE__ */ new Map([["", yield* caller.openDirectory(destination)]]);
|
|
554
|
+
for (const entry of entries) {
|
|
555
|
+
const split = entry.relative.lastIndexOf("/");
|
|
556
|
+
const parent2 = bases.get(split < 0 ? "" : entry.relative.slice(0, split));
|
|
557
|
+
if (parent2 === void 0) {
|
|
558
|
+
return yield* new Vfs.FsError({ code: "NotFound", operation: "copy" });
|
|
559
|
+
}
|
|
560
|
+
const relative = { relativeTo: parent2 };
|
|
561
|
+
if (entry.metadata.kind === "directory") {
|
|
562
|
+
const made = yield* Effect2.result(caller.mkdir(entry.name, { ...relative, mode: entry.metadata.mode }));
|
|
563
|
+
if (made._tag === "Failure" && (!options2?.overwrite || made.failure.code !== "AlreadyExists")) {
|
|
564
|
+
return yield* made.failure;
|
|
565
|
+
}
|
|
566
|
+
bases.set(entry.relative, yield* caller.openDirectory(entry.name, relative));
|
|
567
|
+
} else if (entry.metadata.kind === "file") {
|
|
568
|
+
const previous = copiedNodes.get(entry.metadata.ino);
|
|
569
|
+
const existing2 = yield* Effect2.result(caller.lstat(entry.name, relative));
|
|
570
|
+
if (previous !== void 0 && existing2._tag === "Failure" && existing2.failure.code === "NotFound") {
|
|
571
|
+
yield* caller.link(previous.name, entry.name, {
|
|
572
|
+
sourceRelativeTo: previous.base,
|
|
573
|
+
destinationRelativeTo: parent2
|
|
574
|
+
});
|
|
575
|
+
} else {
|
|
576
|
+
yield* writeCopiedFile(
|
|
577
|
+
entry.name,
|
|
578
|
+
yield* caller.readFile(entry.name, { relativeTo: entry.base }),
|
|
579
|
+
entry.metadata.mode,
|
|
580
|
+
{
|
|
581
|
+
...relative,
|
|
582
|
+
create: options2?.overwrite ? "ifMissing" : "exclusive",
|
|
583
|
+
replaceFinalSymlink: true
|
|
584
|
+
}
|
|
585
|
+
);
|
|
586
|
+
copiedNodes.set(entry.metadata.ino, { base: parent2, name: entry.name });
|
|
587
|
+
}
|
|
588
|
+
} else {
|
|
589
|
+
const previous = copiedNodes.get(entry.metadata.ino);
|
|
590
|
+
const existing2 = yield* Effect2.result(caller.lstat(entry.name, relative));
|
|
591
|
+
if (existing2._tag === "Success") {
|
|
592
|
+
if (!options2?.overwrite) {
|
|
593
|
+
return yield* new Vfs.FsError({ code: "AlreadyExists", operation: "copy" });
|
|
594
|
+
}
|
|
595
|
+
yield* caller.unlink(entry.name, relative);
|
|
596
|
+
} else if (existing2.failure.code !== "NotFound") return yield* existing2.failure;
|
|
597
|
+
if (previous !== void 0) {
|
|
598
|
+
yield* caller.link(previous.name, entry.name, {
|
|
599
|
+
sourceRelativeTo: previous.base,
|
|
600
|
+
destinationRelativeTo: parent2
|
|
601
|
+
});
|
|
602
|
+
} else {
|
|
603
|
+
yield* caller.symlink(
|
|
604
|
+
yield* caller.readLink(entry.name, { relativeTo: entry.base }),
|
|
605
|
+
entry.name,
|
|
606
|
+
relative
|
|
607
|
+
);
|
|
608
|
+
copiedNodes.set(entry.metadata.ino, { base: parent2, name: entry.name });
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
if (options2?.preserveTimestamps && entry.metadata.kind === "directory") {
|
|
612
|
+
directoryTimes.push({ base: parent2, name: entry.name, metadata: entry.metadata });
|
|
613
|
+
}
|
|
614
|
+
if (options2?.preserveTimestamps && entry.metadata.kind !== "directory") {
|
|
615
|
+
yield* caller.utimes(entry.name, {
|
|
616
|
+
access: { kind: "value", nanoseconds: entry.metadata.atimeNs },
|
|
617
|
+
modification: { kind: "value", nanoseconds: entry.metadata.mtimeNs }
|
|
618
|
+
}, { ...relative, followFinalSymlink: false });
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
for (const directory of directoryTimes.reverse()) {
|
|
622
|
+
yield* caller.utimes(directory.name, {
|
|
623
|
+
access: { kind: "value", nanoseconds: directory.metadata.atimeNs },
|
|
624
|
+
modification: { kind: "value", nanoseconds: directory.metadata.mtimeNs }
|
|
625
|
+
}, { relativeTo: directory.base });
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
if (options2?.preserveTimestamps) {
|
|
629
|
+
yield* caller.utimes(destination, {
|
|
630
|
+
access: { kind: "value", nanoseconds: sourceNode.atimeNs },
|
|
631
|
+
modification: { kind: "value", nanoseconds: sourceNode.mtimeNs }
|
|
632
|
+
}, { followFinalSymlink: false });
|
|
633
|
+
}
|
|
634
|
+
})),
|
|
635
|
+
"copy",
|
|
636
|
+
source
|
|
637
|
+
);
|
|
638
|
+
}
|
|
639
|
+
);
|
|
640
|
+
const copyFile = Effect2.fn("MemoryFileSystem.copyFile")(
|
|
641
|
+
function* (source, destination) {
|
|
642
|
+
return yield* mapped(
|
|
643
|
+
Effect2.gen(function* () {
|
|
644
|
+
const metadata = yield* caller.stat(source);
|
|
645
|
+
if (metadata.kind !== "file") return yield* new Vfs.FsError({ code: "IsDirectory", operation: "copyFile" });
|
|
646
|
+
const target = yield* Effect2.result(caller.stat(destination));
|
|
647
|
+
if (target._tag === "Success" && target.success.ino === metadata.ino) return;
|
|
648
|
+
yield* writeCopiedFile(destination, yield* caller.readFile(source), metadata.mode, { create: "ifMissing" });
|
|
649
|
+
}),
|
|
650
|
+
"copyFile",
|
|
651
|
+
source
|
|
652
|
+
);
|
|
653
|
+
}
|
|
654
|
+
);
|
|
655
|
+
const temp = Effect2.fnUntraced(
|
|
656
|
+
function* (method, file, options2) {
|
|
657
|
+
for (const value of [options2?.prefix ?? "", options2?.suffix ?? ""]) {
|
|
658
|
+
if (value.includes("/") || value.includes("\0")) {
|
|
659
|
+
return yield* argumentError2(method, "temporary fragments cannot contain separators");
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
const parent = yield* mapped(caller.realPath(options2?.directory ?? "/tmp"), method, options2?.directory ?? "/tmp");
|
|
663
|
+
while (true) {
|
|
664
|
+
const directory = childPath(
|
|
665
|
+
parent,
|
|
666
|
+
`${options2?.prefix ?? ""}${(nextTemporary++).toString(36).padStart(8, "0")}`
|
|
667
|
+
);
|
|
668
|
+
const result2 = yield* Effect2.result(caller.mkdir(directory));
|
|
669
|
+
if (result2._tag === "Failure") {
|
|
670
|
+
if (result2.failure.code === "AlreadyExists") continue;
|
|
671
|
+
return yield* translate(result2.failure, method, directory);
|
|
672
|
+
}
|
|
673
|
+
if (!file) return directory;
|
|
674
|
+
const path = childPath(directory, `${(nextTemporary++).toString(36).padStart(8, "0")}${options2?.suffix ?? ""}`);
|
|
675
|
+
yield* mapped(
|
|
676
|
+
caller.writeFile(path, new Uint8Array(0), { access: "write", create: "exclusive", mode: 420 }),
|
|
677
|
+
method,
|
|
678
|
+
path
|
|
679
|
+
).pipe(Effect2.onError(() => remove(directory, { recursive: true, force: true }).pipe(Effect2.orDie)));
|
|
680
|
+
return path;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
);
|
|
684
|
+
return FileSystem.make({
|
|
685
|
+
access: (path) => mapped(Effect2.asVoid(caller.stat(path)), "access", path),
|
|
686
|
+
stat: (path) => mapped(caller.stat(path), "stat", path).pipe(Effect2.flatMap((value) => info(value, path))),
|
|
687
|
+
chmod: Effect2.fn("MemoryFileSystem.chmod")(function* (path, mode) {
|
|
688
|
+
yield* validateMode(mode, "chmod");
|
|
689
|
+
yield* mapped(caller.chmod(path, mode & 4095), "chmod", path);
|
|
690
|
+
}),
|
|
691
|
+
chown: Effect2.fn("MemoryFileSystem.chown")(function* (path, uid, gid) {
|
|
692
|
+
if (![uid, gid].every((id) => Number.isInteger(id) && id >= 0 && id <= 4294967295)) {
|
|
693
|
+
return yield* argumentError2("chown", "owner IDs must be unsigned 32-bit integers");
|
|
694
|
+
}
|
|
695
|
+
yield* mapped(caller.chown(path, { uid, gid }), "chown", path);
|
|
696
|
+
}),
|
|
697
|
+
utimes: Effect2.fn("MemoryFileSystem.utimes")(function* (path, atime, mtime) {
|
|
698
|
+
const access = typeof atime === "number" ? atime * 1e3 : atime.getTime();
|
|
699
|
+
const modification = typeof mtime === "number" ? mtime * 1e3 : mtime.getTime();
|
|
700
|
+
if (![access, modification].every((value) => Number.isFinite(value) && Math.abs(value) <= 864e13)) {
|
|
701
|
+
return yield* argumentError2("utimes", "timestamps must be valid dates");
|
|
702
|
+
}
|
|
703
|
+
yield* mapped(
|
|
704
|
+
caller.utimes(path, {
|
|
705
|
+
access: { kind: "value", nanoseconds: BigInt(Math.trunc(access)) * 1000000n },
|
|
706
|
+
modification: { kind: "value", nanoseconds: BigInt(Math.trunc(modification)) * 1000000n }
|
|
707
|
+
}),
|
|
708
|
+
"utimes",
|
|
709
|
+
path
|
|
710
|
+
);
|
|
711
|
+
}),
|
|
712
|
+
open,
|
|
713
|
+
makeDirectory,
|
|
714
|
+
readDirectory,
|
|
715
|
+
remove,
|
|
716
|
+
copy,
|
|
717
|
+
copyFile,
|
|
718
|
+
readFile: (path) => mapped(caller.readFile(path), "readFile", path),
|
|
719
|
+
writeFile: Effect2.fn("MemoryFileSystem.writeFile")(function* (path, data, options2) {
|
|
720
|
+
const bytes = new Uint8Array(data);
|
|
721
|
+
const chosen = yield* openOptions(options2?.flag ?? "w", options2?.mode, "writeFile");
|
|
722
|
+
yield* mapped(caller.writeFile(path, bytes, chosen), "writeFile", path);
|
|
723
|
+
}),
|
|
724
|
+
readLink: (path) => mapped(caller.readLink(path), "readLink", path),
|
|
725
|
+
realPath: (path) => mapped(caller.realPath(path), "realPath", path),
|
|
726
|
+
rename: Effect2.fn("MemoryFileSystem.rename")(
|
|
727
|
+
function* (source, destination) {
|
|
728
|
+
const sourceInfo = yield* caller.lstat(source);
|
|
729
|
+
const target = sourceInfo.kind === "directory" && destination.endsWith("/") ? destination.replace(/\/+$/, "") || "/" : destination;
|
|
730
|
+
yield* caller.rename(source, target);
|
|
731
|
+
},
|
|
732
|
+
(effect2, source) => mapped(effect2, "rename", source)
|
|
733
|
+
),
|
|
734
|
+
link: (source, destination) => mapped(caller.link(source, destination), "link", source),
|
|
735
|
+
symlink: (target, path) => mapped(caller.symlink(target, path), "symlink", path),
|
|
736
|
+
truncate: Effect2.fn("MemoryFileSystem.truncate")(function* (path, length) {
|
|
737
|
+
yield* mapped(caller.truncate(path, yield* sizeInput(length, "truncate")), "truncate", path);
|
|
738
|
+
}),
|
|
739
|
+
makeTempDirectory: (options2) => temp("makeTempDirectory", false, options2),
|
|
740
|
+
makeTempFile: (options2) => temp("makeTempFile", true, options2),
|
|
741
|
+
makeTempDirectoryScoped: (options2) => Effect2.acquireRelease(
|
|
742
|
+
temp("makeTempDirectoryScoped", false, options2),
|
|
743
|
+
(path) => remove(path, { recursive: true, force: true }).pipe(Effect2.orDie)
|
|
744
|
+
),
|
|
745
|
+
makeTempFileScoped: (options2) => Effect2.acquireRelease(
|
|
746
|
+
temp("makeTempFileScoped", true, options2),
|
|
747
|
+
(path) => remove(path.slice(0, path.lastIndexOf("/")), { recursive: true, force: true }).pipe(Effect2.orDie)
|
|
748
|
+
),
|
|
749
|
+
watch: (path, options2) => Stream.unwrap(Effect2.gen(function* () {
|
|
750
|
+
const stream = yield* volume.watch;
|
|
751
|
+
const resolved = yield* mapped(caller.realPath(path), "stat", path);
|
|
752
|
+
const prefix = new TextEncoder().encode(resolved);
|
|
753
|
+
return stream.pipe(
|
|
754
|
+
Stream.filterEffect(
|
|
755
|
+
(event) => mapped(Vfs.pathToBytes(event.path), "watch", path).pipe(Effect2.map((bytes) => {
|
|
756
|
+
if (!prefix.every((byte, index) => bytes[index] === byte)) return false;
|
|
757
|
+
if (bytes.length === prefix.length) return true;
|
|
758
|
+
const start = resolved === "/" ? 1 : prefix.length + 1;
|
|
759
|
+
if (resolved !== "/" && bytes[prefix.length] !== 47) return false;
|
|
760
|
+
return options2?.recursive === true || !bytes.subarray(start).includes(47);
|
|
761
|
+
}))
|
|
762
|
+
),
|
|
763
|
+
Stream.mapEffect(
|
|
764
|
+
(event) => mapped(textPath(event.path, "watch"), "watch", path).pipe(
|
|
765
|
+
Effect2.map((name) => ({ _tag: event._tag, path: name }))
|
|
766
|
+
)
|
|
767
|
+
)
|
|
768
|
+
);
|
|
769
|
+
})),
|
|
770
|
+
glob: Effect2.fn("MemoryFileSystem.glob")(function* (pattern, options2) {
|
|
771
|
+
const include = yield* compileGlobPatterns("glob", pattern);
|
|
772
|
+
const exclude = (yield* Effect2.forEach(options2?.exclude ?? [], (pattern2) => compileGlobPatterns("glob", pattern2))).flat();
|
|
773
|
+
return yield* mapped(
|
|
774
|
+
Effect2.scoped(Effect2.gen(function* () {
|
|
775
|
+
const entries = yield* walk(options2?.root ?? "/");
|
|
776
|
+
if (exclude.some((pattern2) => matchesGlob(pattern2, [], true))) return [];
|
|
777
|
+
const output = include.some((pattern2) => matchesGlob(pattern2, [], true)) ? ["."] : [];
|
|
778
|
+
const excludedDirectories = [];
|
|
779
|
+
for (const entry of entries) {
|
|
780
|
+
const parts = entry.relative.split("/");
|
|
781
|
+
const directory = entry.metadata.kind === "directory";
|
|
782
|
+
const excluded = excludedDirectories.some((prefix) => entry.relative.startsWith(`${prefix}/`)) || exclude.some((pattern2) => matchesGlob(pattern2, parts, directory));
|
|
783
|
+
if (excluded) {
|
|
784
|
+
if (directory) excludedDirectories.push(entry.relative);
|
|
785
|
+
continue;
|
|
786
|
+
}
|
|
787
|
+
if (include.some((pattern2) => matchesGlob(pattern2, parts, directory))) output.push(entry.relative);
|
|
788
|
+
}
|
|
789
|
+
return output.sort();
|
|
790
|
+
})),
|
|
791
|
+
"glob",
|
|
792
|
+
options2?.root ?? "/"
|
|
793
|
+
);
|
|
794
|
+
})
|
|
795
|
+
});
|
|
796
|
+
});
|
|
797
|
+
var make3 = Effect2.gen(function* () {
|
|
798
|
+
const volume = yield* Vfs.fromFixture({ entries: [{ kind: "directory", path: "/tmp" }] });
|
|
799
|
+
return yield* bind(volume);
|
|
800
|
+
}).pipe(Effect2.orDie);
|
|
801
|
+
var layer = Layer.effect(FileSystem.FileSystem, make3);
|
|
802
|
+
|
|
803
|
+
// src/MemoryFileSystem.ts
|
|
804
|
+
var make4 = make3;
|
|
805
|
+
var layer2 = layer;
|
|
806
|
+
var bind2 = bind;
|
|
807
|
+
|
|
808
|
+
export {
|
|
809
|
+
make4 as make,
|
|
810
|
+
layer2 as layer,
|
|
811
|
+
bind2 as bind,
|
|
812
|
+
MemoryFileSystem_exports
|
|
813
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,gBAAgB,MAAM,uBAAuB,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compiles and matches the bounded POSIX glob syntax used by the memory adapter.
|
|
3
|
+
*
|
|
4
|
+
* @internal
|
|
5
|
+
*/
|
|
6
|
+
import * as Data from "effect/Data";
|
|
7
|
+
import * as Effect from "effect/Effect";
|
|
8
|
+
interface GlobLiteral {
|
|
9
|
+
readonly _tag: "Literal";
|
|
10
|
+
readonly value: string;
|
|
11
|
+
}
|
|
12
|
+
interface GlobStar {
|
|
13
|
+
readonly _tag: "Star";
|
|
14
|
+
}
|
|
15
|
+
interface GlobOne {
|
|
16
|
+
readonly _tag: "One";
|
|
17
|
+
}
|
|
18
|
+
interface GlobCharacterClass {
|
|
19
|
+
readonly _tag: "CharacterClass";
|
|
20
|
+
readonly negated: boolean;
|
|
21
|
+
readonly ranges: ReadonlyArray<readonly [string, string]>;
|
|
22
|
+
readonly literals: ReadonlyArray<string>;
|
|
23
|
+
}
|
|
24
|
+
type GlobToken = GlobLiteral | GlobStar | GlobOne | GlobCharacterClass;
|
|
25
|
+
declare const GlobToken: {
|
|
26
|
+
readonly $is: <Tag extends "CharacterClass" | "Literal" | "One" | "Star">(tag: Tag) => (u: unknown) => u is Extract<GlobCharacterClass, {
|
|
27
|
+
readonly _tag: Tag;
|
|
28
|
+
}> | Extract<GlobLiteral, {
|
|
29
|
+
readonly _tag: Tag;
|
|
30
|
+
}> | Extract<GlobOne, {
|
|
31
|
+
readonly _tag: Tag;
|
|
32
|
+
}> | Extract<GlobStar, {
|
|
33
|
+
readonly _tag: Tag;
|
|
34
|
+
}>;
|
|
35
|
+
readonly $match: {
|
|
36
|
+
<Cases extends { readonly [Tag in "CharacterClass" | "Literal" | "One" | "Star"]: (args: Extract<GlobToken, {
|
|
37
|
+
readonly _tag: Tag;
|
|
38
|
+
}>) => any; }>(cases: Cases): (value: GlobToken) => import("effect/Unify").Unify<ReturnType<Cases["CharacterClass" | "Literal" | "One" | "Star"]>>;
|
|
39
|
+
<Cases extends { readonly [Tag in "CharacterClass" | "Literal" | "One" | "Star"]: (args: Extract<GlobToken, {
|
|
40
|
+
readonly _tag: Tag;
|
|
41
|
+
}>) => any; }>(value: GlobToken, cases: Cases): import("effect/Unify").Unify<ReturnType<Cases["CharacterClass" | "Literal" | "One" | "Star"]>>;
|
|
42
|
+
};
|
|
43
|
+
readonly CharacterClass: Data.TaggedEnum.ConstructorFrom<GlobCharacterClass, "_tag">;
|
|
44
|
+
readonly Literal: Data.TaggedEnum.ConstructorFrom<GlobLiteral, "_tag">;
|
|
45
|
+
readonly One: Data.TaggedEnum.ConstructorFrom<GlobOne, "_tag">;
|
|
46
|
+
readonly Star: Data.TaggedEnum.ConstructorFrom<GlobStar, "_tag">;
|
|
47
|
+
};
|
|
48
|
+
interface GlobSegment {
|
|
49
|
+
readonly _tag: "Segment";
|
|
50
|
+
readonly tokens: ReadonlyArray<GlobToken>;
|
|
51
|
+
readonly startsWithDot: boolean;
|
|
52
|
+
}
|
|
53
|
+
interface GlobGlobstar {
|
|
54
|
+
readonly _tag: "Globstar";
|
|
55
|
+
}
|
|
56
|
+
type CompiledGlobSegment = GlobSegment | GlobGlobstar;
|
|
57
|
+
interface CompiledGlobPattern {
|
|
58
|
+
readonly segments: ReadonlyArray<CompiledGlobSegment>;
|
|
59
|
+
readonly directoryOnly: boolean;
|
|
60
|
+
}
|
|
61
|
+
/** @internal */
|
|
62
|
+
export declare const compileGlobPatterns: (method: string, pattern: string) => Effect.Effect<{
|
|
63
|
+
segments: ({
|
|
64
|
+
_tag: "Globstar";
|
|
65
|
+
tokens?: never;
|
|
66
|
+
startsWithDot?: never;
|
|
67
|
+
} | {
|
|
68
|
+
_tag: "Segment";
|
|
69
|
+
tokens: GlobToken[];
|
|
70
|
+
startsWithDot: boolean;
|
|
71
|
+
})[];
|
|
72
|
+
directoryOnly: boolean;
|
|
73
|
+
}[], import("effect/PlatformError").PlatformError, never>;
|
|
74
|
+
/** @internal */
|
|
75
|
+
export declare const matchesGlob: (pattern: CompiledGlobPattern, path: ReadonlyArray<string>, directory: boolean) => boolean;
|
|
76
|
+
export {};
|
|
77
|
+
//# sourceMappingURL=glob.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"glob.d.ts","sourceRoot":"","sources":["../../src/internal/glob.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,IAAI,MAAM,aAAa,CAAA;AACnC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAOvC,UAAU,WAAW;IACnB,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CACvB;AAED,UAAU,QAAQ;IAChB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAED,UAAU,OAAO;IACf,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAA;CACrB;AAED,UAAU,kBAAkB;IAC1B,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAA;IAC/B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IACzD,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;CACzC;AAED,KAAK,SAAS,GAAG,WAAW,GAAG,QAAQ,GAAG,OAAO,GAAG,kBAAkB,CAAA;AAEtE,QAAA,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;CAA+B,CAAA;AAE9C,UAAU,WAAW;IACnB,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;IACxB,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,SAAS,CAAC,CAAA;IACzC,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAA;CAChC;AAED,UAAU,YAAY;IACpB,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;CAC1B;AAED,KAAK,mBAAmB,GAAG,WAAW,GAAG,YAAY,CAAA;AAErD,UAAU,mBAAmB;IAC3B,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC,mBAAmB,CAAC,CAAA;IACrD,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAA;CAChC;AA+LD,gBAAgB;AAChB,eAAO,MAAM,mBAAmB;;;;;;;;;;;yDAG9B,CAAA;AA2CF,gBAAgB;AAChB,eAAO,MAAM,WAAW,YAAa,mBAAmB,QAAQ,aAAa,CAAC,MAAM,CAAC,aAAa,OAAO,KAAG,OAsB3G,CAAA"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Implements the Effect `FileSystem` adapter over `@effect-vfs/core`.
|
|
3
|
+
*
|
|
4
|
+
* This module owns string-path conversion, Effect cursor compatibility, error
|
|
5
|
+
* translation, recursive helpers, temporary resources, globbing, and watches.
|
|
6
|
+
*
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
import { VirtualFileSystem as Vfs } from "@effect-vfs/core";
|
|
10
|
+
import * as Effect from "effect/Effect";
|
|
11
|
+
import * as FileSystem from "effect/FileSystem";
|
|
12
|
+
import * as Layer from "effect/Layer";
|
|
13
|
+
/** @internal */
|
|
14
|
+
export declare const bind: (volume: Vfs.Volume, options?: {
|
|
15
|
+
readonly identity?: {
|
|
16
|
+
readonly uid: number;
|
|
17
|
+
readonly gid: number;
|
|
18
|
+
readonly groups: readonly number[];
|
|
19
|
+
readonly privileged: boolean;
|
|
20
|
+
};
|
|
21
|
+
readonly umask?: number;
|
|
22
|
+
} | undefined) => Effect.Effect<FileSystem.FileSystem, Vfs.ConfigurationError, never>;
|
|
23
|
+
/** @internal */
|
|
24
|
+
export declare const make: Effect.Effect<FileSystem.FileSystem>;
|
|
25
|
+
/** @internal */
|
|
26
|
+
export declare const layer: Layer.Layer<FileSystem.FileSystem, never, never>;
|
|
27
|
+
//# sourceMappingURL=memoryFileSystem.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memoryFileSystem.d.ts","sourceRoot":"","sources":["../../src/internal/memoryFileSystem.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,iBAAiB,IAAI,GAAG,EAAE,MAAM,kBAAkB,CAAA;AAE3D,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,UAAU,MAAM,mBAAmB,CAAA;AAC/C,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AAuGrC,gBAAgB;AAChB,eAAO,MAAM,IAAI;;;;;;;;qFAgef,CAAA;AAEF,gBAAgB;AAChB,eAAO,MAAM,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,UAAU,CAGjC,CAAA;AAErB,gBAAgB;AAChB,eAAO,MAAM,KAAK,kDAA4C,CAAA"}
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@effect-vfs/memory",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "An in-memory implementation of Effect's FileSystem service.",
|
|
5
|
+
"homepage": "https://github.com/lloydrichards/effect-virtual-fs#readme",
|
|
6
|
+
"bugs": {
|
|
7
|
+
"url": "https://github.com/lloydrichards/effect-virtual-fs/issues"
|
|
8
|
+
},
|
|
9
|
+
"keywords": ["effect", "filesystem", "memory", "testing", "typescript"],
|
|
10
|
+
"type": "module",
|
|
11
|
+
"sideEffects": false,
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"main": "./dist/index.js",
|
|
14
|
+
"module": "./dist/index.js",
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"import": "./dist/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./MemoryFileSystem": {
|
|
22
|
+
"types": "./dist/MemoryFileSystem.d.ts",
|
|
23
|
+
"import": "./dist/MemoryFileSystem.js"
|
|
24
|
+
},
|
|
25
|
+
"./internal/*": null,
|
|
26
|
+
"./index": null,
|
|
27
|
+
"./*/index": null,
|
|
28
|
+
"./package.json": "./package.json"
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"dist/**",
|
|
32
|
+
"README.md",
|
|
33
|
+
"LICENSE"
|
|
34
|
+
],
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"access": "public"
|
|
37
|
+
},
|
|
38
|
+
"repository": {
|
|
39
|
+
"type": "git",
|
|
40
|
+
"url": "git+https://github.com/lloydrichards/effect-virtual-fs.git",
|
|
41
|
+
"directory": "packages/memory"
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "tsup && tsc --project tsconfig.build.json && tsc --project compatibility/node-next/tsconfig.json && bun build compatibility/browser/index.ts --target=browser --outdir=.cache/browser && node compatibility/browser/check.mjs",
|
|
45
|
+
"clean": "git clean -xdf .cache .turbo dist node_modules tsconfig.tsbuildinfo",
|
|
46
|
+
"test": "vitest run",
|
|
47
|
+
"type-check": "tsc --noEmit"
|
|
48
|
+
},
|
|
49
|
+
"peerDependencies": {
|
|
50
|
+
"effect": "4.0.0-rc.112"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@effect/vitest": "4.0.0-rc.112",
|
|
54
|
+
"@repo/config-typescript": "workspace:*",
|
|
55
|
+
"effect": "4.0.0-rc.112",
|
|
56
|
+
"tsup": "8.5.1"
|
|
57
|
+
},
|
|
58
|
+
"dependencies": {
|
|
59
|
+
"@effect-vfs/core": "^0.0.1"
|
|
60
|
+
}
|
|
61
|
+
}
|