@statewalker/webrun-files 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,199 @@
1
+ # @statewalker/webrun-files
2
+
3
+ The core filesystem API for cross-platform file operations. This package provides a clean, minimal interface for reading, writing, and managing files that works consistently whether you're running in Node.js or need an in-memory implementation for browsers and testing.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @statewalker/webrun-files
9
+ # or
10
+ pnpm add @statewalker/webrun-files
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ The fastest way to get going is to create a `FilesApi` instance with one of the built-in backends. Here's an example using the in-memory implementation:
16
+
17
+ ```typescript
18
+ import { FilesApi, MemFilesApi } from '@statewalker/webrun-files';
19
+
20
+ const files = new FilesApi(new MemFilesApi());
21
+
22
+ // Write a file
23
+ await files.write('/documents/notes.txt', [
24
+ new TextEncoder().encode('Remember to water the plants')
25
+ ]);
26
+
27
+ // Read it back
28
+ const content = await files.readFile('/documents/notes.txt');
29
+ console.log(new TextDecoder().decode(content));
30
+ ```
31
+
32
+ For Node.js applications that need to work with the actual filesystem, switch to `NodeFilesApi`:
33
+
34
+ ```typescript
35
+ import { FilesApi, NodeFilesApi } from '@statewalker/webrun-files';
36
+
37
+ const files = new FilesApi(new NodeFilesApi('/path/to/root'));
38
+ ```
39
+
40
+ The `NodeFilesApi` constructor accepts an optional root directory path. When provided, all file operations will be relative to this directory, effectively sandboxing the API.
41
+
42
+ ## Working with Files
43
+
44
+ The `FilesApi` class provides everything you need for common file operations. Writing content is straightforward—just pass an array of `Uint8Array` chunks:
45
+
46
+ ```typescript
47
+ const text = new TextEncoder().encode('Hello, World!');
48
+ await files.write('/greeting.txt', [text]);
49
+ ```
50
+
51
+ Reading comes in two flavors. Use `readFile` when you want the entire contents as a single buffer:
52
+
53
+ ```typescript
54
+ const content = await files.readFile('/greeting.txt');
55
+ ```
56
+
57
+ For large files, `read` returns an async generator that yields chunks as they come in, keeping memory usage under control:
58
+
59
+ ```typescript
60
+ for await (const chunk of files.read('/large-file.bin')) {
61
+ process.stdout.write(chunk);
62
+ }
63
+ ```
64
+
65
+ You can also read specific portions of a file by specifying start and end positions:
66
+
67
+ ```typescript
68
+ for await (const chunk of files.read('/data.bin', { start: 100, end: 200 })) {
69
+ // Only bytes 100-199
70
+ }
71
+ ```
72
+
73
+ ## Managing Files and Directories
74
+
75
+ Checking whether a file or directory exists is simple:
76
+
77
+ ```typescript
78
+ if (await files.exists('/config.json')) {
79
+ // Load configuration
80
+ }
81
+ ```
82
+
83
+ To get detailed information about a file, use `stats`:
84
+
85
+ ```typescript
86
+ const info = await files.stats('/photo.jpg');
87
+ if (info) {
88
+ console.log(`Size: ${info.size} bytes`);
89
+ console.log(`Modified: ${new Date(info.lastModified)}`);
90
+ console.log(`Type: ${info.kind}`); // "file" or "directory"
91
+ }
92
+ ```
93
+
94
+ Listing directory contents returns an async generator of file info objects:
95
+
96
+ ```typescript
97
+ for await (const entry of files.list('/documents')) {
98
+ console.log(`${entry.name} (${entry.kind})`);
99
+ }
100
+ ```
101
+
102
+ Pass `{ recursive: true }` to traverse subdirectories:
103
+
104
+ ```typescript
105
+ for await (const entry of files.list('/project', { recursive: true })) {
106
+ console.log(entry.path);
107
+ }
108
+ ```
109
+
110
+ Creating directories, copying, moving, and deleting all work as you'd expect:
111
+
112
+ ```typescript
113
+ await files.mkdir('/archive/2024');
114
+ await files.copy('/report.pdf', '/archive/2024/report.pdf');
115
+ await files.move('/temp/draft.txt', '/documents/final.txt');
116
+ await files.remove('/old-stuff'); // Recursively deletes directories
117
+ ```
118
+
119
+ ## Low-Level File Handle Operations
120
+
121
+ When you need fine-grained control over file operations, work directly with file handles. Opening a file gives you a handle that supports reading, writing, and appending at specific positions:
122
+
123
+ ```typescript
124
+ const handle = await files.open('/data.bin');
125
+
126
+ // Get the current file size
127
+ console.log(`File is ${handle.size} bytes`);
128
+
129
+ // Read a specific range
130
+ for await (const chunk of handle.createReadStream({ start: 0, end: 100 })) {
131
+ // First 100 bytes
132
+ }
133
+
134
+ // Write at a specific position
135
+ await handle.createWriteStream([newData], { start: 50 });
136
+
137
+ // Append to the end
138
+ await handle.appendFile([moreData]);
139
+
140
+ // Always close when done
141
+ await handle.close();
142
+ ```
143
+
144
+ ## Building Your Own Backend
145
+
146
+ The `IFilesApi` interface defines the contract that all backends must implement. At minimum, you need four methods:
147
+
148
+ ```typescript
149
+ interface IFilesApi {
150
+ list(file: FileRef, options?: ListOptions): AsyncGenerator<FileInfo>;
151
+ stats(file: FileRef): Promise<FileInfo | undefined>;
152
+ remove(file: FileRef): Promise<boolean>;
153
+ open(file: FileRef): Promise<FileHandle>;
154
+ }
155
+ ```
156
+
157
+ The `FilesApi` wrapper class builds convenience methods like `read`, `write`, `copy`, and `move` on top of these primitives. Backends can optionally implement native versions of `move`, `copy`, and `mkdir` for better performance.
158
+
159
+ Take a look at `MemFilesApi` or `NodeFilesApi` in the source code for reference implementations.
160
+
161
+ ## Type Reference
162
+
163
+ The package exports all the TypeScript types you'll need:
164
+
165
+ ```typescript
166
+ import type {
167
+ IFilesApi, // Core interface for backends
168
+ FilesApi, // Wrapper class with convenience methods
169
+ FileInfo, // Metadata returned by stats() and list()
170
+ FileHandle, // Low-level file operations
171
+ FileRef, // string | { path: string }
172
+ FileKind, // "file" | "directory"
173
+ BinaryStream, // AsyncIterable<Uint8Array> | Iterable<Uint8Array>
174
+ ListOptions, // { recursive?: boolean }
175
+ CopyOptions, // { recursive?: boolean }
176
+ ReadStreamOptions, // { start?: number, end?: number, signal?: AbortSignal }
177
+ WriteStreamOptions, // { start?: number, signal?: AbortSignal }
178
+ } from '@statewalker/webrun-files';
179
+ ```
180
+
181
+ ## Utility Functions
182
+
183
+ The package includes helpful utilities for working with paths and streams:
184
+
185
+ ```typescript
186
+ import {
187
+ normalizePath, // Clean up path strings
188
+ resolveFileRef, // Convert FileRef to normalized path
189
+ joinPath, // Join path segments
190
+ dirname, // Get directory portion of path
191
+ basename, // Get filename portion
192
+ extname, // Get file extension
193
+ collectGenerator, // Collect async iterable into array
194
+ } from '@statewalker/webrun-files';
195
+ ```
196
+
197
+ ## License
198
+
199
+ MIT
@@ -25,6 +25,21 @@ function toBinaryAsyncIterable(data) {
25
25
  return toAsyncIterable(data);
26
26
  }
