@componentor/fs 1.2.2 → 1.2.4

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.js CHANGED
@@ -653,12 +653,29 @@ var PackedStorage = class {
653
653
  useChecksum;
654
654
  index = null;
655
655
  indexLoaded = false;
656
+ lockPromise = null;
656
657
  constructor(handleManager, useSync, useCompression = false, useChecksum = true) {
657
658
  this.handleManager = handleManager;
658
659
  this.useSync = useSync;
659
660
  this.useCompression = useCompression && typeof CompressionStream !== "undefined";
660
661
  this.useChecksum = useChecksum;
661
662
  }
663
+ /**
664
+ * Acquire lock for pack file access (prevents concurrent handle conflicts)
665
+ */
666
+ async acquireLock() {
667
+ while (this.lockPromise) {
668
+ await this.lockPromise;
669
+ }
670
+ let release;
671
+ this.lockPromise = new Promise((resolve) => {
672
+ release = () => {
673
+ this.lockPromise = null;
674
+ resolve();
675
+ };
676
+ });
677
+ return release;
678
+ }
662
679
  /**
663
680
  * Reset pack storage state (memory only)
664
681
  */
@@ -681,6 +698,7 @@ var PackedStorage = class {
681
698
  /**
682
699
  * Load pack index from disk (always reloads to support hybrid mode)
683
700
  * Verifies CRC32 checksum for integrity
701
+ * Note: Caller must hold the lock
684
702
  */
685
703
  async loadIndex() {
686
704
  try {
@@ -690,28 +708,30 @@ var PackedStorage = class {
690
708
  }
691
709
  if (this.useSync) {
692
710
  const access = await fileHandle.createSyncAccessHandle();
693
- const size = access.getSize();
694
- if (size < 8) {
695
- access.close();
696
- return {};
697
- }
698
- const header = new Uint8Array(8);
699
- access.read(header, { at: 0 });
700
- const view = new DataView(header.buffer);
701
- const indexLen = view.getUint32(0, true);
702
- const storedCrc = view.getUint32(4, true);
703
- const contentSize = size - 8;
704
- const content = new Uint8Array(contentSize);
705
- access.read(content, { at: 8 });
706
- access.close();
707
- if (this.useChecksum && storedCrc !== 0) {
708
- const calculatedCrc = crc32(content);
709
- if (calculatedCrc !== storedCrc) {
710
- throw createECORRUPTED(PACK_FILE);
711
+ try {
712
+ const size = access.getSize();
713
+ if (size < 8) {
714
+ return {};
715
+ }
716
+ const header = new Uint8Array(8);
717
+ access.read(header, { at: 0 });
718
+ const view = new DataView(header.buffer);
719
+ const indexLen = view.getUint32(0, true);
720
+ const storedCrc = view.getUint32(4, true);
721
+ const contentSize = size - 8;
722
+ const content = new Uint8Array(contentSize);
723
+ access.read(content, { at: 8 });
724
+ if (this.useChecksum && storedCrc !== 0) {
725
+ const calculatedCrc = crc32(content);
726
+ if (calculatedCrc !== storedCrc) {
727
+ throw createECORRUPTED(PACK_FILE);
728
+ }
711
729
  }
730
+ const indexJson = new TextDecoder().decode(content.subarray(0, indexLen));
731
+ return JSON.parse(indexJson);
732
+ } finally {
733
+ access.close();
712
734
  }
713
- const indexJson = new TextDecoder().decode(content.subarray(0, indexLen));
714
- return JSON.parse(indexJson);
715
735
  } else {
716
736
  const file = await fileHandle.getFile();
717
737
  const data = new Uint8Array(await file.arrayBuffer());
@@ -739,44 +759,62 @@ var PackedStorage = class {
739
759
  * Check if a path exists in the pack
740
760
  */
741
761
  async has(path) {
742
- const index = await this.loadIndex();
743
- return path in index;
762
+ const release = await this.acquireLock();
763
+ try {
764
+ const index = await this.loadIndex();
765
+ return path in index;
766
+ } finally {
767
+ release();
768
+ }
744
769
  }
745
770
  /**
746
771
  * Get file size from pack (for stat)
747
772
  * Returns originalSize if compressed, otherwise size
748
773
  */
749
774
  async getSize(path) {
750
- const index = await this.loadIndex();
751
- const entry = index[path];
752
- if (!entry) return null;
753
- return entry.originalSize ?? entry.size;
775
+ const release = await this.acquireLock();
776
+ try {
777
+ const index = await this.loadIndex();
778
+ const entry = index[path];
779
+ if (!entry) return null;
780
+ return entry.originalSize ?? entry.size;
781
+ } finally {
782
+ release();
783
+ }
754
784
  }
755
785
  /**
756
786
  * Read a file from the pack
757
787
  * Handles decompression if file was stored compressed
758
788
  */
759
789
  async read(path) {
760
- const index = await this.loadIndex();
761
- const entry = index[path];
762
- if (!entry) return null;
763
- const { fileHandle } = await this.handleManager.getHandle(PACK_FILE);
764
- if (!fileHandle) return null;
765
- let buffer;
766
- if (this.useSync) {
767
- const access = await fileHandle.createSyncAccessHandle();
768
- buffer = new Uint8Array(entry.size);
769
- access.read(buffer, { at: entry.offset });
770
- access.close();
771
- } else {
772
- const file = await fileHandle.getFile();
773
- const data = new Uint8Array(await file.arrayBuffer());
774
- buffer = data.slice(entry.offset, entry.offset + entry.size);
775
- }
776
- if (entry.originalSize !== void 0) {
777
- return decompress(buffer);
790
+ const release = await this.acquireLock();
791
+ try {
792
+ const index = await this.loadIndex();
793
+ const entry = index[path];
794
+ if (!entry) return null;
795
+ const { fileHandle } = await this.handleManager.getHandle(PACK_FILE);
796
+ if (!fileHandle) return null;
797
+ let buffer;
798
+ if (this.useSync) {
799
+ const access = await fileHandle.createSyncAccessHandle();
800
+ try {
801
+ buffer = new Uint8Array(entry.size);
802
+ access.read(buffer, { at: entry.offset });
803
+ } finally {
804
+ access.close();
805
+ }
806
+ } else {
807
+ const file = await fileHandle.getFile();
808
+ const data = new Uint8Array(await file.arrayBuffer());
809
+ buffer = data.slice(entry.offset, entry.offset + entry.size);
810
+ }
811
+ if (entry.originalSize !== void 0) {
812
+ return decompress(buffer);
813
+ }
814
+ return buffer;
815
+ } finally {
816
+ release();
778
817
  }
779
- return buffer;
780
818
  }
781
819
  /**
782
820
  * Read multiple files from the pack in a single operation
@@ -786,53 +824,61 @@ var PackedStorage = class {
786
824
  async readBatch(paths) {
787
825
  const results = /* @__PURE__ */ new Map();
788
826
  if (paths.length === 0) return results;
789
- const index = await this.loadIndex();
790
- const toRead = [];
791
- for (const path of paths) {
792
- const entry = index[path];
793
- if (entry) {
794
- toRead.push({ path, offset: entry.offset, size: entry.size, originalSize: entry.originalSize });
795
- } else {
796
- results.set(path, null);
797
- }
798
- }
799
- if (toRead.length === 0) return results;
800
- const { fileHandle } = await this.handleManager.getHandle(PACK_FILE);
801
- if (!fileHandle) {
802
- for (const { path } of toRead) {
803
- results.set(path, null);
804
- }
805
- return results;
806
- }
807
- const decompressPromises = [];
808
- if (this.useSync) {
809
- const access = await fileHandle.createSyncAccessHandle();
810
- for (const { path, offset, size, originalSize } of toRead) {
811
- const buffer = new Uint8Array(size);
812
- access.read(buffer, { at: offset });
813
- if (originalSize !== void 0) {
814
- decompressPromises.push({ path, promise: decompress(buffer) });
827
+ const release = await this.acquireLock();
828
+ try {
829
+ const index = await this.loadIndex();
830
+ const toRead = [];
831
+ for (const path of paths) {
832
+ const entry = index[path];
833
+ if (entry) {
834
+ toRead.push({ path, offset: entry.offset, size: entry.size, originalSize: entry.originalSize });
815
835
  } else {
816
- results.set(path, buffer);
836
+ results.set(path, null);
817
837
  }
818
838
  }
819
- access.close();
820
- } else {
821
- const file = await fileHandle.getFile();
822
- const data = new Uint8Array(await file.arrayBuffer());
823
- for (const { path, offset, size, originalSize } of toRead) {
824
- const buffer = data.slice(offset, offset + size);
825
- if (originalSize !== void 0) {
826
- decompressPromises.push({ path, promise: decompress(buffer) });
827
- } else {
828
- results.set(path, buffer);
839
+ if (toRead.length === 0) return results;
840
+ const { fileHandle } = await this.handleManager.getHandle(PACK_FILE);
841
+ if (!fileHandle) {
842
+ for (const { path } of toRead) {
843
+ results.set(path, null);
829
844
  }
845
+ return results;
830
846
  }
847
+ const decompressPromises = [];
848
+ if (this.useSync) {
849
+ const access = await fileHandle.createSyncAccessHandle();
850
+ try {
851
+ for (const { path, offset, size, originalSize } of toRead) {
852
+ const buffer = new Uint8Array(size);
853
+ access.read(buffer, { at: offset });
854
+ if (originalSize !== void 0) {
855
+ decompressPromises.push({ path, promise: decompress(buffer) });
856
+ } else {
857
+ results.set(path, buffer);
858
+ }
859
+ }
860
+ } finally {
861
+ access.close();
862
+ }
863
+ } else {
864
+ const file = await fileHandle.getFile();
865
+ const data = new Uint8Array(await file.arrayBuffer());
866
+ for (const { path, offset, size, originalSize } of toRead) {
867
+ const buffer = data.slice(offset, offset + size);
868
+ if (originalSize !== void 0) {
869
+ decompressPromises.push({ path, promise: decompress(buffer) });
870
+ } else {
871
+ results.set(path, buffer);
872
+ }
873
+ }
874
+ }
875
+ for (const { path, promise } of decompressPromises) {
876
+ results.set(path, await promise);
877
+ }
878
+ return results;
879
+ } finally {
880
+ release();
831
881
  }
832
- for (const { path, promise } of decompressPromises) {
833
- results.set(path, await promise);
834
- }
835
- return results;
836
882
  }
837
883
  /**
838
884
  * Write multiple files to the pack in a single operation
@@ -843,69 +889,78 @@ var PackedStorage = class {
843
889
  */
844
890
  async writeBatch(entries) {
845
891
  if (entries.length === 0) return;
846
- const encoder = new TextEncoder();
847
- let processedEntries;
848
- if (this.useCompression) {
849
- processedEntries = await Promise.all(
850
- entries.map(async ({ path, data }) => {
851
- const compressed = await compress(data);
852
- if (compressed.length < data.length) {
853
- return { path, data: compressed, originalSize: data.length };
892
+ const release = await this.acquireLock();
893
+ try {
894
+ const encoder = new TextEncoder();
895
+ let processedEntries;
896
+ if (this.useCompression) {
897
+ processedEntries = await Promise.all(
898
+ entries.map(async ({ path, data }) => {
899
+ const compressed = await compress(data);
900
+ if (compressed.length < data.length) {
901
+ return { path, data: compressed, originalSize: data.length };
902
+ }
903
+ return { path, data };
904
+ })
905
+ );
906
+ } else {
907
+ processedEntries = entries;
908
+ }
909
+ let totalDataSize = 0;
910
+ for (const { data } of processedEntries) {
911
+ totalDataSize += data.length;
912
+ }
913
+ const newIndex = {};
914
+ let headerSize = 8;
915
+ let prevHeaderSize = 0;
916
+ while (headerSize !== prevHeaderSize) {
917
+ prevHeaderSize = headerSize;
918
+ let currentOffset = headerSize;
919
+ for (const { path, data, originalSize } of processedEntries) {
920
+ const entry = { offset: currentOffset, size: data.length };
921
+ if (originalSize !== void 0) {
922
+ entry.originalSize = originalSize;
854
923
  }
855
- return { path, data };
856
- })
857
- );
858
- } else {
859
- processedEntries = entries;
860
- }
861
- let totalDataSize = 0;
862
- for (const { data } of processedEntries) {
863
- totalDataSize += data.length;
864
- }
865
- const newIndex = {};
866
- let headerSize = 8;
867
- let prevHeaderSize = 0;
868
- while (headerSize !== prevHeaderSize) {
869
- prevHeaderSize = headerSize;
870
- let currentOffset = headerSize;
871
- for (const { path, data, originalSize } of processedEntries) {
872
- const entry = { offset: currentOffset, size: data.length };
873
- if (originalSize !== void 0) {
874
- entry.originalSize = originalSize;
924
+ newIndex[path] = entry;
925
+ currentOffset += data.length;
875
926
  }
876
- newIndex[path] = entry;
877
- currentOffset += data.length;
878
- }
879
- const indexBuf = encoder.encode(JSON.stringify(newIndex));
880
- headerSize = 8 + indexBuf.length;
881
- }
882
- const finalIndexBuf = encoder.encode(JSON.stringify(newIndex));
883
- const totalSize = headerSize + totalDataSize;
884
- const packBuffer = new Uint8Array(totalSize);
885
- const view = new DataView(packBuffer.buffer);
886
- packBuffer.set(finalIndexBuf, 8);
887
- for (const { path, data } of processedEntries) {
888
- const entry = newIndex[path];
889
- packBuffer.set(data, entry.offset);
927
+ const indexBuf = encoder.encode(JSON.stringify(newIndex));
928
+ headerSize = 8 + indexBuf.length;
929
+ }
930
+ const finalIndexBuf = encoder.encode(JSON.stringify(newIndex));
931
+ const totalSize = headerSize + totalDataSize;
932
+ const packBuffer = new Uint8Array(totalSize);
933
+ const view = new DataView(packBuffer.buffer);
934
+ packBuffer.set(finalIndexBuf, 8);
935
+ for (const { path, data } of processedEntries) {
936
+ const entry = newIndex[path];
937
+ packBuffer.set(data, entry.offset);
938
+ }
939
+ const content = packBuffer.subarray(8);
940
+ const checksum = this.useChecksum ? crc32(content) : 0;
941
+ view.setUint32(0, finalIndexBuf.length, true);
942
+ view.setUint32(4, checksum, true);
943
+ await this.writePackFile(packBuffer);
944
+ this.index = newIndex;
945
+ } finally {
946
+ release();
890
947
  }
891
- const content = packBuffer.subarray(8);
892
- const checksum = this.useChecksum ? crc32(content) : 0;
893
- view.setUint32(0, finalIndexBuf.length, true);
894
- view.setUint32(4, checksum, true);
895
- await this.writePackFile(packBuffer);
896
- this.index = newIndex;
897
948
  }
898
949
  /**
899
950
  * Write the pack file to OPFS
951
+ * Note: Caller must hold the lock
900
952
  */
901
953
  async writePackFile(data) {
902
954
  const { fileHandle } = await this.handleManager.getHandle(PACK_FILE, { create: true });
903
955
  if (!fileHandle) return;
904
956
  if (this.useSync) {
905
957
  const access = await fileHandle.createSyncAccessHandle();
906
- access.truncate(data.length);
907
- access.write(data, { at: 0 });
908
- access.close();
958
+ try {
959
+ access.truncate(data.length);
960
+ access.write(data, { at: 0 });
961
+ } finally {
962
+ access.close();
963
+ }
909
964
  } else {
910
965
  const writable = await fileHandle.createWritable();
911
966
  await writable.write(data);
@@ -917,69 +972,82 @@ var PackedStorage = class {
917
972
  * Note: Doesn't reclaim space, just removes from index and recalculates CRC32
918
973
  */
919
974
  async remove(path) {
920
- const index = await this.loadIndex();
921
- if (!(path in index)) return false;
922
- delete index[path];
923
- const { fileHandle } = await this.handleManager.getHandle(PACK_FILE);
924
- if (!fileHandle) return true;
925
- const encoder = new TextEncoder();
926
- const newIndexBuf = encoder.encode(JSON.stringify(index));
927
- if (this.useSync) {
928
- const access = await fileHandle.createSyncAccessHandle();
929
- const size = access.getSize();
930
- const oldHeader = new Uint8Array(8);
931
- access.read(oldHeader, { at: 0 });
932
- const oldIndexLen = new DataView(oldHeader.buffer).getUint32(0, true);
933
- const dataStart = 8 + oldIndexLen;
934
- const dataSize = size - dataStart;
935
- const dataPortion = new Uint8Array(dataSize);
936
- if (dataSize > 0) {
937
- access.read(dataPortion, { at: dataStart });
938
- }
939
- const newContent = new Uint8Array(newIndexBuf.length + dataSize);
940
- newContent.set(newIndexBuf, 0);
941
- if (dataSize > 0) {
975
+ const release = await this.acquireLock();
976
+ try {
977
+ const index = await this.loadIndex();
978
+ if (!(path in index)) return false;
979
+ delete index[path];
980
+ const { fileHandle } = await this.handleManager.getHandle(PACK_FILE);
981
+ if (!fileHandle) return true;
982
+ const encoder = new TextEncoder();
983
+ const newIndexBuf = encoder.encode(JSON.stringify(index));
984
+ if (this.useSync) {
985
+ const access = await fileHandle.createSyncAccessHandle();
986
+ try {
987
+ const size = access.getSize();
988
+ const oldHeader = new Uint8Array(8);
989
+ access.read(oldHeader, { at: 0 });
990
+ const oldIndexLen = new DataView(oldHeader.buffer).getUint32(0, true);
991
+ const dataStart = 8 + oldIndexLen;
992
+ const dataSize = size - dataStart;
993
+ const dataPortion = new Uint8Array(dataSize);
994
+ if (dataSize > 0) {
995
+ access.read(dataPortion, { at: dataStart });
996
+ }
997
+ const newContent = new Uint8Array(newIndexBuf.length + dataSize);
998
+ newContent.set(newIndexBuf, 0);
999
+ if (dataSize > 0) {
1000
+ newContent.set(dataPortion, newIndexBuf.length);
1001
+ }
1002
+ const checksum = this.useChecksum ? crc32(newContent) : 0;
1003
+ const newHeader = new Uint8Array(8);
1004
+ const view = new DataView(newHeader.buffer);
1005
+ view.setUint32(0, newIndexBuf.length, true);
1006
+ view.setUint32(4, checksum, true);
1007
+ const newFile = new Uint8Array(8 + newContent.length);
1008
+ newFile.set(newHeader, 0);
1009
+ newFile.set(newContent, 8);
1010
+ access.truncate(newFile.length);
1011
+ access.write(newFile, { at: 0 });
1012
+ } finally {
1013
+ access.close();
1014
+ }
1015
+ } else {
1016
+ const file = await fileHandle.getFile();
1017
+ const oldData = new Uint8Array(await file.arrayBuffer());
1018
+ if (oldData.length < 8) return true;
1019
+ const oldIndexLen = new DataView(oldData.buffer).getUint32(0, true);
1020
+ const dataStart = 8 + oldIndexLen;
1021
+ const dataPortion = oldData.subarray(dataStart);
1022
+ const newContent = new Uint8Array(newIndexBuf.length + dataPortion.length);
1023
+ newContent.set(newIndexBuf, 0);
942
1024
  newContent.set(dataPortion, newIndexBuf.length);
1025
+ const checksum = this.useChecksum ? crc32(newContent) : 0;
1026
+ const newFile = new Uint8Array(8 + newContent.length);
1027
+ const view = new DataView(newFile.buffer);
1028
+ view.setUint32(0, newIndexBuf.length, true);
1029
+ view.setUint32(4, checksum, true);
1030
+ newFile.set(newContent, 8);
1031
+ const writable = await fileHandle.createWritable();
1032
+ await writable.write(newFile);
1033
+ await writable.close();
943
1034
  }
944
- const checksum = this.useChecksum ? crc32(newContent) : 0;
945
- const newHeader = new Uint8Array(8);
946
- const view = new DataView(newHeader.buffer);
947
- view.setUint32(0, newIndexBuf.length, true);
948
- view.setUint32(4, checksum, true);
949
- const newFile = new Uint8Array(8 + newContent.length);
950
- newFile.set(newHeader, 0);
951
- newFile.set(newContent, 8);
952
- access.truncate(newFile.length);
953
- access.write(newFile, { at: 0 });
954
- access.close();
955
- } else {
956
- const file = await fileHandle.getFile();
957
- const oldData = new Uint8Array(await file.arrayBuffer());
958
- if (oldData.length < 8) return true;
959
- const oldIndexLen = new DataView(oldData.buffer).getUint32(0, true);
960
- const dataStart = 8 + oldIndexLen;
961
- const dataPortion = oldData.subarray(dataStart);
962
- const newContent = new Uint8Array(newIndexBuf.length + dataPortion.length);
963
- newContent.set(newIndexBuf, 0);
964
- newContent.set(dataPortion, newIndexBuf.length);
965
- const checksum = this.useChecksum ? crc32(newContent) : 0;
966
- const newFile = new Uint8Array(8 + newContent.length);
967
- const view = new DataView(newFile.buffer);
968
- view.setUint32(0, newIndexBuf.length, true);
969
- view.setUint32(4, checksum, true);
970
- newFile.set(newContent, 8);
971
- const writable = await fileHandle.createWritable();
972
- await writable.write(newFile);
973
- await writable.close();
1035
+ return true;
1036
+ } finally {
1037
+ release();
974
1038
  }
975
- return true;
976
1039
  }
977
1040
  /**
978
1041
  * Check if pack file is being used (has entries)
979
1042
  */
980
1043
  async isEmpty() {
981
- const index = await this.loadIndex();
982
- return Object.keys(index).length === 0;
1044
+ const release = await this.acquireLock();
1045
+ try {
1046
+ const index = await this.loadIndex();
1047
+ return Object.keys(index).length === 0;
1048
+ } finally {
1049
+ release();
1050
+ }
983
1051
  }
984
1052
  };
985
1053