@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.
- package/dist/cjs/index.cjs +564 -0
- package/dist/esm/index.js +551 -0
- package/dist/files-api.d.ts +44 -0
- package/dist/files-api.d.ts.map +1 -0
- package/dist/impl/index.d.ts +6 -0
- package/dist/impl/index.d.ts.map +1 -0
- package/dist/impl/mem-files-api.d.ts +13 -0
- package/dist/impl/mem-files-api.d.ts.map +1 -0
- package/dist/impl/node-files-api.d.ts +24 -0
- package/dist/impl/node-files-api.d.ts.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/types.d.ts +57 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils/collect-stream.d.ts +16 -0
- package/dist/utils/collect-stream.d.ts.map +1 -0
- package/dist/utils/index.d.ts +7 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/normalize-path.d.ts +21 -0
- package/dist/utils/normalize-path.d.ts.map +1 -0
- package/dist/utils/path-utils.d.ts +20 -0
- package/dist/utils/path-utils.d.ts.map +1 -0
- package/package.json +29 -31
- package/src/files-api.ts +167 -0
- package/src/impl/index.ts +6 -0
- package/src/impl/mem-files-api.ts +306 -0
- package/src/impl/node-files-api.ts +261 -0
- package/src/index.ts +35 -0
- package/src/types.ts +68 -0
- package/src/utils/collect-stream.ts +35 -0
- package/src/utils/index.ts +7 -0
- package/src/utils/normalize-path.ts +34 -0
- package/src/utils/path-utils.ts +46 -0
- package/CHANGELOG.md +0 -2
- package/README.md +0 -17
- package/dist/index-umd.js +0 -1054
- package/dist/index-umd.min.js +0 -2
- package/dist/index.js +0 -1033
- package/dist/index.min.js +0 -2
- package/index.js +0 -1
- package/src/BrowserFilesApi.js +0 -335
- package/src/FilesApi.js +0 -133
- package/src/MemFilesApi.js +0 -129
- package/src/NodeFilesApi.js +0 -222
- package/src/addFilesApiLogger.js +0 -25
- package/src/getMimeType.js +0 -19
- package/src/index.js +0 -10
- package/src/mimeTypes.js +0 -96
- package/src/normalizePath.js +0 -8
- package/src/notImplemented.js +0 -3
- package/src/openBrowserFilesApi.js +0 -67
|
@@ -0,0 +1,551 @@
|
|
|
1
|
+
//#region src/utils/collect-stream.ts
|
|
2
|
+
/**
|
|
3
|
+
* Stream collection utilities
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Collects all values from an async generator into an array.
|
|
7
|
+
*/
|
|
8
|
+
async function collectGenerator(generator) {
|
|
9
|
+
const results = [];
|
|
10
|
+
for await (const item of generator) results.push(item);
|
|
11
|
+
return results;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Converts a synchronous iterable to an async iterable.
|
|
15
|
+
*/
|
|
16
|
+
async function* toAsyncIterable(iterable) {
|
|
17
|
+
for (const item of iterable) yield item;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Normalizes BinaryStream to AsyncIterable<Uint8Array>.
|
|
21
|
+
*/
|
|
22
|
+
function toBinaryAsyncIterable(data) {
|
|
23
|
+
if (Symbol.asyncIterator in data) return data;
|
|
24
|
+
return toAsyncIterable(data);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
//#endregion
|
|
28
|
+
//#region src/utils/normalize-path.ts
|
|
29
|
+
/**
|
|
30
|
+
* Normalizes a file path to a consistent format.
|
|
31
|
+
* - Adds leading slash if missing
|
|
32
|
+
* - Removes trailing slash
|
|
33
|
+
* - Removes `.` segments
|
|
34
|
+
* - Collapses multiple slashes
|
|
35
|
+
*/
|
|
36
|
+
function normalizePath(filePath) {
|
|
37
|
+
const segments = filePath.split("/").filter((s) => !!s && s !== ".");
|
|
38
|
+
if (segments.length === 0) return "/";
|
|
39
|
+
return `/${segments.join("/")}`;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Extracts path string from FileRef.
|
|
43
|
+
*/
|
|
44
|
+
function toPath(file) {
|
|
45
|
+
return typeof file === "object" ? file.path : String(file);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Normalizes FileRef to a path string.
|
|
49
|
+
*/
|
|
50
|
+
function resolveFileRef(file) {
|
|
51
|
+
return normalizePath(toPath(file));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
//#endregion
|
|
55
|
+
//#region src/utils/path-utils.ts
|
|
56
|
+
/**
|
|
57
|
+
* Path manipulation utilities
|
|
58
|
+
*/
|
|
59
|
+
/**
|
|
60
|
+
* Joins path segments with proper normalization.
|
|
61
|
+
*/
|
|
62
|
+
function joinPath(...segments) {
|
|
63
|
+
return normalizePath(segments.join("/"));
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Returns the directory portion of a path.
|
|
67
|
+
*/
|
|
68
|
+
function dirname(path) {
|
|
69
|
+
const normalized = normalizePath(path);
|
|
70
|
+
const lastSlash = normalized.lastIndexOf("/");
|
|
71
|
+
if (lastSlash <= 0) return "/";
|
|
72
|
+
return normalized.substring(0, lastSlash);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Returns the filename portion of a path.
|
|
76
|
+
*/
|
|
77
|
+
function basename(path, ext) {
|
|
78
|
+
const normalized = normalizePath(path);
|
|
79
|
+
const lastSlash = normalized.lastIndexOf("/");
|
|
80
|
+
const name = lastSlash === -1 ? normalized : normalized.substring(lastSlash + 1);
|
|
81
|
+
if (ext && name.endsWith(ext)) return name.substring(0, name.length - ext.length);
|
|
82
|
+
return name;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Returns the file extension including the dot.
|
|
86
|
+
*/
|
|
87
|
+
function extname(path) {
|
|
88
|
+
const name = basename(path);
|
|
89
|
+
const dotIndex = name.lastIndexOf(".");
|
|
90
|
+
if (dotIndex <= 0) return "";
|
|
91
|
+
return name.substring(dotIndex);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
//#endregion
|
|
95
|
+
//#region src/files-api.ts
|
|
96
|
+
const IGNORE_FILE = ".gitkeep";
|
|
97
|
+
var FilesApi = class {
|
|
98
|
+
constructor(fs) {
|
|
99
|
+
this.fs = fs;
|
|
100
|
+
}
|
|
101
|
+
list(file, options) {
|
|
102
|
+
return this.fs.list(file, options);
|
|
103
|
+
}
|
|
104
|
+
stats(file) {
|
|
105
|
+
return this.fs.stats(file);
|
|
106
|
+
}
|
|
107
|
+
remove(file) {
|
|
108
|
+
return this.fs.remove(file);
|
|
109
|
+
}
|
|
110
|
+
open(file) {
|
|
111
|
+
return this.fs.open(file);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Checks if a file or directory exists.
|
|
115
|
+
*/
|
|
116
|
+
async exists(file) {
|
|
117
|
+
return await this.stats(file) !== void 0;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Reads file content as a stream.
|
|
121
|
+
*/
|
|
122
|
+
async *read(file, options = {}) {
|
|
123
|
+
const handle = await this.open(file);
|
|
124
|
+
try {
|
|
125
|
+
yield* handle.createReadStream(options);
|
|
126
|
+
} finally {
|
|
127
|
+
await handle.close();
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Writes content to a file.
|
|
132
|
+
*/
|
|
133
|
+
async write(file, content) {
|
|
134
|
+
const handle = await this.open(file);
|
|
135
|
+
try {
|
|
136
|
+
await handle.createWriteStream(toBinaryAsyncIterable(content));
|
|
137
|
+
} finally {
|
|
138
|
+
await handle.close();
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Reads entire file into a single buffer.
|
|
143
|
+
*/
|
|
144
|
+
async readFile(file) {
|
|
145
|
+
const chunks = [];
|
|
146
|
+
let totalLength = 0;
|
|
147
|
+
for await (const chunk of this.read(file)) {
|
|
148
|
+
chunks.push(chunk);
|
|
149
|
+
totalLength += chunk.length;
|
|
150
|
+
}
|
|
151
|
+
if (chunks.length === 0) return new Uint8Array(0);
|
|
152
|
+
if (chunks.length === 1) return chunks[0];
|
|
153
|
+
const result = new Uint8Array(totalLength);
|
|
154
|
+
let offset = 0;
|
|
155
|
+
for (const chunk of chunks) {
|
|
156
|
+
result.set(chunk, offset);
|
|
157
|
+
offset += chunk.length;
|
|
158
|
+
}
|
|
159
|
+
return result;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Copies file or directory.
|
|
163
|
+
* Uses native implementation if available, otherwise falls back to read/write.
|
|
164
|
+
*/
|
|
165
|
+
async copy(source, target, options = {}) {
|
|
166
|
+
if (this.fs.copy) return this.fs.copy(source, target, options);
|
|
167
|
+
const sourceInfo = await this.stats(source);
|
|
168
|
+
if (!sourceInfo) return false;
|
|
169
|
+
const sourcePath = resolveFileRef(source);
|
|
170
|
+
const targetPath = resolveFileRef(target);
|
|
171
|
+
const recursive = options.recursive ?? true;
|
|
172
|
+
if (sourceInfo.kind === "directory") for await (const entry of this.list(source, { recursive })) {
|
|
173
|
+
if (entry.kind === "directory") continue;
|
|
174
|
+
const newTargetPath = targetPath + entry.path.substring(sourcePath.length);
|
|
175
|
+
await this.write(newTargetPath, this.read(entry.path));
|
|
176
|
+
}
|
|
177
|
+
else await this.write(targetPath, this.read(sourcePath));
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Moves file or directory.
|
|
182
|
+
* Uses native implementation if available, otherwise falls back to copy+delete.
|
|
183
|
+
*/
|
|
184
|
+
async move(source, target) {
|
|
185
|
+
if (this.fs.move) return this.fs.move(source, target);
|
|
186
|
+
if (!await this.copy(source, target)) return false;
|
|
187
|
+
await this.remove(source);
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Creates a directory.
|
|
192
|
+
* Uses native implementation if available, otherwise creates a placeholder file.
|
|
193
|
+
*/
|
|
194
|
+
async mkdir(file) {
|
|
195
|
+
if (this.fs.mkdir) return this.fs.mkdir(file);
|
|
196
|
+
const placeholderPath = joinPath(resolveFileRef(file), IGNORE_FILE);
|
|
197
|
+
await this.write(placeholderPath, [new Uint8Array(0)]);
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
//#endregion
|
|
202
|
+
//#region src/impl/mem-files-api.ts
|
|
203
|
+
var MemFileHandle = class {
|
|
204
|
+
constructor(store, path) {
|
|
205
|
+
this.store = store;
|
|
206
|
+
this.path = path;
|
|
207
|
+
}
|
|
208
|
+
get size() {
|
|
209
|
+
const entry = this.store.get(this.path);
|
|
210
|
+
if (entry?.kind === "file") return entry.content.length;
|
|
211
|
+
return 0;
|
|
212
|
+
}
|
|
213
|
+
getOrCreateFile() {
|
|
214
|
+
let entry = this.store.get(this.path);
|
|
215
|
+
if (!entry || entry.kind !== "file") {
|
|
216
|
+
entry = {
|
|
217
|
+
kind: "file",
|
|
218
|
+
content: new Uint8Array(0),
|
|
219
|
+
lastModified: Date.now()
|
|
220
|
+
};
|
|
221
|
+
this.store.set(this.path, entry);
|
|
222
|
+
}
|
|
223
|
+
return entry;
|
|
224
|
+
}
|
|
225
|
+
async close() {}
|
|
226
|
+
async appendFile(data, options = {}) {
|
|
227
|
+
const file = this.getOrCreateFile();
|
|
228
|
+
const chunks = [file.content];
|
|
229
|
+
let bytesWritten = 0;
|
|
230
|
+
for await (const chunk of toBinaryAsyncIterable(data)) {
|
|
231
|
+
if (options.signal?.aborted) throw new Error("Operation aborted");
|
|
232
|
+
chunks.push(chunk);
|
|
233
|
+
bytesWritten += chunk.length;
|
|
234
|
+
}
|
|
235
|
+
const totalLength = chunks.reduce((sum, c) => sum + c.length, 0);
|
|
236
|
+
const merged = new Uint8Array(totalLength);
|
|
237
|
+
let offset = 0;
|
|
238
|
+
for (const chunk of chunks) {
|
|
239
|
+
merged.set(chunk, offset);
|
|
240
|
+
offset += chunk.length;
|
|
241
|
+
}
|
|
242
|
+
file.content = merged;
|
|
243
|
+
file.lastModified = Date.now();
|
|
244
|
+
return bytesWritten;
|
|
245
|
+
}
|
|
246
|
+
async *createReadStream(options = {}) {
|
|
247
|
+
const entry = this.store.get(this.path);
|
|
248
|
+
if (!entry || entry.kind !== "file") return;
|
|
249
|
+
const { start = 0, end = Infinity, signal } = options;
|
|
250
|
+
const bufferSize = 8192;
|
|
251
|
+
const content = entry.content;
|
|
252
|
+
const actualEnd = Math.min(end, content.length);
|
|
253
|
+
let position = start;
|
|
254
|
+
while (position < actualEnd) {
|
|
255
|
+
if (signal?.aborted) throw new Error("Operation aborted");
|
|
256
|
+
const remaining = actualEnd - position;
|
|
257
|
+
const toRead = Math.min(bufferSize, remaining);
|
|
258
|
+
yield content.subarray(position, position + toRead);
|
|
259
|
+
position += toRead;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
async createWriteStream(data, options = {}) {
|
|
263
|
+
const { start = 0, signal } = options;
|
|
264
|
+
const file = this.getOrCreateFile();
|
|
265
|
+
const chunks = [];
|
|
266
|
+
let bytesWritten = 0;
|
|
267
|
+
if (start > 0 && file.content.length > 0) chunks.push(file.content.subarray(0, Math.min(start, file.content.length)));
|
|
268
|
+
if (start > file.content.length) chunks.push(new Uint8Array(start - file.content.length));
|
|
269
|
+
for await (const chunk of toBinaryAsyncIterable(data)) {
|
|
270
|
+
if (signal?.aborted) throw new Error("Operation aborted");
|
|
271
|
+
chunks.push(chunk);
|
|
272
|
+
bytesWritten += chunk.length;
|
|
273
|
+
}
|
|
274
|
+
const totalLength = chunks.reduce((sum, c) => sum + c.length, 0);
|
|
275
|
+
const merged = new Uint8Array(totalLength);
|
|
276
|
+
let offset = 0;
|
|
277
|
+
for (const chunk of chunks) {
|
|
278
|
+
merged.set(chunk, offset);
|
|
279
|
+
offset += chunk.length;
|
|
280
|
+
}
|
|
281
|
+
file.content = merged;
|
|
282
|
+
file.lastModified = Date.now();
|
|
283
|
+
return bytesWritten;
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
var MemFilesApi = class {
|
|
287
|
+
store = /* @__PURE__ */ new Map();
|
|
288
|
+
async *list(file, options = {}) {
|
|
289
|
+
const normalized = resolveFileRef(file);
|
|
290
|
+
const prefix = normalized === "/" ? "/" : `${normalized}/`;
|
|
291
|
+
const { recursive = false } = options;
|
|
292
|
+
const seen = /* @__PURE__ */ new Set();
|
|
293
|
+
for (const [path, entry] of this.store.entries()) {
|
|
294
|
+
if (!path.startsWith(prefix)) continue;
|
|
295
|
+
const segments = path.substring(prefix.length).split("/");
|
|
296
|
+
if (segments.length === 0 || segments[0] === "") continue;
|
|
297
|
+
if (segments.length === 1) {
|
|
298
|
+
if (seen.has(path)) continue;
|
|
299
|
+
seen.add(path);
|
|
300
|
+
const info = {
|
|
301
|
+
kind: entry.kind,
|
|
302
|
+
name: basename(path),
|
|
303
|
+
path,
|
|
304
|
+
lastModified: entry.lastModified
|
|
305
|
+
};
|
|
306
|
+
if (entry.kind === "file") info.size = entry.content.length;
|
|
307
|
+
yield info;
|
|
308
|
+
} else if (recursive) {
|
|
309
|
+
if (seen.has(path)) continue;
|
|
310
|
+
seen.add(path);
|
|
311
|
+
const info = {
|
|
312
|
+
kind: entry.kind,
|
|
313
|
+
name: basename(path),
|
|
314
|
+
path,
|
|
315
|
+
lastModified: entry.lastModified
|
|
316
|
+
};
|
|
317
|
+
if (entry.kind === "file") info.size = entry.content.length;
|
|
318
|
+
yield info;
|
|
319
|
+
} else {
|
|
320
|
+
const dirName = segments[0];
|
|
321
|
+
const dirPath = joinPath(normalized, dirName);
|
|
322
|
+
if (!seen.has(dirPath)) {
|
|
323
|
+
seen.add(dirPath);
|
|
324
|
+
yield {
|
|
325
|
+
kind: "directory",
|
|
326
|
+
name: dirName,
|
|
327
|
+
path: dirPath,
|
|
328
|
+
lastModified: 0
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
async stats(file) {
|
|
335
|
+
const normalized = resolveFileRef(file);
|
|
336
|
+
const entry = this.store.get(normalized);
|
|
337
|
+
if (entry) {
|
|
338
|
+
const info = {
|
|
339
|
+
kind: entry.kind,
|
|
340
|
+
name: basename(normalized),
|
|
341
|
+
path: normalized,
|
|
342
|
+
lastModified: entry.lastModified
|
|
343
|
+
};
|
|
344
|
+
if (entry.kind === "file") info.size = entry.content.length;
|
|
345
|
+
return info;
|
|
346
|
+
}
|
|
347
|
+
if (normalized === "/") return {
|
|
348
|
+
kind: "directory",
|
|
349
|
+
name: "",
|
|
350
|
+
path: "/",
|
|
351
|
+
lastModified: 0
|
|
352
|
+
};
|
|
353
|
+
const prefix = `${normalized}/`;
|
|
354
|
+
for (const path of this.store.keys()) if (path.startsWith(prefix)) return {
|
|
355
|
+
kind: "directory",
|
|
356
|
+
name: basename(normalized),
|
|
357
|
+
path: normalized,
|
|
358
|
+
lastModified: 0
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
async remove(file) {
|
|
362
|
+
const normalized = resolveFileRef(file);
|
|
363
|
+
const prefix = `${normalized}/`;
|
|
364
|
+
let removed = false;
|
|
365
|
+
if (this.store.delete(normalized)) removed = true;
|
|
366
|
+
for (const path of [...this.store.keys()]) if (path.startsWith(prefix)) {
|
|
367
|
+
this.store.delete(path);
|
|
368
|
+
removed = true;
|
|
369
|
+
}
|
|
370
|
+
return removed;
|
|
371
|
+
}
|
|
372
|
+
async open(file) {
|
|
373
|
+
const normalized = resolveFileRef(file);
|
|
374
|
+
return new MemFileHandle(this.store, normalized);
|
|
375
|
+
}
|
|
376
|
+
async mkdir(file) {
|
|
377
|
+
const normalized = resolveFileRef(file);
|
|
378
|
+
if (!this.store.has(normalized)) this.store.set(normalized, {
|
|
379
|
+
kind: "directory",
|
|
380
|
+
lastModified: Date.now()
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
//#endregion
|
|
386
|
+
//#region src/impl/node-files-api.ts
|
|
387
|
+
/**
|
|
388
|
+
* Wraps Node.js FileHandle to implement our FileHandle interface.
|
|
389
|
+
*/
|
|
390
|
+
var NodeFileHandleWrapper = class {
|
|
391
|
+
constructor(handle, _size) {
|
|
392
|
+
this.handle = handle;
|
|
393
|
+
this._size = _size;
|
|
394
|
+
}
|
|
395
|
+
get size() {
|
|
396
|
+
return this._size;
|
|
397
|
+
}
|
|
398
|
+
async close() {
|
|
399
|
+
await this.handle.close();
|
|
400
|
+
}
|
|
401
|
+
async appendFile(data, options = {}) {
|
|
402
|
+
let bytesWritten = 0;
|
|
403
|
+
const asyncData = toBinaryAsyncIterable(data);
|
|
404
|
+
for await (const chunk of asyncData) {
|
|
405
|
+
if (options.signal?.aborted) throw new Error("Operation aborted");
|
|
406
|
+
await this.handle.appendFile(chunk);
|
|
407
|
+
bytesWritten += chunk.length;
|
|
408
|
+
}
|
|
409
|
+
this._size = (await this.handle.stat()).size;
|
|
410
|
+
return bytesWritten;
|
|
411
|
+
}
|
|
412
|
+
async *createReadStream(options = {}) {
|
|
413
|
+
const { start = 0, end = Infinity, signal } = options;
|
|
414
|
+
const bufferSize = 8192;
|
|
415
|
+
let position = start;
|
|
416
|
+
const actualEnd = Math.min(end, this._size);
|
|
417
|
+
while (position < actualEnd) {
|
|
418
|
+
if (signal?.aborted) throw new Error("Operation aborted");
|
|
419
|
+
const remaining = actualEnd - position;
|
|
420
|
+
const toRead = Math.min(bufferSize, remaining);
|
|
421
|
+
const buffer = new Uint8Array(toRead);
|
|
422
|
+
const { bytesRead } = await this.handle.read(buffer, 0, toRead, position);
|
|
423
|
+
if (bytesRead === 0) break;
|
|
424
|
+
yield buffer.subarray(0, bytesRead);
|
|
425
|
+
position += bytesRead;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
async createWriteStream(data, options = {}) {
|
|
429
|
+
const { start = 0, signal } = options;
|
|
430
|
+
let position = start;
|
|
431
|
+
let bytesWritten = 0;
|
|
432
|
+
const asyncData = toBinaryAsyncIterable(data);
|
|
433
|
+
await this.handle.truncate(start);
|
|
434
|
+
for await (const chunk of asyncData) {
|
|
435
|
+
if (signal?.aborted) throw new Error("Operation aborted");
|
|
436
|
+
const { bytesWritten: written } = await this.handle.write(chunk, 0, chunk.length, position);
|
|
437
|
+
position += written;
|
|
438
|
+
bytesWritten += written;
|
|
439
|
+
}
|
|
440
|
+
this._size = (await this.handle.stat()).size;
|
|
441
|
+
return bytesWritten;
|
|
442
|
+
}
|
|
443
|
+
async chmod(mode) {
|
|
444
|
+
await this.handle.chmod(mode);
|
|
445
|
+
}
|
|
446
|
+
async chown(uid, gid) {
|
|
447
|
+
await this.handle.chown(uid, gid);
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
var NodeFilesApi = class {
|
|
451
|
+
fs;
|
|
452
|
+
rootDir;
|
|
453
|
+
constructor(options) {
|
|
454
|
+
this.fs = options.fs;
|
|
455
|
+
this.rootDir = options.rootDir ?? "";
|
|
456
|
+
}
|
|
457
|
+
resolvePath(file) {
|
|
458
|
+
const normalized = resolveFileRef(file);
|
|
459
|
+
return this.rootDir + normalized;
|
|
460
|
+
}
|
|
461
|
+
async *list(file, options = {}) {
|
|
462
|
+
const dirPath = this.resolvePath(file);
|
|
463
|
+
const { recursive = false } = options;
|
|
464
|
+
try {
|
|
465
|
+
const entries = await this.fs.readdir(dirPath, { withFileTypes: true });
|
|
466
|
+
for (const entry of entries) {
|
|
467
|
+
const entryPath = joinPath(resolveFileRef(file), entry.name);
|
|
468
|
+
const fullPath = this.rootDir + entryPath;
|
|
469
|
+
const info = {
|
|
470
|
+
kind: entry.isDirectory() ? "directory" : "file",
|
|
471
|
+
name: entry.name,
|
|
472
|
+
path: entryPath,
|
|
473
|
+
lastModified: 0
|
|
474
|
+
};
|
|
475
|
+
try {
|
|
476
|
+
const stat = await this.fs.stat(fullPath);
|
|
477
|
+
info.size = stat.size;
|
|
478
|
+
info.lastModified = stat.mtimeMs;
|
|
479
|
+
} catch {}
|
|
480
|
+
yield info;
|
|
481
|
+
if (recursive && entry.isDirectory()) yield* this.list(entryPath, options);
|
|
482
|
+
}
|
|
483
|
+
} catch {}
|
|
484
|
+
}
|
|
485
|
+
async stats(file) {
|
|
486
|
+
const fullPath = this.resolvePath(file);
|
|
487
|
+
const normalized = resolveFileRef(file);
|
|
488
|
+
try {
|
|
489
|
+
const stat = await this.fs.stat(fullPath);
|
|
490
|
+
return {
|
|
491
|
+
kind: stat.isDirectory() ? "directory" : "file",
|
|
492
|
+
name: basename(normalized),
|
|
493
|
+
path: normalized,
|
|
494
|
+
size: stat.size,
|
|
495
|
+
lastModified: stat.mtimeMs
|
|
496
|
+
};
|
|
497
|
+
} catch {
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
async remove(file) {
|
|
502
|
+
const fullPath = this.resolvePath(file);
|
|
503
|
+
try {
|
|
504
|
+
if ((await this.fs.stat(fullPath)).isDirectory()) await this.fs.rm(fullPath, { recursive: true });
|
|
505
|
+
else await this.fs.unlink(fullPath);
|
|
506
|
+
return true;
|
|
507
|
+
} catch {
|
|
508
|
+
return false;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
async open(file) {
|
|
512
|
+
const fullPath = this.resolvePath(file);
|
|
513
|
+
const normalized = resolveFileRef(file);
|
|
514
|
+
const dir = this.rootDir + dirname(normalized);
|
|
515
|
+
await this.fs.mkdir(dir, { recursive: true });
|
|
516
|
+
const handle = await this.fs.open(fullPath, "a+");
|
|
517
|
+
return new NodeFileHandleWrapper(handle, (await handle.stat()).size);
|
|
518
|
+
}
|
|
519
|
+
async move(source, target) {
|
|
520
|
+
const sourcePath = this.resolvePath(source);
|
|
521
|
+
const targetPath = this.resolvePath(target);
|
|
522
|
+
try {
|
|
523
|
+
const targetDir = dirname(targetPath);
|
|
524
|
+
await this.fs.mkdir(targetDir, { recursive: true });
|
|
525
|
+
await this.fs.rename(sourcePath, targetPath);
|
|
526
|
+
return true;
|
|
527
|
+
} catch {
|
|
528
|
+
return false;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
async copy(source, target, options = {}) {
|
|
532
|
+
const sourcePath = this.resolvePath(source);
|
|
533
|
+
const targetPath = this.resolvePath(target);
|
|
534
|
+
const { recursive = true } = options;
|
|
535
|
+
try {
|
|
536
|
+
const targetDir = dirname(targetPath);
|
|
537
|
+
await this.fs.mkdir(targetDir, { recursive: true });
|
|
538
|
+
await this.fs.cp(sourcePath, targetPath, { recursive });
|
|
539
|
+
return true;
|
|
540
|
+
} catch {
|
|
541
|
+
return false;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
async mkdir(file) {
|
|
545
|
+
const fullPath = this.resolvePath(file);
|
|
546
|
+
await this.fs.mkdir(fullPath, { recursive: true });
|
|
547
|
+
}
|
|
548
|
+
};
|
|
549
|
+
|
|
550
|
+
//#endregion
|
|
551
|
+
export { FilesApi, MemFilesApi, NodeFilesApi, basename, collectGenerator, dirname, extname, joinPath, normalizePath, resolveFileRef, toAsyncIterable, toBinaryAsyncIterable, toPath };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FilesApi wrapper class providing convenience methods on top of IFilesApi
|
|
3
|
+
*/
|
|
4
|
+
import type { BinaryStream, CopyOptions, FileHandle, FileInfo, FileRef, IFilesApi, ListOptions, ReadStreamOptions } from "./types.js";
|
|
5
|
+
export declare class FilesApi implements IFilesApi {
|
|
6
|
+
private fs;
|
|
7
|
+
constructor(fs: IFilesApi);
|
|
8
|
+
list(file: FileRef, options?: ListOptions): AsyncGenerator<FileInfo>;
|
|
9
|
+
stats(file: FileRef): Promise<FileInfo | undefined>;
|
|
10
|
+
remove(file: FileRef): Promise<boolean>;
|
|
11
|
+
open(file: FileRef): Promise<FileHandle>;
|
|
12
|
+
/**
|
|
13
|
+
* Checks if a file or directory exists.
|
|
14
|
+
*/
|
|
15
|
+
exists(file: FileRef): Promise<boolean>;
|
|
16
|
+
/**
|
|
17
|
+
* Reads file content as a stream.
|
|
18
|
+
*/
|
|
19
|
+
read(file: FileRef, options?: ReadStreamOptions): AsyncGenerator<Uint8Array>;
|
|
20
|
+
/**
|
|
21
|
+
* Writes content to a file.
|
|
22
|
+
*/
|
|
23
|
+
write(file: FileRef, content: BinaryStream): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Reads entire file into a single buffer.
|
|
26
|
+
*/
|
|
27
|
+
readFile(file: FileRef): Promise<Uint8Array>;
|
|
28
|
+
/**
|
|
29
|
+
* Copies file or directory.
|
|
30
|
+
* Uses native implementation if available, otherwise falls back to read/write.
|
|
31
|
+
*/
|
|
32
|
+
copy(source: FileRef, target: FileRef, options?: CopyOptions): Promise<boolean>;
|
|
33
|
+
/**
|
|
34
|
+
* Moves file or directory.
|
|
35
|
+
* Uses native implementation if available, otherwise falls back to copy+delete.
|
|
36
|
+
*/
|
|
37
|
+
move(source: FileRef, target: FileRef): Promise<boolean>;
|
|
38
|
+
/**
|
|
39
|
+
* Creates a directory.
|
|
40
|
+
* Uses native implementation if available, otherwise creates a placeholder file.
|
|
41
|
+
*/
|
|
42
|
+
mkdir(file: FileRef): Promise<void>;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=files-api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"files-api.d.ts","sourceRoot":"","sources":["../src/files-api.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EACV,YAAY,EACZ,WAAW,EACX,UAAU,EACV,QAAQ,EACR,OAAO,EACP,SAAS,EACT,WAAW,EACX,iBAAiB,EAClB,MAAM,YAAY,CAAC;AAMpB,qBAAa,QAAS,YAAW,SAAS;IAC5B,OAAO,CAAC,EAAE;gBAAF,EAAE,EAAE,SAAS;IAMjC,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,cAAc,CAAC,QAAQ,CAAC;IAIpE,KAAK,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC;IAInD,MAAM,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAIvC,IAAI,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,UAAU,CAAC;IAQxC;;OAEG;IACG,MAAM,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAK7C;;OAEG;IACI,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,GAAE,iBAAsB,GAAG,cAAc,CAAC,UAAU,CAAC;IASvF;;OAEG;IACG,KAAK,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAShE;;OAEG;IACG,QAAQ,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,UAAU,CAAC;IAqBlD;;;OAGG;IACG,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,OAAO,CAAC;IA4BzF;;;OAGG;IACG,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAa9D;;;OAGG;IACG,KAAK,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;CAW1C"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/impl/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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;AA6IrB,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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-files-api.d.ts","sourceRoot":"","sources":["../../src/impl/node-files-api.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,KAAK,MAAM,MAAM,kBAAkB,CAAC;AAEhD,OAAO,KAAK,EAGV,WAAW,EACX,UAAU,EACV,QAAQ,EACR,OAAO,EACP,SAAS,EACT,WAAW,EAGZ,MAAM,aAAa,CAAC;AAIrB,UAAU,mBAAmB;IAC3B,EAAE,EAAE,OAAO,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAkGD,qBAAa,YAAa,YAAW,SAAS;IAC5C,OAAO,CAAC,EAAE,CAAgB;IAC1B,OAAO,CAAC,OAAO,CAAS;gBAEZ,OAAO,EAAE,mBAAmB;IAKxC,OAAO,CAAC,WAAW;IAKZ,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,GAAE,WAAgB,GAAG,cAAc,CAAC,QAAQ,CAAC;IAsCzE,KAAK,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC;IAkBnD,MAAM,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAgBvC,IAAI,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,UAAU,CAAC;IAexC,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAgBxD,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,OAAO,CAAC;IAiBnF,KAAK,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;CAI1C"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* webrun-files - Cross-platform filesystem API
|
|
3
|
+
*/
|
|
4
|
+
export { FilesApi } from "./files-api.js";
|
|
5
|
+
export { MemFilesApi } from "./impl/mem-files-api.js";
|
|
6
|
+
export { NodeFilesApi } from "./impl/node-files-api.js";
|
|
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 { basename, dirname, extname, joinPath } from "./utils/path-utils.js";
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE1C,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAExD,YAAY,EACV,aAAa,EACb,YAAY,EACZ,WAAW,EACX,UAAU,EACV,QAAQ,EACR,QAAQ,EACR,OAAO,EACP,SAAS,EACT,WAAW,EACX,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,qBAAqB,GACtB,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EACL,aAAa,EACb,cAAc,EACd,MAAM,GACP,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC"}
|