27
27
 
28
+ //#endregion
29
+ //#region src/utils/file-info-utils.ts
30
+ /**
31
+ * Check if FileInfo represents a directory
32
+ */
33
+ function isDirectory(info) {
34
+ return info?.kind === "directory";
35
+ }
36
+ /**
37
+ * Check if FileInfo represents a file
38
+ */
39
+ function isFile(info) {
40
+ return info?.kind === "file";
41
+ }
42
+
28
43
  //#endregion
29
44
  //#region src/utils/normalize-path.ts
30
45
  /**
@@ -94,7 +109,7 @@ function extname(path) {
94
109
 
95
110
  //#endregion
96
111
  //#region src/files-api.ts
97
- const IGNORE_FILE = ".gitkeep";
112
+ const IGNORE_FILE = ".ignore";
98
113
  var FilesApi = class {
99
114
  constructor(fs) {
100
115
  this.fs = fs;
@@ -283,6 +298,16 @@ var MemFileHandle = class {
283
298
  file.lastModified = Date.now();
284
299
  return bytesWritten;
285
300
  }
301
+ async read(buffer, offset, length, position) {
302
+ const entry = this.store.get(this.path);
303
+ if (!entry || entry.kind !== "file") return 0;
304
+ const content = entry.content;
305
+ const available = content.length - position;
306
+ if (available <= 0) return 0;
307
+ const bytesToRead = Math.min(length, available, buffer.length - offset);
308
+ buffer.set(content.subarray(position, position + bytesToRead), offset);
309
+ return bytesToRead;
310
+ }
286
311
  };
287
312
  var MemFilesApi = class {
288
313
  store = /* @__PURE__ */ new Map();
