@componentor/fs 1.2.3 → 1.2.5

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
@@ -145,6 +145,52 @@ function segments(path) {
145
145
  // src/handle-manager.ts
146
146
  var FILE_HANDLE_POOL_SIZE = 50;
147
147
  var DIR_CACHE_MAX_SIZE = 200;
148
+ var FileLockManager = class {
149
+ locks = /* @__PURE__ */ new Map();
150
+ lockResolvers = /* @__PURE__ */ new Map();
151
+ waitQueues = /* @__PURE__ */ new Map();
152
+ /**
153
+ * Acquire an exclusive lock on a file path.
154
+ * If the file is already locked, waits until it's released.
155
+ * Returns a release function that MUST be called when done.
156
+ */
157
+ async acquire(path) {
158
+ const normalizedPath = normalize(path);
159
+ while (this.locks.has(normalizedPath)) {
160
+ await this.locks.get(normalizedPath);
161
+ }
162
+ let resolve;
163
+ const lockPromise = new Promise((r) => {
164
+ resolve = r;
165
+ });
166
+ this.locks.set(normalizedPath, lockPromise);
167
+ this.lockResolvers.set(normalizedPath, resolve);
168
+ return () => {
169
+ const resolver = this.lockResolvers.get(normalizedPath);
170
+ this.locks.delete(normalizedPath);
171
+ this.lockResolvers.delete(normalizedPath);
172
+ if (resolver) resolver();
173
+ };
174
+ }
175
+ /**
176
+ * Check if a file is currently locked
177
+ */
178
+ isLocked(path) {
179
+ return this.locks.has(normalize(path));
180
+ }
181
+ /**
182
+ * Clear all locks (use with caution, mainly for cleanup)
183
+ */
184
+ clearAll() {
185
+ for (const resolver of this.lockResolvers.values()) {
186
+ resolver();
187
+ }
188
+ this.locks.clear();
189
+ this.lockResolvers.clear();
190
+ this.waitQueues.clear();
191
+ }
192
+ };
193
+ var fileLockManager = new FileLockManager();
148
194
  var HandleManager = class {
149
195
  rootPromise;
150
196
  dirCache = /* @__PURE__ */ new Map();
@@ -402,13 +448,21 @@ var SymlinkManager = class {
402
448
  if (!fileHandle) return;
403
449
  const buffer = new TextEncoder().encode(data);
404
450
  if (this.useSync) {
405
- const access = await fileHandle.createSyncAccessHandle();
406
- access.truncate(0);
407
- let written = 0;
408
- while (written < buffer.length) {
409
- written += access.write(buffer.subarray(written), { at: written });
451
+ const releaseLock = await fileLockManager.acquire(SYMLINK_FILE);
452
+ try {
453
+ const access = await fileHandle.createSyncAccessHandle();
454
+ try {
455
+ access.truncate(0);
456
+ let written = 0;
457
+ while (written < buffer.length) {
458
+ written += access.write(buffer.subarray(written), { at: written });
459
+ }
460
+ } finally {
461
+ access.close();
462
+ }
463
+ } finally {
464
+ releaseLock();
410
465
  }
411
- access.close();
412
466
  } else {
413
467
  const writable = await fileHandle.createWritable();
414
468
  await writable.write(buffer);
@@ -681,6 +735,7 @@ var PackedStorage = class {
681
735
  /**
682
736
  * Load pack index from disk (always reloads to support hybrid mode)
683
737
  * Verifies CRC32 checksum for integrity
738
+ * Note: Caller must hold the lock
684
739
  */
685
740
  async loadIndex() {
686
741
  try {
@@ -689,29 +744,36 @@ var PackedStorage = class {
689
744
  return {};
690
745
  }
691
746
  if (this.useSync) {
692
- 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);
747
+ const releaseLock = await fileLockManager.acquire(PACK_FILE);
748
+ try {
749
+ const access = await fileHandle.createSyncAccessHandle();
750
+ try {
751
+ const size = access.getSize();
752
+ if (size < 8) {
753
+ return {};
754
+ }
755
+ const header = new Uint8Array(8);
756
+ access.read(header, { at: 0 });
757
+ const view = new DataView(header.buffer);
758
+ const indexLen = view.getUint32(0, true);
759
+ const storedCrc = view.getUint32(4, true);
760
+ const contentSize = size - 8;
761
+ const content = new Uint8Array(contentSize);
762
+ access.read(content, { at: 8 });
763
+ if (this.useChecksum && storedCrc !== 0) {
764
+ const calculatedCrc = crc32(content);
765
+ if (calculatedCrc !== storedCrc) {
766
+ throw createECORRUPTED(PACK_FILE);
767
+ }
768
+ }
769
+ const indexJson = new TextDecoder().decode(content.subarray(0, indexLen));
770
+ return JSON.parse(indexJson);
771
+ } finally {
772
+ access.close();
711
773
  }
774
+ } finally {
775
+ releaseLock();
712
776
  }
713
- const indexJson = new TextDecoder().decode(content.subarray(0, indexLen));
714
- return JSON.parse(indexJson);
715
777
  } else {
716
778
  const file = await fileHandle.getFile();
717
779
  const data = new Uint8Array(await file.arrayBuffer());
@@ -764,10 +826,18 @@ var PackedStorage = class {
764
826
  if (!fileHandle) return null;
765
827
  let buffer;
766
828
  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();
829
+ const releaseLock = await fileLockManager.acquire(PACK_FILE);
830
+ try {
831
+ const access = await fileHandle.createSyncAccessHandle();
832
+ try {
833
+ buffer = new Uint8Array(entry.size);
834
+ access.read(buffer, { at: entry.offset });
835
+ } finally {
836
+ access.close();
837
+ }
838
+ } finally {
839
+ releaseLock();
840
+ }
771
841
  } else {
772
842
  const file = await fileHandle.getFile();
773
843
  const data = new Uint8Array(await file.arrayBuffer());
@@ -806,17 +876,25 @@ var PackedStorage = class {
806
876
  }
807
877
  const decompressPromises = [];
808
878
  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) });
815
- } else {
816
- results.set(path, buffer);
879
+ const releaseLock = await fileLockManager.acquire(PACK_FILE);
880
+ try {
881
+ const access = await fileHandle.createSyncAccessHandle();
882
+ try {
883
+ for (const { path, offset, size, originalSize } of toRead) {
884
+ const buffer = new Uint8Array(size);
885
+ access.read(buffer, { at: offset });
886
+ if (originalSize !== void 0) {
887
+ decompressPromises.push({ path, promise: decompress(buffer) });
888
+ } else {
889
+ results.set(path, buffer);
890
+ }
891
+ }
892
+ } finally {
893
+ access.close();
817
894
  }
895
+ } finally {
896
+ releaseLock();
818
897
  }
819
- access.close();
820
898
  } else {
821
899
  const file = await fileHandle.getFile();
822
900
  const data = new Uint8Array(await file.arrayBuffer());
@@ -897,15 +975,24 @@ var PackedStorage = class {
897
975
  }
898
976
  /**
899
977
  * Write the pack file to OPFS
978
+ * Note: Caller must hold the lock
900
979
  */
901
980
  async writePackFile(data) {
902
981
  const { fileHandle } = await this.handleManager.getHandle(PACK_FILE, { create: true });
903
982
  if (!fileHandle) return;
904
983
  if (this.useSync) {
905
- const access = await fileHandle.createSyncAccessHandle();
906
- access.truncate(data.length);
907
- access.write(data, { at: 0 });
908
- access.close();
984
+ const releaseLock = await fileLockManager.acquire(PACK_FILE);
985
+ try {
986
+ const access = await fileHandle.createSyncAccessHandle();
987
+ try {
988
+ access.truncate(data.length);
989
+ access.write(data, { at: 0 });
990
+ } finally {
991
+ access.close();
992
+ }
993
+ } finally {
994
+ releaseLock();
995
+ }
909
996
  } else {
910
997
  const writable = await fileHandle.createWritable();
911
998
  await writable.write(data);
@@ -925,33 +1012,41 @@ var PackedStorage = class {
925
1012
  const encoder = new TextEncoder();
926
1013
  const newIndexBuf = encoder.encode(JSON.stringify(index));
927
1014
  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) {
942
- newContent.set(dataPortion, newIndexBuf.length);
1015
+ const releaseLock = await fileLockManager.acquire(PACK_FILE);
1016
+ try {
1017
+ const access = await fileHandle.createSyncAccessHandle();
1018
+ try {
1019
+ const size = access.getSize();
1020
+ const oldHeader = new Uint8Array(8);
1021
+ access.read(oldHeader, { at: 0 });
1022
+ const oldIndexLen = new DataView(oldHeader.buffer).getUint32(0, true);
1023
+ const dataStart = 8 + oldIndexLen;
1024
+ const dataSize = size - dataStart;
1025
+ const dataPortion = new Uint8Array(dataSize);
1026
+ if (dataSize > 0) {
1027
+ access.read(dataPortion, { at: dataStart });
1028
+ }
1029
+ const newContent = new Uint8Array(newIndexBuf.length + dataSize);
1030
+ newContent.set(newIndexBuf, 0);
1031
+ if (dataSize > 0) {
1032
+ newContent.set(dataPortion, newIndexBuf.length);
1033
+ }
1034
+ const checksum = this.useChecksum ? crc32(newContent) : 0;
1035
+ const newHeader = new Uint8Array(8);
1036
+ const view = new DataView(newHeader.buffer);
1037
+ view.setUint32(0, newIndexBuf.length, true);
1038
+ view.setUint32(4, checksum, true);
1039
+ const newFile = new Uint8Array(8 + newContent.length);
1040
+ newFile.set(newHeader, 0);
1041
+ newFile.set(newContent, 8);
1042
+ access.truncate(newFile.length);
1043
+ access.write(newFile, { at: 0 });
1044
+ } finally {
1045
+ access.close();
1046
+ }
1047
+ } finally {
1048
+ releaseLock();
943
1049
  }
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
1050
  } else {
956
1051
  const file = await fileHandle.getFile();
957
1052
  const oldData = new Uint8Array(await file.arrayBuffer());
@@ -1621,11 +1716,19 @@ var OPFS = class {
1621
1716
  if (fileHandle) {
1622
1717
  let buffer;
1623
1718
  if (this.useSync) {
1624
- const access = await fileHandle.createSyncAccessHandle();
1625
- const size = access.getSize();
1626
- buffer = new Uint8Array(size);
1627
- access.read(buffer);
1628
- access.close();
1719
+ const releaseLock = await fileLockManager.acquire(resolvedPath);
1720
+ try {
1721
+ const access = await fileHandle.createSyncAccessHandle();
1722
+ try {
1723
+ const size = access.getSize();
1724
+ buffer = new Uint8Array(size);
1725
+ access.read(buffer);
1726
+ } finally {
1727
+ access.close();
1728
+ }
1729
+ } finally {
1730
+ releaseLock();
1731
+ }
1629
1732
  } else {
1630
1733
  const file = await fileHandle.getFile();
1631
1734
  buffer = new Uint8Array(await file.arrayBuffer());
@@ -1682,11 +1785,19 @@ var OPFS = class {
1682
1785
  }
1683
1786
  let buffer;
1684
1787
  if (this.useSync) {
1685
- const access = await fileHandle.createSyncAccessHandle();
1686
- const size = access.getSize();
1687
- buffer = new Uint8Array(size);
1688
- access.read(buffer);
1689
- access.close();
1788
+ const releaseLock = await fileLockManager.acquire(resolvedPath);
1789
+ try {
1790
+ const access = await fileHandle.createSyncAccessHandle();
1791
+ try {
1792
+ const size = access.getSize();
1793
+ buffer = new Uint8Array(size);
1794
+ access.read(buffer);
1795
+ } finally {
1796
+ access.close();
1797
+ }
1798
+ } finally {
1799
+ releaseLock();
1800
+ }
1690
1801
  } else {
1691
1802
  const file = await fileHandle.getFile();
1692
1803
  buffer = new Uint8Array(await file.arrayBuffer());
@@ -1718,10 +1829,18 @@ var OPFS = class {
1718
1829
  const { fileHandle } = await this.handleManager.getHandle(resolvedPath, { create: true });
1719
1830
  const buffer = typeof data === "string" ? new TextEncoder().encode(data) : data;
1720
1831
  if (this.useSync) {
1721
- const access = await fileHandle.createSyncAccessHandle();
1722
- access.truncate(buffer.length);
1723
- access.write(buffer, { at: 0 });
1724
- access.close();
1832
+ const releaseLock = await fileLockManager.acquire(resolvedPath);
1833
+ try {
1834
+ const access = await fileHandle.createSyncAccessHandle();
1835
+ try {
1836
+ access.truncate(buffer.length);
1837
+ access.write(buffer, { at: 0 });
1838
+ } finally {
1839
+ access.close();
1840
+ }
1841
+ } finally {
1842
+ releaseLock();
1843
+ }
1725
1844
  } else {
1726
1845
  const writable = await fileHandle.createWritable();
1727
1846
  await writable.write(buffer);
@@ -2334,9 +2453,17 @@ var OPFS = class {
2334
2453
  const { fileHandle } = await this.handleManager.getHandle(resolvedPath);
2335
2454
  if (!fileHandle) throw createENOENT(path);
2336
2455
  if (this.useSync) {
2337
- const access = await fileHandle.createSyncAccessHandle();
2338
- access.truncate(len);
2339
- access.close();
2456
+ const releaseLock = await fileLockManager.acquire(resolvedPath);
2457
+ try {
2458
+ const access = await fileHandle.createSyncAccessHandle();
2459
+ try {
2460
+ access.truncate(len);
2461
+ } finally {
2462
+ access.close();
2463
+ }
2464
+ } finally {
2465
+ releaseLock();
2466
+ }
2340
2467
  } else {
2341
2468
  const file = await fileHandle.getFile();
2342
2469
  const data = new Uint8Array(await file.arrayBuffer());