@effected/walker 0.3.2 → 0.3.3
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/Walker.js +58 -65
- package/index.d.ts +59 -6
- package/package.json +1 -1
package/Walker.js
CHANGED
|
@@ -1,30 +1,6 @@
|
|
|
1
1
|
import { Effect, FileSystem, Option, Path } from "effect";
|
|
2
2
|
|
|
3
3
|
//#region src/Walker.ts
|
|
4
|
-
/**
|
|
5
|
-
* Ascend from `start` toward the filesystem root, yielding each directory,
|
|
6
|
-
* nearest first.
|
|
7
|
-
*
|
|
8
|
-
* @remarks
|
|
9
|
-
* Lexical, not physical: `Path.dirname` does not resolve symlinks, so ascending
|
|
10
|
-
* out of a symlinked directory follows the path you were given rather than the
|
|
11
|
-
* real filesystem parent. That is deliberate — config discovery wants the file
|
|
12
|
-
* nearest the path the user named.
|
|
13
|
-
*
|
|
14
|
-
* Bounded twice over: `dirname` is a fixpoint at the root, and `maxDepth` guards
|
|
15
|
-
* a pathological `Path` implementation that never reaches one.
|
|
16
|
-
*
|
|
17
|
-
* `stopAt` is compared in normalized form — see {@link AscendOptions.stopAt}.
|
|
18
|
-
* Raw string equality made the ceiling fail OPEN: an unnormalized ceiling
|
|
19
|
-
* matched nothing and the ascent ran silently to the filesystem root, which is
|
|
20
|
-
* the unbounded walk the option exists to prevent, with no error to notice it
|
|
21
|
-
* by. A relative ceiling **dies** for the same reason — resolving one against
|
|
22
|
-
* `process.cwd()` would reintroduce the silent-wrong-walk failure through a
|
|
23
|
-
* different door. See {@link AscendOptions.stopAt} for why that is a defect
|
|
24
|
-
* rather than a typed error; the error channel stays `never`.
|
|
25
|
-
*
|
|
26
|
-
* @public
|
|
27
|
-
*/
|
|
28
4
|
const ascend = (start, options) => Effect.gen(function* () {
|
|
29
5
|
const path = yield* Path.Path;
|
|
30
6
|
const maxDepth = options?.maxDepth ?? 256;
|
|
@@ -42,63 +18,80 @@ const ascend = (start, options) => Effect.gen(function* () {
|
|
|
42
18
|
}
|
|
43
19
|
return dirs;
|
|
44
20
|
});
|
|
45
|
-
/**
|
|
46
|
-
* The first candidate whose `predicate` reports true, or `Option.none()`.
|
|
47
|
-
*
|
|
48
|
-
* @remarks
|
|
49
|
-
* Each predicate is absorbed **individually**: a failure on one candidate is
|
|
50
|
-
* treated as "this candidate did not match" and the scan continues. One
|
|
51
|
-
* unreadable ancestor must never abort the walk, or a permission error deep in
|
|
52
|
-
* the tree would hide a valid root above it. Not-found and cannot-look are
|
|
53
|
-
* therefore indistinguishable to the caller; discovery is best-effort.
|
|
54
|
-
*
|
|
55
|
-
* `Effect.catch` catches failures, **not defects**. A predicate that throws is
|
|
56
|
-
* programmer error and surfaces as a defect. Do not change this to
|
|
57
|
-
* `Effect.catchCause` — the distinction is load-bearing.
|
|
58
|
-
*
|
|
59
|
-
* @public
|
|
60
|
-
*/
|
|
61
21
|
const firstMatch = (candidates, predicate) => Effect.gen(function* () {
|
|
62
22
|
for (const candidate of candidates) if (yield* Effect.catch(predicate(candidate), () => Effect.succeed(false))) return Option.some(candidate);
|
|
63
23
|
return Option.none();
|
|
64
24
|
});
|
|
65
|
-
/**
|
|
66
|
-
* The first existing path among the candidates `candidatesFor` produces for each
|
|
67
|
-
* directory in `dirs`, scanned in order. Nearer directories win.
|
|
68
|
-
*
|
|
69
|
-
* @remarks
|
|
70
|
-
* Candidates materialize up front, bounded by `dirs.length × candidatesFor`'s
|
|
71
|
-
* output — a few hundred strings under the default `maxDepth`.
|
|
72
|
-
*
|
|
73
|
-
* @public
|
|
74
|
-
*/
|
|
75
25
|
const findUpward = (dirs, candidatesFor) => Effect.gen(function* () {
|
|
76
26
|
const fs = yield* FileSystem.FileSystem;
|
|
77
27
|
const candidates = [];
|
|
78
28
|
for (const dir of dirs) candidates.push(...candidatesFor(dir));
|
|
79
29
|
return yield* firstMatch(candidates, (candidate) => fs.exists(candidate));
|
|
80
30
|
});
|
|
81
|
-
/**
|
|
82
|
-
* The first directory in `dirs` that `isRoot` accepts.
|
|
83
|
-
*
|
|
84
|
-
* @remarks
|
|
85
|
-
* `firstMatch` over the directories themselves — the candidate expansion is the
|
|
86
|
-
* identity. `isRoot` is a caller-supplied marker test (a `.git` entry, a
|
|
87
|
-
* `pnpm-workspace.yaml`), and its failures are absorbed per directory.
|
|
88
|
-
*
|
|
89
|
-
* @public
|
|
90
|
-
*/
|
|
91
31
|
const findRoot = (dirs, isRoot) => firstMatch(dirs, isRoot);
|
|
92
32
|
/**
|
|
93
33
|
* Upward path traversal primitives.
|
|
94
34
|
*
|
|
95
35
|
* @public
|
|
96
36
|
*/
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
37
|
+
var Walker = class {
|
|
38
|
+
constructor() {}
|
|
39
|
+
/**
|
|
40
|
+
* Ascend from `start` toward the filesystem root, yielding each directory,
|
|
41
|
+
* nearest first.
|
|
42
|
+
*
|
|
43
|
+
* @remarks
|
|
44
|
+
* Lexical, not physical: `Path.dirname` does not resolve symlinks, so ascending
|
|
45
|
+
* out of a symlinked directory follows the path you were given rather than the
|
|
46
|
+
* real filesystem parent. That is deliberate — config discovery wants the file
|
|
47
|
+
* nearest the path the user named.
|
|
48
|
+
*
|
|
49
|
+
* Bounded twice over: `dirname` is a fixpoint at the root, and `maxDepth` guards
|
|
50
|
+
* a pathological `Path` implementation that never reaches one.
|
|
51
|
+
*
|
|
52
|
+
* `stopAt` is compared in normalized form — see {@link AscendOptions.stopAt}.
|
|
53
|
+
* Raw string equality made the ceiling fail OPEN: an unnormalized ceiling
|
|
54
|
+
* matched nothing and the ascent ran silently to the filesystem root, which is
|
|
55
|
+
* the unbounded walk the option exists to prevent, with no error to notice it
|
|
56
|
+
* by. A relative ceiling **dies** for the same reason — resolving one against
|
|
57
|
+
* `process.cwd()` would reintroduce the silent-wrong-walk failure through a
|
|
58
|
+
* different door. See {@link AscendOptions.stopAt} for why that is a defect
|
|
59
|
+
* rather than a typed error; the error channel stays `never`.
|
|
60
|
+
*/
|
|
61
|
+
static ascend = ascend;
|
|
62
|
+
/**
|
|
63
|
+
* The first candidate whose `predicate` reports true, or `Option.none()`.
|
|
64
|
+
*
|
|
65
|
+
* @remarks
|
|
66
|
+
* Each predicate is absorbed **individually**: a failure on one candidate is
|
|
67
|
+
* treated as "this candidate did not match" and the scan continues. One
|
|
68
|
+
* unreadable ancestor must never abort the walk, or a permission error deep in
|
|
69
|
+
* the tree would hide a valid root above it. Not-found and cannot-look are
|
|
70
|
+
* therefore indistinguishable to the caller; discovery is best-effort.
|
|
71
|
+
*
|
|
72
|
+
* `Effect.catch` catches failures, **not defects**. A predicate that throws is
|
|
73
|
+
* programmer error and surfaces as a defect. Do not change this to
|
|
74
|
+
* `Effect.catchCause` — the distinction is load-bearing.
|
|
75
|
+
*/
|
|
76
|
+
static firstMatch = firstMatch;
|
|
77
|
+
/**
|
|
78
|
+
* The first existing path among the candidates `candidatesFor` produces for each
|
|
79
|
+
* directory in `dirs`, scanned in order. Nearer directories win.
|
|
80
|
+
*
|
|
81
|
+
* @remarks
|
|
82
|
+
* Candidates materialize up front, bounded by `dirs.length × candidatesFor`'s
|
|
83
|
+
* output — a few hundred strings under the default `maxDepth`.
|
|
84
|
+
*/
|
|
85
|
+
static findUpward = findUpward;
|
|
86
|
+
/**
|
|
87
|
+
* The first directory in `dirs` that `isRoot` accepts.
|
|
88
|
+
*
|
|
89
|
+
* @remarks
|
|
90
|
+
* `firstMatch` over the directories themselves — the candidate expansion is the
|
|
91
|
+
* identity. `isRoot` is a caller-supplied marker test (a `.git` entry, a
|
|
92
|
+
* `pnpm-workspace.yaml`), and its failures are absorbed per directory.
|
|
93
|
+
*/
|
|
94
|
+
static findRoot = findRoot;
|
|
102
95
|
};
|
|
103
96
|
|
|
104
97
|
//#endregion
|
package/index.d.ts
CHANGED
|
@@ -229,12 +229,65 @@ interface AscendOptions {
|
|
|
229
229
|
*
|
|
230
230
|
* @public
|
|
231
231
|
*/
|
|
232
|
-
declare
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
232
|
+
declare class Walker {
|
|
233
|
+
private constructor();
|
|
234
|
+
/**
|
|
235
|
+
* Ascend from `start` toward the filesystem root, yielding each directory,
|
|
236
|
+
* nearest first.
|
|
237
|
+
*
|
|
238
|
+
* @remarks
|
|
239
|
+
* Lexical, not physical: `Path.dirname` does not resolve symlinks, so ascending
|
|
240
|
+
* out of a symlinked directory follows the path you were given rather than the
|
|
241
|
+
* real filesystem parent. That is deliberate — config discovery wants the file
|
|
242
|
+
* nearest the path the user named.
|
|
243
|
+
*
|
|
244
|
+
* Bounded twice over: `dirname` is a fixpoint at the root, and `maxDepth` guards
|
|
245
|
+
* a pathological `Path` implementation that never reaches one.
|
|
246
|
+
*
|
|
247
|
+
* `stopAt` is compared in normalized form — see {@link AscendOptions.stopAt}.
|
|
248
|
+
* Raw string equality made the ceiling fail OPEN: an unnormalized ceiling
|
|
249
|
+
* matched nothing and the ascent ran silently to the filesystem root, which is
|
|
250
|
+
* the unbounded walk the option exists to prevent, with no error to notice it
|
|
251
|
+
* by. A relative ceiling **dies** for the same reason — resolving one against
|
|
252
|
+
* `process.cwd()` would reintroduce the silent-wrong-walk failure through a
|
|
253
|
+
* different door. See {@link AscendOptions.stopAt} for why that is a defect
|
|
254
|
+
* rather than a typed error; the error channel stays `never`.
|
|
255
|
+
*/
|
|
256
|
+
static readonly ascend: (start: string, options?: AscendOptions) => Effect.Effect<ReadonlyArray<string>, never, Path.Path>;
|
|
257
|
+
/**
|
|
258
|
+
* The first candidate whose `predicate` reports true, or `Option.none()`.
|
|
259
|
+
*
|
|
260
|
+
* @remarks
|
|
261
|
+
* Each predicate is absorbed **individually**: a failure on one candidate is
|
|
262
|
+
* treated as "this candidate did not match" and the scan continues. One
|
|
263
|
+
* unreadable ancestor must never abort the walk, or a permission error deep in
|
|
264
|
+
* the tree would hide a valid root above it. Not-found and cannot-look are
|
|
265
|
+
* therefore indistinguishable to the caller; discovery is best-effort.
|
|
266
|
+
*
|
|
267
|
+
* `Effect.catch` catches failures, **not defects**. A predicate that throws is
|
|
268
|
+
* programmer error and surfaces as a defect. Do not change this to
|
|
269
|
+
* `Effect.catchCause` — the distinction is load-bearing.
|
|
270
|
+
*/
|
|
271
|
+
static readonly firstMatch: <E, R>(candidates: ReadonlyArray<string>, predicate: (candidate: string) => Effect.Effect<boolean, E, R>) => Effect.Effect<Option.Option<string>, never, R>;
|
|
272
|
+
/**
|
|
273
|
+
* The first existing path among the candidates `candidatesFor` produces for each
|
|
274
|
+
* directory in `dirs`, scanned in order. Nearer directories win.
|
|
275
|
+
*
|
|
276
|
+
* @remarks
|
|
277
|
+
* Candidates materialize up front, bounded by `dirs.length × candidatesFor`'s
|
|
278
|
+
* output — a few hundred strings under the default `maxDepth`.
|
|
279
|
+
*/
|
|
280
|
+
static readonly findUpward: (dirs: ReadonlyArray<string>, candidatesFor: (dir: string) => ReadonlyArray<string>) => Effect.Effect<Option.Option<string>, never, FileSystem.FileSystem>;
|
|
281
|
+
/**
|
|
282
|
+
* The first directory in `dirs` that `isRoot` accepts.
|
|
283
|
+
*
|
|
284
|
+
* @remarks
|
|
285
|
+
* `firstMatch` over the directories themselves — the candidate expansion is the
|
|
286
|
+
* identity. `isRoot` is a caller-supplied marker test (a `.git` entry, a
|
|
287
|
+
* `pnpm-workspace.yaml`), and its failures are absorbed per directory.
|
|
288
|
+
*/
|
|
289
|
+
static readonly findRoot: <E, R>(dirs: ReadonlyArray<string>, isRoot: (dir: string) => Effect.Effect<boolean, E, R>) => Effect.Effect<Option.Option<string>, never, R>;
|
|
290
|
+
}
|
|
238
291
|
//#endregion
|
|
239
292
|
export { type AscendOptions, type CompileAndExpandOptions, DescendError, type DescendOptions, GlobExpansionError, Walker, compileAndExpand, descend };
|
|
240
293
|
//# sourceMappingURL=index.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effected/walker",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Path traversal as Effect primitives: ascend a directory chain to the first matching candidate, or descend a tree expanding a glob against the filesystem.",
|
|
6
6
|
"keywords": [
|