@statewalker/webrun-files 0.5.0 → 0.9.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 (42) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +131 -114
  3. package/dist/cjs/index.cjs +85 -529
  4. package/dist/esm/index.js +78 -519
  5. package/dist/file-utils.d.ts +47 -0
  6. package/dist/file-utils.d.ts.map +1 -0
  7. package/dist/index.d.ts +14 -8
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/{utils/path-utils.d.ts → path-utils.d.ts} +10 -1
  10. package/dist/path-utils.d.ts.map +1 -0
  11. package/dist/types.d.ts +130 -51
  12. package/dist/types.d.ts.map +1 -1
  13. package/package.json +7 -10
  14. package/src/file-utils.ts +106 -0
  15. package/src/index.ts +38 -27
  16. package/src/{utils/path-utils.ts → path-utils.ts} +16 -2
  17. package/src/types.ts +136 -53
  18. package/dist/files-api.d.ts +0 -44
  19. package/dist/files-api.d.ts.map +0 -1
  20. package/dist/impl/index.d.ts +0 -6
  21. package/dist/impl/index.d.ts.map +0 -1
  22. package/dist/impl/mem-files-api.d.ts +0 -13
  23. package/dist/impl/mem-files-api.d.ts.map +0 -1
  24. package/dist/impl/node-files-api.d.ts +0 -24
  25. package/dist/impl/node-files-api.d.ts.map +0 -1
  26. package/dist/utils/collect-stream.d.ts +0 -16
  27. package/dist/utils/collect-stream.d.ts.map +0 -1
  28. package/dist/utils/file-info-utils.d.ts +0 -10
  29. package/dist/utils/file-info-utils.d.ts.map +0 -1
  30. package/dist/utils/index.d.ts +0 -8
  31. package/dist/utils/index.d.ts.map +0 -1
  32. package/dist/utils/normalize-path.d.ts +0 -21
  33. package/dist/utils/normalize-path.d.ts.map +0 -1
  34. package/dist/utils/path-utils.d.ts.map +0 -1
  35. package/src/files-api.ts +0 -168
  36. package/src/impl/index.ts +0 -6
  37. package/src/impl/mem-files-api.ts +0 -323
  38. package/src/impl/node-files-api.ts +0 -271
  39. package/src/utils/collect-stream.ts +0 -35
  40. package/src/utils/file-info-utils.ts +0 -15
  41. package/src/utils/index.ts +0 -8
  42. package/src/utils/normalize-path.ts +0 -34
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE1C,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAExD,YAAY,EACV,aAAa,EACb,YAAY,EACZ,WAAW,EACX,UAAU,EACV,QAAQ,EACR,QAAQ,EACR,OAAO,EACP,SAAS,EACT,WAAW,EACX,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,qBAAqB,GACtB,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EACL,aAAa,EACb,cAAc,EACd,MAAM,GACP,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,EACL,MAAM,EACN,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,WAAW,EACX,WAAW,EACX,SAAS,GACV,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,QAAQ,EACR,OAAO,EACP,OAAO,EACP,QAAQ,EACR,aAAa,GACd,MAAM,iBAAiB,CAAC;AAEzB,YAAY,EACV,kBAAkB,EAClB,mBAAmB,EACnB,aAAa,EACb,iBAAiB,EACjB,cAAc,EACd,QAAQ,EACR,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,WAAW,EACX,WAAW,GACZ,MAAM,YAAY,CAAC"}
@@ -1,6 +1,15 @@
1
1
  /**
2
- * Path manipulation utilities
2
+ * Path manipulation utilities for virtual filesystem paths.
3
+ * All paths use forward slashes and start with "/".
3
4
  */
5
+ /**
6
+ * Normalizes a file path to a consistent format.
7
+ * - Adds leading slash if missing
8
+ * - Removes trailing slash
9
+ * - Removes `.` segments
10
+ * - Collapses multiple slashes
11
+ */
12
+ export declare function normalizePath(filePath: string): string;
4
13
  /**
5
14
  * Joins path segments with proper normalization.
6
15
  */
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path-utils.d.ts","sourceRoot":"","sources":["../src/path-utils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAMtD;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,GAAG,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,CAGtD;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAK5C;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAQ3D;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAK5C"}
package/dist/types.d.ts CHANGED
@@ -1,67 +1,146 @@
1
1
  /**
2
- * Type definitions for IFilesApi
2
+ * Core types for FilesApi abstraction
3
3
  */
