@anabranch/fs 0.1.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.
Files changed (49) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +45 -0
  3. package/esm/_dnt.polyfills.d.ts +7 -0
  4. package/esm/_dnt.polyfills.d.ts.map +1 -0
  5. package/esm/_dnt.polyfills.js +1 -0
  6. package/esm/anabranch/index.d.ts +44 -0
  7. package/esm/anabranch/index.d.ts.map +1 -0
  8. package/esm/anabranch/index.js +41 -0
  9. package/esm/anabranch/streams/channel.d.ts +15 -0
  10. package/esm/anabranch/streams/channel.d.ts.map +1 -0
  11. package/esm/anabranch/streams/channel.js +122 -0
  12. package/esm/anabranch/streams/source.d.ts +68 -0
  13. package/esm/anabranch/streams/source.d.ts.map +1 -0
  14. package/esm/anabranch/streams/source.js +72 -0
  15. package/esm/anabranch/streams/stream.d.ts +431 -0
  16. package/esm/anabranch/streams/stream.d.ts.map +1 -0
  17. package/esm/anabranch/streams/stream.js +625 -0
  18. package/esm/anabranch/streams/task.d.ts +117 -0
  19. package/esm/anabranch/streams/task.d.ts.map +1 -0
  20. package/esm/anabranch/streams/task.js +416 -0
  21. package/esm/anabranch/streams/util.d.ts +33 -0
  22. package/esm/anabranch/streams/util.d.ts.map +1 -0
  23. package/esm/anabranch/streams/util.js +18 -0
  24. package/esm/fs/dir.d.ts +17 -0
  25. package/esm/fs/dir.d.ts.map +1 -0
  26. package/esm/fs/dir.js +90 -0
  27. package/esm/fs/errors.d.ts +64 -0
  28. package/esm/fs/errors.d.ts.map +1 -0
  29. package/esm/fs/errors.js +125 -0
  30. package/esm/fs/glob_match.d.ts +15 -0
  31. package/esm/fs/glob_match.d.ts.map +1 -0
  32. package/esm/fs/glob_match.js +73 -0
  33. package/esm/fs/index.d.ts +38 -0
  34. package/esm/fs/index.d.ts.map +1 -0
  35. package/esm/fs/index.js +31 -0
  36. package/esm/fs/read.d.ts +22 -0
  37. package/esm/fs/read.d.ts.map +1 -0
  38. package/esm/fs/read.js +75 -0
  39. package/esm/fs/types.d.ts +67 -0
  40. package/esm/fs/types.d.ts.map +1 -0
  41. package/esm/fs/types.js +1 -0
  42. package/esm/fs/watch.d.ts +17 -0
  43. package/esm/fs/watch.d.ts.map +1 -0
  44. package/esm/fs/watch.js +37 -0
  45. package/esm/fs/write.d.ts +16 -0
  46. package/esm/fs/write.d.ts.map +1 -0
  47. package/esm/fs/write.js +42 -0
  48. package/esm/package.json +3 -0
  49. package/package.json +24 -0
