@statewalker/webrun-files 0.5.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.
- package/LICENSE +1 -1
- package/README.md +93 -113
- package/dist/cjs/index.cjs +84 -526
- package/dist/esm/index.js +78 -517
- package/dist/file-utils.d.ts +47 -0
- package/dist/file-utils.d.ts.map +1 -0
- package/dist/index.d.ts +14 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/{utils/path-utils.d.ts → path-utils.d.ts} +10 -1
- package/dist/path-utils.d.ts.map +1 -0
- package/dist/types.d.ts +90 -54
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -7
- package/src/file-utils.ts +106 -0
- package/src/index.ts +33 -27
- package/src/{utils/path-utils.ts → path-utils.ts} +16 -2
- package/src/types.ts +90 -53
- package/dist/files-api.d.ts +0 -44
- package/dist/files-api.d.ts.map +0 -1
- package/dist/impl/index.d.ts +0 -6
- package/dist/impl/index.d.ts.map +0 -1
- package/dist/impl/mem-files-api.d.ts +0 -13
- package/dist/impl/mem-files-api.d.ts.map +0 -1
- package/dist/impl/node-files-api.d.ts +0 -24
- package/dist/impl/node-files-api.d.ts.map +0 -1
- package/dist/utils/collect-stream.d.ts +0 -16
- package/dist/utils/collect-stream.d.ts.map +0 -1
- package/dist/utils/file-info-utils.d.ts +0 -10
- package/dist/utils/file-info-utils.d.ts.map +0 -1
- package/dist/utils/index.d.ts +0 -8
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/normalize-path.d.ts +0 -21
- package/dist/utils/normalize-path.d.ts.map +0 -1
- package/dist/utils/path-utils.d.ts.map +0 -1
- package/src/files-api.ts +0 -168
- package/src/impl/index.ts +0 -6
- package/src/impl/mem-files-api.ts +0 -323
- package/src/impl/node-files-api.ts +0 -271
- package/src/utils/collect-stream.ts +0 -35
- package/src/utils/file-info-utils.ts +0 -15
- package/src/utils/index.ts +0 -8
- package/src/utils/normalize-path.ts +0 -34
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* In-memory implementation of IFilesApi for testing
|
|
3
|
-
*/
|
|
4
|
-
import type { FileHandle, FileInfo, FileRef, IFilesApi, ListOptions } from "../types.js";
|
|
5
|
-
export declare class MemFilesApi implements IFilesApi {
|
|
6
|
-
private store;
|
|
7
|
-
list(file: FileRef, options?: ListOptions): AsyncGenerator<FileInfo>;
|
|
8
|
-
stats(file: FileRef): Promise<FileInfo | undefined>;
|
|
9
|
-
remove(file: FileRef): Promise<boolean>;
|
|
10
|
-
open(file: FileRef): Promise<FileHandle>;
|
|
11
|
-
mkdir(file: FileRef): Promise<void>;
|
|
12
|
-
}
|
|
13
|
-
//# sourceMappingURL=mem-files-api.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
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,24 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Node.js implementation of IFilesApi
|
|
3
|
-
*/
|
|
4
|
-
import type * as NodeFS from "node:fs/promises";
|
|
5
|
-
import type { CopyOptions, FileHandle, FileInfo, FileRef, IFilesApi, ListOptions } from "../types.js";
|
|
6
|
-
interface NodeFilesApiOptions {
|
|
7
|
-
fs: typeof NodeFS;
|
|
8
|
-
rootDir?: string;
|
|
9
|
-
}
|
|
10
|
-
export declare class NodeFilesApi implements IFilesApi {
|
|
11
|
-
private fs;
|
|
12
|
-
private rootDir;
|
|
13
|
-
constructor(options: NodeFilesApiOptions);
|
|
14
|
-
private resolvePath;
|
|
15
|
-
list(file: FileRef, options?: ListOptions): AsyncGenerator<FileInfo>;
|
|
16
|
-
stats(file: FileRef): Promise<FileInfo | undefined>;
|
|
17
|
-
remove(file: FileRef): Promise<boolean>;
|
|
18
|
-
open(file: FileRef): Promise<FileHandle>;
|
|
19
|
-
move(source: FileRef, target: FileRef): Promise<boolean>;
|
|
20
|
-
copy(source: FileRef, target: FileRef, options?: CopyOptions): Promise<boolean>;
|
|
21
|
-
mkdir(file: FileRef): Promise<void>;
|
|
22
|
-
}
|
|
23
|
-
export {};
|
|
24
|
-
//# sourceMappingURL=node-files-api.d.ts.map
|
|
@@ -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;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"}
|
|
@@ -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,10 +0,0 @@
|
|
|
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
|
|
@@ -1 +0,0 @@
|
|
|
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"}
|
package/dist/utils/index.d.ts
DELETED
|
@@ -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,sBAAsB,CAAC;AACrC,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,168 +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 { resolveFileRef } from "./utils/index.js";
|
|
17
|
-
import { joinPath } from "./utils/path-utils.js";
|
|
18
|
-
|
|
19
|
-
const IGNORE_FILE = ".ignore";
|
|
20
|
-
|
|
21
|
-
export class FilesApi implements IFilesApi {
|
|
22
|
-
constructor(private fs: IFilesApi) {}
|
|
23
|
-
|
|
24
|
-
// ========================================
|
|
25
|
-
// IFilesApi delegation
|
|
26
|
-
// ========================================
|
|
27
|
-
|
|
28
|
-
list(file: FileRef, options?: ListOptions): AsyncGenerator<FileInfo> {
|
|
29
|
-
return this.fs.list(file, options);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
stats(file: FileRef): Promise<FileInfo | undefined> {
|
|
33
|
-
return this.fs.stats(file);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
remove(file: FileRef): Promise<boolean> {
|
|
37
|
-
return this.fs.remove(file);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
open(file: FileRef): Promise<FileHandle> {
|
|
41
|
-
return this.fs.open(file);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// ========================================
|
|
45
|
-
// Convenience methods built on core API
|
|
46
|
-
// ========================================
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Checks if a file or directory exists.
|
|
50
|
-
*/
|
|
51
|
-
async exists(file: FileRef): Promise<boolean> {
|
|
52
|
-
const info = await this.stats(file);
|
|
53
|
-
return info !== undefined;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Reads file content as a stream.
|
|
58
|
-
*/
|
|
59
|
-
async *read(file: FileRef, options: ReadStreamOptions = {}): AsyncGenerator<Uint8Array> {
|
|
60
|
-
const handle = await this.open(file);
|
|
61
|
-
try {
|
|
62
|
-
yield* handle.createReadStream(options);
|
|
63
|
-
} finally {
|
|
64
|
-
await handle.close();
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Writes content to a file.
|
|
70
|
-
*/
|
|
71
|
-
async write(file: FileRef, content: BinaryStream): Promise<void> {
|
|
72
|
-
const handle = await this.open(file);
|
|
73
|
-
try {
|
|
74
|
-
await handle.createWriteStream(toBinaryAsyncIterable(content));
|
|
75
|
-
} finally {
|
|
76
|
-
await handle.close();
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Reads entire file into a single buffer.
|
|
82
|
-
*/
|
|
83
|
-
async readFile(file: FileRef): Promise<Uint8Array> {
|
|
84
|
-
const chunks: Uint8Array[] = [];
|
|
85
|
-
let totalLength = 0;
|
|
86
|
-
|
|
87
|
-
for await (const chunk of this.read(file)) {
|
|
88
|
-
chunks.push(chunk);
|
|
89
|
-
totalLength += chunk.length;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
if (chunks.length === 0) return new Uint8Array(0);
|
|
93
|
-
if (chunks.length === 1) return chunks[0];
|
|
94
|
-
|
|
95
|
-
const result = new Uint8Array(totalLength);
|
|
96
|
-
let offset = 0;
|
|
97
|
-
for (const chunk of chunks) {
|
|
98
|
-
result.set(chunk, offset);
|
|
99
|
-
offset += chunk.length;
|
|
100
|
-
}
|
|
101
|
-
return result;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Copies file or directory.
|
|
106
|
-
* Uses native implementation if available, otherwise falls back to read/write.
|
|
107
|
-
*/
|
|
108
|
-
async copy(source: FileRef, target: FileRef, options: CopyOptions = {}): Promise<boolean> {
|
|
109
|
-
// Try native implementation first
|
|
110
|
-
if (this.fs.copy) {
|
|
111
|
-
return this.fs.copy(source, target, options);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Fallback: read from source, write to target
|
|
115
|
-
const sourceInfo = await this.stats(source);
|
|
116
|
-
if (!sourceInfo) return false;
|
|
117
|
-
|
|
118
|
-
const sourcePath = resolveFileRef(source);
|
|
119
|
-
const targetPath = resolveFileRef(target);
|
|
120
|
-
const recursive = options.recursive ?? true;
|
|
121
|
-
|
|
122
|
-
if (sourceInfo.kind === "directory") {
|
|
123
|
-
for await (const entry of this.list(source, { recursive })) {
|
|
124
|
-
if (entry.kind === "directory") continue;
|
|
125
|
-
const suffix = entry.path.substring(sourcePath.length);
|
|
126
|
-
const newTargetPath = targetPath + suffix;
|
|
127
|
-
await this.write(newTargetPath, this.read(entry.path));
|
|
128
|
-
}
|
|
129
|
-
} else {
|
|
130
|
-
await this.write(targetPath, this.read(sourcePath));
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
return true;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Moves file or directory.
|
|
138
|
-
* Uses native implementation if available, otherwise falls back to copy+delete.
|
|
139
|
-
*/
|
|
140
|
-
async move(source: FileRef, target: FileRef): Promise<boolean> {
|
|
141
|
-
// Try native implementation first
|
|
142
|
-
if (this.fs.move) {
|
|
143
|
-
return this.fs.move(source, target);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// Fallback: copy then delete
|
|
147
|
-
const copied = await this.copy(source, target);
|
|
148
|
-
if (!copied) return false;
|
|
149
|
-
await this.remove(source);
|
|
150
|
-
return true;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Creates a directory.
|
|
155
|
-
* Uses native implementation if available, otherwise creates a placeholder file.
|
|
156
|
-
*/
|
|
157
|
-
async mkdir(file: FileRef): Promise<void> {
|
|
158
|
-
// Try native implementation first
|
|
159
|
-
if (this.fs.mkdir) {
|
|
160
|
-
return this.fs.mkdir(file);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Fallback: create a placeholder file to establish the directory
|
|
164
|
-
const dirPath = resolveFileRef(file);
|
|
165
|
-
const placeholderPath = joinPath(dirPath, IGNORE_FILE);
|
|
166
|
-
await this.write(placeholderPath, [new Uint8Array(0)]);
|
|
167
|
-
}
|
|
168
|
-
}
|
package/src/impl/index.ts
DELETED
|
@@ -1,323 +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
|
-
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
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
export class MemFilesApi implements IFilesApi {
|
|
174
|
-
private store = new Map<string, Entry>();
|
|
175
|
-
|
|
176
|
-
async *list(file: FileRef, options: ListOptions = {}): AsyncGenerator<FileInfo> {
|
|
177
|
-
const normalized = resolveFileRef(file);
|
|
178
|
-
const prefix = normalized === "/" ? "/" : `${normalized}/`;
|
|
179
|
-
const { recursive = false } = options;
|
|
180
|
-
|
|
181
|
-
const seen = new Set<string>();
|
|
182
|
-
|
|
183
|
-
for (const [path, entry] of this.store.entries()) {
|
|
184
|
-
if (!path.startsWith(prefix)) continue;
|
|
185
|
-
|
|
186
|
-
const relativePath = path.substring(prefix.length);
|
|
187
|
-
const segments = relativePath.split("/");
|
|
188
|
-
|
|
189
|
-
if (segments.length === 0 || segments[0] === "") continue;
|
|
190
|
-
|
|
191
|
-
if (segments.length === 1) {
|
|
192
|
-
// Direct child file or directory
|
|
193
|
-
if (seen.has(path)) continue;
|
|
194
|
-
seen.add(path);
|
|
195
|
-
|
|
196
|
-
const info: FileInfo = {
|
|
197
|
-
kind: entry.kind,
|
|
198
|
-
name: basename(path),
|
|
199
|
-
path,
|
|
200
|
-
lastModified: entry.lastModified,
|
|
201
|
-
};
|
|
202
|
-
|
|
203
|
-
if (entry.kind === "file") {
|
|
204
|
-
info.size = entry.content.length;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
yield info;
|
|
208
|
-
} else {
|
|
209
|
-
// This is a nested path
|
|
210
|
-
if (recursive) {
|
|
211
|
-
// Yield all nested entries
|
|
212
|
-
if (seen.has(path)) continue;
|
|
213
|
-
seen.add(path);
|
|
214
|
-
|
|
215
|
-
const info: FileInfo = {
|
|
216
|
-
kind: entry.kind,
|
|
217
|
-
name: basename(path),
|
|
218
|
-
path,
|
|
219
|
-
lastModified: entry.lastModified,
|
|
220
|
-
};
|
|
221
|
-
|
|
222
|
-
if (entry.kind === "file") {
|
|
223
|
-
info.size = entry.content.length;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
yield info;
|
|
227
|
-
} else {
|
|
228
|
-
// Non-recursive: yield the intermediate directory
|
|
229
|
-
const dirName = segments[0];
|
|
230
|
-
const dirPath = joinPath(normalized, dirName);
|
|
231
|
-
if (!seen.has(dirPath)) {
|
|
232
|
-
seen.add(dirPath);
|
|
233
|
-
yield {
|
|
234
|
-
kind: "directory",
|
|
235
|
-
name: dirName,
|
|
236
|
-
path: dirPath,
|
|
237
|
-
lastModified: 0,
|
|
238
|
-
};
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
async stats(file: FileRef): Promise<FileInfo | undefined> {
|
|
246
|
-
const normalized = resolveFileRef(file);
|
|
247
|
-
|
|
248
|
-
const entry = this.store.get(normalized);
|
|
249
|
-
if (entry) {
|
|
250
|
-
const info: FileInfo = {
|
|
251
|
-
kind: entry.kind,
|
|
252
|
-
name: basename(normalized),
|
|
253
|
-
path: normalized,
|
|
254
|
-
lastModified: entry.lastModified,
|
|
255
|
-
};
|
|
256
|
-
if (entry.kind === "file") {
|
|
257
|
-
info.size = entry.content.length;
|
|
258
|
-
}
|
|
259
|
-
return info;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
// Check if it's an implicit directory (root always exists)
|
|
263
|
-
if (normalized === "/") {
|
|
264
|
-
return {
|
|
265
|
-
kind: "directory",
|
|
266
|
-
name: "",
|
|
267
|
-
path: "/",
|
|
268
|
-
lastModified: 0,
|
|
269
|
-
};
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
const prefix = `${normalized}/`;
|
|
273
|
-
for (const path of this.store.keys()) {
|
|
274
|
-
if (path.startsWith(prefix)) {
|
|
275
|
-
return {
|
|
276
|
-
kind: "directory",
|
|
277
|
-
name: basename(normalized),
|
|
278
|
-
path: normalized,
|
|
279
|
-
lastModified: 0,
|
|
280
|
-
};
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
return undefined;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
async remove(file: FileRef): Promise<boolean> {
|
|
288
|
-
const normalized = resolveFileRef(file);
|
|
289
|
-
const prefix = `${normalized}/`;
|
|
290
|
-
|
|
291
|
-
let removed = false;
|
|
292
|
-
|
|
293
|
-
// Remove exact match
|
|
294
|
-
if (this.store.delete(normalized)) {
|
|
295
|
-
removed = true;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
// Remove all children (for directories)
|
|
299
|
-
for (const path of [...this.store.keys()]) {
|
|
300
|
-
if (path.startsWith(prefix)) {
|
|
301
|
-
this.store.delete(path);
|
|
302
|
-
removed = true;
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
return removed;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
async open(file: FileRef): Promise<FileHandle> {
|
|
310
|
-
const normalized = resolveFileRef(file);
|
|
311
|
-
return new MemFileHandle(this.store, normalized);
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
async mkdir(file: FileRef): Promise<void> {
|
|
315
|
-
const normalized = resolveFileRef(file);
|
|
316
|
-
if (!this.store.has(normalized)) {
|
|
317
|
-
this.store.set(normalized, {
|
|
318
|
-
kind: "directory",
|
|
319
|
-
lastModified: Date.now(),
|
|
320
|
-
});
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
}
|