@@ -447,6 +472,11 @@ var NodeFileHandleWrapper = class {
447
472
  async chown(uid, gid) {
448
473
  await this.handle.chown(uid, gid);
449
474
  }
475
+ async read(buffer, offset, length, position) {
476
+ const actualLength = Math.min(length, buffer.length - offset);
477
+ if (actualLength <= 0) return 0;
478
+ return (await this.handle.read(buffer, offset, actualLength, position)).bytesRead;
479
+ }
450
480
  };
451
481
  var NodeFilesApi = class {
452
482
  fs;
@@ -502,7 +532,10 @@ var NodeFilesApi = class {
502
532
  async remove(file) {
503
533
  const fullPath = this.resolvePath(file);
504
534
  try {
505
- if ((await this.fs.stat(fullPath)).isDirectory()) await this.fs.rm(fullPath, { recursive: true });
535
+ if ((await this.fs.stat(fullPath)).isDirectory()) await this.fs.rm(fullPath, {
536
+ recursive: true,
537
+ force: true
538
+ });
506
539
  else await this.fs.unlink(fullPath);
507
540
  return true;
508
541
  } catch {
@@ -556,6 +589,8 @@ exports.basename = basename;
556
589
  exports.collectGenerator = collectGenerator;
557
590
  exports.dirname = dirname;
558
591
  exports.extname = extname;
592
+ exports.isDirectory = isDirectory;
593
+ exports.isFile = isFile;
559
594
  exports.joinPath = joinPath;
560
595
  exports.normalizePath = normalizePath;
561
596
  exports.resolveFileRef = resolveFileRef;
package/dist/esm/index.js CHANGED
@@ -24,6 +24,21 @@ function toBinaryAsyncIterable(data) {
24
24
  return toAsyncIterable(data);
25
25
  }
26
26
 
27
+ //#endregion
28
+ //#region src/utils/file-info-utils.ts
29
+ /**
30
+ * Check if FileInfo represents a directory
31
+ */
32
+ function isDirectory(info) {
33
+ return info?.kind === "directory";
34
+ }
35
+ /**
36
+ * Check if FileInfo represents a file
37
+ */
38
+ function isFile(info) {
39
+ return info?.kind === "file";
40
+ }
41
+
27
42
  //#endregion
28
43
  //#region src/utils/normalize-path.ts
29
44
  /**
@@ -93,7 +108,7 @@ function extname(path) {
93
108
 
94
109
  //#endregion
95
110
  //#region src/files-api.ts
96
- const IGNORE_FILE = ".gitkeep";
111
+ const IGNORE_FILE = ".ignore";
97
112
  var FilesApi = class {
98
113
  constructor(fs) {
99
114
  this.fs = fs;
@@ -282,6 +297,16 @@ var MemFileHandle = class {
282
297
  file.lastModified = Date.now();
283
298
  return bytesWritten;
284
299
  }
300
+ async read(buffer, offset, length, position) {
301
+ const entry = this.store.get(this.path);
302
+ if (!entry || entry.kind !== "file") return 0;
303
+ const content = entry.content;
304
+ const available = content.length - position;
305
+ if (available <= 0) return 0;
306
+ const bytesToRead = Math.min(length, available, buffer.length - offset);
307
+ buffer.set(content.subarray(position, position + bytesToRead), offset);
308
+ return bytesToRead;
309
+ }
285
310
  };
286
311
  var MemFilesApi = class {
287
312
  store = /* @__PURE__ */ new Map();
@@ -446,6 +471,11 @@ var NodeFileHandleWrapper = class {
446
471
  async chown(uid, gid) {
447
472
  await this.handle.chown(uid, gid);
448
473
  }
474
+ async read(buffer, offset, length, position) {
475
+ const actualLength = Math.min(length, buffer.length - offset);
476
+ if (actualLength <= 0) return 0;
477
+ return (await this.handle.read(buffer, offset, actualLength, position)).bytesRead;
478
+ }
449
479
  };
450
480
  var NodeFilesApi = class {
451
481
  fs;
@@ -501,7 +531,10 @@ var NodeFilesApi = class {
501
531
  async remove(file) {
502
532
  const fullPath = this.resolvePath(file);
503
533
  try {
504
- if ((await this.fs.stat(fullPath)).isDirectory()) await this.fs.rm(fullPath, { recursive: true });
534
+ if ((await this.fs.stat(fullPath)).isDirectory()) await this.fs.rm(fullPath, {
535
+ recursive: true,
536
+ force: true
537
+ });
505
538
  else await this.fs.unlink(fullPath);
506
539
  return true;
507
540
  } catch {
@@ -548,4 +581,4 @@ var NodeFilesApi = class {
548
581
  };
549
582
 
550
583
  //#endregion
551
- export { FilesApi, MemFilesApi, NodeFilesApi, basename, collectGenerator, dirname, extname, joinPath, normalizePath, resolveFileRef, toAsyncIterable, toBinaryAsyncIterable, toPath };
584
+ export { FilesApi, MemFilesApi, NodeFilesApi, basename, collectGenerator, dirname, extname, isDirectory, isFile, joinPath, normalizePath, resolveFileRef, toAsyncIterable, toBinaryAsyncIterable, toPath };
@@ -1 +1 @@
1
- {"version":3,"file":"files-api.d.ts","sourceRoot":"","sources":["../src/files-api.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EACV,YAAY,EACZ,WAAW,EACX,UAAU,EACV,QAAQ,EACR,OAAO,EACP,SAAS,EACT,WAAW,EACX,iBAAiB,EAClB,MAAM,YAAY,CAAC;AAMpB,qBAAa,QAAS,YAAW,SAAS;IAC5B,OAAO,CAAC,EAAE;gBAAF,EAAE,EAAE,SAAS;IAMjC,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,cAAc,CAAC,QAAQ,CAAC;IAIpE,KAAK,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC;IAInD,MAAM,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAIvC,IAAI,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,UAAU,CAAC;IAQxC;;OAEG;IACG,MAAM,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAK7C;;OAEG;IACI,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,GAAE,iBAAsB,GAAG,cAAc,CAAC,UAAU,CAAC;IASvF;;OAEG;IACG,KAAK,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAShE;;OAEG;IACG,QAAQ,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,UAAU,CAAC;IAqBlD;;;OAGG;IACG,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,OAAO,CAAC;IA4BzF;;;OAGG;IACG,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAa9D;;;OAGG;IACG,KAAK,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;CAW1C"}
1
+ {"version":3,"file":"files-api.d.ts","sourceRoot":"","sources":["../src/files-api.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EACV,YAAY,EACZ,WAAW,EACX,UAAU,EACV,QAAQ,EACR,OAAO,EACP,SAAS,EACT,WAAW,EACX,iBAAiB,EAClB,MAAM,YAAY,CAAC;AAOpB,qBAAa,QAAS,YAAW,SAAS;IAC5B,OAAO,CAAC,EAAE;gBAAF,EAAE,EAAE,SAAS;IAMjC,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,cAAc,CAAC,QAAQ,CAAC;IAIpE,KAAK,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC;IAInD,MAAM,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAIvC,IAAI,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,UAAU,CAAC;IAQxC;;OAEG;IACG,MAAM,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAK7C;;OAEG;IACI,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,GAAE,iBAAsB,GAAG,cAAc,CAAC,UAAU,CAAC;IASvF;;OAEG;IACG,KAAK,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAShE;;OAEG;IACG,QAAQ,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,UAAU,CAAC;IAqBlD;;;OAGG;IACG,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,OAAO,CAAC;IA4BzF;;;OAGG;IACG,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAa9D;;;OAGG;IACG,KAAK,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;CAW1C"}
@@ -1 +1 @@
1
- {"version":3,"file":"mem-files-api.d.ts","sourceRoot":"","sources":["../../src/impl/mem-files-api.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAGV,UAAU,EACV,QAAQ,EACR,OAAO,EACP,SAAS,EACT,WAAW,EAGZ,MAAM,aAAa,CAAC;AA6IrB,qBAAa,WAAY,YAAW,SAAS;IAC3C,OAAO,CAAC,KAAK,CAA4B;IAElC,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,GAAE,WAAgB,GAAG,cAAc,CAAC,QAAQ,CAAC;IAqEzE,KAAK,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC;IA0CnD,MAAM,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAsBvC,IAAI,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,UAAU,CAAC;IAKxC,KAAK,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;CAS1C"}
1
+ {"version":3,"file":"mem-files-api.d.ts","sourceRoot":"","sources":["../../src/impl/mem-files-api.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAGV,UAAU,EACV,QAAQ,EACR,OAAO,EACP,SAAS,EACT,WAAW,EAGZ,MAAM,aAAa,CAAC;AA8JrB,qBAAa,WAAY,YAAW,SAAS;IAC3C,OAAO,CAAC,KAAK,CAA4B;IAElC,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,GAAE,WAAgB,GAAG,cAAc,CAAC,QAAQ,CAAC;IAqEzE,KAAK,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC;IA0CnD,MAAM,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAsBvC,IAAI,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,UAAU,CAAC;IAKxC,KAAK,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;CAS1C"}
@@ -1 +1 @@
1
- {"version":3,"file":"node-files-api.d.ts","sourceRoot":"","sources":["../../src/impl/node-files-api.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,KAAK,MAAM,MAAM,kBAAkB,CAAC;AAEhD,OAAO,KAAK,EAGV,WAAW,EACX,UAAU,EACV,QAAQ,EACR,OAAO,EACP,SAAS,EACT,WAAW,EAGZ,MAAM,aAAa,CAAC;AAIrB,UAAU,mBAAmB;IAC3B,EAAE,EAAE,OAAO,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAkGD,qBAAa,YAAa,YAAW,SAAS;IAC5C,OAAO,CAAC,EAAE,CAAgB;IAC1B,OAAO,CAAC,OAAO,CAAS;gBAEZ,OAAO,EAAE,mBAAmB;IAKxC,OAAO,CAAC,WAAW;IAKZ,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,GAAE,WAAgB,GAAG,cAAc,CAAC,QAAQ,CAAC;IAsCzE,KAAK,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC;IAkBnD,MAAM,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAgBvC,IAAI,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,UAAU,CAAC;IAexC,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAgBxD,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,OAAO,CAAC;IAiBnF,KAAK,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;CAI1C"}
1
+ {"version":3,"file":"node-files-api.d.ts","sourceRoot":"","sources":["../../src/impl/node-files-api.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,KAAK,MAAM,MAAM,kBAAkB,CAAC;AAEhD,OAAO,KAAK,EAGV,WAAW,EACX,UAAU,EACV,QAAQ,EACR,OAAO,EACP,SAAS,EACT,WAAW,EAGZ,MAAM,aAAa,CAAC;AAIrB,UAAU,mBAAmB;IAC3B,EAAE,EAAE,OAAO,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AA4GD,qBAAa,YAAa,YAAW,SAAS;IAC5C,OAAO,CAAC,EAAE,CAAgB;IAC1B,OAAO,CAAC,OAAO,CAAS;gBAEZ,OAAO,EAAE,mBAAmB;IAKxC,OAAO,CAAC,WAAW;IAKZ,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,GAAE,WAAgB,GAAG,cAAc,CAAC,QAAQ,CAAC;IAsCzE,KAAK,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC;IAkBnD,MAAM,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAgBvC,IAAI,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,UAAU,CAAC;IAexC,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAgBxD,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,OAAO,CAAC;IAiBnF,KAAK,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;CAI1C"}
package/dist/index.d.ts CHANGED
@@ -7,5 +7,6 @@ export { NodeFilesApi } from "./impl/node-files-api.js";
7
7
  export type { AppendOptions, BinaryStream, CopyOptions, FileHandle, FileInfo, FileKind, FileRef, IFilesApi, ListOptions, ReadStreamOptions, WriteStreamOptions, } from "./types.js";
8
8
  export { collectGenerator, toAsyncIterable, toBinaryAsyncIterable, } from "./utils/collect-stream.js";
9
9
  export { normalizePath, resolveFileRef, toPath, } from "./utils/normalize-path.js";
10
+ export { isDirectory, isFile } from "./utils/file-info-utils.js";
10
11
  export { basename, dirname, extname, joinPath } from "./utils/path-utils.js";
11
12
  //# sourceMappingURL=index.d.ts.map
@@ -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,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;;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"}
package/dist/types.d.ts CHANGED
@@ -38,6 +38,16 @@ export interface FileHandle {
38
38
  appendFile(data: BinaryStream, options?: AppendOptions): Promise<number>;
39
39
  createReadStream(options?: ReadStreamOptions): AsyncGenerator<Uint8Array>;
40
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>;
41
51
  chmod?(mode: number): Promise<void>;
42
52
  chown?(uid: number, gid: number): Promise<void>;
43
53
  }
@@ -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,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,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"}
@@ -0,0 +1,10 @@
1
+ import type { FileInfo } from "../types.js";
2
+ /**
3
+ * Check if FileInfo represents a directory
4
+ */
5
+ export declare function isDirectory(info: FileInfo | undefined): boolean;
6
+ /**
7
+ * Check if FileInfo represents a file
8
+ */
9
+ export declare function isFile(info: FileInfo | undefined): boolean;
10
+ //# sourceMappingURL=file-info-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-info-utils.d.ts","sourceRoot":"","sources":["../../src/utils/file-info-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5C;;GAEG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,QAAQ,GAAG,SAAS,GAAG,OAAO,CAE/D;AAED;;GAEG;AACH,wBAAgB,MAAM,CAAC,IAAI,EAAE,QAAQ,GAAG,SAAS,GAAG,OAAO,CAE1D"}
@@ -2,6 +2,7 @@
2
2
  * Utility functions
3
3
  */
4
4
  export * from "./collect-stream.js";
5
+ export * from "./file-info-utils.js";
5
6
  export * from "./normalize-path.js";
6
7
  export * from "./path-utils.js";
7
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@statewalker/webrun-files",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Basic files API",
@@ -33,7 +33,8 @@
33
33
  "rimraf": "^6.1.2",
34
34
  "rolldown": "^1.0.0-beta.53",
35
35
  "typescript": "^5.6.0",
36
- "vitest": "^4.0.12"
36
+ "vitest": "^4.0.12",
37
+ "@statewalker/webrun-files-tests": "1.0.0"
37
38
  },
38
39
  "sideEffects": false,
39
40
  "publishConfig": {
package/src/files-api.ts CHANGED
@@ -13,9 +13,10 @@ import type {
13
13
  ReadStreamOptions,
14
14
  } from "./types.js";
15
15
  import { toBinaryAsyncIterable } from "./utils/collect-stream.js";
16
- import { joinPath, resolveFileRef } from "./utils/index.js";
16
+ import { resolveFileRef } from "./utils/index.js";
17
+ import { joinPath } from "./utils/path-utils.js";
17
18
 
18
- const IGNORE_FILE = ".gitkeep";
19
+ const IGNORE_FILE = ".ignore";
19
20
 
20
21
  export class FilesApi implements IFilesApi {
21
22
  constructor(private fs: IFilesApi) {}
@@ -151,6 +151,23 @@ class MemFileHandle implements FileHandle {
151
151
 
152
152
  return bytesWritten;
153
153
  }
154
+
155
+ async read(buffer: Uint8Array, offset: number, length: number, position: number): Promise<number> {
156
+ const entry = this.store.get(this.path);
157
+ if (!entry || entry.kind !== "file") {
158
+ return 0;
159
+ }
160
+
161
+ const content = entry.content;
162
+ const available = content.length - position;
163
+ if (available <= 0) {
164
+ return 0;
165
+ }
166
+
167
+ const bytesToRead = Math.min(length, available, buffer.length - offset);
168
+ buffer.set(content.subarray(position, position + bytesToRead), offset);
169
+ return bytesToRead;
170
+ }
154
171
  }
155
172
 
156
173
  export class MemFilesApi implements IFilesApi {
@@ -118,6 +118,16 @@ class NodeFileHandleWrapper implements FileHandle {
118
118
  async chown(uid: number, gid: number): Promise<void> {
119
119
  await this.handle.chown(uid, gid);
120
120
  }
121
+
122
+ async read(buffer: Uint8Array, offset: number, length: number, position: number): Promise<number> {
123
+ // Limit length to what can fit in the buffer at the given offset
124
+ const actualLength = Math.min(length, buffer.length - offset);
125
+ if (actualLength <= 0) {
126
+ return 0;
127
+ }
128
+ const result = await this.handle.read(buffer, offset, actualLength, position);
129
+ return result.bytesRead;
130
+ }
121
131
  }
122
132
 
123
133
  export class NodeFilesApi implements IFilesApi {
@@ -196,7 +206,7 @@ export class NodeFilesApi implements IFilesApi {
196
206
  try {
197
207
  const stat = await this.fs.stat(fullPath);
198
208
  if (stat.isDirectory()) {
199
- await this.fs.rm(fullPath, { recursive: true });
209
+ await this.fs.rm(fullPath, { recursive: true, force: true });
200
210
  } else {
201
211
  await this.fs.unlink(fullPath);
202
212
  }
package/src/index.ts CHANGED
@@ -32,4 +32,5 @@ export {
32
32
  resolveFileRef,
33
33
  toPath,
34
34
  } from "./utils/normalize-path.js";
35
+ export { isDirectory, isFile } from "./utils/file-info-utils.js";
35
36
  export { basename, dirname, extname, joinPath } from "./utils/path-utils.js";
package/src/types.ts CHANGED
@@ -48,6 +48,17 @@ export interface FileHandle {
48
48
  createReadStream(options?: ReadStreamOptions): AsyncGenerator<Uint8Array>;
49
49
  createWriteStream(data: BinaryStream, options?: WriteStreamOptions): Promise<number>;
50
50
 
51
+ /**
52
+ * Read bytes from the file at a specific position.
53
+ *
54
+ * @param buffer - The buffer to read bytes into
55
+ * @param offset - The offset in the buffer to start writing at
56
+ * @param length - The number of bytes to read
57
+ * @param position - The position in the file to start reading from
58
+ * @returns The number of bytes actually read (may be less than length at EOF)
59
+ */
60
+ read(buffer: Uint8Array, offset: number, length: number, position: number): Promise<number>;
61
+
51
62
  chmod?(mode: number): Promise<void>;
52
63
  chown?(uid: number, gid: number): Promise<void>;
53
64
  }
@@ -0,0 +1,15 @@
1
+ import type { FileInfo } from "../types.js";
2
+
3
+ /**
4
+ * Check if FileInfo represents a directory
5
+ */
6
+ export function isDirectory(info: FileInfo | undefined): boolean {
7
+ return info?.kind === "directory";
8
+ }
9
+
10
+ /**
11
+ * Check if FileInfo represents a file
12
+ */
13
+ export function isFile(info: FileInfo | undefined): boolean {
14
+ return info?.kind === "file";
15
+ }
@@ -3,5 +3,6 @@
3
3
  */
4
4
 
5
5
  export * from "./collect-stream.js";
6
+ export * from "./file-info-utils.js";
6
7
  export * from "./normalize-path.js";
7
8
  export * from "./path-utils.js";