package/esm/fs/dir.js ADDED
@@ -0,0 +1,90 @@
1
+ import { readdir } from "node:fs/promises";
2
+ import { join, relative } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { Source } from "../anabranch/index.js";
5
+ import { _matchGlob } from "./glob_match.js";
6
+ import { nodeErrorToFSError, } from "./errors.js";
7
+ /**
8
+ * Lists the immediate children of a directory.
9
+ */
10
+ export function readDir(path) {
11
+ return Source.from(async function* () {
12
+ try {
13
+ const entries = await readdir(path, { withFileTypes: true });
14
+ for (const entry of entries) {
15
+ yield {
16
+ name: entry.name,
17
+ isFile: entry.isFile(),
18
+ isDirectory: entry.isDirectory(),
19
+ isSymlink: entry.isSymbolicLink(),
20
+ };
21
+ }
22
+ }
23
+ catch (error) {
24
+ throw nodeErrorToFSError(error, path);
25
+ }
26
+ });
27
+ }
28
+ /**
29
+ * Recursively walks a directory tree, yielding each entry.
30
+ */
31
+ export function walk(root, options) {
32
+ return Source.from(async function* () {
33
+ const rootPath = root instanceof URL ? fileURLToPath(root) : root;
34
+ const maxDepth = options?.maxDepth ?? Infinity;
35
+ const includeFiles = options?.includeFiles ?? true;
36
+ const includeDirs = options?.includeDirs ?? true;
37
+ const includeSymlinks = options?.includeSymlinks ?? true;
38
+ const match = options?.match;
39
+ const skip = options?.skip;
40
+ const stack = [[rootPath, 0]];
41
+ while (stack.length > 0) {
42
+ const [dirPath, depth] = stack.pop();
43
+ let entries;
44
+ try {
45
+ entries = await readdir(dirPath, { withFileTypes: true });
46
+ }
47
+ catch (error) {
48
+ throw nodeErrorToFSError(error, dirPath);
49
+ }
50
+ for (const entry of entries) {
51
+ const entryPath = join(dirPath, entry.name);
52
+ const relPath = relative(rootPath, entryPath).replace(/\\/g, "/");
53
+ const isFile = entry.isFile();
54
+ const isDirectory = entry.isDirectory();
55
+ const isSymlink = entry.isSymbolicLink();
56
+ if (skip && skip.some((r) => r.test(relPath)))
57
+ continue;
58
+ const matchesFilter = !match || match.some((r) => r.test(relPath));
59
+ const walkEntry = {
60
+ name: entry.name,
61
+ path: entryPath,
62
+ isFile,
63
+ isDirectory,
64
+ isSymlink,
65
+ };
66
+ if (isFile && includeFiles && matchesFilter) {
67
+ yield walkEntry;
68
+ }
69
+ else if (isDirectory) {
70
+ if (includeDirs && matchesFilter) {
71
+ yield walkEntry;
72
+ }
73
+ if (depth < maxDepth) {
74
+ stack.push([entryPath, depth + 1]);
75
+ }
76
+ }
77
+ else if (isSymlink && includeSymlinks && matchesFilter) {
78
+ yield walkEntry;
79
+ }
80
+ }
81
+ }
82
+ });
83
+ }
84
+ /**
85
+ * Finds all entries under `root` whose relative path matches the glob `pattern`.
86
+ */
87
+ export function glob(root, pattern, options) {
88
+ const regex = _matchGlob(pattern);
89
+ return walk(root, { ...options, match: [regex] });
90
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Structured error types for file-system operations.
3
+ *
4
+ * @example
5
+ * ```ts
6
+ * import { readTextFile, FSError, FSErrors } from "@anabranch/fs";
7
+ *
8
+ * const result = await readTextFile("./config.json").run();
9
+ * if (result.type === "error") {
10
+ * const err = result.error;
11
+ * if (err instanceof FSError) {
12
+ * console.error(`${err.kind}: ${err.path} - ${err.message}`);
13
+ * }
14
+ * }
15
+ * ```
16
+ */
17
+ export declare class FSError extends Error {
18
+ readonly kind: string;
19
+ readonly path: string | URL;
20
+ constructor(kind: string, path: string | URL, message: string);
21
+ }
22
+ export declare class NotFound extends FSError {
23
+ constructor(path: string | URL, message: string);
24
+ }
25
+ export declare class AlreadyExists extends FSError {
26
+ constructor(path: string | URL, message: string);
27
+ }
28
+ export declare class IsDirectory extends FSError {
29
+ constructor(path: string | URL, message: string);
30
+ }
31
+ export declare class NotDirectory extends FSError {
32
+ constructor(path: string | URL, message: string);
33
+ }
34
+ export declare class PermissionDenied extends FSError {
35
+ constructor(path: string | URL, message: string);
36
+ }
37
+ export declare class ReadError extends FSError {
38
+ constructor(path: string | URL, message: string);
39
+ }
40
+ export declare class WriteError extends FSError {
41
+ constructor(path: string | URL, message: string);
42
+ }
43
+ export declare class InvalidData extends FSError {
44
+ readonly cause?: unknown;
45
+ constructor(path: string | URL, message: string, cause?: unknown);
46
+ }
47
+ export declare class Unknown extends FSError {
48
+ readonly cause?: unknown;
49
+ constructor(path: string | URL, message: string, cause?: unknown);
50
+ }
51
+ export declare const FSErrors: {
52
+ readonly NotFound: typeof NotFound;
53
+ readonly AlreadyExists: typeof AlreadyExists;
54
+ readonly IsDirectory: typeof IsDirectory;
55
+ readonly NotDirectory: typeof NotDirectory;
56
+ readonly PermissionDenied: typeof PermissionDenied;
57
+ readonly ReadError: typeof ReadError;
58
+ readonly WriteError: typeof WriteError;
59
+ readonly InvalidData: typeof InvalidData;
60
+ readonly Unknown: typeof Unknown;
61
+ };
62
+ declare function nodeErrorToFSError(error: unknown, path: string | URL): FSError;
63
+ export { nodeErrorToFSError };
64
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/fs/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,OAAQ,SAAQ,KAAK;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,CAAC;gBAEhB,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,GAAG,EAAE,OAAO,EAAE,MAAM;CAK9D;AAED,qBAAa,QAAS,SAAQ,OAAO;gBACvB,IAAI,EAAE,MAAM,GAAG,GAAG,EAAE,OAAO,EAAE,MAAM;CAGhD;AAED,qBAAa,aAAc,SAAQ,OAAO;gBAC5B,IAAI,EAAE,MAAM,GAAG,GAAG,EAAE,OAAO,EAAE,MAAM;CAGhD;AAED,qBAAa,WAAY,SAAQ,OAAO;gBAC1B,IAAI,EAAE,MAAM,GAAG,GAAG,EAAE,OAAO,EAAE,MAAM;CAGhD;AAED,qBAAa,YAAa,SAAQ,OAAO;gBAC3B,IAAI,EAAE,MAAM,GAAG,GAAG,EAAE,OAAO,EAAE,MAAM;CAGhD;AAED,qBAAa,gBAAiB,SAAQ,OAAO;gBAC/B,IAAI,EAAE,MAAM,GAAG,GAAG,EAAE,OAAO,EAAE,MAAM;CAGhD;AAED,qBAAa,SAAU,SAAQ,OAAO;gBACxB,IAAI,EAAE,MAAM,GAAG,GAAG,EAAE,OAAO,EAAE,MAAM;CAGhD;AAED,qBAAa,UAAW,SAAQ,OAAO;gBACzB,IAAI,EAAE,MAAM,GAAG,GAAG,EAAE,OAAO,EAAE,MAAM;CAGhD;AAED,qBAAa,WAAY,SAAQ,OAAO;IACtC,SAAkB,KAAK,CAAC,EAAE,OAAO,CAAC;gBAEtB,IAAI,EAAE,MAAM,GAAG,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;CAIjE;AAED,qBAAa,OAAQ,SAAQ,OAAO;IAClC,SAAkB,KAAK,CAAC,EAAE,OAAO,CAAC;gBAEtB,IAAI,EAAE,MAAM,GAAG,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;CAIjE;AAED,eAAO,MAAM,QAAQ;;;;;;;;;;CAUX,CAAC;AAEX,iBAAS,kBAAkB,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,GAAG,GAAG,OAAO,CAmBvE;AAED,OAAO,EAAE,kBAAkB,EAAE,CAAC"}
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Structured error types for file-system operations.
3
+ *
4
+ * @example
5
+ * ```ts
6
+ * import { readTextFile, FSError, FSErrors } from "@anabranch/fs";
7
+ *
8
+ * const result = await readTextFile("./config.json").run();
9
+ * if (result.type === "error") {
10
+ * const err = result.error;
11
+ * if (err instanceof FSError) {
12
+ * console.error(`${err.kind}: ${err.path} - ${err.message}`);
13
+ * }
14
+ * }
15
+ * ```
16
+ */
17
+ export class FSError extends Error {
18
+ constructor(kind, path, message) {
19
+ super(message);
20
+ Object.defineProperty(this, "kind", {
21
+ enumerable: true,
22
+ configurable: true,
23
+ writable: true,
24
+ value: void 0
25
+ });
26
+ Object.defineProperty(this, "path", {
27
+ enumerable: true,
28
+ configurable: true,
29
+ writable: true,
30
+ value: void 0
31
+ });
32
+ this.kind = kind;
33
+ this.path = path;
34
+ }
35
+ }
36
+ export class NotFound extends FSError {
37
+ constructor(path, message) {
38
+ super("NotFound", path, message);
39
+ }
40
+ }
41
+ export class AlreadyExists extends FSError {
42
+ constructor(path, message) {
43
+ super("AlreadyExists", path, message);
44
+ }
45
+ }
46
+ export class IsDirectory extends FSError {
47
+ constructor(path, message) {
48
+ super("IsDirectory", path, message);
49
+ }
50
+ }
51
+ export class NotDirectory extends FSError {
52
+ constructor(path, message) {
53
+ super("NotDirectory", path, message);
54
+ }
55
+ }
56
+ export class PermissionDenied extends FSError {
57
+ constructor(path, message) {
58
+ super("PermissionDenied", path, message);
59
+ }
60
+ }
61
+ export class ReadError extends FSError {
62
+ constructor(path, message) {
63
+ super("ReadError", path, message);
64
+ }
65
+ }
66
+ export class WriteError extends FSError {
67
+ constructor(path, message) {
68
+ super("WriteError", path, message);
69
+ }
70
+ }
71
+ export class InvalidData extends FSError {
72
+ constructor(path, message, cause) {
73
+ super("InvalidData", path, message);
74
+ Object.defineProperty(this, "cause", {
75
+ enumerable: true,
76
+ configurable: true,
77
+ writable: true,
78
+ value: void 0
79
+ });
80
+ this.cause = cause;
81
+ }
82
+ }
83
+ export class Unknown extends FSError {
84
+ constructor(path, message, cause) {
85
+ super("Unknown", path, message);
86
+ Object.defineProperty(this, "cause", {
87
+ enumerable: true,
88
+ configurable: true,
89
+ writable: true,
90
+ value: void 0
91
+ });
92
+ this.cause = cause;
93
+ }
94
+ }
95
+ export const FSErrors = {
96
+ NotFound,
97
+ AlreadyExists,
98
+ IsDirectory,
99
+ NotDirectory,
100
+ PermissionDenied,
101
+ ReadError,
102
+ WriteError,
103
+ InvalidData,
104
+ Unknown,
105
+ };
106
+ function nodeErrorToFSError(error, path) {
107
+ const message = error instanceof Error ? error.message : String(error);
108
+ const nodeError = error;
109
+ switch (nodeError.code) {
110
+ case "ENOENT":
111
+ return new NotFound(path, message);
112
+ case "EEXIST":
113
+ return new AlreadyExists(path, message);
114
+ case "EISDIR":
115
+ return new IsDirectory(path, message);
116
+ case "ENOTDIR":
117
+ return new NotDirectory(path, message);
118
+ case "EACCES":
119
+ case "EPERM":
120
+ return new PermissionDenied(path, message);
121
+ default:
122
+ return new Unknown(path, message, error);
123
+ }
124
+ }
125
+ export { nodeErrorToFSError };
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Converts a glob pattern string to a `RegExp`.
3
+ *
4
+ * Supported syntax:
5
+ * - `*` — any sequence of non-separator characters
6
+ * - `**` — any sequence of characters including path separators
7
+ * - `?` — any single non-separator character
8
+ * - `{a,b}` — alternatives (may contain nested glob syntax)
9
+ * - `[abc]` — character classes (passed through verbatim)
10
+ *
11
+ * The resulting regex is anchored (`^...$`) and should be tested against
12
+ * forward-slash-normalised relative paths.
13
+ */
14
+ export declare function _matchGlob(pattern: string): RegExp;
15
+ //# sourceMappingURL=glob_match.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"glob_match.d.ts","sourceRoot":"","sources":["../../src/fs/glob_match.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAElD"}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Converts a glob pattern string to a `RegExp`.
3
+ *
4
+ * Supported syntax:
5
+ * - `*` — any sequence of non-separator characters
6
+ * - `**` — any sequence of characters including path separators
7
+ * - `?` — any single non-separator character
8
+ * - `{a,b}` — alternatives (may contain nested glob syntax)
9
+ * - `[abc]` — character classes (passed through verbatim)
10
+ *
11
+ * The resulting regex is anchored (`^...$`) and should be tested against
12
+ * forward-slash-normalised relative paths.
13
+ */
14
+ export function _matchGlob(pattern) {
15
+ return new RegExp(`^${patternToRegexStr(pattern)}$`);
16
+ }
17
+ function patternToRegexStr(pattern) {
18
+ const notSep = "[^/]";
19
+ let regex = "";
20
+ let i = 0;
21
+ while (i < pattern.length) {
22
+ const ch = pattern[i];
23
+ if (ch === "*") {
24
+ if (i + 1 < pattern.length && pattern[i + 1] === "*") {
25
+ regex += ".*";
26
+ i += 2;
27
+ // consume the separator that follows **, e.g. "**/"
28
+ if (i < pattern.length && (pattern[i] === "/" || pattern[i] === "\\")) {
29
+ i++;
30
+ }
31
+ }
32
+ else {
33
+ regex += `${notSep}*`;
34
+ i++;
35
+ }
36
+ }
37
+ else if (ch === "?") {
38
+ regex += notSep;
39
+ i++;
40
+ }
41
+ else if (ch === "{") {
42
+ const end = pattern.indexOf("}", i + 1);
43
+ if (end === -1) {
44
+ regex += "\\{";
45
+ i++;
46
+ }
47
+ else {
48
+ const alternatives = pattern.slice(i + 1, end).split(",");
49
+ regex += `(?:${alternatives.map(patternToRegexStr).join("|")})`;
50
+ i = end + 1;
51
+ }
52
+ }
53
+ else if (ch === "[") {
54
+ const end = pattern.indexOf("]", i + 1);
55
+ if (end === -1) {
56
+ regex += "\\[";
57
+ i++;
58
+ }
59
+ else {
60
+ regex += pattern.slice(i, end + 1);
61
+ i = end + 1;
62
+ }
63
+ }
64
+ else {
65
+ regex += escapeRegexChar(ch);
66
+ i++;
67
+ }
68
+ }
69
+ return regex;
70
+ }
71
+ function escapeRegexChar(ch) {
72
+ return /[.*+?^${}()|[\]\\]/.test(ch) ? `\\${ch}` : ch;
73
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Streaming file-system utilities for the anabranch ecosystem.
3
+ *
4
+ * Multi-value operations return a {@link Source} so results can be streamed,
5
+ * filtered, and transformed with the full anabranch API. Single-value
6
+ * operations return a {@link Task} for composable error handling.
7
+ *
8
+ * @example Read all lines from a file
9
+ * ```ts
10
+ * import { readLines } from "@anabranch/fs";
11
+ *
12
+ * const { successes } = await readLines("./data.txt").partition();
13
+ * console.log(successes.join("\n"));
14
+ * ```
15
+ *
16
+ * @example Walk a directory and find TypeScript files
17
+ * ```ts
18
+ * import { glob } from "@anabranch/fs";
19
+ *
20
+ * const results = await glob("./src", "*.ts").collect();
21
+ * console.log(results.map((e) => e.path));
22
+ * ```
23
+ *
24
+ * @module
25
+ */
26
+ import "../_dnt.polyfills.js";
27
+ export { readFile, readJson, readLines, readTextFile } from "./read.js";
28
+ export type { ReadFileError, ReadJsonError } from "./read.js";
29
+ export { writeFile, writeJson, writeTextFile } from "./write.js";
30
+ export type { WriteFileError } from "./write.js";
31
+ export { glob, readDir, walk } from "./dir.js";
32
+ export type { DirError } from "./dir.js";
33
+ export { watch } from "./watch.js";
34
+ export type { WatchError } from "./watch.js";
35
+ export type { DirEntry, FsEvent, GlobOptions, WalkEntry, WalkOptions, WatchOptions, } from "./types.js";
36
+ export { FSError, FSErrors } from "./errors.js";
37
+ export type { AlreadyExists, InvalidData, IsDirectory, NotDirectory, NotFound, PermissionDenied, ReadError, Unknown, WriteError, } from "./errors.js";
38
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/fs/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,sBAAsB,CAAC;AAE9B,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACxE,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC9D,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACjE,YAAY,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAC/C,YAAY,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7C,YAAY,EACV,QAAQ,EACR,OAAO,EACP,WAAW,EACX,SAAS,EACT,WAAW,EACX,YAAY,GACb,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAChD,YAAY,EACV,aAAa,EACb,WAAW,EACX,WAAW,EACX,YAAY,EACZ,QAAQ,EACR,gBAAgB,EAChB,SAAS,EACT,OAAO,EACP,UAAU,GACX,MAAM,aAAa,CAAC"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Streaming file-system utilities for the anabranch ecosystem.
3
+ *
4
+ * Multi-value operations return a {@link Source} so results can be streamed,
5
+ * filtered, and transformed with the full anabranch API. Single-value
6
+ * operations return a {@link Task} for composable error handling.
7
+ *
8
+ * @example Read all lines from a file
9
+ * ```ts
10
+ * import { readLines } from "@anabranch/fs";
11
+ *
12
+ * const { successes } = await readLines("./data.txt").partition();
13
+ * console.log(successes.join("\n"));
14
+ * ```
15
+ *
16
+ * @example Walk a directory and find TypeScript files
17
+ * ```ts
18
+ * import { glob } from "@anabranch/fs";
19
+ *
20
+ * const results = await glob("./src", "*.ts").collect();
21
+ * console.log(results.map((e) => e.path));
22
+ * ```
23
+ *
24
+ * @module
25
+ */
26
+ import "../_dnt.polyfills.js";
27
+ export { readFile, readJson, readLines, readTextFile } from "./read.js";
28
+ export { writeFile, writeJson, writeTextFile } from "./write.js";
29
+ export { glob, readDir, walk } from "./dir.js";
30
+ export { watch } from "./watch.js";
31
+ export { FSError, FSErrors } from "./errors.js";
@@ -0,0 +1,22 @@
1
+ import { Source, Task } from "../anabranch/index.js";
2
+ import { FSError, FSErrors, type NotFound, type PermissionDenied, type ReadError, type Unknown } from "./errors.js";
3
+ export { FSError, FSErrors };
4
+ /**
5
+ * Streams lines from a text file one at a time using `node:readline`.
6
+ */
7
+ export declare function readLines(path: string | URL): Source<string, ReadFileError>;
8
+ /**
9
+ * Reads an entire file as a UTF-8 string.
10
+ */
11
+ export declare function readTextFile(path: string | URL): Task<string, ReadFileError>;
12
+ /**
13
+ * Reads an entire file as a `Uint8Array`.
14
+ */
15
+ export declare function readFile(path: string | URL): Task<Uint8Array, ReadFileError>;
16
+ /**
17
+ * Reads a JSON file and parses it, returning the value typed as `T`.
18
+ */
19
+ export declare function readJson<T>(path: string | URL): Task<T, ReadJsonError>;
20
+ export type ReadFileError = NotFound | PermissionDenied | ReadError | Unknown;
21
+ export type ReadJsonError = NotFound | PermissionDenied | ReadError | InstanceType<typeof FSErrors.InvalidData> | Unknown;
22
+ //# sourceMappingURL=read.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"read.d.ts","sourceRoot":"","sources":["../../src/fs/read.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EACL,OAAO,EACP,QAAQ,EAER,KAAK,QAAQ,EACb,KAAK,gBAAgB,EACrB,KAAK,SAAS,EACd,KAAK,OAAO,EACb,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;AAE7B;;GAEG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAe3E;AAED;;GAEG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,MAAM,GAAG,GAAG,GACjB,IAAI,CAAC,MAAM,EAAE,aAAa,CAAC,CAQ7B;AAED;;GAEG;AACH,wBAAgB,QAAQ,CACtB,IAAI,EAAE,MAAM,GAAG,GAAG,GACjB,IAAI,CAAC,UAAU,EAAE,aAAa,CAAC,CASjC;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,CAAC,EAAE,aAAa,CAAC,CAkBtE;AAED,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,gBAAgB,GAAG,SAAS,GAAG,OAAO,CAAC;AAE9E,MAAM,MAAM,aAAa,GACrB,QAAQ,GACR,gBAAgB,GAChB,SAAS,GACT,YAAY,CAAC,OAAO,QAAQ,CAAC,WAAW,CAAC,GACzC,OAAO,CAAC"}
package/esm/fs/read.js ADDED
@@ -0,0 +1,75 @@
1
+ import { createReadStream } from "node:fs";
2
+ import { readFile as fsReadFile } from "node:fs/promises";
3
+ import { createInterface } from "node:readline";
4
+ import { Source, Task } from "../anabranch/index.js";
5
+ import { FSError, FSErrors, nodeErrorToFSError, } from "./errors.js";
6
+ export { FSError, FSErrors };
7
+ /**
8
+ * Streams lines from a text file one at a time using `node:readline`.
9
+ */
10
+ export function readLines(path) {
11
+ return Source.from(async function* () {
12
+ const stream = createReadStream(path);
13
+ const rl = createInterface({ input: stream, crlfDelay: Infinity });
14
+ try {
15
+ for await (const line of rl) {
16
+ yield line;
17
+ }
18
+ }
19
+ catch (error) {
20
+ throw nodeErrorToFSError(error, path);
21
+ }
22
+ finally {
23
+ rl.close();
24
+ stream.destroy();
25
+ }
26
+ });
27
+ }
28
+ /**
29
+ * Reads an entire file as a UTF-8 string.
30
+ */
31
+ export function readTextFile(path) {
32
+ return Task.of(async () => {
33
+ try {
34
+ return await fsReadFile(path, "utf8");
35
+ }
36
+ catch (error) {
37
+ throw nodeErrorToFSError(error, path);
38
+ }
39
+ });
40
+ }
41
+ /**
42
+ * Reads an entire file as a `Uint8Array`.
43
+ */
44
+ export function readFile(path) {
45
+ return Task.of(async () => {
46
+ try {
47
+ const buf = await fsReadFile(path);
48
+ return new Uint8Array(buf);
49
+ }
50
+ catch (error) {
51
+ throw nodeErrorToFSError(error, path);
52
+ }
53
+ });
54
+ }
55
+ /**
56
+ * Reads a JSON file and parses it, returning the value typed as `T`.
57
+ */
58
+ export function readJson(path) {
59
+ return Task.of(async () => {
60
+ try {
61
+ const text = await fsReadFile(path, "utf8");
62
+ try {
63
+ return JSON.parse(text);
64
+ }
65
+ catch (error) {
66
+ throw new FSErrors.InvalidData(path, error.message, error);
67
+ }
68
+ }
69
+ catch (error) {
70
+ if (error instanceof FSError)
71
+ throw error;
72
+ throw nodeErrorToFSError(error, path);
73
+ }
74
+ });
75
+ }
@@ -0,0 +1,67 @@
1
+ /** An entry in a directory listing. */
2
+ export interface DirEntry {
3
+ /** The entry's file name (not the full path). */
4
+ name: string;
5
+ /** Whether the entry is a regular file. */
6
+ isFile: boolean;
7
+ /** Whether the entry is a directory. */
8
+ isDirectory: boolean;
9
+ /** Whether the entry is a symbolic link. */
10
+ isSymlink: boolean;
11
+ }
12
+ /** A directory entry with its full path, as yielded by {@link walk} and {@link glob}. */
13
+ export interface WalkEntry extends DirEntry {
14
+ /** The absolute path to the entry. */
15
+ path: string;
16
+ }
17
+ /** Options for {@link walk}. */
18
+ export interface WalkOptions {
19
+ /**
20
+ * Maximum recursion depth. `0` means only immediate children of root.
21
+ * @default Infinity
22
+ */
23
+ maxDepth?: number;
24
+ /**
25
+ * Whether to include regular files in results.
26
+ * @default true
27
+ */
28
+ includeFiles?: boolean;
29
+ /**
30
+ * Whether to include directories in results.
31
+ * @default true
32
+ */
33
+ includeDirs?: boolean;
34
+ /**
35
+ * Whether to include symbolic links in results.
36
+ * @default true
37
+ */
38
+ includeSymlinks?: boolean;
39
+ /**
40
+ * Only yield entries whose path (relative to root) matches at least one pattern.
41
+ * When omitted all entries are included.
42
+ */
43
+ match?: RegExp[];
44
+ /**
45
+ * Skip entries whose path (relative to root) matches any of these patterns.
46
+ */
47
+ skip?: RegExp[];
48
+ }
49
+ /** Options for {@link glob}. Same as {@link WalkOptions} without `match`. */
50
+ export interface GlobOptions extends Omit<WalkOptions, "match"> {
51
+ }
52
+ /** A file-system change event, as yielded by {@link watch}. */
53
+ export interface FsEvent {
54
+ /** The type of change. */
55
+ kind: "create" | "modify" | "remove";
56
+ /** Absolute paths of the affected files. */
57
+ paths: string[];
58
+ }
59
+ /** Options for {@link watch}. */
60
+ export interface WatchOptions {
61
+ /**
62
+ * Whether to watch subdirectories recursively.
63
+ * @default true
64
+ */
65
+ recursive?: boolean;
66
+ }
67
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/fs/types.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,MAAM,WAAW,QAAQ;IACvB,iDAAiD;IACjD,IAAI,EAAE,MAAM,CAAC;IACb,2CAA2C;IAC3C,MAAM,EAAE,OAAO,CAAC;IAChB,wCAAwC;IACxC,WAAW,EAAE,OAAO,CAAC;IACrB,4CAA4C;IAC5C,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,yFAAyF;AACzF,MAAM,WAAW,SAAU,SAAQ,QAAQ;IACzC,sCAAsC;IACtC,IAAI,EAAE,MAAM,CAAC;CACd;AAED,gCAAgC;AAChC,MAAM,WAAW,WAAW;IAC1B;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;OAGG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,6EAA6E;AAC7E,MAAM,WAAW,WAAY,SAAQ,IAAI,CAAC,WAAW,EAAE,OAAO,CAAC;CAAG;AAElE,+DAA+D;AAC/D,MAAM,WAAW,OAAO;IACtB,0BAA0B;IAC1B,IAAI,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;IACrC,4CAA4C;IAC5C,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,iCAAiC;AACjC,MAAM,WAAW,YAAY;IAC3B;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,17 @@
1
+ import type { Stream } from "../anabranch/index.js";
2
+ import type { FsEvent, WatchOptions } from "./types.js";
3
+ import { type NotFound, type PermissionDenied } from "./errors.js";
4
+ export type WatchError = NotFound | PermissionDenied;
5
+ /**
6
+ * Watches `path` for file-system changes, yielding a {@link FsEvent} for each.
7
+ *
8
+ * Uses a push-based queue internally so events are never dropped between yields.
9
+ * Cancel the stream to stop watching (e.g. via `take`, breaking `for await`, or
10
+ * returning from the generator).
11
+ *
12
+ * Note: on some platforms `rename` events cannot distinguish creation from
13
+ * deletion without a stat check. This implementation infers the kind by
14
+ * checking whether the file still exists at event time.
15
+ */
16
+ export declare function watch(path: string | URL, options?: WatchOptions): Stream<FsEvent, WatchError>;
17
+ //# sourceMappingURL=watch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watch.d.ts","sourceRoot":"","sources":["../../src/fs/watch.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AACxD,OAAO,EAEL,KAAK,QAAQ,EACb,KAAK,gBAAgB,EACtB,MAAM,aAAa,CAAC;AAErB,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,gBAAgB,CAAC;AAErD;;;;;;;;;;GAUG;AACH,wBAAgB,KAAK,CACnB,IAAI,EAAE,MAAM,GAAG,GAAG,EAClB,OAAO,CAAC,EAAE,YAAY,GACrB,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,CA0B7B"}