4
4
  export type FileKind = "file" | "directory";
5
- export interface FileInfo {
6
- kind: FileKind;
7
- name: string;
8
- path: string;
9
- type?: string;
10
- size?: number;
11
- lastModified: number;
5
+ /**
6
+ * Options for reading file content with range support.
7
+ *
8
+ * @example
9
+ * // Read 100 bytes starting at position 50
10
+ * api.read("/file.bin", { start: 50, length: 100 })
11
+ *
12
+ * @example
13
+ * // Read from position 1000 to end of file
14
+ * api.read("/file.bin", { start: 1000 })
15
+ */
16
+ export interface ReadOptions {
17
+ /** Starting byte position (0-indexed). Defaults to 0. */
18
+ start?: number;
19
+ /** Number of bytes to read. If omitted, reads to end of file. */
20
+ length?: number;
21
+ /** AbortSignal for cancellation support. */
22
+ signal?: AbortSignal;
12
23
  }
13
- export type FileRef = string | {
14
- path: string;
15
- };
24
+ /**
25
+ * Options for listing directory contents.
26
+ */
16
27
  export interface ListOptions {
28
+ /** If true, lists all descendants recursively. Defaults to false. */
17
29
  recursive?: boolean;
18
30
  }
19
- export interface CopyOptions {
20
- recursive?: boolean;
31
+ /**
32
+ * Metadata of a regular file.
33
+ *
34
+ * `size` and `lastModified` are REQUIRED. Every storage that can hold a file
35
+ * can say how large it is and when it last changed; an implementation that
36
+ * cannot is claiming less than it must, and the omission must surface as a
37
+ * failure rather than as an `undefined` a consumer has to defend against.
38
+ */
39
+ export interface FileEntryStats {
40
+ kind: "file";
41
+ /** Length in bytes. A zero-byte file is `0`, never `undefined`. */
42
+ size: number;
43
+ /** Modification time as milliseconds since the epoch. */
44
+ lastModified: number;
21
45
  }
22
- export interface AppendOptions {
23
- signal?: AbortSignal;
46
+ /**
47
+ * Metadata of a directory.
48
+ *
49
+ * Deliberately carries nothing else. A directory has no size, and a
50
+ * modification time is available on some storages (memory, a local
51
+ * filesystem) and unavailable on others (an object store, where a directory
52
+ * is a common prefix that exists only as an artefact of key naming). Neither
53
+ * value may be relied upon, so neither is part of the contract: an
54
+ * implementation that happens to know one drops it here instead of leaking a
55
+ * value consumers would have to treat as optional anyway.
56
+ */
57
+ export interface DirectoryEntryStats {
58
+ kind: "directory";
24
59
  }
25
- export interface ReadStreamOptions {
26
- start?: number;
27
- end?: number;
28
- signal?: AbortSignal;
60
+ /**
61
+ * File or directory metadata returned by `stats()`.
62
+ *
63
+ * A DISCRIMINATED UNION on `kind`, not a single shape with optional fields.
64
+ * The optionality this replaces never expressed what a given implementation
65
+ * could report — it expressed a property of the entry kind, collapsed into
66
+ * one type because `kind` was an ordinary field. Narrow on `kind` and the
67
+ * fields that exist for that kind are then known to be present:
68
+ *
69
+ * @example
70
+ * const stats = await api.stats("/some/path");
71
+ * if (stats?.kind === "file") {
72
+ * console.log(stats.size, stats.lastModified); // both `number`
73
+ * }
74
+ */
75
+ export type FileStats = FileEntryStats | DirectoryEntryStats;
76
+ /** Name and path carried by every entry a `list()` yields. */
77
+ export interface FileEntryLocation {
78
+ name: string;
79
+ path: string;
29
80
  }
30
- export interface WriteStreamOptions {
31
- start?: number;
32
- signal?: AbortSignal;
81
+ /** A file yielded by `list()`. */
82
+ export interface FileEntryInfo extends FileEntryStats, FileEntryLocation {
33
83
  }
34
- export type BinaryStream = AsyncIterable<Uint8Array> | Iterable<Uint8Array>;
35
- export interface FileHandle {
36
- readonly size: number;
37
- close(): Promise<void>;
38
- appendFile(data: BinaryStream, options?: AppendOptions): Promise<number>;
39
- createReadStream(options?: ReadStreamOptions): AsyncGenerator<Uint8Array>;
40
- createWriteStream(data: BinaryStream, options?: WriteStreamOptions): Promise<number>;
41
- /**
42
- * Read bytes from the file at a specific position.
43
- *
44
- * @param buffer - The buffer to read bytes into
45
- * @param offset - The offset in the buffer to start writing at
46
- * @param length - The number of bytes to read
47
- * @param position - The position in the file to start reading from
48
- * @returns The number of bytes actually read (may be less than length at EOF)
49
- */
50
- read(buffer: Uint8Array, offset: number, length: number, position: number): Promise<number>;
51
- chmod?(mode: number): Promise<void>;
52
- chown?(uid: number, gid: number): Promise<void>;
84
+ /** A directory yielded by `list()`. */
85
+ export interface DirectoryEntryInfo extends DirectoryEntryStats, FileEntryLocation {
53
86
  }
54
87
  /**
55
- * Core filesystem interface - minimal contract for implementations.
56
- * Platform-specific implementations should implement this interface.
88
+ * File or directory information returned by `list()`.
89
+ *
90
+ * The same union as {@link FileStats}, plus `name` and `path`, so a listing
91
+ * consumer narrows per entry exactly as a `stats()` consumer does.
57
92
  */
58
- export interface IFilesApi {
59
- list(file: FileRef, options?: ListOptions): AsyncGenerator<FileInfo>;
60
- stats(file: FileRef): Promise<FileInfo | undefined>;
61
- remove(file: FileRef): Promise<boolean>;
62
- open(file: FileRef): Promise<FileHandle>;
63
- move?(source: FileRef, target: FileRef): Promise<boolean>;
64
- copy?(source: FileRef, target: FileRef, options?: CopyOptions): Promise<boolean>;
65
- mkdir?(file: FileRef): Promise<void>;
93
+ export type FileInfo = FileEntryInfo | DirectoryEntryInfo;
94
+ /**
95
+ * Cross-platform filesystem abstraction interface.
96
+ *
97
+ * All paths are virtual paths using forward slashes and starting with "/".
98
+ * Implementations handle mapping to underlying storage.
99
+ */
100
+ export interface FilesApi {
101
+ /**
102
+ * Read file content as an async iterable of chunks.
103
+ * Returns empty iterable for non-existent files.
104
+ */
105
+ read(path: string, options?: ReadOptions): AsyncIterable<Uint8Array>;
106
+ /**
107
+ * Write content to a file, creating parent directories as needed.
108
+ * Overwrites existing file content.
109
+ */
110
+ write(path: string, content: Iterable<Uint8Array> | AsyncIterable<Uint8Array>): Promise<void>;
111
+ /**
112
+ * Create a directory and all parent directories.
113
+ * No-op if directory already exists.
114
+ */
115
+ mkdir(path: string): Promise<void>;
116
+ /**
117
+ * List directory contents.
118
+ * Returns empty iterable for non-existent or non-directory paths.
119
+ */
120
+ list(path: string, options?: ListOptions): AsyncIterable<FileInfo>;
121
+ /**
122
+ * Get file or directory metadata.
123
+ * Returns undefined for non-existent paths.
124
+ */
125
+ stats(path: string): Promise<FileStats | undefined>;
126
+ /**
127
+ * Check if a path exists.
128
+ */
129
+ exists(path: string): Promise<boolean>;
130
+ /**
131
+ * Remove a file or directory (recursively).
132
+ * Returns true if something was removed, false if path didn't exist.
133
+ */
134
+ remove(path: string): Promise<boolean>;
135
+ /**
136
+ * Move/rename a file or directory.
137
+ * Returns true on success, false if source doesn't exist.
138
+ */
139
+ move(source: string, target: string): Promise<boolean>;
140
+ /**
141
+ * Copy a file or directory (recursively).
142
+ * Returns true on success, false if source doesn't exist.
143
+ */
144
+ copy(source: string, target: string): Promise<boolean>;
66
145
  }
67
146
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAC;AAE5C,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,QAAQ,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,MAAM,OAAO,GAAG,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAEhD,MAAM,WAAW,WAAW;IAC1B,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,MAAM,YAAY,GAAG,aAAa,CAAC,UAAU,CAAC,GAAG,QAAQ,CAAC,UAAU,CAAC,CAAC;AAE5E,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvB,UAAU,CAAC,IAAI,EAAE,YAAY,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACzE,gBAAgB,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,cAAc,CAAC,UAAU,CAAC,CAAC;IAC1E,iBAAiB,CAAC,IAAI,EAAE,YAAY,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAErF;;;;;;;;OAQG;IACH,IAAI,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAE5F,KAAK,CAAC,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,KAAK,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACjD;AAED;;;GAGG;AACH,MAAM,WAAW,SAAS;IACxB,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,cAAc,CAAC,QAAQ,CAAC,CAAC;IACrE,KAAK,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC,CAAC;IACpD,MAAM,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACxC,IAAI,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAEzC,IAAI,CAAC,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1D,IAAI,CAAC,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACjF,KAAK,CAAC,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACtC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAC;AAE5C;;;;;;;;;;GAUG;AACH,MAAM,WAAW,WAAW;IAC1B,yDAAyD;IACzD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iEAAiE;IACjE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,4CAA4C;IAC5C,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,qEAAqE;IACrE,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,mEAAmE;IACnE,IAAI,EAAE,MAAM,CAAC;IACb,yDAAyD;IACzD,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,WAAW,CAAC;CACnB;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,SAAS,GAAG,cAAc,GAAG,mBAAmB,CAAC;AAE7D,8DAA8D;AAC9D,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,kCAAkC;AAClC,MAAM,WAAW,aAAc,SAAQ,cAAc,EAAE,iBAAiB;CAAG;AAE3E,uCAAuC;AACvC,MAAM,WAAW,kBAAmB,SAAQ,mBAAmB,EAAE,iBAAiB;CAAG;AAErF;;;;;GAKG;AACH,MAAM,MAAM,QAAQ,GAAG,aAAa,GAAG,kBAAkB,CAAC;AAE1D;;;;;GAKG;AACH,MAAM,WAAW,QAAQ;IACvB;;;OAGG;IACH,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,aAAa,CAAC,UAAU,CAAC,CAAC;IAErE;;;OAGG;IACH,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC,UAAU,CAAC,GAAG,aAAa,CAAC,UAAU,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE9F;;;OAGG;IACH,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEnC;;;OAGG;IACH,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;IAEnE;;;OAGG;IACH,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC,CAAC;IAEpD;;OAEG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAEvC;;;OAGG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAEvC;;;OAGG;IACH,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAEvD;;;OAGG;IACH,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACxD"}
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@statewalker/webrun-files",
3
- "version": "0.5.0",
3
+ "version": "0.9.0",
4
4
  "private": false,
5
5
  "type": "module",
6
- "description": "Basic files API",
6
+ "description": "FilesApi interface and utilities for cross-platform filesystem abstraction",
7
7
  "homepage": "https://github.com/statewalker/webrun-files",
8
8
  "author": {
9
9
  "name": "Mikhail Kotelnikov",
@@ -29,12 +29,10 @@
29
29
  "src"
30
30
  ],
31
31
  "devDependencies": {
32
- "@types/node": "^24.10.1",
33
- "rimraf": "^6.1.2",
34
- "rolldown": "^1.0.0-beta.53",
35
- "typescript": "^5.6.0",
36
- "vitest": "^4.0.12",
37
- "@statewalker/webrun-files-tests": "1.0.0"
32
+ "@types/node": "^26.2.0",
33
+ "rimraf": "^6.1.3",
34
+ "rolldown": "^1.2.4",
35
+ "typescript": "^7.0.2"
38
36
  },
39
37
  "sideEffects": false,
40
38
  "publishConfig": {
@@ -42,7 +40,6 @@
42
40
  },
43
41
  "scripts": {
44
42
  "build": "rimraf dist && rolldown -c && tsc --emitDeclarationOnly --declaration",
45
- "test": "vitest run",
46
- "lint": "biome lint src tests"
43
+ "lint": "biome lint src"
47
44
  }
48
45
  }
@@ -0,0 +1,106 @@
1
+ import type { FilesApi } from "./types.js";
2
+
3
+ /**
4
+ * Collect async iterable chunks into a single Uint8Array.
5
+ */
6
+ async function collectChunks(chunks: AsyncIterable<Uint8Array>): Promise<Uint8Array> {
7
+ const parts: Uint8Array[] = [];
8
+ let totalLength = 0;
9
+ for await (const chunk of chunks) {
10
+ parts.push(chunk);
11
+ totalLength += chunk.length;
12
+ }
13
+ if (parts.length === 0) return new Uint8Array(0);
14
+ if (parts.length === 1) return parts[0];
15
+ const result = new Uint8Array(totalLength);
16
+ let offset = 0;
17
+ for (const chunk of parts) {
18
+ result.set(chunk, offset);
19
+ offset += chunk.length;
20
+ }
21
+ return result;
22
+ }
23
+
24
+ /**
25
+ * Read entire file content into a Uint8Array.
26
+ * Returns empty array if file doesn't exist.
27
+ */
28
+ export async function readFile(files: FilesApi, path: string): Promise<Uint8Array> {
29
+ return collectChunks(files.read(path));
30
+ }
31
+
32
+ /**
33
+ * Read entire file content as a UTF-8 string.
34
+ * Returns empty string if file doesn't exist.
35
+ */
36
+ export async function readText(files: FilesApi, path: string): Promise<string> {
37
+ const data = await readFile(files, path);
38
+ return new TextDecoder().decode(data);
39
+ }
40
+
41
+ /**
42
+ * Read file content, returning undefined if file doesn't exist.
43
+ */
44
+ export async function tryReadFile(files: FilesApi, path: string): Promise<Uint8Array | undefined> {
45
+ if (!(await files.exists(path))) return undefined;
46
+ return readFile(files, path);
47
+ }
48
+
49
+ /**
50
+ * Read file content as string, returning undefined if file doesn't exist.
51
+ */
52
+ export async function tryReadText(files: FilesApi, path: string): Promise<string | undefined> {
53
+ const data = await tryReadFile(files, path);
54
+ return data ? new TextDecoder().decode(data) : undefined;
55
+ }
56
+
57
+ /**
58
+ * Read a specific byte range from a file.
59
+ *
60
+ * @param files - FilesApi instance
61
+ * @param path - File path
62
+ * @param position - Starting byte position
63
+ * @param length - Number of bytes to read
64
+ * @returns Uint8Array with requested bytes (may be shorter if EOF reached)
65
+ */
66
+ export async function readRange(
67
+ files: FilesApi,
68
+ path: string,
69
+ position: number,
70
+ length: number,
71
+ ): Promise<Uint8Array> {
72
+ return collectChunks(files.read(path, { start: position, length }));
73
+ }
74
+
75
+ /**
76
+ * Read bytes from a file into a buffer at a specific offset.
77
+ * Similar to Node.js fs.read() signature.
78
+ *
79
+ * @param files - FilesApi instance
80
+ * @param path - File path
81
+ * @param buffer - Target buffer to write into
82
+ * @param bufferOffset - Offset in buffer where to start writing
83
+ * @param length - Number of bytes to read
84
+ * @param position - Position in file where to start reading
85
+ * @returns Number of bytes actually read
86
+ */
87
+ export async function readAt(
88
+ files: FilesApi,
89
+ path: string,
90
+ buffer: Uint8Array,
91
+ bufferOffset: number,
92
+ length: number,
93
+ position: number,
94
+ ): Promise<number> {
95
+ const data = await readRange(files, path, position, length);
96
+ const bytesToCopy = Math.min(data.length, buffer.length - bufferOffset);
97
+ buffer.set(data.subarray(0, bytesToCopy), bufferOffset);
98
+ return bytesToCopy;
99
+ }
100
+
101
+ /**
102
+ * Write a string to a file as UTF-8.
103
+ */
104
+ export async function writeText(files: FilesApi, path: string, content: string): Promise<void> {
105
+ await files.write(path, [new TextEncoder().encode(content)]);
106
+ }
package/src/index.ts CHANGED
@@ -1,36 +1,47 @@
1
1
  /**
2
2
  * webrun-files - Cross-platform filesystem API
3
+ *
4
+ * This package provides:
5
+ * - FilesApi interface for filesystem abstraction
6
+ * - Utility functions for common file operations
7
+ * - Path manipulation utilities
8
+ *
9
+ * For implementations, use:
10
+ * - @statewalker/webrun-files-mem (in-memory)
11
+ * - @statewalker/webrun-files-node (Node.js)
12
+ * - @statewalker/webrun-files-browser (browser)
13
+ * - @statewalker/webrun-files-s3 (S3)
3
14
  */
4
15
 
5
- // Wrapper class
6
- export { FilesApi } from "./files-api.js";
7
- // Implementations
8
- export { MemFilesApi } from "./impl/mem-files-api.js";
9
- export { NodeFilesApi } from "./impl/node-files-api.js";
10
- // Types
16
+ // File utilities
17
+ export {
18
+ readAt,
19
+ readFile,
20
+ readRange,
21
+ readText,
22
+ tryReadFile,
23
+ tryReadText,
24
+ writeText,
25
+ } from "./file-utils.js";
26
+ // Path utilities
27
+ export {
28
+ basename,
29
+ dirname,
30
+ extname,
31
+ joinPath,
32
+ normalizePath,
33
+ } from "./path-utils.js";
34
+ // Core types
11
35
  export type {
12
- AppendOptions,
13
- BinaryStream,
14
- CopyOptions,
15
- FileHandle,
36
+ DirectoryEntryInfo,
37
+ DirectoryEntryStats,
38
+ FileEntryInfo,
39
+ FileEntryLocation,
40
+ FileEntryStats,
16
41
  FileInfo,
17
42
  FileKind,
18
- FileRef,
19
- IFilesApi,
43
+ FileStats,
44
+ FilesApi,
20
45
  ListOptions,
21
- ReadStreamOptions,
22
- WriteStreamOptions,
46
+ ReadOptions,
23
47
  } from "./types.js";
24
- export {
25
- collectGenerator,
26
- toAsyncIterable,
27
- toBinaryAsyncIterable,
28
- } from "./utils/collect-stream.js";
29
- // Utilities
30
- export {
31
- normalizePath,
32
- resolveFileRef,
33
- toPath,
34
- } from "./utils/normalize-path.js";
35
- export { isDirectory, isFile } from "./utils/file-info-utils.js";
36
- export { basename, dirname, extname, joinPath } from "./utils/path-utils.js";
@@ -1,8 +1,22 @@
1
1
  /**
2
- * Path manipulation utilities
2
+ * Path manipulation utilities for virtual filesystem paths.
3
+ * All paths use forward slashes and start with "/".
3
4
  */
4
5
 
5
- import { normalizePath } from "./normalize-path.js";
6
+ /**
7
+ * Normalizes a file path to a consistent format.
8
+ * - Adds leading slash if missing
9
+ * - Removes trailing slash
10
+ * - Removes `.` segments
11
+ * - Collapses multiple slashes
12
+ */
13
+ export function normalizePath(filePath: string): string {
14
+ const segments = filePath.split("/").filter((s) => !!s && s !== ".");
15
+ if (segments.length === 0) {
16
+ return "/";
17
+ }
18
+ return `/${segments.join("/")}`;
19
+ }
6
20
 
7
21
  /**
8
22
  * Joins path segments with proper normalization.