@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
package/dist/esm/index.js
CHANGED
|
@@ -1,46 +1,99 @@
|
|
|
1
|
-
//#region src/utils
|
|
1
|
+
//#region src/file-utils.ts
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Collect async iterable chunks into a single Uint8Array.
|
|
4
4
|
*/
|
|
5
|
+
async function collectChunks(chunks) {
|
|
6
|
+
const parts = [];
|
|
7
|
+
let totalLength = 0;
|
|
8
|
+
for await (const chunk of chunks) {
|
|
9
|
+
parts.push(chunk);
|
|
10
|
+
totalLength += chunk.length;
|
|
11
|
+
}
|
|
12
|
+
if (parts.length === 0) return new Uint8Array(0);
|
|
13
|
+
if (parts.length === 1) return parts[0];
|
|
14
|
+
const result = new Uint8Array(totalLength);
|
|
15
|
+
let offset = 0;
|
|
16
|
+
for (const chunk of parts) {
|
|
17
|
+
result.set(chunk, offset);
|
|
18
|
+
offset += chunk.length;
|
|
19
|
+
}
|
|
20
|
+
return result;
|
|
21
|
+
}
|
|
5
22
|
/**
|
|
6
|
-
*
|
|
23
|
+
* Read entire file content into a Uint8Array.
|
|
24
|
+
* Returns empty array if file doesn't exist.
|
|
7
25
|
*/
|
|
8
|
-
async function
|
|
9
|
-
|
|
10
|
-
for await (const item of generator) results.push(item);
|
|
11
|
-
return results;
|
|
26
|
+
async function readFile(files, path) {
|
|
27
|
+
return collectChunks(files.read(path));
|
|
12
28
|
}
|
|
13
29
|
/**
|
|
14
|
-
*
|
|
30
|
+
* Read entire file content as a UTF-8 string.
|
|
31
|
+
* Returns empty string if file doesn't exist.
|
|
15
32
|
*/
|
|
16
|
-
async function
|
|
17
|
-
|
|
33
|
+
async function readText(files, path) {
|
|
34
|
+
const data = await readFile(files, path);
|
|
35
|
+
return new TextDecoder().decode(data);
|
|
18
36
|
}
|
|
19
37
|
/**
|
|
20
|
-
*
|
|
38
|
+
* Read file content, returning undefined if file doesn't exist.
|
|
21
39
|
*/
|
|
22
|
-
function
|
|
23
|
-
if (
|
|
24
|
-
return
|
|
40
|
+
async function tryReadFile(files, path) {
|
|
41
|
+
if (!await files.exists(path)) return void 0;
|
|
42
|
+
return readFile(files, path);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Read file content as string, returning undefined if file doesn't exist.
|
|
46
|
+
*/
|
|
47
|
+
async function tryReadText(files, path) {
|
|
48
|
+
const data = await tryReadFile(files, path);
|
|
49
|
+
return data ? new TextDecoder().decode(data) : void 0;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Read a specific byte range from a file.
|
|
53
|
+
*
|
|
54
|
+
* @param files - FilesApi instance
|
|
55
|
+
* @param path - File path
|
|
56
|
+
* @param position - Starting byte position
|
|
57
|
+
* @param length - Number of bytes to read
|
|
58
|
+
* @returns Uint8Array with requested bytes (may be shorter if EOF reached)
|
|
59
|
+
*/
|
|
60
|
+
async function readRange(files, path, position, length) {
|
|
61
|
+
return collectChunks(files.read(path, {
|
|
62
|
+
start: position,
|
|
63
|
+
length
|
|
64
|
+
}));
|
|
25
65
|
}
|
|
26
|
-
|
|
27
|
-
//#endregion
|
|
28
|
-
//#region src/utils/file-info-utils.ts
|
|
29
66
|
/**
|
|
30
|
-
*
|
|
67
|
+
* Read bytes from a file into a buffer at a specific offset.
|
|
68
|
+
* Similar to Node.js fs.read() signature.
|
|
69
|
+
*
|
|
70
|
+
* @param files - FilesApi instance
|
|
71
|
+
* @param path - File path
|
|
72
|
+
* @param buffer - Target buffer to write into
|
|
73
|
+
* @param bufferOffset - Offset in buffer where to start writing
|
|
74
|
+
* @param length - Number of bytes to read
|
|
75
|
+
* @param position - Position in file where to start reading
|
|
76
|
+
* @returns Number of bytes actually read
|
|
31
77
|
*/
|
|
32
|
-
function
|
|
33
|
-
|
|
78
|
+
async function readAt(files, path, buffer, bufferOffset, length, position) {
|
|
79
|
+
const data = await readRange(files, path, position, length);
|
|
80
|
+
const bytesToCopy = Math.min(data.length, buffer.length - bufferOffset);
|
|
81
|
+
buffer.set(data.subarray(0, bytesToCopy), bufferOffset);
|
|
82
|
+
return bytesToCopy;
|
|
34
83
|
}
|
|
35
84
|
/**
|
|
36
|
-
*
|
|
85
|
+
* Write a string to a file as UTF-8.
|
|
37
86
|
*/
|
|
38
|
-
function
|
|
39
|
-
|
|
87
|
+
async function writeText(files, path, content) {
|
|
88
|
+
await files.write(path, [new TextEncoder().encode(content)]);
|
|
40
89
|
}
|
|
41
90
|
|
|
42
91
|
//#endregion
|
|
43
|
-
//#region src/utils
|
|
92
|
+
//#region src/path-utils.ts
|
|
93
|
+
/**
|
|
94
|
+
* Path manipulation utilities for virtual filesystem paths.
|
|
95
|
+
* All paths use forward slashes and start with "/".
|
|
96
|
+
*/
|
|
44
97
|
/**
|
|
45
98
|
* Normalizes a file path to a consistent format.
|
|
46
99
|
* - Adds leading slash if missing
|
|
@@ -54,24 +107,6 @@ function normalizePath(filePath) {
|
|
|
54
107
|
return `/${segments.join("/")}`;
|
|
55
108
|
}
|
|
56
109
|
/**
|
|
57
|
-
* Extracts path string from FileRef.
|
|
58
|
-
*/
|
|
59
|
-
function toPath(file) {
|
|
60
|
-
return typeof file === "object" ? file.path : String(file);
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* Normalizes FileRef to a path string.
|
|
64
|
-
*/
|
|
65
|
-
function resolveFileRef(file) {
|
|
66
|
-
return normalizePath(toPath(file));
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
//#endregion
|
|
70
|
-
//#region src/utils/path-utils.ts
|
|
71
|
-
/**
|
|
72
|
-
* Path manipulation utilities
|
|
73
|
-
*/
|
|
74
|
-
/**
|
|
75
110
|
* Joins path segments with proper normalization.
|
|
76
111
|
*/
|
|
77
112
|
function joinPath(...segments) {
|
|
@@ -107,478 +142,4 @@ function extname(path) {
|
|
|
107
142
|
}
|
|
108
143
|
|
|
109
144
|
//#endregion
|
|
110
|
-
|
|
111
|
-
const IGNORE_FILE = ".ignore";
|
|
112
|
-
var FilesApi = class {
|
|
113
|
-
constructor(fs) {
|
|
114
|
-
this.fs = fs;
|
|
115
|
-
}
|
|
116
|
-
list(file, options) {
|
|
117
|
-
return this.fs.list(file, options);
|
|
118
|
-
}
|
|
119
|
-
stats(file) {
|
|
120
|
-
return this.fs.stats(file);
|
|
121
|
-
}
|
|
122
|
-
remove(file) {
|
|
123
|
-
return this.fs.remove(file);
|
|
124
|
-
}
|
|
125
|
-
open(file) {
|
|
126
|
-
return this.fs.open(file);
|
|
127
|
-
}
|
|
128
|
-
/**
|
|
129
|
-
* Checks if a file or directory exists.
|
|
130
|
-
*/
|
|
131
|
-
async exists(file) {
|
|
132
|
-
return await this.stats(file) !== void 0;
|
|
133
|
-
}
|
|
134
|
-
/**
|
|
135
|
-
* Reads file content as a stream.
|
|
136
|
-
*/
|
|
137
|
-
async *read(file, options = {}) {
|
|
138
|
-
const handle = await this.open(file);
|
|
139
|
-
try {
|
|
140
|
-
yield* handle.createReadStream(options);
|
|
141
|
-
} finally {
|
|
142
|
-
await handle.close();
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
/**
|
|
146
|
-
* Writes content to a file.
|
|
147
|
-
*/
|
|
148
|
-
async write(file, content) {
|
|
149
|
-
const handle = await this.open(file);
|
|
150
|
-
try {
|
|
151
|
-
await handle.createWriteStream(toBinaryAsyncIterable(content));
|
|
152
|
-
} finally {
|
|
153
|
-
await handle.close();
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
/**
|
|
157
|
-
* Reads entire file into a single buffer.
|
|
158
|
-
*/
|
|
159
|
-
async readFile(file) {
|
|
160
|
-
const chunks = [];
|
|
161
|
-
let totalLength = 0;
|
|
162
|
-
for await (const chunk of this.read(file)) {
|
|
163
|
-
chunks.push(chunk);
|
|
164
|
-
totalLength += chunk.length;
|
|
165
|
-
}
|
|
166
|
-
if (chunks.length === 0) return new Uint8Array(0);
|
|
167
|
-
if (chunks.length === 1) return chunks[0];
|
|
168
|
-
const result = new Uint8Array(totalLength);
|
|
169
|
-
let offset = 0;
|
|
170
|
-
for (const chunk of chunks) {
|
|
171
|
-
result.set(chunk, offset);
|
|
172
|
-
offset += chunk.length;
|
|
173
|
-
}
|
|
174
|
-
return result;
|
|
175
|
-
}
|
|
176
|
-
/**
|
|
177
|
-
* Copies file or directory.
|
|
178
|
-
* Uses native implementation if available, otherwise falls back to read/write.
|
|
179
|
-
*/
|
|
180
|
-
async copy(source, target, options = {}) {
|
|
181
|
-
if (this.fs.copy) return this.fs.copy(source, target, options);
|
|
182
|
-
const sourceInfo = await this.stats(source);
|
|
183
|
-
if (!sourceInfo) return false;
|
|
184
|
-
const sourcePath = resolveFileRef(source);
|
|
185
|
-
const targetPath = resolveFileRef(target);
|
|
186
|
-
const recursive = options.recursive ?? true;
|
|
187
|
-
if (sourceInfo.kind === "directory") for await (const entry of this.list(source, { recursive })) {
|
|
188
|
-
if (entry.kind === "directory") continue;
|
|
189
|
-
const newTargetPath = targetPath + entry.path.substring(sourcePath.length);
|
|
190
|
-
await this.write(newTargetPath, this.read(entry.path));
|
|
191
|
-
}
|
|
192
|
-
else await this.write(targetPath, this.read(sourcePath));
|
|
193
|
-
return true;
|
|
194
|
-
}
|
|
195
|
-
/**
|
|
196
|
-
* Moves file or directory.
|
|
197
|
-
* Uses native implementation if available, otherwise falls back to copy+delete.
|
|
198
|
-
*/
|
|
199
|
-
async move(source, target) {
|
|
200
|
-
if (this.fs.move) return this.fs.move(source, target);
|
|
201
|
-
if (!await this.copy(source, target)) return false;
|
|
202
|
-
await this.remove(source);
|
|
203
|
-
return true;
|
|
204
|
-
}
|
|
205
|
-
/**
|
|
206
|
-
* Creates a directory.
|
|
207
|
-
* Uses native implementation if available, otherwise creates a placeholder file.
|
|
208
|
-
*/
|
|
209
|
-
async mkdir(file) {
|
|
210
|
-
if (this.fs.mkdir) return this.fs.mkdir(file);
|
|
211
|
-
const placeholderPath = joinPath(resolveFileRef(file), IGNORE_FILE);
|
|
212
|
-
await this.write(placeholderPath, [new Uint8Array(0)]);
|
|
213
|
-
}
|
|
214
|
-
};
|
|
215
|
-
|
|
216
|
-
//#endregion
|
|
217
|
-
//#region src/impl/mem-files-api.ts
|
|
218
|
-
var MemFileHandle = class {
|
|
219
|
-
constructor(store, path) {
|
|
220
|
-
this.store = store;
|
|
221
|
-
this.path = path;
|
|
222
|
-
}
|
|
223
|
-
get size() {
|
|
224
|
-
const entry = this.store.get(this.path);
|
|
225
|
-
if (entry?.kind === "file") return entry.content.length;
|
|
226
|
-
return 0;
|
|
227
|
-
}
|
|
228
|
-
getOrCreateFile() {
|
|
229
|
-
let entry = this.store.get(this.path);
|
|
230
|
-
if (!entry || entry.kind !== "file") {
|
|
231
|
-
entry = {
|
|
232
|
-
kind: "file",
|
|
233
|
-
content: new Uint8Array(0),
|
|
234
|
-
lastModified: Date.now()
|
|
235
|
-
};
|
|
236
|
-
this.store.set(this.path, entry);
|
|
237
|
-
}
|
|
238
|
-
return entry;
|
|
239
|
-
}
|
|
240
|
-
async close() {}
|
|
241
|
-
async appendFile(data, options = {}) {
|
|
242
|
-
const file = this.getOrCreateFile();
|
|
243
|
-
const chunks = [file.content];
|
|
244
|
-
let bytesWritten = 0;
|
|
245
|
-
for await (const chunk of toBinaryAsyncIterable(data)) {
|
|
246
|
-
if (options.signal?.aborted) throw new Error("Operation aborted");
|
|
247
|
-
chunks.push(chunk);
|
|
248
|
-
bytesWritten += chunk.length;
|
|
249
|
-
}
|
|
250
|
-
const totalLength = chunks.reduce((sum, c) => sum + c.length, 0);
|
|
251
|
-
const merged = new Uint8Array(totalLength);
|
|
252
|
-
let offset = 0;
|
|
253
|
-
for (const chunk of chunks) {
|
|
254
|
-
merged.set(chunk, offset);
|
|
255
|
-
offset += chunk.length;
|
|
256
|
-
}
|
|
257
|
-
file.content = merged;
|
|
258
|
-
file.lastModified = Date.now();
|
|
259
|
-
return bytesWritten;
|
|
260
|
-
}
|
|
261
|
-
async *createReadStream(options = {}) {
|
|
262
|
-
const entry = this.store.get(this.path);
|
|
263
|
-
if (!entry || entry.kind !== "file") return;
|
|
264
|
-
const { start = 0, end = Infinity, signal } = options;
|
|
265
|
-
const bufferSize = 8192;
|
|
266
|
-
const content = entry.content;
|
|
267
|
-
const actualEnd = Math.min(end, content.length);
|
|
268
|
-
let position = start;
|
|
269
|
-
while (position < actualEnd) {
|
|
270
|
-
if (signal?.aborted) throw new Error("Operation aborted");
|
|
271
|
-
const remaining = actualEnd - position;
|
|
272
|
-
const toRead = Math.min(bufferSize, remaining);
|
|
273
|
-
yield content.subarray(position, position + toRead);
|
|
274
|
-
position += toRead;
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
async createWriteStream(data, options = {}) {
|
|
278
|
-
const { start = 0, signal } = options;
|
|
279
|
-
const file = this.getOrCreateFile();
|
|
280
|
-
const chunks = [];
|
|
281
|
-
let bytesWritten = 0;
|
|
282
|
-
if (start > 0 && file.content.length > 0) chunks.push(file.content.subarray(0, Math.min(start, file.content.length)));
|
|
283
|
-
if (start > file.content.length) chunks.push(new Uint8Array(start - file.content.length));
|
|
284
|
-
for await (const chunk of toBinaryAsyncIterable(data)) {
|
|
285
|
-
if (signal?.aborted) throw new Error("Operation aborted");
|
|
286
|
-
chunks.push(chunk);
|
|
287
|
-
bytesWritten += chunk.length;
|
|
288
|
-
}
|
|
289
|
-
const totalLength = chunks.reduce((sum, c) => sum + c.length, 0);
|
|
290
|
-
const merged = new Uint8Array(totalLength);
|
|
291
|
-
let offset = 0;
|
|
292
|
-
for (const chunk of chunks) {
|
|
293
|
-
merged.set(chunk, offset);
|
|
294
|
-
offset += chunk.length;
|
|
295
|
-
}
|
|
296
|
-
file.content = merged;
|
|
297
|
-
file.lastModified = Date.now();
|
|
298
|
-
return bytesWritten;
|
|
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
|
-
}
|
|
310
|
-
};
|
|
311
|
-
var MemFilesApi = class {
|
|
312
|
-
store = /* @__PURE__ */ new Map();
|
|
313
|
-
async *list(file, options = {}) {
|
|
314
|
-
const normalized = resolveFileRef(file);
|
|
315
|
-
const prefix = normalized === "/" ? "/" : `${normalized}/`;
|
|
316
|
-
const { recursive = false } = options;
|
|
317
|
-
const seen = /* @__PURE__ */ new Set();
|
|
318
|
-
for (const [path, entry] of this.store.entries()) {
|
|
319
|
-
if (!path.startsWith(prefix)) continue;
|
|
320
|
-
const segments = path.substring(prefix.length).split("/");
|
|
321
|
-
if (segments.length === 0 || segments[0] === "") continue;
|
|
322
|
-
if (segments.length === 1) {
|
|
323
|
-
if (seen.has(path)) continue;
|
|
324
|
-
seen.add(path);
|
|
325
|
-
const info = {
|
|
326
|
-
kind: entry.kind,
|
|
327
|
-
name: basename(path),
|
|
328
|
-
path,
|
|
329
|
-
lastModified: entry.lastModified
|
|
330
|
-
};
|
|
331
|
-
if (entry.kind === "file") info.size = entry.content.length;
|
|
332
|
-
yield info;
|
|
333
|
-
} else if (recursive) {
|
|
334
|
-
if (seen.has(path)) continue;
|
|
335
|
-
seen.add(path);
|
|
336
|
-
const info = {
|
|
337
|
-
kind: entry.kind,
|
|
338
|
-
name: basename(path),
|
|
339
|
-
path,
|
|
340
|
-
lastModified: entry.lastModified
|
|
341
|
-
};
|
|
342
|
-
if (entry.kind === "file") info.size = entry.content.length;
|
|
343
|
-
yield info;
|
|
344
|
-
} else {
|
|
345
|
-
const dirName = segments[0];
|
|
346
|
-
const dirPath = joinPath(normalized, dirName);
|
|
347
|
-
if (!seen.has(dirPath)) {
|
|
348
|
-
seen.add(dirPath);
|
|
349
|
-
yield {
|
|
350
|
-
kind: "directory",
|
|
351
|
-
name: dirName,
|
|
352
|
-
path: dirPath,
|
|
353
|
-
lastModified: 0
|
|
354
|
-
};
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
async stats(file) {
|
|
360
|
-
const normalized = resolveFileRef(file);
|
|
361
|
-
const entry = this.store.get(normalized);
|
|
362
|
-
if (entry) {
|
|
363
|
-
const info = {
|
|
364
|
-
kind: entry.kind,
|
|
365
|
-
name: basename(normalized),
|
|
366
|
-
path: normalized,
|
|
367
|
-
lastModified: entry.lastModified
|
|
368
|
-
};
|
|
369
|
-
if (entry.kind === "file") info.size = entry.content.length;
|
|
370
|
-
return info;
|
|
371
|
-
}
|
|
372
|
-
if (normalized === "/") return {
|
|
373
|
-
kind: "directory",
|
|
374
|
-
name: "",
|
|
375
|
-
path: "/",
|
|
376
|
-
lastModified: 0
|
|
377
|
-
};
|
|
378
|
-
const prefix = `${normalized}/`;
|
|
379
|
-
for (const path of this.store.keys()) if (path.startsWith(prefix)) return {
|
|
380
|
-
kind: "directory",
|
|
381
|
-
name: basename(normalized),
|
|
382
|
-
path: normalized,
|
|
383
|
-
lastModified: 0
|
|
384
|
-
};
|
|
385
|
-
}
|
|
386
|
-
async remove(file) {
|
|
387
|
-
const normalized = resolveFileRef(file);
|
|
388
|
-
const prefix = `${normalized}/`;
|
|
389
|
-
let removed = false;
|
|
390
|
-
if (this.store.delete(normalized)) removed = true;
|
|
391
|
-
for (const path of [...this.store.keys()]) if (path.startsWith(prefix)) {
|
|
392
|
-
this.store.delete(path);
|
|
393
|
-
removed = true;
|
|
394
|
-
}
|
|
395
|
-
return removed;
|
|
396
|
-
}
|
|
397
|
-
async open(file) {
|
|
398
|
-
const normalized = resolveFileRef(file);
|
|
399
|
-
return new MemFileHandle(this.store, normalized);
|
|
400
|
-
}
|
|
401
|
-
async mkdir(file) {
|
|
402
|
-
const normalized = resolveFileRef(file);
|
|
403
|
-
if (!this.store.has(normalized)) this.store.set(normalized, {
|
|
404
|
-
kind: "directory",
|
|
405
|
-
lastModified: Date.now()
|
|
406
|
-
});
|
|
407
|
-
}
|
|
408
|
-
};
|
|
409
|
-
|
|
410
|
-
//#endregion
|
|
411
|
-
//#region src/impl/node-files-api.ts
|
|
412
|
-
/**
|
|
413
|
-
* Wraps Node.js FileHandle to implement our FileHandle interface.
|
|
414
|
-
*/
|
|
415
|
-
var NodeFileHandleWrapper = class {
|
|
416
|
-
constructor(handle, _size) {
|
|
417
|
-
this.handle = handle;
|
|
418
|
-
this._size = _size;
|
|
419
|
-
}
|
|
420
|
-
get size() {
|
|
421
|
-
return this._size;
|
|
422
|
-
}
|
|
423
|
-
async close() {
|
|
424
|
-
await this.handle.close();
|
|
425
|
-
}
|
|
426
|
-
async appendFile(data, options = {}) {
|
|
427
|
-
let bytesWritten = 0;
|
|
428
|
-
const asyncData = toBinaryAsyncIterable(data);
|
|
429
|
-
for await (const chunk of asyncData) {
|
|
430
|
-
if (options.signal?.aborted) throw new Error("Operation aborted");
|
|
431
|
-
await this.handle.appendFile(chunk);
|
|
432
|
-
bytesWritten += chunk.length;
|
|
433
|
-
}
|
|
434
|
-
this._size = (await this.handle.stat()).size;
|
|
435
|
-
return bytesWritten;
|
|
436
|
-
}
|
|
437
|
-
async *createReadStream(options = {}) {
|
|
438
|
-
const { start = 0, end = Infinity, signal } = options;
|
|
439
|
-
const bufferSize = 8192;
|
|
440
|
-
let position = start;
|
|
441
|
-
const actualEnd = Math.min(end, this._size);
|
|
442
|
-
while (position < actualEnd) {
|
|
443
|
-
if (signal?.aborted) throw new Error("Operation aborted");
|
|
444
|
-
const remaining = actualEnd - position;
|
|
445
|
-
const toRead = Math.min(bufferSize, remaining);
|
|
446
|
-
const buffer = new Uint8Array(toRead);
|
|
447
|
-
const { bytesRead } = await this.handle.read(buffer, 0, toRead, position);
|
|
448
|
-
if (bytesRead === 0) break;
|
|
449
|
-
yield buffer.subarray(0, bytesRead);
|
|
450
|
-
position += bytesRead;
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
async createWriteStream(data, options = {}) {
|
|
454
|
-
const { start = 0, signal } = options;
|
|
455
|
-
let position = start;
|
|
456
|
-
let bytesWritten = 0;
|
|
457
|
-
const asyncData = toBinaryAsyncIterable(data);
|
|
458
|
-
await this.handle.truncate(start);
|
|
459
|
-
for await (const chunk of asyncData) {
|
|
460
|
-
if (signal?.aborted) throw new Error("Operation aborted");
|
|
461
|
-
const { bytesWritten: written } = await this.handle.write(chunk, 0, chunk.length, position);
|
|
462
|
-
position += written;
|
|
463
|
-
bytesWritten += written;
|
|
464
|
-
}
|
|
465
|
-
this._size = (await this.handle.stat()).size;
|
|
466
|
-
return bytesWritten;
|
|
467
|
-
}
|
|
468
|
-
async chmod(mode) {
|
|
469
|
-
await this.handle.chmod(mode);
|
|
470
|
-
}
|
|
471
|
-
async chown(uid, gid) {
|
|
472
|
-
await this.handle.chown(uid, gid);
|
|
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
|
-
}
|
|
479
|
-
};
|
|
480
|
-
var NodeFilesApi = class {
|
|
481
|
-
fs;
|
|
482
|
-
rootDir;
|
|
483
|
-
constructor(options) {
|
|
484
|
-
this.fs = options.fs;
|
|
485
|
-
this.rootDir = options.rootDir ?? "";
|
|
486
|
-
}
|
|
487
|
-
resolvePath(file) {
|
|
488
|
-
const normalized = resolveFileRef(file);
|
|
489
|
-
return this.rootDir + normalized;
|
|
490
|
-
}
|
|
491
|
-
async *list(file, options = {}) {
|
|
492
|
-
const dirPath = this.resolvePath(file);
|
|
493
|
-
const { recursive = false } = options;
|
|
494
|
-
try {
|
|
495
|
-
const entries = await this.fs.readdir(dirPath, { withFileTypes: true });
|
|
496
|
-
for (const entry of entries) {
|
|
497
|
-
const entryPath = joinPath(resolveFileRef(file), entry.name);
|
|
498
|
-
const fullPath = this.rootDir + entryPath;
|
|
499
|
-
const info = {
|
|
500
|
-
kind: entry.isDirectory() ? "directory" : "file",
|
|
501
|
-
name: entry.name,
|
|
502
|
-
path: entryPath,
|
|
503
|
-
lastModified: 0
|
|
504
|
-
};
|
|
505
|
-
try {
|
|
506
|
-
const stat = await this.fs.stat(fullPath);
|
|
507
|
-
info.size = stat.size;
|
|
508
|
-
info.lastModified = stat.mtimeMs;
|
|
509
|
-
} catch {}
|
|
510
|
-
yield info;
|
|
511
|
-
if (recursive && entry.isDirectory()) yield* this.list(entryPath, options);
|
|
512
|
-
}
|
|
513
|
-
} catch {}
|
|
514
|
-
}
|
|
515
|
-
async stats(file) {
|
|
516
|
-
const fullPath = this.resolvePath(file);
|
|
517
|
-
const normalized = resolveFileRef(file);
|
|
518
|
-
try {
|
|
519
|
-
const stat = await this.fs.stat(fullPath);
|
|
520
|
-
return {
|
|
521
|
-
kind: stat.isDirectory() ? "directory" : "file",
|
|
522
|
-
name: basename(normalized),
|
|
523
|
-
path: normalized,
|
|
524
|
-
size: stat.size,
|
|
525
|
-
lastModified: stat.mtimeMs
|
|
526
|
-
};
|
|
527
|
-
} catch {
|
|
528
|
-
return;
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
async remove(file) {
|
|
532
|
-
const fullPath = this.resolvePath(file);
|
|
533
|
-
try {
|
|
534
|
-
if ((await this.fs.stat(fullPath)).isDirectory()) await this.fs.rm(fullPath, {
|
|
535
|
-
recursive: true,
|
|
536
|
-
force: true
|
|
537
|
-
});
|
|
538
|
-
else await this.fs.unlink(fullPath);
|
|
539
|
-
return true;
|
|
540
|
-
} catch {
|
|
541
|
-
return false;
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
async open(file) {
|
|
545
|
-
const fullPath = this.resolvePath(file);
|
|
546
|
-
const normalized = resolveFileRef(file);
|
|
547
|
-
const dir = this.rootDir + dirname(normalized);
|
|
548
|
-
await this.fs.mkdir(dir, { recursive: true });
|
|
549
|
-
const handle = await this.fs.open(fullPath, "a+");
|
|
550
|
-
return new NodeFileHandleWrapper(handle, (await handle.stat()).size);
|
|
551
|
-
}
|
|
552
|
-
async move(source, target) {
|
|
553
|
-
const sourcePath = this.resolvePath(source);
|
|
554
|
-
const targetPath = this.resolvePath(target);
|
|
555
|
-
try {
|
|
556
|
-
const targetDir = dirname(targetPath);
|
|
557
|
-
await this.fs.mkdir(targetDir, { recursive: true });
|
|
558
|
-
await this.fs.rename(sourcePath, targetPath);
|
|
559
|
-
return true;
|
|
560
|
-
} catch {
|
|
561
|
-
return false;
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
async copy(source, target, options = {}) {
|
|
565
|
-
const sourcePath = this.resolvePath(source);
|
|
566
|
-
const targetPath = this.resolvePath(target);
|
|
567
|
-
const { recursive = true } = options;
|
|
568
|
-
try {
|
|
569
|
-
const targetDir = dirname(targetPath);
|
|
570
|
-
await this.fs.mkdir(targetDir, { recursive: true });
|
|
571
|
-
await this.fs.cp(sourcePath, targetPath, { recursive });
|
|
572
|
-
return true;
|
|
573
|
-
} catch {
|
|
574
|
-
return false;
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
async mkdir(file) {
|
|
578
|
-
const fullPath = this.resolvePath(file);
|
|
579
|
-
await this.fs.mkdir(fullPath, { recursive: true });
|
|
580
|
-
}
|
|
581
|
-
};
|
|
582
|
-
|
|
583
|
-
//#endregion
|
|
584
|
-
export { FilesApi, MemFilesApi, NodeFilesApi, basename, collectGenerator, dirname, extname, isDirectory, isFile, joinPath, normalizePath, resolveFileRef, toAsyncIterable, toBinaryAsyncIterable, toPath };
|
|
145
|
+
export { basename, dirname, extname, joinPath, normalizePath, readAt, readFile, readRange, readText, tryReadFile, tryReadText, writeText };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { FilesApi } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Read entire file content into a Uint8Array.
|
|
4
|
+
* Returns empty array if file doesn't exist.
|
|
5
|
+
*/
|
|
6
|
+
export declare function readFile(files: FilesApi, path: string): Promise<Uint8Array>;
|
|
7
|
+
/**
|
|
8
|
+
* Read entire file content as a UTF-8 string.
|
|
9
|
+
* Returns empty string if file doesn't exist.
|
|
10
|
+
*/
|
|
11
|
+
export declare function readText(files: FilesApi, path: string): Promise<string>;
|
|
12
|
+
/**
|
|
13
|
+
* Read file content, returning undefined if file doesn't exist.
|
|
14
|
+
*/
|
|
15
|
+
export declare function tryReadFile(files: FilesApi, path: string): Promise<Uint8Array | undefined>;
|
|
16
|
+
/**
|
|
17
|
+
* Read file content as string, returning undefined if file doesn't exist.
|
|
18
|
+
*/
|
|
19
|
+
export declare function tryReadText(files: FilesApi, path: string): Promise<string | undefined>;
|
|
20
|
+
/**
|
|
21
|
+
* Read a specific byte range from a file.
|
|
22
|
+
*
|
|
23
|
+
* @param files - FilesApi instance
|
|
24
|
+
* @param path - File path
|
|
25
|
+
* @param position - Starting byte position
|
|
26
|
+
* @param length - Number of bytes to read
|
|
27
|
+
* @returns Uint8Array with requested bytes (may be shorter if EOF reached)
|
|
28
|
+
*/
|
|
29
|
+
export declare function readRange(files: FilesApi, path: string, position: number, length: number): Promise<Uint8Array>;
|
|
30
|
+
/**
|
|
31
|
+
* Read bytes from a file into a buffer at a specific offset.
|
|
32
|
+
* Similar to Node.js fs.read() signature.
|
|
33
|
+
*
|
|
34
|
+
* @param files - FilesApi instance
|
|
35
|
+
* @param path - File path
|
|
36
|
+
* @param buffer - Target buffer to write into
|
|
37
|
+
* @param bufferOffset - Offset in buffer where to start writing
|
|
38
|
+
* @param length - Number of bytes to read
|
|
39
|
+
* @param position - Position in file where to start reading
|
|
40
|
+
* @returns Number of bytes actually read
|
|
41
|
+
*/
|
|
42
|
+
export declare function readAt(files: FilesApi, path: string, buffer: Uint8Array, bufferOffset: number, length: number, position: number): Promise<number>;
|
|
43
|
+
/**
|
|
44
|
+
* Write a string to a file as UTF-8.
|
|
45
|
+
*/
|
|
46
|
+
export declare function writeText(files: FilesApi, path: string, content: string): Promise<void>;
|
|
47
|
+
//# sourceMappingURL=file-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-utils.d.ts","sourceRoot":"","sources":["../src/file-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAuB3C;;;GAGG;AACH,wBAAsB,QAAQ,CAAC,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAEjF;AAED;;;GAGG;AACH,wBAAsB,QAAQ,CAAC,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAG7E;AAED;;GAEG;AACH,wBAAsB,WAAW,CAAC,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAGhG;AAED;;GAEG;AACH,wBAAsB,WAAW,CAAC,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAG5F;AAED;;;;;;;;GAQG;AACH,wBAAsB,SAAS,CAC7B,KAAK,EAAE,QAAQ,EACf,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,UAAU,CAAC,CAErB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,MAAM,CAC1B,KAAK,EAAE,QAAQ,EACf,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,UAAU,EAClB,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,CAAC,CAKjB;AAED;;GAEG;AACH,wBAAsB,SAAS,CAAC,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE7F"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* webrun-files - Cross-platform filesystem API
|
|
3
|
+
*
|
|
4
|
+
* This package provides:
|
|
5
|
+
* - FilesApi interface for filesystem abstraction
|
|
6
|
+
* - Utility functions for common file operations
|
|
7
|
+
* - Path manipulation utilities
|
|
8
|
+
*
|
|
9
|
+
* For implementations, use:
|
|
10
|
+
* - @statewalker/webrun-files-mem (in-memory)
|
|
11
|
+
* - @statewalker/webrun-files-node (Node.js)
|
|
12
|
+
* - @statewalker/webrun-files-browser (browser)
|
|
13
|
+
* - @statewalker/webrun-files-s3 (S3)
|
|
3
14
|
*/
|
|
4
|
-
export {
|
|
5
|
-
export {
|
|
6
|
-
export {
|
|
7
|
-
export type { AppendOptions, BinaryStream, CopyOptions, FileHandle, FileInfo, FileKind, FileRef, IFilesApi, ListOptions, ReadStreamOptions, WriteStreamOptions, } from "./types.js";
|
|
8
|
-
export { collectGenerator, toAsyncIterable, toBinaryAsyncIterable, } from "./utils/collect-stream.js";
|
|
9
|
-
export { normalizePath, resolveFileRef, toPath, } from "./utils/normalize-path.js";
|
|
10
|
-
export { isDirectory, isFile } from "./utils/file-info-utils.js";
|
|
11
|
-
export { basename, dirname, extname, joinPath } from "./utils/path-utils.js";
|
|
15
|
+
export { readAt, readFile, readRange, readText, tryReadFile, tryReadText, writeText, } from "./file-utils.js";
|
|
16
|
+
export { basename, dirname, extname, joinPath, normalizePath, } from "./path-utils.js";
|
|
17
|
+
export type { FileInfo, FileKind, FileStats, FilesApi, ListOptions, ReadOptions, } from "./types.js";
|
|
12
18
|
//# sourceMappingURL=index.d.ts.map
|