@tinycloud/vfs 0.1.0-beta.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/index.cjs +910 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +394 -0
- package/dist/index.d.ts +394 -0
- package/dist/index.js +869 -0
- package/dist/index.js.map +1 -0
- package/dist/worker.cjs +611 -0
- package/dist/worker.cjs.map +1 -0
- package/dist/worker.js +609 -0
- package/dist/worker.js.map +1 -0
- package/package.json +41 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,869 @@
|
|
|
1
|
+
// src/TinyCloudVfsProvider.ts
|
|
2
|
+
import { Buffer as Buffer3 } from "buffer";
|
|
3
|
+
import { VirtualProvider, create } from "@platformatic/vfs";
|
|
4
|
+
|
|
5
|
+
// src/bridge.ts
|
|
6
|
+
import { MessageChannel, Worker, receiveMessageOnPort } from "worker_threads";
|
|
7
|
+
import { join } from "path";
|
|
8
|
+
|
|
9
|
+
// src/errors.ts
|
|
10
|
+
var ERRNO = {
|
|
11
|
+
EPERM: -1,
|
|
12
|
+
ENOENT: -2,
|
|
13
|
+
EIO: -5,
|
|
14
|
+
EBADF: -9,
|
|
15
|
+
EACCES: -13,
|
|
16
|
+
EBUSY: -16,
|
|
17
|
+
EEXIST: -17,
|
|
18
|
+
ENOTDIR: -20,
|
|
19
|
+
EISDIR: -21,
|
|
20
|
+
EINVAL: -22,
|
|
21
|
+
ENOTEMPTY: -39,
|
|
22
|
+
EROFS: -30
|
|
23
|
+
};
|
|
24
|
+
function createNodeError(code, message, syscall, path) {
|
|
25
|
+
const error = new Error(message);
|
|
26
|
+
error.code = code;
|
|
27
|
+
error.errno = ERRNO[code] ?? -1;
|
|
28
|
+
error.syscall = syscall;
|
|
29
|
+
if (path) {
|
|
30
|
+
error.path = path;
|
|
31
|
+
}
|
|
32
|
+
return error;
|
|
33
|
+
}
|
|
34
|
+
function createEISDIR(syscall, path) {
|
|
35
|
+
return createNodeError("EISDIR", `illegal operation on a directory, ${syscall} '${path}'`, syscall, path);
|
|
36
|
+
}
|
|
37
|
+
function createEROFS(syscall, path) {
|
|
38
|
+
return createNodeError("EROFS", `read-only file system, ${syscall} '${path}'`, syscall, path);
|
|
39
|
+
}
|
|
40
|
+
function createEIO(syscall, path, message) {
|
|
41
|
+
return createNodeError("EIO", `${message}, ${syscall} '${path}'`, syscall, path);
|
|
42
|
+
}
|
|
43
|
+
function fromWorkerError(error) {
|
|
44
|
+
return createNodeError(
|
|
45
|
+
error.code,
|
|
46
|
+
error.message,
|
|
47
|
+
error.syscall ?? "vfs",
|
|
48
|
+
error.path
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// src/bridge.ts
|
|
53
|
+
var WAIT_SLICE_MS = 1e3;
|
|
54
|
+
function createWaitSignal() {
|
|
55
|
+
const buffer = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT);
|
|
56
|
+
return {
|
|
57
|
+
buffer,
|
|
58
|
+
view: new Int32Array(buffer)
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function resolveWorkerSpecifier() {
|
|
62
|
+
if (typeof __dirname !== "undefined") {
|
|
63
|
+
return join(__dirname, "worker.cjs");
|
|
64
|
+
}
|
|
65
|
+
return new URL("./worker.js", import.meta.url);
|
|
66
|
+
}
|
|
67
|
+
var TinyCloudVfsBridge = class {
|
|
68
|
+
worker;
|
|
69
|
+
constructor(init) {
|
|
70
|
+
const specifier = resolveWorkerSpecifier();
|
|
71
|
+
this.worker = typeof specifier === "string" ? new Worker(specifier) : new Worker(specifier);
|
|
72
|
+
const response = this.requestSync({ type: "init", init });
|
|
73
|
+
if (!response.ok) {
|
|
74
|
+
this.worker.terminate();
|
|
75
|
+
throw createEIO("init", "/", response.error.message);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
close() {
|
|
79
|
+
void this.worker.terminate();
|
|
80
|
+
}
|
|
81
|
+
requestSync(request) {
|
|
82
|
+
const { port1, port2 } = new MessageChannel();
|
|
83
|
+
const { buffer, view } = createWaitSignal();
|
|
84
|
+
this.worker.postMessage(
|
|
85
|
+
{
|
|
86
|
+
request,
|
|
87
|
+
replyPort: port1,
|
|
88
|
+
waitBuffer: buffer
|
|
89
|
+
},
|
|
90
|
+
[port1]
|
|
91
|
+
);
|
|
92
|
+
while (Atomics.load(view, 0) === 0) {
|
|
93
|
+
Atomics.wait(view, 0, 0, WAIT_SLICE_MS);
|
|
94
|
+
}
|
|
95
|
+
const response = receiveMessageOnPort(port2)?.message;
|
|
96
|
+
port2.close();
|
|
97
|
+
if (!response) {
|
|
98
|
+
throw createEIO("bridge", "/", "worker did not return a response");
|
|
99
|
+
}
|
|
100
|
+
return response;
|
|
101
|
+
}
|
|
102
|
+
async requestAsync(request) {
|
|
103
|
+
return this.requestSync(request);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// src/fsPatch.ts
|
|
108
|
+
import { Buffer as Buffer2 } from "buffer";
|
|
109
|
+
import fs from "fs";
|
|
110
|
+
import fsPromises from "fs/promises";
|
|
111
|
+
import { syncBuiltinESMExports } from "module";
|
|
112
|
+
import { fileURLToPath } from "url";
|
|
113
|
+
var activeVfs = /* @__PURE__ */ new Set();
|
|
114
|
+
var mutableFs = fs;
|
|
115
|
+
var mutableFsPromises = fsPromises;
|
|
116
|
+
var originalFsMethods = {
|
|
117
|
+
writeFileSync: mutableFs.writeFileSync.bind(mutableFs),
|
|
118
|
+
appendFileSync: mutableFs.appendFileSync.bind(mutableFs),
|
|
119
|
+
mkdirSync: mutableFs.mkdirSync.bind(mutableFs),
|
|
120
|
+
renameSync: mutableFs.renameSync.bind(mutableFs),
|
|
121
|
+
unlinkSync: mutableFs.unlinkSync.bind(mutableFs),
|
|
122
|
+
rmdirSync: mutableFs.rmdirSync.bind(mutableFs),
|
|
123
|
+
writeFile: mutableFs.writeFile.bind(mutableFs),
|
|
124
|
+
appendFile: mutableFs.appendFile.bind(mutableFs),
|
|
125
|
+
mkdir: mutableFs.mkdir.bind(mutableFs),
|
|
126
|
+
rename: mutableFs.rename.bind(mutableFs),
|
|
127
|
+
unlink: mutableFs.unlink.bind(mutableFs),
|
|
128
|
+
rmdir: mutableFs.rmdir.bind(mutableFs)
|
|
129
|
+
};
|
|
130
|
+
var originalPromiseMethods = {
|
|
131
|
+
writeFile: mutableFsPromises.writeFile.bind(mutableFsPromises),
|
|
132
|
+
appendFile: mutableFsPromises.appendFile.bind(mutableFsPromises),
|
|
133
|
+
mkdir: mutableFsPromises.mkdir.bind(mutableFsPromises),
|
|
134
|
+
rename: mutableFsPromises.rename.bind(mutableFsPromises),
|
|
135
|
+
unlink: mutableFsPromises.unlink.bind(mutableFsPromises),
|
|
136
|
+
rmdir: mutableFsPromises.rmdir.bind(mutableFsPromises)
|
|
137
|
+
};
|
|
138
|
+
var installed = false;
|
|
139
|
+
function toPathString(path) {
|
|
140
|
+
if (typeof path === "string") {
|
|
141
|
+
return path;
|
|
142
|
+
}
|
|
143
|
+
if (Buffer2.isBuffer(path)) {
|
|
144
|
+
return path.toString();
|
|
145
|
+
}
|
|
146
|
+
if (path instanceof URL) {
|
|
147
|
+
return fileURLToPath(path);
|
|
148
|
+
}
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
function activeVfsBySpecificity() {
|
|
152
|
+
return [...activeVfs].sort((left, right) => {
|
|
153
|
+
return (right.mountPoint?.length ?? 0) - (left.mountPoint?.length ?? 0);
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
function findMountedVfs(path) {
|
|
157
|
+
const normalizedPath = toPathString(path);
|
|
158
|
+
if (!normalizedPath) {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
for (const vfs of activeVfsBySpecificity()) {
|
|
162
|
+
if (vfs.mounted && vfs.shouldHandle(normalizedPath)) {
|
|
163
|
+
return { vfs, path: normalizedPath };
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
function findMountedVfsForPaths(paths, syscall) {
|
|
169
|
+
let selected = null;
|
|
170
|
+
const normalizedPaths = [];
|
|
171
|
+
for (const path of paths) {
|
|
172
|
+
const normalizedPath = toPathString(path);
|
|
173
|
+
if (!normalizedPath) {
|
|
174
|
+
normalizedPaths.push(String(path));
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
normalizedPaths.push(normalizedPath);
|
|
178
|
+
const match = findMountedVfs(normalizedPath);
|
|
179
|
+
if (!match) {
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
if (selected && selected !== match.vfs) {
|
|
183
|
+
throw createNodeError(
|
|
184
|
+
"EXDEV",
|
|
185
|
+
`cross-device link not permitted, ${syscall} '${normalizedPaths.join("' -> '")}'`,
|
|
186
|
+
syscall,
|
|
187
|
+
normalizedPath
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
selected = match.vfs;
|
|
191
|
+
}
|
|
192
|
+
if (!selected) {
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
return { vfs: selected, paths: normalizedPaths };
|
|
196
|
+
}
|
|
197
|
+
function withCallback(promise, callback) {
|
|
198
|
+
promise.then(
|
|
199
|
+
(result) => process.nextTick(callback, null, result),
|
|
200
|
+
(error) => process.nextTick(callback, error)
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
function installFsWritePatches() {
|
|
204
|
+
if (installed) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
mutableFs.writeFileSync = ((file, data, options) => {
|
|
208
|
+
const match = findMountedVfs(file);
|
|
209
|
+
if (!match) {
|
|
210
|
+
return originalFsMethods.writeFileSync(file, data, options);
|
|
211
|
+
}
|
|
212
|
+
return match.vfs.writeFileSync(match.path, data, options);
|
|
213
|
+
});
|
|
214
|
+
mutableFs.appendFileSync = ((file, data, options) => {
|
|
215
|
+
const match = findMountedVfs(file);
|
|
216
|
+
if (!match) {
|
|
217
|
+
return originalFsMethods.appendFileSync(file, data, options);
|
|
218
|
+
}
|
|
219
|
+
return match.vfs.appendFileSync(match.path, data, options);
|
|
220
|
+
});
|
|
221
|
+
mutableFs.mkdirSync = ((path, options) => {
|
|
222
|
+
const match = findMountedVfs(path);
|
|
223
|
+
if (!match) {
|
|
224
|
+
return originalFsMethods.mkdirSync(path, options);
|
|
225
|
+
}
|
|
226
|
+
return match.vfs.mkdirSync(match.path, options);
|
|
227
|
+
});
|
|
228
|
+
mutableFs.renameSync = ((oldPath, newPath) => {
|
|
229
|
+
const match = findMountedVfsForPaths([oldPath, newPath], "rename");
|
|
230
|
+
if (!match) {
|
|
231
|
+
return originalFsMethods.renameSync(oldPath, newPath);
|
|
232
|
+
}
|
|
233
|
+
const [normalizedOld, normalizedNew] = match.paths;
|
|
234
|
+
return match.vfs.renameSync(normalizedOld, normalizedNew);
|
|
235
|
+
});
|
|
236
|
+
mutableFs.unlinkSync = ((path) => {
|
|
237
|
+
const match = findMountedVfs(path);
|
|
238
|
+
if (!match) {
|
|
239
|
+
return originalFsMethods.unlinkSync(path);
|
|
240
|
+
}
|
|
241
|
+
return match.vfs.unlinkSync(match.path);
|
|
242
|
+
});
|
|
243
|
+
mutableFs.rmdirSync = ((path, options) => {
|
|
244
|
+
const match = findMountedVfs(path);
|
|
245
|
+
if (!match) {
|
|
246
|
+
return originalFsMethods.rmdirSync(path, options);
|
|
247
|
+
}
|
|
248
|
+
return match.vfs.rmdirSync(match.path, options);
|
|
249
|
+
});
|
|
250
|
+
mutableFs.writeFile = ((file, data, options, callback) => {
|
|
251
|
+
const match = findMountedVfs(file);
|
|
252
|
+
if (!match) {
|
|
253
|
+
return originalFsMethods.writeFile(file, data, options, callback);
|
|
254
|
+
}
|
|
255
|
+
const resolvedOptions = typeof options === "function" ? void 0 : options;
|
|
256
|
+
const resolvedCallback = typeof options === "function" ? options : callback;
|
|
257
|
+
if (typeof resolvedCallback !== "function") {
|
|
258
|
+
return originalFsMethods.writeFile(file, data, options, callback);
|
|
259
|
+
}
|
|
260
|
+
withCallback(match.vfs.promises.writeFile(match.path, data, resolvedOptions), resolvedCallback);
|
|
261
|
+
});
|
|
262
|
+
mutableFs.appendFile = ((file, data, options, callback) => {
|
|
263
|
+
const match = findMountedVfs(file);
|
|
264
|
+
if (!match) {
|
|
265
|
+
return originalFsMethods.appendFile(file, data, options, callback);
|
|
266
|
+
}
|
|
267
|
+
const resolvedOptions = typeof options === "function" ? void 0 : options;
|
|
268
|
+
const resolvedCallback = typeof options === "function" ? options : callback;
|
|
269
|
+
if (typeof resolvedCallback !== "function") {
|
|
270
|
+
return originalFsMethods.appendFile(file, data, options, callback);
|
|
271
|
+
}
|
|
272
|
+
withCallback(match.vfs.promises.appendFile(match.path, data, resolvedOptions), resolvedCallback);
|
|
273
|
+
});
|
|
274
|
+
mutableFs.mkdir = ((path, options, callback) => {
|
|
275
|
+
const match = findMountedVfs(path);
|
|
276
|
+
if (!match) {
|
|
277
|
+
return originalFsMethods.mkdir(path, options, callback);
|
|
278
|
+
}
|
|
279
|
+
const resolvedOptions = typeof options === "function" ? void 0 : options;
|
|
280
|
+
const resolvedCallback = typeof options === "function" ? options : callback;
|
|
281
|
+
if (typeof resolvedCallback !== "function") {
|
|
282
|
+
return originalFsMethods.mkdir(path, options, callback);
|
|
283
|
+
}
|
|
284
|
+
withCallback(match.vfs.promises.mkdir(match.path, resolvedOptions), resolvedCallback);
|
|
285
|
+
});
|
|
286
|
+
mutableFs.rename = ((oldPath, newPath, callback) => {
|
|
287
|
+
const match = findMountedVfsForPaths([oldPath, newPath], "rename");
|
|
288
|
+
if (!match) {
|
|
289
|
+
return originalFsMethods.rename(oldPath, newPath, callback);
|
|
290
|
+
}
|
|
291
|
+
if (typeof callback !== "function") {
|
|
292
|
+
return originalFsMethods.rename(oldPath, newPath, callback);
|
|
293
|
+
}
|
|
294
|
+
const [normalizedOld, normalizedNew] = match.paths;
|
|
295
|
+
withCallback(match.vfs.promises.rename(normalizedOld, normalizedNew), callback);
|
|
296
|
+
});
|
|
297
|
+
mutableFs.unlink = ((path, callback) => {
|
|
298
|
+
const match = findMountedVfs(path);
|
|
299
|
+
if (!match) {
|
|
300
|
+
return originalFsMethods.unlink(path, callback);
|
|
301
|
+
}
|
|
302
|
+
if (typeof callback !== "function") {
|
|
303
|
+
return originalFsMethods.unlink(path, callback);
|
|
304
|
+
}
|
|
305
|
+
withCallback(match.vfs.promises.unlink(match.path), callback);
|
|
306
|
+
});
|
|
307
|
+
mutableFs.rmdir = ((path, options, callback) => {
|
|
308
|
+
const match = findMountedVfs(path);
|
|
309
|
+
if (!match) {
|
|
310
|
+
return originalFsMethods.rmdir(path, options, callback);
|
|
311
|
+
}
|
|
312
|
+
const resolvedCallback = typeof options === "function" ? options : callback;
|
|
313
|
+
if (typeof resolvedCallback !== "function") {
|
|
314
|
+
return originalFsMethods.rmdir(path, options, callback);
|
|
315
|
+
}
|
|
316
|
+
withCallback(match.vfs.promises.rmdir(match.path, typeof options === "function" ? void 0 : options), resolvedCallback);
|
|
317
|
+
});
|
|
318
|
+
const promisePatches = {
|
|
319
|
+
async writeFile(file, data, options) {
|
|
320
|
+
const match = findMountedVfs(file);
|
|
321
|
+
if (!match) {
|
|
322
|
+
return originalPromiseMethods.writeFile(file, data, options);
|
|
323
|
+
}
|
|
324
|
+
return match.vfs.promises.writeFile(match.path, data, options);
|
|
325
|
+
},
|
|
326
|
+
async appendFile(file, data, options) {
|
|
327
|
+
const match = findMountedVfs(file);
|
|
328
|
+
if (!match) {
|
|
329
|
+
return originalPromiseMethods.appendFile(file, data, options);
|
|
330
|
+
}
|
|
331
|
+
return match.vfs.promises.appendFile(match.path, data, options);
|
|
332
|
+
},
|
|
333
|
+
async mkdir(path, options) {
|
|
334
|
+
const match = findMountedVfs(path);
|
|
335
|
+
if (!match) {
|
|
336
|
+
return originalPromiseMethods.mkdir(path, options);
|
|
337
|
+
}
|
|
338
|
+
return match.vfs.promises.mkdir(match.path, options);
|
|
339
|
+
},
|
|
340
|
+
async rename(oldPath, newPath) {
|
|
341
|
+
const match = findMountedVfsForPaths([oldPath, newPath], "rename");
|
|
342
|
+
if (!match) {
|
|
343
|
+
return originalPromiseMethods.rename(oldPath, newPath);
|
|
344
|
+
}
|
|
345
|
+
const [normalizedOld, normalizedNew] = match.paths;
|
|
346
|
+
return match.vfs.promises.rename(normalizedOld, normalizedNew);
|
|
347
|
+
},
|
|
348
|
+
async unlink(path) {
|
|
349
|
+
const match = findMountedVfs(path);
|
|
350
|
+
if (!match) {
|
|
351
|
+
return originalPromiseMethods.unlink(path);
|
|
352
|
+
}
|
|
353
|
+
return match.vfs.promises.unlink(match.path);
|
|
354
|
+
},
|
|
355
|
+
async rmdir(path, options) {
|
|
356
|
+
const match = findMountedVfs(path);
|
|
357
|
+
if (!match) {
|
|
358
|
+
return originalPromiseMethods.rmdir(path, options);
|
|
359
|
+
}
|
|
360
|
+
return match.vfs.promises.rmdir(match.path, options);
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
mutableFs.promises.writeFile = promisePatches.writeFile;
|
|
364
|
+
mutableFs.promises.appendFile = promisePatches.appendFile;
|
|
365
|
+
mutableFs.promises.mkdir = promisePatches.mkdir;
|
|
366
|
+
mutableFs.promises.rename = promisePatches.rename;
|
|
367
|
+
mutableFs.promises.unlink = promisePatches.unlink;
|
|
368
|
+
mutableFs.promises.rmdir = promisePatches.rmdir;
|
|
369
|
+
mutableFsPromises.writeFile = promisePatches.writeFile;
|
|
370
|
+
mutableFsPromises.appendFile = promisePatches.appendFile;
|
|
371
|
+
mutableFsPromises.mkdir = promisePatches.mkdir;
|
|
372
|
+
mutableFsPromises.rename = promisePatches.rename;
|
|
373
|
+
mutableFsPromises.unlink = promisePatches.unlink;
|
|
374
|
+
mutableFsPromises.rmdir = promisePatches.rmdir;
|
|
375
|
+
syncBuiltinESMExports();
|
|
376
|
+
installed = true;
|
|
377
|
+
}
|
|
378
|
+
function uninstallFsWritePatches() {
|
|
379
|
+
if (!installed || activeVfs.size > 0) {
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
mutableFs.writeFileSync = originalFsMethods.writeFileSync;
|
|
383
|
+
mutableFs.appendFileSync = originalFsMethods.appendFileSync;
|
|
384
|
+
mutableFs.mkdirSync = originalFsMethods.mkdirSync;
|
|
385
|
+
mutableFs.renameSync = originalFsMethods.renameSync;
|
|
386
|
+
mutableFs.unlinkSync = originalFsMethods.unlinkSync;
|
|
387
|
+
mutableFs.rmdirSync = originalFsMethods.rmdirSync;
|
|
388
|
+
mutableFs.writeFile = originalFsMethods.writeFile;
|
|
389
|
+
mutableFs.appendFile = originalFsMethods.appendFile;
|
|
390
|
+
mutableFs.mkdir = originalFsMethods.mkdir;
|
|
391
|
+
mutableFs.rename = originalFsMethods.rename;
|
|
392
|
+
mutableFs.unlink = originalFsMethods.unlink;
|
|
393
|
+
mutableFs.rmdir = originalFsMethods.rmdir;
|
|
394
|
+
mutableFs.promises.writeFile = originalPromiseMethods.writeFile;
|
|
395
|
+
mutableFs.promises.appendFile = originalPromiseMethods.appendFile;
|
|
396
|
+
mutableFs.promises.mkdir = originalPromiseMethods.mkdir;
|
|
397
|
+
mutableFs.promises.rename = originalPromiseMethods.rename;
|
|
398
|
+
mutableFs.promises.unlink = originalPromiseMethods.unlink;
|
|
399
|
+
mutableFs.promises.rmdir = originalPromiseMethods.rmdir;
|
|
400
|
+
mutableFsPromises.writeFile = originalPromiseMethods.writeFile;
|
|
401
|
+
mutableFsPromises.appendFile = originalPromiseMethods.appendFile;
|
|
402
|
+
mutableFsPromises.mkdir = originalPromiseMethods.mkdir;
|
|
403
|
+
mutableFsPromises.rename = originalPromiseMethods.rename;
|
|
404
|
+
mutableFsPromises.unlink = originalPromiseMethods.unlink;
|
|
405
|
+
mutableFsPromises.rmdir = originalPromiseMethods.rmdir;
|
|
406
|
+
syncBuiltinESMExports();
|
|
407
|
+
installed = false;
|
|
408
|
+
}
|
|
409
|
+
function registerMountedVfs(vfs) {
|
|
410
|
+
activeVfs.add(vfs);
|
|
411
|
+
installFsWritePatches();
|
|
412
|
+
}
|
|
413
|
+
function deregisterMountedVfs(vfs) {
|
|
414
|
+
activeVfs.delete(vfs);
|
|
415
|
+
uninstallFsWritePatches();
|
|
416
|
+
}
|
|
417
|
+
function wrapVfsWithFsWritePatches(vfs) {
|
|
418
|
+
const patchableVfs = vfs;
|
|
419
|
+
const originalMount = vfs.mount.bind(vfs);
|
|
420
|
+
const originalUnmount = vfs.unmount.bind(vfs);
|
|
421
|
+
const originalDispose = patchableVfs[Symbol.dispose]?.bind(vfs);
|
|
422
|
+
let registered = false;
|
|
423
|
+
patchableVfs.mount = ((prefix) => {
|
|
424
|
+
const mounted = originalMount(prefix);
|
|
425
|
+
if (!registered) {
|
|
426
|
+
registerMountedVfs(patchableVfs);
|
|
427
|
+
registered = true;
|
|
428
|
+
}
|
|
429
|
+
return mounted;
|
|
430
|
+
});
|
|
431
|
+
patchableVfs.unmount = (() => {
|
|
432
|
+
if (registered) {
|
|
433
|
+
deregisterMountedVfs(patchableVfs);
|
|
434
|
+
registered = false;
|
|
435
|
+
}
|
|
436
|
+
return originalUnmount();
|
|
437
|
+
});
|
|
438
|
+
if (originalDispose) {
|
|
439
|
+
patchableVfs[Symbol.dispose] = (() => {
|
|
440
|
+
if (registered) {
|
|
441
|
+
deregisterMountedVfs(patchableVfs);
|
|
442
|
+
registered = false;
|
|
443
|
+
}
|
|
444
|
+
return originalDispose();
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
return patchableVfs;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// src/pathing.ts
|
|
451
|
+
import { posix as pathPosix } from "path";
|
|
452
|
+
function normalizeVfsPath(inputPath) {
|
|
453
|
+
const normalized = pathPosix.normalize(inputPath.replace(/\\/g, "/"));
|
|
454
|
+
const absolute = normalized.startsWith("/") ? normalized : `/${normalized}`;
|
|
455
|
+
return absolute;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// src/metadata.ts
|
|
459
|
+
function normalizeMode(kind, mode) {
|
|
460
|
+
if (typeof mode === "number" && Number.isFinite(mode)) {
|
|
461
|
+
return mode;
|
|
462
|
+
}
|
|
463
|
+
return kind === "directory" ? 493 : 420;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// src/TinyCloudVfsProvider.ts
|
|
467
|
+
var S_IFREG = 32768;
|
|
468
|
+
var S_IFDIR = 16384;
|
|
469
|
+
var DEFAULT_BLOCK_SIZE = 4096;
|
|
470
|
+
function createStats(metadata) {
|
|
471
|
+
const mode = metadata.kind === "directory" ? metadata.mode | S_IFDIR : metadata.mode | S_IFREG;
|
|
472
|
+
return {
|
|
473
|
+
dev: 0,
|
|
474
|
+
mode,
|
|
475
|
+
nlink: 1,
|
|
476
|
+
uid: process.getuid?.() ?? 0,
|
|
477
|
+
gid: process.getgid?.() ?? 0,
|
|
478
|
+
rdev: 0,
|
|
479
|
+
blksize: DEFAULT_BLOCK_SIZE,
|
|
480
|
+
ino: 0,
|
|
481
|
+
size: metadata.kind === "directory" ? DEFAULT_BLOCK_SIZE : metadata.size,
|
|
482
|
+
blocks: metadata.kind === "directory" ? 8 : Math.ceil(metadata.size / 512),
|
|
483
|
+
atimeMs: metadata.mtimeMs,
|
|
484
|
+
mtimeMs: metadata.mtimeMs,
|
|
485
|
+
ctimeMs: metadata.ctimeMs,
|
|
486
|
+
birthtimeMs: metadata.birthtimeMs,
|
|
487
|
+
atime: new Date(metadata.mtimeMs),
|
|
488
|
+
mtime: new Date(metadata.mtimeMs),
|
|
489
|
+
ctime: new Date(metadata.ctimeMs),
|
|
490
|
+
birthtime: new Date(metadata.birthtimeMs),
|
|
491
|
+
isFile: () => metadata.kind === "file",
|
|
492
|
+
isDirectory: () => metadata.kind === "directory",
|
|
493
|
+
isSymbolicLink: () => false,
|
|
494
|
+
isBlockDevice: () => false,
|
|
495
|
+
isCharacterDevice: () => false,
|
|
496
|
+
isFIFO: () => false,
|
|
497
|
+
isSocket: () => false
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
function createDirent(entry) {
|
|
501
|
+
return {
|
|
502
|
+
name: entry.name,
|
|
503
|
+
parentPath: entry.parentPath,
|
|
504
|
+
path: entry.parentPath,
|
|
505
|
+
isFile: () => entry.kind === "file",
|
|
506
|
+
isDirectory: () => entry.kind === "directory",
|
|
507
|
+
isSymbolicLink: () => false,
|
|
508
|
+
isBlockDevice: () => false,
|
|
509
|
+
isCharacterDevice: () => false,
|
|
510
|
+
isFIFO: () => false,
|
|
511
|
+
isSocket: () => false
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
function expectStatResult(response) {
|
|
515
|
+
if (!response.ok) {
|
|
516
|
+
throw fromWorkerError(response.error);
|
|
517
|
+
}
|
|
518
|
+
if (!response.result || !("metadata" in response.result)) {
|
|
519
|
+
throw new Error("worker returned an unexpected stat payload");
|
|
520
|
+
}
|
|
521
|
+
return response.result.metadata;
|
|
522
|
+
}
|
|
523
|
+
function expectReadFileResult(response) {
|
|
524
|
+
if (!response.ok) {
|
|
525
|
+
throw fromWorkerError(response.error);
|
|
526
|
+
}
|
|
527
|
+
if (!response.result || !("metadata" in response.result) || !("content" in response.result)) {
|
|
528
|
+
throw new Error("worker returned an unexpected readFile payload");
|
|
529
|
+
}
|
|
530
|
+
return response.result;
|
|
531
|
+
}
|
|
532
|
+
function expectReaddirResult(response) {
|
|
533
|
+
if (!response.ok) {
|
|
534
|
+
throw fromWorkerError(response.error);
|
|
535
|
+
}
|
|
536
|
+
if (!response.result || !("entries" in response.result)) {
|
|
537
|
+
throw new Error("worker returned an unexpected readdir payload");
|
|
538
|
+
}
|
|
539
|
+
return response.result.entries;
|
|
540
|
+
}
|
|
541
|
+
function isWriteFlag(flags = "r") {
|
|
542
|
+
return /[wa+]/.test(flags);
|
|
543
|
+
}
|
|
544
|
+
function isAppendFlag(flags = "r") {
|
|
545
|
+
return flags.startsWith("a");
|
|
546
|
+
}
|
|
547
|
+
function isTruncateFlag(flags = "r") {
|
|
548
|
+
return flags.startsWith("w");
|
|
549
|
+
}
|
|
550
|
+
var TinyCloudVfsFileHandle = class {
|
|
551
|
+
bridge;
|
|
552
|
+
state;
|
|
553
|
+
closed = false;
|
|
554
|
+
dirty = false;
|
|
555
|
+
position = 0;
|
|
556
|
+
constructor(bridge, state) {
|
|
557
|
+
this.bridge = bridge;
|
|
558
|
+
this.state = state;
|
|
559
|
+
if (isAppendFlag(state.flags)) {
|
|
560
|
+
this.position = state.content.length;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
ensureOpen() {
|
|
564
|
+
if (this.closed) {
|
|
565
|
+
throw new Error("file handle is closed");
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
commit() {
|
|
569
|
+
if (!this.dirty) {
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
const response = this.bridge.requestSync({
|
|
573
|
+
type: "writeFile",
|
|
574
|
+
path: this.state.path,
|
|
575
|
+
content: this.state.content,
|
|
576
|
+
mode: this.state.mode
|
|
577
|
+
});
|
|
578
|
+
if (!response.ok) {
|
|
579
|
+
throw fromWorkerError(response.error);
|
|
580
|
+
}
|
|
581
|
+
this.dirty = false;
|
|
582
|
+
}
|
|
583
|
+
readSync(buffer, offset, length, position) {
|
|
584
|
+
this.ensureOpen();
|
|
585
|
+
const readFrom = position ?? this.position;
|
|
586
|
+
const available = Math.max(0, this.state.content.length - readFrom);
|
|
587
|
+
const bytesToRead = Math.min(length, available);
|
|
588
|
+
if (bytesToRead === 0) {
|
|
589
|
+
return 0;
|
|
590
|
+
}
|
|
591
|
+
this.state.content.copy(buffer, offset, readFrom, readFrom + bytesToRead);
|
|
592
|
+
if (position === null || position === void 0) {
|
|
593
|
+
this.position = readFrom + bytesToRead;
|
|
594
|
+
}
|
|
595
|
+
return bytesToRead;
|
|
596
|
+
}
|
|
597
|
+
writeSync(buffer, offset, length, position) {
|
|
598
|
+
this.ensureOpen();
|
|
599
|
+
const writeFrom = position ?? this.position;
|
|
600
|
+
const data = buffer.subarray(offset, offset + length);
|
|
601
|
+
const requiredLength = writeFrom + data.length;
|
|
602
|
+
if (requiredLength > this.state.content.length) {
|
|
603
|
+
const next = Buffer3.alloc(requiredLength);
|
|
604
|
+
this.state.content.copy(next, 0, 0, this.state.content.length);
|
|
605
|
+
this.state.content = next;
|
|
606
|
+
}
|
|
607
|
+
data.copy(this.state.content, writeFrom);
|
|
608
|
+
this.state.metadata = {
|
|
609
|
+
...this.state.metadata,
|
|
610
|
+
size: this.state.content.length,
|
|
611
|
+
mtimeMs: Date.now(),
|
|
612
|
+
ctimeMs: Date.now()
|
|
613
|
+
};
|
|
614
|
+
this.dirty = true;
|
|
615
|
+
if (position === null || position === void 0) {
|
|
616
|
+
this.position = writeFrom + data.length;
|
|
617
|
+
}
|
|
618
|
+
return data.length;
|
|
619
|
+
}
|
|
620
|
+
readFileSync(options) {
|
|
621
|
+
this.ensureOpen();
|
|
622
|
+
const encoding = typeof options === "string" ? options : options?.encoding;
|
|
623
|
+
if (encoding) {
|
|
624
|
+
return this.state.content.toString(encoding);
|
|
625
|
+
}
|
|
626
|
+
return Buffer3.from(this.state.content);
|
|
627
|
+
}
|
|
628
|
+
writeFileSync(data, options) {
|
|
629
|
+
this.ensureOpen();
|
|
630
|
+
const encoding = typeof options === "string" ? options : options?.encoding;
|
|
631
|
+
const buffer = typeof data === "string" ? Buffer3.from(data, encoding) : Buffer3.from(data);
|
|
632
|
+
if (isAppendFlag(this.state.flags)) {
|
|
633
|
+
this.state.content = Buffer3.concat([this.state.content, buffer]);
|
|
634
|
+
} else {
|
|
635
|
+
this.state.content = Buffer3.from(buffer);
|
|
636
|
+
}
|
|
637
|
+
this.state.metadata = {
|
|
638
|
+
...this.state.metadata,
|
|
639
|
+
size: this.state.content.length,
|
|
640
|
+
mtimeMs: Date.now(),
|
|
641
|
+
ctimeMs: Date.now(),
|
|
642
|
+
mode: normalizeMode("file", typeof options === "object" ? options?.mode : this.state.mode)
|
|
643
|
+
};
|
|
644
|
+
this.dirty = true;
|
|
645
|
+
this.position = this.state.content.length;
|
|
646
|
+
}
|
|
647
|
+
statSync() {
|
|
648
|
+
this.ensureOpen();
|
|
649
|
+
return createStats({
|
|
650
|
+
...this.state.metadata,
|
|
651
|
+
size: this.state.content.length
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
closeSync() {
|
|
655
|
+
this.ensureOpen();
|
|
656
|
+
this.commit();
|
|
657
|
+
this.closed = true;
|
|
658
|
+
}
|
|
659
|
+
};
|
|
660
|
+
var TinyCloudVfsProvider = class extends VirtualProvider {
|
|
661
|
+
bridge;
|
|
662
|
+
forceReadOnly;
|
|
663
|
+
constructor(options) {
|
|
664
|
+
super();
|
|
665
|
+
this.forceReadOnly = options.readOnly === true;
|
|
666
|
+
this.bridge = new TinyCloudVfsBridge({
|
|
667
|
+
source: options.source,
|
|
668
|
+
mountPrefix: options.mountPrefix ?? ""
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
get readonly() {
|
|
672
|
+
return this.forceReadOnly;
|
|
673
|
+
}
|
|
674
|
+
close() {
|
|
675
|
+
this.bridge.close();
|
|
676
|
+
}
|
|
677
|
+
ensureWritable(path, syscall) {
|
|
678
|
+
if (this.forceReadOnly) {
|
|
679
|
+
throw createEROFS(syscall, path);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
normalize(path) {
|
|
683
|
+
return normalizeVfsPath(path);
|
|
684
|
+
}
|
|
685
|
+
openSync(path, flags = "r", mode) {
|
|
686
|
+
const normalized = this.normalize(path);
|
|
687
|
+
const writable = isWriteFlag(flags);
|
|
688
|
+
if (writable) {
|
|
689
|
+
this.ensureWritable(normalized, "open");
|
|
690
|
+
}
|
|
691
|
+
let metadata;
|
|
692
|
+
let content = Buffer3.alloc(0);
|
|
693
|
+
try {
|
|
694
|
+
const response = expectReadFileResult(this.bridge.requestSync({ type: "readFile", path: normalized }));
|
|
695
|
+
metadata = response.metadata;
|
|
696
|
+
content = Buffer3.from(response.content);
|
|
697
|
+
if (isTruncateFlag(flags)) {
|
|
698
|
+
content = Buffer3.alloc(0);
|
|
699
|
+
metadata = {
|
|
700
|
+
...metadata,
|
|
701
|
+
size: 0,
|
|
702
|
+
mode: normalizeMode("file", mode ?? metadata.mode)
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
} catch (error) {
|
|
706
|
+
const typed = error;
|
|
707
|
+
if (typed.code !== "ENOENT" || !writable) {
|
|
708
|
+
throw error;
|
|
709
|
+
}
|
|
710
|
+
metadata = {
|
|
711
|
+
kind: "file",
|
|
712
|
+
size: 0,
|
|
713
|
+
mode: normalizeMode("file", mode),
|
|
714
|
+
ctimeMs: Date.now(),
|
|
715
|
+
mtimeMs: Date.now(),
|
|
716
|
+
birthtimeMs: Date.now()
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
if (metadata.kind !== "file") {
|
|
720
|
+
throw createEISDIR("open", normalized);
|
|
721
|
+
}
|
|
722
|
+
return new TinyCloudVfsFileHandle(this.bridge, {
|
|
723
|
+
path: normalized,
|
|
724
|
+
flags,
|
|
725
|
+
mode: normalizeMode("file", mode ?? metadata.mode),
|
|
726
|
+
content,
|
|
727
|
+
metadata
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
async open(path, flags = "r", mode) {
|
|
731
|
+
return this.openSync(path, flags, mode);
|
|
732
|
+
}
|
|
733
|
+
statSync(path) {
|
|
734
|
+
const normalized = this.normalize(path);
|
|
735
|
+
return createStats(expectStatResult(this.bridge.requestSync({ type: "stat", path: normalized })));
|
|
736
|
+
}
|
|
737
|
+
async stat(path) {
|
|
738
|
+
return this.statSync(path);
|
|
739
|
+
}
|
|
740
|
+
lstatSync(path) {
|
|
741
|
+
return this.statSync(path);
|
|
742
|
+
}
|
|
743
|
+
async lstat(path) {
|
|
744
|
+
return this.lstatSync(path);
|
|
745
|
+
}
|
|
746
|
+
readdirSync(path, options) {
|
|
747
|
+
const normalized = this.normalize(path);
|
|
748
|
+
const entries = expectReaddirResult(this.bridge.requestSync({ type: "readdir", path: normalized }));
|
|
749
|
+
if (options?.withFileTypes) {
|
|
750
|
+
return entries.map(createDirent);
|
|
751
|
+
}
|
|
752
|
+
return entries.map((entry) => entry.name);
|
|
753
|
+
}
|
|
754
|
+
async readdir(path, options) {
|
|
755
|
+
return this.readdirSync(path, options);
|
|
756
|
+
}
|
|
757
|
+
mkdirSync(path, options) {
|
|
758
|
+
const normalized = this.normalize(path);
|
|
759
|
+
this.ensureWritable(normalized, "mkdir");
|
|
760
|
+
const response = this.bridge.requestSync({
|
|
761
|
+
type: "mkdir",
|
|
762
|
+
path: normalized,
|
|
763
|
+
recursive: options?.recursive === true,
|
|
764
|
+
mode: options?.mode
|
|
765
|
+
});
|
|
766
|
+
if (!response.ok) {
|
|
767
|
+
throw fromWorkerError(response.error);
|
|
768
|
+
}
|
|
769
|
+
return options?.recursive ? normalized : void 0;
|
|
770
|
+
}
|
|
771
|
+
async mkdir(path, options) {
|
|
772
|
+
return this.mkdirSync(path, options);
|
|
773
|
+
}
|
|
774
|
+
rmdirSync(path) {
|
|
775
|
+
const normalized = this.normalize(path);
|
|
776
|
+
this.ensureWritable(normalized, "rmdir");
|
|
777
|
+
const response = this.bridge.requestSync({ type: "rmdir", path: normalized });
|
|
778
|
+
if (!response.ok) {
|
|
779
|
+
throw fromWorkerError(response.error);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
async rmdir(path) {
|
|
783
|
+
this.rmdirSync(path);
|
|
784
|
+
}
|
|
785
|
+
unlinkSync(path) {
|
|
786
|
+
const normalized = this.normalize(path);
|
|
787
|
+
this.ensureWritable(normalized, "unlink");
|
|
788
|
+
const response = this.bridge.requestSync({ type: "unlink", path: normalized });
|
|
789
|
+
if (!response.ok) {
|
|
790
|
+
throw fromWorkerError(response.error);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
async unlink(path) {
|
|
794
|
+
this.unlinkSync(path);
|
|
795
|
+
}
|
|
796
|
+
renameSync(oldPath, newPath) {
|
|
797
|
+
const normalizedOld = this.normalize(oldPath);
|
|
798
|
+
const normalizedNew = this.normalize(newPath);
|
|
799
|
+
this.ensureWritable(normalizedOld, "rename");
|
|
800
|
+
const response = this.bridge.requestSync({
|
|
801
|
+
type: "rename",
|
|
802
|
+
oldPath: normalizedOld,
|
|
803
|
+
newPath: normalizedNew
|
|
804
|
+
});
|
|
805
|
+
if (!response.ok) {
|
|
806
|
+
throw fromWorkerError(response.error);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
async rename(oldPath, newPath) {
|
|
810
|
+
this.renameSync(oldPath, newPath);
|
|
811
|
+
}
|
|
812
|
+
};
|
|
813
|
+
function toSessionSource(node, host) {
|
|
814
|
+
if (!node.session) {
|
|
815
|
+
throw new Error("TinyCloudNode has no active session; call signIn() or restoreSession() first.");
|
|
816
|
+
}
|
|
817
|
+
const resolvedHost = host ?? node.config?.host ?? "https://node.tinycloud.xyz";
|
|
818
|
+
return {
|
|
819
|
+
kind: "session",
|
|
820
|
+
host: resolvedHost,
|
|
821
|
+
session: node.session
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
function createTinyCloudVfs(options) {
|
|
825
|
+
const provider = new TinyCloudVfsProvider(options);
|
|
826
|
+
const vfs = wrapVfsWithFsWritePatches(create(provider, {
|
|
827
|
+
moduleHooks: options.moduleHooks,
|
|
828
|
+
overlay: options.overlay,
|
|
829
|
+
virtualCwd: options.virtualCwd
|
|
830
|
+
}));
|
|
831
|
+
if (options.mountPoint) {
|
|
832
|
+
vfs.mount(options.mountPoint);
|
|
833
|
+
}
|
|
834
|
+
return { provider, vfs };
|
|
835
|
+
}
|
|
836
|
+
function createTinyCloudVfsFromNode(node, options = {}) {
|
|
837
|
+
return createTinyCloudVfs({
|
|
838
|
+
...options,
|
|
839
|
+
source: toSessionSource(node, options.host)
|
|
840
|
+
});
|
|
841
|
+
}
|
|
842
|
+
async function createTinyCloudDelegatedVfs(options) {
|
|
843
|
+
if (!options.node.session) {
|
|
844
|
+
throw new Error("TinyCloudNode has no active session; call signIn() or restoreSession() first.");
|
|
845
|
+
}
|
|
846
|
+
if (typeof options.node.useDelegation !== "function") {
|
|
847
|
+
throw new Error("TinyCloudNode does not expose useDelegation().");
|
|
848
|
+
}
|
|
849
|
+
const access = await options.node.useDelegation(options.delegation);
|
|
850
|
+
if (!access?.session) {
|
|
851
|
+
throw new Error("Delegated access does not expose a resolved session snapshot.");
|
|
852
|
+
}
|
|
853
|
+
return createTinyCloudVfs({
|
|
854
|
+
...options,
|
|
855
|
+
source: {
|
|
856
|
+
kind: "resolved-delegation",
|
|
857
|
+
host: options.host ?? options.delegation.host ?? "https://node.tinycloud.xyz",
|
|
858
|
+
session: access.session,
|
|
859
|
+
kvPrefix: access.kv?.config?.prefix ?? ""
|
|
860
|
+
}
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
export {
|
|
864
|
+
TinyCloudVfsProvider,
|
|
865
|
+
createTinyCloudDelegatedVfs,
|
|
866
|
+
createTinyCloudVfs,
|
|
867
|
+
createTinyCloudVfsFromNode
|
|
868
|
+
};
|
|
869
|
+
//# sourceMappingURL=index.js.map
|