@statewalker/webrun-files 0.3.1 → 0.4.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 (51) hide show
  1. package/dist/cjs/index.cjs +564 -0
  2. package/dist/esm/index.js +551 -0
  3. package/dist/files-api.d.ts +44 -0
  4. package/dist/files-api.d.ts.map +1 -0
  5. package/dist/impl/index.d.ts +6 -0
  6. package/dist/impl/index.d.ts.map +1 -0
  7. package/dist/impl/mem-files-api.d.ts +13 -0
  8. package/dist/impl/mem-files-api.d.ts.map +1 -0
  9. package/dist/impl/node-files-api.d.ts +24 -0
  10. package/dist/impl/node-files-api.d.ts.map +1 -0
  11. package/dist/index.d.ts +11 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/types.d.ts +57 -0
  14. package/dist/types.d.ts.map +1 -0
  15. package/dist/utils/collect-stream.d.ts +16 -0
  16. package/dist/utils/collect-stream.d.ts.map +1 -0
  17. package/dist/utils/index.d.ts +7 -0
  18. package/dist/utils/index.d.ts.map +1 -0
  19. package/dist/utils/normalize-path.d.ts +21 -0
  20. package/dist/utils/normalize-path.d.ts.map +1 -0
  21. package/dist/utils/path-utils.d.ts +20 -0
  22. package/dist/utils/path-utils.d.ts.map +1 -0
  23. package/package.json +29 -31
  24. package/src/files-api.ts +167 -0
  25. package/src/impl/index.ts +6 -0
  26. package/src/impl/mem-files-api.ts +306 -0
  27. package/src/impl/node-files-api.ts +261 -0
  28. package/src/index.ts +35 -0
  29. package/src/types.ts +68 -0
  30. package/src/utils/collect-stream.ts +35 -0
  31. package/src/utils/index.ts +7 -0
  32. package/src/utils/normalize-path.ts +34 -0
  33. package/src/utils/path-utils.ts +46 -0
  34. package/CHANGELOG.md +0 -2
  35. package/README.md +0 -17
  36. package/dist/index-umd.js +0 -1054
  37. package/dist/index-umd.min.js +0 -2
  38. package/dist/index.js +0 -1033
  39. package/dist/index.min.js +0 -2
  40. package/index.js +0 -1
  41. package/src/BrowserFilesApi.js +0 -335
  42. package/src/FilesApi.js +0 -133
  43. package/src/MemFilesApi.js +0 -129
  44. package/src/NodeFilesApi.js +0 -222
  45. package/src/addFilesApiLogger.js +0 -25
  46. package/src/getMimeType.js +0 -19
  47. package/src/index.js +0 -10
  48. package/src/mimeTypes.js +0 -96
  49. package/src/normalizePath.js +0 -8
  50. package/src/notImplemented.js +0 -3
  51. package/src/openBrowserFilesApi.js +0 -67
