@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.
- package/LICENSE +1 -1
- package/README.md +179 -0
- package/dist/cjs/index.cjs +90 -497
- package/dist/esm/index.js +84 -490
- package/dist/file-utils.d.ts +47 -0
- package/dist/file-utils.d.ts.map +1 -0
- package/dist/index.d.ts +14 -7
- 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 -44
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -6
- package/src/file-utils.ts +106 -0
- package/src/index.ts +33 -26
- package/src/{utils/path-utils.ts → path-utils.ts} +16 -2
- package/src/types.ts +93 -45
- 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/index.d.ts +0 -7
- 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 -167
- package/src/impl/index.ts +0 -6
- package/src/impl/mem-files-api.ts +0 -306
- package/src/impl/node-files-api.ts +0 -261
- package/src/utils/collect-stream.ts +0 -35
- package/src/utils/index.ts +0 -7
- package/src/utils/normalize-path.ts +0 -34
|
@@ -1,261 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,35 +0,0 @@
|
|
|
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
|
-
}
|
package/src/utils/index.ts
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Path normalization utilities
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import type { FileRef } from "../types.js";
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Normalizes a file path to a consistent format.
|
|
9
|
-
* - Adds leading slash if missing
|
|
10
|
-
* - Removes trailing slash
|
|
11
|
-
* - Removes `.` segments
|
|
12
|
-
* - Collapses multiple slashes
|
|
13
|
-
*/
|
|
14
|
-
export function normalizePath(filePath: string): string {
|
|
15
|
-
const segments = filePath.split("/").filter((s) => !!s && s !== ".");
|
|
16
|
-
if (segments.length === 0) {
|
|
17
|
-
return "/";
|
|
18
|
-
}
|
|
19
|
-
return `/${segments.join("/")}`;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Extracts path string from FileRef.
|
|
24
|
-
*/
|
|
25
|
-
export function toPath(file: FileRef): string {
|
|
26
|
-
return typeof file === "object" ? file.path : String(file);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Normalizes FileRef to a path string.
|
|
31
|
-
*/
|
|
32
|
-
export function resolveFileRef(file: FileRef): string {
|
|
33
|
-
return normalizePath(toPath(file));
|
|
34
|
-
}
|