@componentor/fs 2.0.6 → 2.0.8
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 +8 -14
- package/dist/index.cjs +89 -31
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +22 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +89 -31
- package/dist/index.js.map +1 -1
- package/dist/kernel.js +54 -39
- package/dist/kernel.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -315,6 +315,28 @@ declare class OPFSFileSystem {
|
|
|
315
315
|
* Use between major operations to ensure clean state.
|
|
316
316
|
*/
|
|
317
317
|
purgeSync(): void;
|
|
318
|
+
/**
|
|
319
|
+
* Enable or disable debug tracing for handle operations.
|
|
320
|
+
* When enabled, logs handle cache hits, acquisitions, releases, and mode information.
|
|
321
|
+
* @param enabled - Whether to enable debug tracing
|
|
322
|
+
* @returns Debug state information including unsafeModeSupported and cache size
|
|
323
|
+
*/
|
|
324
|
+
setDebugSync(enabled: boolean): {
|
|
325
|
+
debugTrace: boolean;
|
|
326
|
+
unsafeModeSupported: boolean;
|
|
327
|
+
cacheSize: number;
|
|
328
|
+
};
|
|
329
|
+
/**
|
|
330
|
+
* Enable or disable debug tracing for handle operations (async version).
|
|
331
|
+
* When enabled, logs handle cache hits, acquisitions, releases, and mode information.
|
|
332
|
+
* @param enabled - Whether to enable debug tracing
|
|
333
|
+
* @returns Debug state information including unsafeModeSupported and cache size
|
|
334
|
+
*/
|
|
335
|
+
setDebug(enabled: boolean): Promise<{
|
|
336
|
+
debugTrace: boolean;
|
|
337
|
+
unsafeModeSupported: boolean;
|
|
338
|
+
cacheSize: number;
|
|
339
|
+
}>;
|
|
318
340
|
accessSync(filePath: string, _mode?: number): void;
|
|
319
341
|
openSync(filePath: string, flags?: string | number): number;
|
|
320
342
|
closeSync(fd: number): void;
|
package/dist/index.d.ts
CHANGED
|
@@ -315,6 +315,28 @@ declare class OPFSFileSystem {
|
|
|
315
315
|
* Use between major operations to ensure clean state.
|
|
316
316
|
*/
|
|
317
317
|
purgeSync(): void;
|
|
318
|
+
/**
|
|
319
|
+
* Enable or disable debug tracing for handle operations.
|
|
320
|
+
* When enabled, logs handle cache hits, acquisitions, releases, and mode information.
|
|
321
|
+
* @param enabled - Whether to enable debug tracing
|
|
322
|
+
* @returns Debug state information including unsafeModeSupported and cache size
|
|
323
|
+
*/
|
|
324
|
+
setDebugSync(enabled: boolean): {
|
|
325
|
+
debugTrace: boolean;
|
|
326
|
+
unsafeModeSupported: boolean;
|
|
327
|
+
cacheSize: number;
|
|
328
|
+
};
|
|
329
|
+
/**
|
|
330
|
+
* Enable or disable debug tracing for handle operations (async version).
|
|
331
|
+
* When enabled, logs handle cache hits, acquisitions, releases, and mode information.
|
|
332
|
+
* @param enabled - Whether to enable debug tracing
|
|
333
|
+
* @returns Debug state information including unsafeModeSupported and cache size
|
|
334
|
+
*/
|
|
335
|
+
setDebug(enabled: boolean): Promise<{
|
|
336
|
+
debugTrace: boolean;
|
|
337
|
+
unsafeModeSupported: boolean;
|
|
338
|
+
cacheSize: number;
|
|
339
|
+
}>;
|
|
318
340
|
accessSync(filePath: string, _mode?: number): void;
|
|
319
341
|
openSync(filePath: string, flags?: string | number): number;
|
|
320
342
|
closeSync(fd: number): void;
|
package/dist/index.js
CHANGED
|
@@ -426,57 +426,90 @@ let isReady = false;
|
|
|
426
426
|
let cachedRoot = null;
|
|
427
427
|
const dirCache = new Map();
|
|
428
428
|
|
|
429
|
-
// Sync handle cache - MAJOR performance optimization
|
|
430
|
-
//
|
|
429
|
+
// Sync handle cache - MAJOR performance optimization (2-5x speedup)
|
|
430
|
+
// Uses readwrite-unsafe mode when available (no exclusive lock, allows external access)
|
|
431
|
+
// Falls back to readwrite with debounced release for older browsers
|
|
431
432
|
const syncHandleCache = new Map();
|
|
432
|
-
const syncHandleLastAccess = new Map();
|
|
433
433
|
const MAX_HANDLES = 100;
|
|
434
|
-
const HANDLE_IDLE_TIMEOUT = 2000;
|
|
435
|
-
let idleCleanupTimer = null;
|
|
436
434
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
435
|
+
// Track if readwrite-unsafe mode is supported (detected on first use)
|
|
436
|
+
let unsafeModeSupported = null;
|
|
437
|
+
|
|
438
|
+
// Debug tracing - set via 'setDebug' message
|
|
439
|
+
let debugTrace = false;
|
|
440
|
+
function trace(...args) {
|
|
441
|
+
if (debugTrace) console.log('[OPFS-T2]', ...args);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Minimal timer for legacy mode only
|
|
445
|
+
let releaseTimer = null;
|
|
446
|
+
const LEGACY_RELEASE_DELAY = 100;
|
|
447
|
+
|
|
448
|
+
function scheduleHandleRelease() {
|
|
449
|
+
if (unsafeModeSupported) return; // No release needed for readwrite-unsafe
|
|
450
|
+
if (releaseTimer) return; // Already scheduled
|
|
451
|
+
releaseTimer = setTimeout(() => {
|
|
452
|
+
releaseTimer = null;
|
|
453
|
+
const count = syncHandleCache.size;
|
|
454
|
+
for (const h of syncHandleCache.values()) {
|
|
455
|
+
try { h.flush(); h.close(); } catch {}
|
|
456
|
+
}
|
|
457
|
+
syncHandleCache.clear();
|
|
458
|
+
trace('Released ' + count + ' handles (legacy mode debounce)');
|
|
459
|
+
}, LEGACY_RELEASE_DELAY);
|
|
451
460
|
}
|
|
452
461
|
|
|
453
462
|
async function getSyncHandle(filePath, create) {
|
|
454
463
|
const cached = syncHandleCache.get(filePath);
|
|
455
464
|
if (cached) {
|
|
456
|
-
|
|
465
|
+
trace('Handle cache HIT: ' + filePath);
|
|
457
466
|
return cached;
|
|
458
467
|
}
|
|
459
468
|
|
|
460
469
|
// Evict oldest handles if cache is full
|
|
461
470
|
if (syncHandleCache.size >= MAX_HANDLES) {
|
|
462
471
|
const keys = Array.from(syncHandleCache.keys()).slice(0, 10);
|
|
472
|
+
trace('LRU evicting ' + keys.length + ' handles');
|
|
463
473
|
for (const key of keys) {
|
|
464
474
|
const h = syncHandleCache.get(key);
|
|
465
|
-
if (h) { try { h.close(); } catch {} syncHandleCache.delete(key);
|
|
475
|
+
if (h) { try { h.close(); } catch {} syncHandleCache.delete(key); }
|
|
466
476
|
}
|
|
467
477
|
}
|
|
468
478
|
|
|
469
479
|
const fh = await getFileHandle(filePath, create);
|
|
470
|
-
|
|
480
|
+
|
|
481
|
+
// Try readwrite-unsafe mode first (no exclusive lock, Chrome 121+)
|
|
482
|
+
let access;
|
|
483
|
+
if (unsafeModeSupported === null) {
|
|
484
|
+
// First time - detect support
|
|
485
|
+
try {
|
|
486
|
+
access = await fh.createSyncAccessHandle({ mode: 'readwrite-unsafe' });
|
|
487
|
+
unsafeModeSupported = true;
|
|
488
|
+
trace('readwrite-unsafe mode SUPPORTED - handles won\\'t block');
|
|
489
|
+
} catch {
|
|
490
|
+
// Not supported, use default mode
|
|
491
|
+
access = await fh.createSyncAccessHandle();
|
|
492
|
+
unsafeModeSupported = false;
|
|
493
|
+
trace('readwrite-unsafe mode NOT supported - using legacy mode');
|
|
494
|
+
}
|
|
495
|
+
} else if (unsafeModeSupported) {
|
|
496
|
+
access = await fh.createSyncAccessHandle({ mode: 'readwrite-unsafe' });
|
|
497
|
+
} else {
|
|
498
|
+
access = await fh.createSyncAccessHandle();
|
|
499
|
+
}
|
|
500
|
+
|
|
471
501
|
syncHandleCache.set(filePath, access);
|
|
472
|
-
|
|
473
|
-
scheduleIdleCleanup();
|
|
502
|
+
trace('Handle ACQUIRED: ' + filePath + ' (cache size: ' + syncHandleCache.size + ')');
|
|
474
503
|
return access;
|
|
475
504
|
}
|
|
476
505
|
|
|
477
506
|
function closeSyncHandle(filePath) {
|
|
478
507
|
const h = syncHandleCache.get(filePath);
|
|
479
|
-
if (h) {
|
|
508
|
+
if (h) {
|
|
509
|
+
try { h.close(); } catch {}
|
|
510
|
+
syncHandleCache.delete(filePath);
|
|
511
|
+
trace('Handle RELEASED: ' + filePath);
|
|
512
|
+
}
|
|
480
513
|
}
|
|
481
514
|
|
|
482
515
|
function closeHandlesUnder(prefix) {
|
|
@@ -484,7 +517,6 @@ function closeHandlesUnder(prefix) {
|
|
|
484
517
|
if (p === prefix || p.startsWith(prefix + '/')) {
|
|
485
518
|
try { h.close(); } catch {}
|
|
486
519
|
syncHandleCache.delete(p);
|
|
487
|
-
syncHandleLastAccess.delete(p);
|
|
488
520
|
}
|
|
489
521
|
}
|
|
490
522
|
}
|
|
@@ -600,11 +632,10 @@ function handleReleaseHandle(filePath) {
|
|
|
600
632
|
|
|
601
633
|
// Force release ALL file handles - use before HMR notifications
|
|
602
634
|
function handleReleaseAllHandles() {
|
|
603
|
-
for (const
|
|
635
|
+
for (const h of syncHandleCache.values()) {
|
|
604
636
|
try { h.close(); } catch {}
|
|
605
637
|
}
|
|
606
638
|
syncHandleCache.clear();
|
|
607
|
-
syncHandleLastAccess.clear();
|
|
608
639
|
return { success: true };
|
|
609
640
|
}
|
|
610
641
|
|
|
@@ -614,7 +645,6 @@ async function handleWrite(filePath, payload) {
|
|
|
614
645
|
const offset = payload.offset ?? 0;
|
|
615
646
|
if (offset === 0) access.truncate(0);
|
|
616
647
|
access.write(payload.data, { at: offset });
|
|
617
|
-
// Only flush if explicitly requested (default: true for safety)
|
|
618
648
|
if (payload?.flush !== false) access.flush();
|
|
619
649
|
}
|
|
620
650
|
return { success: true };
|
|
@@ -803,8 +833,11 @@ function handleFlush() {
|
|
|
803
833
|
}
|
|
804
834
|
|
|
805
835
|
function handlePurge() {
|
|
806
|
-
|
|
807
|
-
|
|
836
|
+
if (releaseTimer) {
|
|
837
|
+
clearTimeout(releaseTimer);
|
|
838
|
+
releaseTimer = null;
|
|
839
|
+
}
|
|
840
|
+
for (const handle of syncHandleCache.values()) {
|
|
808
841
|
try { handle.flush(); handle.close(); } catch {}
|
|
809
842
|
}
|
|
810
843
|
syncHandleCache.clear();
|
|
@@ -833,6 +866,10 @@ async function processMessage(msg) {
|
|
|
833
866
|
case 'purge': return handlePurge();
|
|
834
867
|
case 'releaseHandle': return handleReleaseHandle(path);
|
|
835
868
|
case 'releaseAllHandles': return handleReleaseAllHandles();
|
|
869
|
+
case 'setDebug':
|
|
870
|
+
debugTrace = !!payload?.enabled;
|
|
871
|
+
trace('Debug tracing ' + (debugTrace ? 'ENABLED' : 'DISABLED') + ', unsafeMode: ' + unsafeModeSupported);
|
|
872
|
+
return { success: true, debugTrace, unsafeModeSupported, cacheSize: syncHandleCache.size };
|
|
836
873
|
default: throw new Error('Unknown operation: ' + type);
|
|
837
874
|
}
|
|
838
875
|
}
|
|
@@ -895,6 +932,9 @@ async function handleMessage(msg) {
|
|
|
895
932
|
} else {
|
|
896
933
|
self.postMessage({ id, error: errorCode, code: errorCode });
|
|
897
934
|
}
|
|
935
|
+
} finally {
|
|
936
|
+
// Schedule handle release (debounced - waits 100ms after last operation)
|
|
937
|
+
scheduleHandleRelease();
|
|
898
938
|
}
|
|
899
939
|
}
|
|
900
940
|
|
|
@@ -1686,6 +1726,24 @@ var OPFSFileSystem = class _OPFSFileSystem {
|
|
|
1686
1726
|
this.syncCall("purge", "/");
|
|
1687
1727
|
this.statCache.clear();
|
|
1688
1728
|
}
|
|
1729
|
+
/**
|
|
1730
|
+
* Enable or disable debug tracing for handle operations.
|
|
1731
|
+
* When enabled, logs handle cache hits, acquisitions, releases, and mode information.
|
|
1732
|
+
* @param enabled - Whether to enable debug tracing
|
|
1733
|
+
* @returns Debug state information including unsafeModeSupported and cache size
|
|
1734
|
+
*/
|
|
1735
|
+
setDebugSync(enabled) {
|
|
1736
|
+
return this.syncCall("setDebug", "/", { enabled });
|
|
1737
|
+
}
|
|
1738
|
+
/**
|
|
1739
|
+
* Enable or disable debug tracing for handle operations (async version).
|
|
1740
|
+
* When enabled, logs handle cache hits, acquisitions, releases, and mode information.
|
|
1741
|
+
* @param enabled - Whether to enable debug tracing
|
|
1742
|
+
* @returns Debug state information including unsafeModeSupported and cache size
|
|
1743
|
+
*/
|
|
1744
|
+
async setDebug(enabled) {
|
|
1745
|
+
return this.asyncCall("setDebug", "/", { enabled });
|
|
1746
|
+
}
|
|
1689
1747
|
accessSync(filePath, _mode) {
|
|
1690
1748
|
const exists = this.existsSync(filePath);
|
|
1691
1749
|
if (!exists) {
|