@componentor/fs 3.0.7 → 3.0.9

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/README.md CHANGED
@@ -509,6 +509,20 @@ Make sure `opfsSync` is enabled (it's `true` by default). Files are mirrored to
509
509
 
510
510
  ## Changelog
511
511
 
512
+ ### v3.0.9 (2026)
513
+
514
+ **Improvements:**
515
+ - `unpackToOPFS`, `loadFromOPFS`, and `repairVFS` now accept an optional `fs` parameter (a running `VFSFileSystem` instance) so they work from any tab — leader or follower — without stopping the VFS
516
+ - When `fs` is not provided, falls back to direct `.vfs.bin` access via VFSEngine (requires VFS to be stopped or a Worker context)
517
+ - `repairVFS` with a running instance uses OPFS as source of truth: rebuilds VFS from OPFS, then syncs back for full consistency
518
+
519
+ ### v3.0.8 (2026)
520
+
521
+ **Improvements:**
522
+ - Add VFS helper functions: `unpackToOPFS`, `loadFromOPFS`, and `repairVFS` for VFS maintenance, migration, and recovery
523
+ - Helpers work in both Worker (sync access handle) and main thread (in-memory buffer + async writable) contexts
524
+ - Remove redundant I/O call in `unpackToOPFS` directory creation
525
+
512
526
  ### v3.0.7 (2026)
513
527
 
514
528
  **Fixes:**
package/dist/index.js CHANGED
@@ -3236,6 +3236,86 @@ var VFSEngine = class {
3236
3236
  };
3237
3237
 
3238
3238
  // src/helpers.ts
3239
+ var MemoryHandle = class {
3240
+ buf;
3241
+ len;
3242
+ constructor(initialData) {
3243
+ if (initialData && initialData.byteLength > 0) {
3244
+ this.buf = new Uint8Array(initialData);
3245
+ this.len = initialData.byteLength;
3246
+ } else {
3247
+ this.buf = new Uint8Array(1024 * 1024);
3248
+ this.len = 0;
3249
+ }
3250
+ }
3251
+ getSize() {
3252
+ return this.len;
3253
+ }
3254
+ read(target, opts) {
3255
+ const offset = opts?.at ?? 0;
3256
+ const dst = new Uint8Array(target.buffer, target.byteOffset, target.byteLength);
3257
+ const bytesToRead = Math.min(dst.length, this.len - offset);
3258
+ if (bytesToRead <= 0) return 0;
3259
+ dst.set(this.buf.subarray(offset, offset + bytesToRead));
3260
+ return bytesToRead;
3261
+ }
3262
+ write(data, opts) {
3263
+ const offset = opts?.at ?? 0;
3264
+ const src = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
3265
+ const needed = offset + src.length;
3266
+ if (needed > this.buf.length) {
3267
+ this.grow(needed);
3268
+ }
3269
+ this.buf.set(src, offset);
3270
+ if (needed > this.len) this.len = needed;
3271
+ return src.length;
3272
+ }
3273
+ truncate(size) {
3274
+ if (size > this.buf.length) {
3275
+ this.grow(size);
3276
+ }
3277
+ if (size > this.len) {
3278
+ this.buf.fill(0, this.len, size);
3279
+ }
3280
+ this.len = size;
3281
+ }
3282
+ flush() {
3283
+ }
3284
+ close() {
3285
+ }
3286
+ getBuffer() {
3287
+ return this.buf.buffer.slice(0, this.len);
3288
+ }
3289
+ grow(minSize) {
3290
+ const newSize = Math.max(minSize, this.buf.length * 2);
3291
+ const newBuf = new Uint8Array(newSize);
3292
+ newBuf.set(this.buf.subarray(0, this.len));
3293
+ this.buf = newBuf;
3294
+ }
3295
+ };
3296
+ async function openVFSHandle(fileHandle) {
3297
+ try {
3298
+ const handle = await fileHandle.createSyncAccessHandle();
3299
+ return { handle, isMemory: false };
3300
+ } catch {
3301
+ const file = await fileHandle.getFile();
3302
+ const data = await file.arrayBuffer();
3303
+ return { handle: new MemoryHandle(data), isMemory: true };
3304
+ }
3305
+ }
3306
+ async function openFreshVFSHandle(fileHandle) {
3307
+ try {
3308
+ const handle = await fileHandle.createSyncAccessHandle();
3309
+ return { handle, isMemory: false };
3310
+ } catch {
3311
+ return { handle: new MemoryHandle(), isMemory: true };
3312
+ }
3313
+ }
3314
+ async function saveMemoryHandle(fileHandle, memHandle) {
3315
+ const writable = await fileHandle.createWritable();
3316
+ await writable.write(memHandle.getBuffer());
3317
+ await writable.close();
3318
+ }
3239
3319
  async function navigateToRoot(root) {
3240
3320
  let dir = await navigator.storage.getDirectory();
3241
3321
  if (root && root !== "/") {
@@ -3262,15 +3342,21 @@ async function writeOPFSFile(rootDir, path, data) {
3262
3342
  const parentDir = await ensureParentDirs(rootDir, path);
3263
3343
  const name = basename2(path);
3264
3344
  const fileHandle = await parentDir.getFileHandle(name, { create: true });
3265
- const syncHandle = await fileHandle.createSyncAccessHandle();
3266
3345
  try {
3267
- syncHandle.truncate(0);
3268
- if (data.byteLength > 0) {
3269
- syncHandle.write(data, { at: 0 });
3346
+ const syncHandle = await fileHandle.createSyncAccessHandle();
3347
+ try {
3348
+ syncHandle.truncate(0);
3349
+ if (data.byteLength > 0) {
3350
+ syncHandle.write(data, { at: 0 });
3351
+ }
3352
+ syncHandle.flush();
3353
+ } finally {
3354
+ syncHandle.close();
3270
3355
  }
3271
- syncHandle.flush();
3272
- } finally {
3273
- syncHandle.close();
3356
+ } catch {
3357
+ const writable = await fileHandle.createWritable();
3358
+ await writable.write(data);
3359
+ await writable.close();
3274
3360
  }
3275
3361
  }
3276
3362
  async function clearDirectory(dir, skip) {
@@ -3299,10 +3385,54 @@ async function readOPFSRecursive(dir, prefix, skip) {
3299
3385
  }
3300
3386
  return result;
3301
3387
  }
3302
- async function unpackToOPFS(root = "/") {
3388
+ function readVFSRecursive(fs, vfsPath) {
3389
+ const result = [];
3390
+ let entries;
3391
+ try {
3392
+ entries = fs.readdirSync(vfsPath, { withFileTypes: true });
3393
+ } catch {
3394
+ return result;
3395
+ }
3396
+ for (const entry of entries) {
3397
+ const fullPath = vfsPath === "/" ? `/${entry.name}` : `${vfsPath}/${entry.name}`;
3398
+ if (entry.isDirectory()) {
3399
+ result.push({ path: fullPath, type: "directory" });
3400
+ result.push(...readVFSRecursive(fs, fullPath));
3401
+ } else {
3402
+ try {
3403
+ const data = fs.readFileSync(fullPath);
3404
+ result.push({ path: fullPath, type: "file", data });
3405
+ } catch {
3406
+ }
3407
+ }
3408
+ }
3409
+ return result;
3410
+ }
3411
+ async function unpackToOPFS(root = "/", fs) {
3303
3412
  const rootDir = await navigateToRoot(root);
3413
+ if (fs) {
3414
+ const vfsEntries = readVFSRecursive(fs, "/");
3415
+ let files2 = 0;
3416
+ let directories2 = 0;
3417
+ for (const entry of vfsEntries) {
3418
+ if (entry.type === "directory") {
3419
+ const name = basename2(entry.path);
3420
+ const parent = await ensureParentDirs(rootDir, entry.path);
3421
+ await parent.getDirectoryHandle(name, { create: true });
3422
+ directories2++;
3423
+ } else {
3424
+ try {
3425
+ await writeOPFSFile(rootDir, entry.path, entry.data ?? new Uint8Array(0));
3426
+ files2++;
3427
+ } catch (err) {
3428
+ console.warn(`[VFS] Failed to write OPFS file ${entry.path}: ${err.message}`);
3429
+ }
3430
+ }
3431
+ }
3432
+ return { files: files2, directories: directories2 };
3433
+ }
3304
3434
  const vfsFileHandle = await rootDir.getFileHandle(".vfs.bin");
3305
- const handle = await vfsFileHandle.createSyncAccessHandle();
3435
+ const { handle } = await openVFSHandle(vfsFileHandle);
3306
3436
  let entries;
3307
3437
  try {
3308
3438
  const engine = new VFSEngine();
@@ -3317,30 +3447,65 @@ async function unpackToOPFS(root = "/") {
3317
3447
  for (const entry of entries) {
3318
3448
  if (entry.path === "/") continue;
3319
3449
  if (entry.type === INODE_TYPE.DIRECTORY) {
3320
- await ensureParentDirs(rootDir, entry.path + "/dummy");
3321
3450
  const name = basename2(entry.path);
3322
3451
  const parent = await ensureParentDirs(rootDir, entry.path);
3323
3452
  await parent.getDirectoryHandle(name, { create: true });
3324
3453
  directories++;
3325
- } else if (entry.type === INODE_TYPE.FILE) {
3326
- await writeOPFSFile(rootDir, entry.path, entry.data ?? new Uint8Array(0));
3327
- files++;
3328
- } else if (entry.type === INODE_TYPE.SYMLINK) {
3454
+ } else if (entry.type === INODE_TYPE.FILE || entry.type === INODE_TYPE.SYMLINK) {
3329
3455
  await writeOPFSFile(rootDir, entry.path, entry.data ?? new Uint8Array(0));
3330
3456
  files++;
3331
3457
  }
3332
3458
  }
3333
3459
  return { files, directories };
3334
3460
  }
3335
- async function loadFromOPFS(root = "/") {
3461
+ async function loadFromOPFS(root = "/", fs) {
3336
3462
  const rootDir = await navigateToRoot(root);
3337
3463
  const opfsEntries = await readOPFSRecursive(rootDir, "", /* @__PURE__ */ new Set([".vfs.bin"]));
3464
+ if (fs) {
3465
+ try {
3466
+ const rootEntries = fs.readdirSync("/");
3467
+ for (const entry of rootEntries) {
3468
+ try {
3469
+ fs.rmSync(`/${entry}`, { recursive: true, force: true });
3470
+ } catch {
3471
+ }
3472
+ }
3473
+ } catch {
3474
+ }
3475
+ const dirs = opfsEntries.filter((e) => e.type === "directory").sort((a, b) => a.path.localeCompare(b.path));
3476
+ let files = 0;
3477
+ let directories = 0;
3478
+ for (const dir of dirs) {
3479
+ try {
3480
+ fs.mkdirSync(dir.path, { recursive: true, mode: 493 });
3481
+ directories++;
3482
+ } catch {
3483
+ }
3484
+ }
3485
+ const fileEntries = opfsEntries.filter((e) => e.type === "file");
3486
+ for (const file of fileEntries) {
3487
+ try {
3488
+ const parentPath = file.path.substring(0, file.path.lastIndexOf("/")) || "/";
3489
+ if (parentPath !== "/") {
3490
+ try {
3491
+ fs.mkdirSync(parentPath, { recursive: true, mode: 493 });
3492
+ } catch {
3493
+ }
3494
+ }
3495
+ fs.writeFileSync(file.path, new Uint8Array(file.data));
3496
+ files++;
3497
+ } catch (err) {
3498
+ console.warn(`[VFS] Failed to write ${file.path}: ${err.message}`);
3499
+ }
3500
+ }
3501
+ return { files, directories };
3502
+ }
3338
3503
  try {
3339
3504
  await rootDir.removeEntry(".vfs.bin");
3340
3505
  } catch (_) {
3341
3506
  }
3342
3507
  const vfsFileHandle = await rootDir.getFileHandle(".vfs.bin", { create: true });
3343
- const handle = await vfsFileHandle.createSyncAccessHandle();
3508
+ const { handle, isMemory } = await openFreshVFSHandle(vfsFileHandle);
3344
3509
  try {
3345
3510
  const engine = new VFSEngine();
3346
3511
  engine.init(handle);
@@ -3357,12 +3522,29 @@ async function loadFromOPFS(root = "/") {
3357
3522
  files++;
3358
3523
  }
3359
3524
  engine.flush();
3525
+ if (isMemory) {
3526
+ await saveMemoryHandle(vfsFileHandle, handle);
3527
+ }
3360
3528
  return { files, directories };
3361
3529
  } finally {
3362
3530
  handle.close();
3363
3531
  }
3364
3532
  }
3365
- async function repairVFS(root = "/") {
3533
+ async function repairVFS(root = "/", fs) {
3534
+ if (fs) {
3535
+ const loadResult = await loadFromOPFS(root, fs);
3536
+ await unpackToOPFS(root, fs);
3537
+ const total = loadResult.files + loadResult.directories;
3538
+ return {
3539
+ recovered: total,
3540
+ lost: 0,
3541
+ entries: []
3542
+ // Detailed entries not available in fs-based path
3543
+ };
3544
+ }
3545
+ return repairVFSRaw(root);
3546
+ }
3547
+ async function repairVFSRaw(root) {
3366
3548
  const rootDir = await navigateToRoot(root);
3367
3549
  const vfsFileHandle = await rootDir.getFileHandle(".vfs.bin");
3368
3550
  const file = await vfsFileHandle.getFile();
@@ -3458,7 +3640,7 @@ async function repairVFS(root = "/") {
3458
3640
  }
3459
3641
  await rootDir.removeEntry(".vfs.bin");
3460
3642
  const newFileHandle = await rootDir.getFileHandle(".vfs.bin", { create: true });
3461
- const handle = await newFileHandle.createSyncAccessHandle();
3643
+ const { handle, isMemory } = await openFreshVFSHandle(newFileHandle);
3462
3644
  try {
3463
3645
  const engine = new VFSEngine();
3464
3646
  engine.init(handle);
@@ -3479,6 +3661,9 @@ async function repairVFS(root = "/") {
3479
3661
  if (result.status !== 0) lost++;
3480
3662
  }
3481
3663
  engine.flush();
3664
+ if (isMemory) {
3665
+ await saveMemoryHandle(newFileHandle, handle);
3666
+ }
3482
3667
  } finally {
3483
3668
  handle.close();
3484
3669
  }