@statewalker/webrun-files 0.4.0 → 0.7.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 (39) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +179 -0
  3. package/dist/cjs/index.cjs +90 -497
  4. package/dist/esm/index.js +84 -490
  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 -7
  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 +90 -44
  12. package/dist/types.d.ts.map +1 -1
  13. package/package.json +4 -6
  14. package/src/file-utils.ts +106 -0
  15. package/src/index.ts +33 -26
  16. package/src/{utils/path-utils.ts → path-utils.ts} +16 -2
  17. package/src/types.ts +93 -45
  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/index.d.ts +0 -7
  29. package/dist/utils/index.d.ts.map +0 -1
  30. package/dist/utils/normalize-path.d.ts +0 -21
  31. package/dist/utils/normalize-path.d.ts.map +0 -1
  32. package/dist/utils/path-utils.d.ts.map +0 -1
  33. package/src/files-api.ts +0 -167
  34. package/src/impl/index.ts +0 -6
  35. package/src/impl/mem-files-api.ts +0 -306
  36. package/src/impl/node-files-api.ts +0 -261
  37. package/src/utils/collect-stream.ts +0 -35
  38. package/src/utils/index.ts +0 -7
  39. package/src/utils/normalize-path.ts +0 -34
@@ -1 +0,0 @@
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,16 +0,0 @@
1
- /**
2
- * Stream collection utilities
3
- */
4
- /**
5
- * Collects all values from an async generator into an array.
6
- */
7
- export declare function collectGenerator<T>(generator: AsyncIterable<T>): Promise<T[]>;
8
- /**
9
- * Converts a synchronous iterable to an async iterable.
10
- */
11
- export declare function toAsyncIterable<T>(iterable: Iterable<T>): AsyncIterable<T>;
12
- /**
13
- * Normalizes BinaryStream to AsyncIterable<Uint8Array>.
14
- */
15
- export declare function toBinaryAsyncIterable(data: AsyncIterable<Uint8Array> | Iterable<Uint8Array>): AsyncIterable<Uint8Array>;
16
- //# sourceMappingURL=collect-stream.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"collect-stream.d.ts","sourceRoot":"","sources":["../../src/utils/collect-stream.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,CAAC,EAAE,SAAS,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC,CAMnF;AAED;;GAEG;AACH,wBAAuB,eAAe,CAAC,CAAC,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,CAIjF;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,aAAa,CAAC,UAAU,CAAC,GAAG,QAAQ,CAAC,UAAU,CAAC,GACrD,aAAa,CAAC,UAAU,CAAC,CAK3B"}
@@ -1,7 +0,0 @@
1
- /**
2
- * Utility functions
3
- */
4
- export * from "./collect-stream.js";
5
- export * from "./normalize-path.js";
6
- export * from "./path-utils.js";
7
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
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,21 +0,0 @@
1
- /**
2
- * Path normalization utilities
3
- */
4
- import type { FileRef } from "../types.js";
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;
13
- /**
14
- * Extracts path string from FileRef.
15
- */
16
- export declare function toPath(file: FileRef): string;
17
- /**
18
- * Normalizes FileRef to a path string.
19
- */
20
- export declare function resolveFileRef(file: FileRef): string;
21
- //# sourceMappingURL=normalize-path.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"normalize-path.d.ts","sourceRoot":"","sources":["../../src/utils/normalize-path.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAMtD;AAED;;GAEG;AACH,wBAAgB,MAAM,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAE5C;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAEpD"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"path-utils.d.ts","sourceRoot":"","sources":["../../src/utils/path-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH;;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/src/files-api.ts DELETED
@@ -1,167 +0,0 @@
1
- /**
2
- * FilesApi wrapper class providing convenience methods on top of IFilesApi
3
- */
4
-
5
- import type {
6
- BinaryStream,
7
- CopyOptions,
8
- FileHandle,
9
- FileInfo,
10
- FileRef,
11
- IFilesApi,
12
- ListOptions,
13
- ReadStreamOptions,
14
- } from "./types.js";
15
- import { toBinaryAsyncIterable } from "./utils/collect-stream.js";
16
- import { joinPath, resolveFileRef } from "./utils/index.js";
17
-
18
- const IGNORE_FILE = ".gitkeep";
19
-
20
- export class FilesApi implements IFilesApi {
21
- constructor(private fs: IFilesApi) {}
22
-
23
- // ========================================
24
- // IFilesApi delegation
25
- // ========================================
26
-
27
- list(file: FileRef, options?: ListOptions): AsyncGenerator<FileInfo> {
28
- return this.fs.list(file, options);
29
- }
30
-
31
- stats(file: FileRef): Promise<FileInfo | undefined> {
32
- return this.fs.stats(file);
33
- }
34
-
35
- remove(file: FileRef): Promise<boolean> {
36
- return this.fs.remove(file);
37
- }
38
-
39
- open(file: FileRef): Promise<FileHandle> {
40
- return this.fs.open(file);
41
- }
42
-
43
- // ========================================
44
- // Convenience methods built on core API
45
- // ========================================
46
-
47
- /**
48
- * Checks if a file or directory exists.
49
- */
50
- async exists(file: FileRef): Promise<boolean> {
51
- const info = await this.stats(file);
52
- return info !== undefined;
53
- }
54
-
55
- /**
56
- * Reads file content as a stream.
57
- */
58
- async *read(file: FileRef, options: ReadStreamOptions = {}): AsyncGenerator<Uint8Array> {
59
- const handle = await this.open(file);
60
- try {
61
- yield* handle.createReadStream(options);
62
- } finally {
63
- await handle.close();
64
- }
65
- }
66
-
67
- /**
68
- * Writes content to a file.
69
- */
70
- async write(file: FileRef, content: BinaryStream): Promise<void> {
71
- const handle = await this.open(file);
72
- try {
73
- await handle.createWriteStream(toBinaryAsyncIterable(content));
74
- } finally {
75
- await handle.close();
76
- }
77
- }
78
-
79
- /**
80
- * Reads entire file into a single buffer.
81
- */
82
- async readFile(file: FileRef): Promise<Uint8Array> {
83
- const chunks: Uint8Array[] = [];
84
- let totalLength = 0;
85
-
86
- for await (const chunk of this.read(file)) {
87
- chunks.push(chunk);
88
- totalLength += chunk.length;
89
- }
90
-
91
- if (chunks.length === 0) return new Uint8Array(0);
92
- if (chunks.length === 1) return chunks[0];
93
-
94
- const result = new Uint8Array(totalLength);
95
- let offset = 0;
96
- for (const chunk of chunks) {
97
- result.set(chunk, offset);
98
- offset += chunk.length;
99
- }
100
- return result;
101
- }
102
-
103
- /**
104
- * Copies file or directory.
105
- * Uses native implementation if available, otherwise falls back to read/write.
106
- */
107
- async copy(source: FileRef, target: FileRef, options: CopyOptions = {}): Promise<boolean> {
108
- // Try native implementation first
109
- if (this.fs.copy) {
110
- return this.fs.copy(source, target, options);
111
- }
112
-
113
- // Fallback: read from source, write to target
114
- const sourceInfo = await this.stats(source);
115
- if (!sourceInfo) return false;
116
-
117
- const sourcePath = resolveFileRef(source);
118
- const targetPath = resolveFileRef(target);
119
- const recursive = options.recursive ?? true;
120
-
121
- if (sourceInfo.kind === "directory") {
122
- for await (const entry of this.list(source, { recursive })) {
123
- if (entry.kind === "directory") continue;
124
- const suffix = entry.path.substring(sourcePath.length);
125
- const newTargetPath = targetPath + suffix;
126
- await this.write(newTargetPath, this.read(entry.path));
127
- }
128
- } else {
129
- await this.write(targetPath, this.read(sourcePath));
130
- }
131
-
132
- return true;
133
- }
134
-
135
- /**
136
- * Moves file or directory.
137
- * Uses native implementation if available, otherwise falls back to copy+delete.
138
- */
139
- async move(source: FileRef, target: FileRef): Promise<boolean> {
140
- // Try native implementation first
141
- if (this.fs.move) {
142
- return this.fs.move(source, target);
143
- }
144
-
145
- // Fallback: copy then delete
146
- const copied = await this.copy(source, target);
147
- if (!copied) return false;
148
- await this.remove(source);
149
- return true;
150
- }
151
-
152
- /**
153
- * Creates a directory.
154
- * Uses native implementation if available, otherwise creates a placeholder file.
155
- */
156
- async mkdir(file: FileRef): Promise<void> {
157
- // Try native implementation first
158
- if (this.fs.mkdir) {
159
- return this.fs.mkdir(file);
160
- }
161
-
162
- // Fallback: create a placeholder file to establish the directory
163
- const dirPath = resolveFileRef(file);
164
- const placeholderPath = joinPath(dirPath, IGNORE_FILE);
165
- await this.write(placeholderPath, [new Uint8Array(0)]);
166
- }
167
- }
package/src/impl/index.ts DELETED
@@ -1,6 +0,0 @@
1
- /**
2
- * IFilesApi implementations
3
- */
4
-
5
- export * from "./mem-files-api.js";
6
- export * from "./node-files-api.js";
@@ -1,306 +0,0 @@
1
- /**
2
- * In-memory implementation of IFilesApi for testing
3
- */
4
-
5
- import type {
6
- AppendOptions,
7
- BinaryStream,
8
- FileHandle,
9
- FileInfo,
10
- FileRef,
11
- IFilesApi,
12
- ListOptions,
13
- ReadStreamOptions,
14
- WriteStreamOptions,
15
- } from "../types.js";
16
- import { toBinaryAsyncIterable } from "../utils/collect-stream.js";
17
- import { basename, joinPath, resolveFileRef } from "../utils/index.js";
18
-
19
- interface FileEntry {
20
- kind: "file";
21
- content: Uint8Array;
22
- lastModified: number;
23
- }
24
-
25
- interface DirEntry {
26
- kind: "directory";
27
- lastModified: number;
28
- }
29
-
30
- type Entry = FileEntry | DirEntry;
31
-
32
- class MemFileHandle implements FileHandle {
33
- constructor(
34
- private store: Map<string, Entry>,
35
- private path: string,
36
- ) {}
37
-
38
- get size(): number {
39
- const entry = this.store.get(this.path);
40
- if (entry?.kind === "file") {
41
- return entry.content.length;
42
- }
43
- return 0;
44
- }
45
-
46
- private getOrCreateFile(): FileEntry {
47
- let entry = this.store.get(this.path);
48
- if (!entry || entry.kind !== "file") {
49
- entry = {
50
- kind: "file",
51
- content: new Uint8Array(0),
52
- lastModified: Date.now(),
53
- };
54
- this.store.set(this.path, entry);
55
- }
56
- return entry;
57
- }
58
-
59
- async close(): Promise<void> {
60
- // No-op for memory implementation
61
- }
62
-
63
- async appendFile(data: BinaryStream, options: AppendOptions = {}): Promise<number> {
64
- const file = this.getOrCreateFile();
65
- const chunks: Uint8Array[] = [file.content];
66
- let bytesWritten = 0;
67
-
68
- for await (const chunk of toBinaryAsyncIterable(data)) {
69
- if (options.signal?.aborted) {
70
- throw new Error("Operation aborted");
71
- }
72
- chunks.push(chunk);
73
- bytesWritten += chunk.length;
74
- }
75
-
76
- // Merge all chunks
77
- const totalLength = chunks.reduce((sum, c) => sum + c.length, 0);
78
- const merged = new Uint8Array(totalLength);
79
- let offset = 0;
80
- for (const chunk of chunks) {
81
- merged.set(chunk, offset);
82
- offset += chunk.length;
83
- }
84
-
85
- file.content = merged;
86
- file.lastModified = Date.now();
87
-
88
- return bytesWritten;
89
- }
90
-
91
- async *createReadStream(options: ReadStreamOptions = {}): AsyncGenerator<Uint8Array> {
92
- const entry = this.store.get(this.path);
93
- if (!entry || entry.kind !== "file") return;
94
-
95
- const { start = 0, end = Infinity, signal } = options;
96
- const bufferSize = 8192;
97
- const content = entry.content;
98
- const actualEnd = Math.min(end, content.length);
99
-
100
- let position = start;
101
-
102
- while (position < actualEnd) {
103
- if (signal?.aborted) {
104
- throw new Error("Operation aborted");
105
- }
106
-
107
- const remaining = actualEnd - position;
108
- const toRead = Math.min(bufferSize, remaining);
109
-
110
- yield content.subarray(position, position + toRead);
111
- position += toRead;
112
- }
113
- }
114
-
115
- async createWriteStream(data: BinaryStream, options: WriteStreamOptions = {}): Promise<number> {
116
- const { start = 0, signal } = options;
117
- const file = this.getOrCreateFile();
118
-
119
- const chunks: Uint8Array[] = [];
120
- let bytesWritten = 0;
121
-
122
- // Keep content before start position
123
- if (start > 0 && file.content.length > 0) {
124
- chunks.push(file.content.subarray(0, Math.min(start, file.content.length)));
125
- }
126
-
127
- // Pad with zeros if start is beyond current content
128
- if (start > file.content.length) {
129
- chunks.push(new Uint8Array(start - file.content.length));
130
- }
131
-
132
- for await (const chunk of toBinaryAsyncIterable(data)) {
133
- if (signal?.aborted) {
134
- throw new Error("Operation aborted");
135
- }
136
- chunks.push(chunk);
137
- bytesWritten += chunk.length;
138
- }
139
-
140
- // Merge all chunks
141
- const totalLength = chunks.reduce((sum, c) => sum + c.length, 0);
142
- const merged = new Uint8Array(totalLength);
143
- let offset = 0;
144
- for (const chunk of chunks) {
145
- merged.set(chunk, offset);
146
- offset += chunk.length;
147
- }
148
-
149
- file.content = merged;
150
- file.lastModified = Date.now();
151
-
152
- return bytesWritten;
153
- }
154
- }
155
-
156
- export class MemFilesApi implements IFilesApi {
157
- private store = new Map<string, Entry>();
158
-
159
- async *list(file: FileRef, options: ListOptions = {}): AsyncGenerator<FileInfo> {
160
- const normalized = resolveFileRef(file);
161
- const prefix = normalized === "/" ? "/" : `${normalized}/`;
162
- const { recursive = false } = options;
163
-
164
- const seen = new Set<string>();
165
-
166
- for (const [path, entry] of this.store.entries()) {
167
- if (!path.startsWith(prefix)) continue;
168
-
169
- const relativePath = path.substring(prefix.length);
170
- const segments = relativePath.split("/");
171
-
172
- if (segments.length === 0 || segments[0] === "") continue;
173
-
174
- if (segments.length === 1) {
175
- // Direct child file or directory
176
- if (seen.has(path)) continue;
177
- seen.add(path);
178
-
179
- const info: FileInfo = {
180
- kind: entry.kind,
181
- name: basename(path),
182
- path,
183
- lastModified: entry.lastModified,
184
- };
185
-
186
- if (entry.kind === "file") {
187
- info.size = entry.content.length;
188
- }
189
-
190
- yield info;
191
- } else {
192
- // This is a nested path
193
- if (recursive) {
194
- // Yield all nested entries
195
- if (seen.has(path)) continue;
196
- seen.add(path);
197
-
198
- const info: FileInfo = {
199
- kind: entry.kind,
200
- name: basename(path),
201
- path,
202
- lastModified: entry.lastModified,
203
- };
204
-
205
- if (entry.kind === "file") {
206
- info.size = entry.content.length;
207
- }
208
-
209
- yield info;
210
- } else {
211
- // Non-recursive: yield the intermediate directory
212
- const dirName = segments[0];
213
- const dirPath = joinPath(normalized, dirName);
214
- if (!seen.has(dirPath)) {
215
- seen.add(dirPath);
216
- yield {
217
- kind: "directory",
218
- name: dirName,
219
- path: dirPath,
220
- lastModified: 0,
221
- };
222
- }
223
- }
224
- }
225
- }
226
- }
227
-
228
- async stats(file: FileRef): Promise<FileInfo | undefined> {
229
- const normalized = resolveFileRef(file);
230
-
231
- const entry = this.store.get(normalized);
232
- if (entry) {
233
- const info: FileInfo = {
234
- kind: entry.kind,
235
- name: basename(normalized),
236
- path: normalized,
237
- lastModified: entry.lastModified,
238
- };
239
- if (entry.kind === "file") {
240
- info.size = entry.content.length;
241
- }
242
- return info;
243
- }
244
-
245
- // Check if it's an implicit directory (root always exists)
246
- if (normalized === "/") {
247
- return {
248
- kind: "directory",
249
- name: "",
250
- path: "/",
251
- lastModified: 0,
252
- };
253
- }
254
-
255
- const prefix = `${normalized}/`;
256
- for (const path of this.store.keys()) {
257
- if (path.startsWith(prefix)) {
258
- return {
259
- kind: "directory",
260
- name: basename(normalized),
261
- path: normalized,
262
- lastModified: 0,
263
- };
264
- }
265
- }
266
-
267
- return undefined;
268
- }
269
-
270
- async remove(file: FileRef): Promise<boolean> {
271
- const normalized = resolveFileRef(file);
272
- const prefix = `${normalized}/`;
273
-
274
- let removed = false;
275
-
276
- // Remove exact match
277
- if (this.store.delete(normalized)) {
278
- removed = true;
279
- }
280
-
281
- // Remove all children (for directories)
282
- for (const path of [...this.store.keys()]) {
283
- if (path.startsWith(prefix)) {
284
- this.store.delete(path);
285
- removed = true;
286
- }
287
- }
288
-
289
- return removed;
290
- }
291
-
292
- async open(file: FileRef): Promise<FileHandle> {
293
- const normalized = resolveFileRef(file);
294
- return new MemFileHandle(this.store, normalized);
295
- }
296
-
297
- async mkdir(file: FileRef): Promise<void> {
298
- const normalized = resolveFileRef(file);
299
- if (!this.store.has(normalized)) {
300
- this.store.set(normalized, {
301
- kind: "directory",
302
- lastModified: Date.now(),
303
- });
304
- }
305
- }
306
- }