@@ -0,0 +1,306 @@
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
+ }
@@ -0,0 +1,261 @@
1
+ /**
2
+ * Node.js implementation of IFilesApi
3
+ */
4
+
5
+ import type * as NodeFS from "node:fs/promises";
6
+ import type { FileHandle as NodeFileHandle } from "node:fs/promises";
7
+ import type {
8
+ AppendOptions,
9
+ BinaryStream,
10
+ CopyOptions,
11
+ FileHandle,
12
+ FileInfo,
13
+ FileRef,
14
+ IFilesApi,
15
+ ListOptions,
16
+ ReadStreamOptions,
17
+ WriteStreamOptions,
18
+ } from "../types.js";
19
+ import { toBinaryAsyncIterable } from "../utils/collect-stream.js";
20
+ import { basename, dirname, joinPath, resolveFileRef } from "../utils/index.js";
21
+
22
+ interface NodeFilesApiOptions {
23
+ fs: typeof NodeFS;
24
+ rootDir?: string;
25
+ }
26
+
27
+ /**
28
+ * Wraps Node.js FileHandle to implement our FileHandle interface.
29
+ */
30
+ class NodeFileHandleWrapper implements FileHandle {
31
+ constructor(
32
+ private handle: NodeFileHandle,
33
+ private _size: number,
34
+ ) {}
35
+
36
+ get size(): number {
37
+ return this._size;
38
+ }
39
+
40
+ async close(): Promise<void> {
41
+ await this.handle.close();
42
+ }
43
+
44
+ async appendFile(data: BinaryStream, options: AppendOptions = {}): Promise<number> {
45
+ let bytesWritten = 0;
46
+ const asyncData = toBinaryAsyncIterable(data);
47
+
48
+ for await (const chunk of asyncData) {
49
+ if (options.signal?.aborted) {
50
+ throw new Error("Operation aborted");
51
+ }
52
+ await this.handle.appendFile(chunk);
53
+ bytesWritten += chunk.length;
54
+ }
55
+
56
+ // Update size
57
+ const stat = await this.handle.stat();
58
+ this._size = stat.size;
59
+
60
+ return bytesWritten;
61
+ }
62
+
63
+ async *createReadStream(options: ReadStreamOptions = {}): AsyncGenerator<Uint8Array> {
64
+ const { start = 0, end = Infinity, signal } = options;
65
+ const bufferSize = 8192;
66
+
67
+ let position = start;
68
+ const actualEnd = Math.min(end, this._size);
69
+
70
+ while (position < actualEnd) {
71
+ if (signal?.aborted) {
72
+ throw new Error("Operation aborted");
73
+ }
74
+
75
+ const remaining = actualEnd - position;
76
+ const toRead = Math.min(bufferSize, remaining);
77
+ const buffer = new Uint8Array(toRead);
78
+
79
+ const { bytesRead } = await this.handle.read(buffer, 0, toRead, position);
80
+ if (bytesRead === 0) break;
81
+
82
+ yield buffer.subarray(0, bytesRead);
83
+ position += bytesRead;
84
+ }
85
+ }
86
+
87
+ async createWriteStream(data: BinaryStream, options: WriteStreamOptions = {}): Promise<number> {
88
+ const { start = 0, signal } = options;
89
+ let position = start;
90
+ let bytesWritten = 0;
91
+
92
+ const asyncData = toBinaryAsyncIterable(data);
93
+
94
+ // Truncate file at start position
95
+ await this.handle.truncate(start);
96
+
97
+ for await (const chunk of asyncData) {
98
+ if (signal?.aborted) {
99
+ throw new Error("Operation aborted");
100
+ }
101
+
102
+ const { bytesWritten: written } = await this.handle.write(chunk, 0, chunk.length, position);
103
+ position += written;
104
+ bytesWritten += written;
105
+ }
106
+
107
+ // Update size
108
+ const stat = await this.handle.stat();
109
+ this._size = stat.size;
110
+
111
+ return bytesWritten;
112
+ }
113
+
114
+ async chmod(mode: number): Promise<void> {
115
+ await this.handle.chmod(mode);
116
+ }
117
+
118
+ async chown(uid: number, gid: number): Promise<void> {
119
+ await this.handle.chown(uid, gid);
120
+ }
121
+ }
122
+
123
+ export class NodeFilesApi implements IFilesApi {
124
+ private fs: typeof NodeFS;
125
+ private rootDir: string;
126
+
127
+ constructor(options: NodeFilesApiOptions) {
128
+ this.fs = options.fs;
129
+ this.rootDir = options.rootDir ?? "";
130
+ }
131
+
132
+ private resolvePath(file: FileRef): string {
133
+ const normalized = resolveFileRef(file);
134
+ return this.rootDir + normalized;
135
+ }
136
+
137
+ async *list(file: FileRef, options: ListOptions = {}): AsyncGenerator<FileInfo> {
138
+ const dirPath = this.resolvePath(file);
139
+ const { recursive = false } = options;
140
+
141
+ try {
142
+ const entries = await this.fs.readdir(dirPath, { withFileTypes: true });
143
+
144
+ for (const entry of entries) {
145
+ const entryPath = joinPath(resolveFileRef(file), entry.name);
146
+ const fullPath = this.rootDir + entryPath;
147
+
148
+ const info: FileInfo = {
149
+ kind: entry.isDirectory() ? "directory" : "file",
150
+ name: entry.name,
151
+ path: entryPath,
152
+ lastModified: 0,
153
+ };
154
+
155
+ // Get full stats for size and mtime
156
+ try {
157
+ const stat = await this.fs.stat(fullPath);
158
+ info.size = stat.size;
159
+ info.lastModified = stat.mtimeMs;
160
+ } catch {
161
+ // Ignore stat errors
162
+ }
163
+
164
+ yield info;
165
+
166
+ if (recursive && entry.isDirectory()) {
167
+ yield* this.list(entryPath, options);
168
+ }
169
+ }
170
+ } catch {
171
+ // Directory doesn't exist or isn't readable
172
+ }
173
+ }
174
+
175
+ async stats(file: FileRef): Promise<FileInfo | undefined> {
176
+ const fullPath = this.resolvePath(file);
177
+ const normalized = resolveFileRef(file);
178
+
179
+ try {
180
+ const stat = await this.fs.stat(fullPath);
181
+ return {
182
+ kind: stat.isDirectory() ? "directory" : "file",
183
+ name: basename(normalized),
184
+ path: normalized,
185
+ size: stat.size,
186
+ lastModified: stat.mtimeMs,
187
+ };
188
+ } catch {
189
+ return undefined;
190
+ }
191
+ }
192
+
193
+ async remove(file: FileRef): Promise<boolean> {
194
+ const fullPath = this.resolvePath(file);
195
+
196
+ try {
197
+ const stat = await this.fs.stat(fullPath);
198
+ if (stat.isDirectory()) {
199
+ await this.fs.rm(fullPath, { recursive: true });
200
+ } else {
201
+ await this.fs.unlink(fullPath);
202
+ }
203
+ return true;
204
+ } catch {
205
+ return false;
206
+ }
207
+ }
208
+
209
+ async open(file: FileRef): Promise<FileHandle> {
210
+ const fullPath = this.resolvePath(file);
211
+ const normalized = resolveFileRef(file);
212
+
213
+ // Ensure parent directory exists
214
+ const dir = this.rootDir + dirname(normalized);
215
+ await this.fs.mkdir(dir, { recursive: true });
216
+
217
+ // Open file with read/write access, create if doesn't exist
218
+ const handle = await this.fs.open(fullPath, "a+");
219
+ const stat = await handle.stat();
220
+
221
+ return new NodeFileHandleWrapper(handle, stat.size);
222
+ }
223
+
224
+ async move(source: FileRef, target: FileRef): Promise<boolean> {
225
+ const sourcePath = this.resolvePath(source);
226
+ const targetPath = this.resolvePath(target);
227
+
228
+ try {
229
+ // Ensure target parent directory exists
230
+ const targetDir = dirname(targetPath);
231
+ await this.fs.mkdir(targetDir, { recursive: true });
232
+
233
+ await this.fs.rename(sourcePath, targetPath);
234
+ return true;
235
+ } catch {
236
+ return false;
237
+ }
238
+ }
239
+
240
+ async copy(source: FileRef, target: FileRef, options: CopyOptions = {}): Promise<boolean> {
241
+ const sourcePath = this.resolvePath(source);
242
+ const targetPath = this.resolvePath(target);
243
+ const { recursive = true } = options;
244
+
245
+ try {
246
+ // Ensure target parent directory exists
247
+ const targetDir = dirname(targetPath);
248
+ await this.fs.mkdir(targetDir, { recursive: true });
249
+
250
+ await this.fs.cp(sourcePath, targetPath, { recursive });
251
+ return true;
252
+ } catch {
253
+ return false;
254
+ }
255
+ }
256
+
257
+ async mkdir(file: FileRef): Promise<void> {
258
+ const fullPath = this.resolvePath(file);
259
+ await this.fs.mkdir(fullPath, { recursive: true });
260
+ }
261
+ }
package/src/index.ts ADDED
@@ -0,0 +1,35 @@
1
+ /**
2
+ * webrun-files - Cross-platform filesystem API
3
+ */
4
+
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
11
+ export type {
12
+ AppendOptions,
13
+ BinaryStream,
14
+ CopyOptions,
15
+ FileHandle,
16
+ FileInfo,
17
+ FileKind,
18
+ FileRef,
19
+ IFilesApi,
20
+ ListOptions,
21
+ ReadStreamOptions,
22
+ WriteStreamOptions,
23
+ } 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 { basename, dirname, extname, joinPath } from "./utils/path-utils.js";
package/src/types.ts ADDED
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Type definitions for IFilesApi
3
+ */
4
+
5
+ export type FileKind = "file" | "directory";
6
+
7
+ export interface FileInfo {
8
+ kind: FileKind;
9
+ name: string;
10
+ path: string;
11
+ type?: string;
12
+ size?: number;
13
+ lastModified: number;
14
+ }
15
+
16
+ export type FileRef = string | { path: string };
17
+
18
+ export interface ListOptions {
19
+ recursive?: boolean;
20
+ }
21
+
22
+ export interface CopyOptions {
23
+ recursive?: boolean;
24
+ }
25
+
26
+ export interface AppendOptions {
27
+ signal?: AbortSignal;
28
+ }
29
+
30
+ export interface ReadStreamOptions {
31
+ start?: number;
32
+ end?: number;
33
+ signal?: AbortSignal;
34
+ }
35
+
36
+ export interface WriteStreamOptions {
37
+ start?: number;
38
+ signal?: AbortSignal;
39
+ }
40
+
41
+ export type BinaryStream = AsyncIterable<Uint8Array> | Iterable<Uint8Array>;
42
+
43
+ export interface FileHandle {
44
+ readonly size: number;
45
+ close(): Promise<void>;
46
+
47
+ appendFile(data: BinaryStream, options?: AppendOptions): Promise<number>;
48
+ createReadStream(options?: ReadStreamOptions): AsyncGenerator<Uint8Array>;
49
+ createWriteStream(data: BinaryStream, options?: WriteStreamOptions): Promise<number>;
50
+
51
+ chmod?(mode: number): Promise<void>;
52
+ chown?(uid: number, gid: number): Promise<void>;
53
+ }
54
+
55
+ /**
56
+ * Core filesystem interface - minimal contract for implementations.
57
+ * Platform-specific implementations should implement this interface.
58
+ */
59
+ export interface IFilesApi {
60
+ list(file: FileRef, options?: ListOptions): AsyncGenerator<FileInfo>;
61
+ stats(file: FileRef): Promise<FileInfo | undefined>;
62
+ remove(file: FileRef): Promise<boolean>;
63
+ open(file: FileRef): Promise<FileHandle>;
64
+
65
+ move?(source: FileRef, target: FileRef): Promise<boolean>;
66
+ copy?(source: FileRef, target: FileRef, options?: CopyOptions): Promise<boolean>;
67
+ mkdir?(file: FileRef): Promise<void>;
68
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Stream collection utilities
3
+ */
4
+
5
+ /**
6
+ * Collects all values from an async generator into an array.
7
+ */
8
+ export async function collectGenerator<T>(generator: AsyncIterable<T>): Promise<T[]> {
9
+ const results: T[] = [];
10
+ for await (const item of generator) {
11
+ results.push(item);
12
+ }
13
+ return results;
14
+ }
15
+
16
+ /**
17
+ * Converts a synchronous iterable to an async iterable.
18
+ */
19
+ export async function* toAsyncIterable<T>(iterable: Iterable<T>): AsyncIterable<T> {
20
+ for (const item of iterable) {
21
+ yield item;
22
+ }
23
+ }
24
+
25
+ /**
26
+ * Normalizes BinaryStream to AsyncIterable<Uint8Array>.
27
+ */
28
+ export function toBinaryAsyncIterable(
29
+ data: AsyncIterable<Uint8Array> | Iterable<Uint8Array>,
30
+ ): AsyncIterable<Uint8Array> {
31
+ if (Symbol.asyncIterator in data) {
32
+ return data as AsyncIterable<Uint8Array>;
33
+ }
34
+ return toAsyncIterable(data as Iterable<Uint8Array>);
35
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Utility functions
3
+ */
4
+
5
+ export * from "./collect-stream.js";
6
+ export * from "./normalize-path.js";
7
+ export * from "./path-utils.js";