zen-fs-config 0.5.9 → 0.5.10

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.d.mts CHANGED
@@ -515,8 +515,19 @@ declare class ConfigRepo implements IConfigRepo {
515
515
  /**
516
516
  * Before sync: for each tombstone, delete the actual file on all replicas.
517
517
  * This prevents bi-directional sync from copying the file back.
518
+ *
519
+ * Before calling unlink() on each backend, we check exists() first.
520
+ * This avoids sending wasteful DELETE requests (or GET-then-404) to
521
+ * remote backends when the file was already removed on a previous cycle.
522
+ * Local backends (IndexedDB) are cheap to check, so the guard is
523
+ * effectively free for them.
518
524
  */
519
525
  private processTombstones;
526
+ /**
527
+ * Safe existence check — returns false on any error instead of throwing.
528
+ * Used by processTombstones to avoid unnecessary unlink() calls.
529
+ */
530
+ private safeExists;
520
531
  /** Public wrapper for processTombstones — used by createConfigRepo. */
521
532
  processTombstonesPublic(): Promise<void>;
522
533
  /**
package/dist/index.d.ts CHANGED
@@ -515,8 +515,19 @@ declare class ConfigRepo implements IConfigRepo {
515
515
  /**
516
516
  * Before sync: for each tombstone, delete the actual file on all replicas.
517
517
  * This prevents bi-directional sync from copying the file back.
518
+ *
519
+ * Before calling unlink() on each backend, we check exists() first.
520
+ * This avoids sending wasteful DELETE requests (or GET-then-404) to
521
+ * remote backends when the file was already removed on a previous cycle.
522
+ * Local backends (IndexedDB) are cheap to check, so the guard is
523
+ * effectively free for them.
518
524
  */
519
525
  private processTombstones;
526
+ /**
527
+ * Safe existence check — returns false on any error instead of throwing.
528
+ * Used by processTombstones to avoid unnecessary unlink() calls.
529
+ */
530
+ private safeExists;
520
531
  /** Public wrapper for processTombstones — used by createConfigRepo. */
521
532
  processTombstonesPublic(): Promise<void>;
522
533
  /**
package/dist/index.js CHANGED
@@ -861,37 +861,77 @@ var ConfigRepo = class {
861
861
  /**
862
862
  * Before sync: for each tombstone, delete the actual file on all replicas.
863
863
  * This prevents bi-directional sync from copying the file back.
864
+ *
865
+ * Before calling unlink() on each backend, we check exists() first.
866
+ * This avoids sending wasteful DELETE requests (or GET-then-404) to
867
+ * remote backends when the file was already removed on a previous cycle.
868
+ * Local backends (IndexedDB) are cheap to check, so the guard is
869
+ * effectively free for them.
864
870
  */
865
871
  async processTombstones() {
866
872
  const tombstones = await this.readTombstones();
867
873
  if (tombstones.length === 0) return;
868
- console.log(`[ConfigRepo] processTombstones: ${tombstones.length} tombstone(s)`);
874
+ let processed = 0;
875
+ let alreadyDeleted = 0;
869
876
  for (const tombstone of tombstones) {
870
877
  const tVersionPath = versionPathFor(tombstone.path);
871
- try {
872
- await this.cachedFS.unlink(tombstone.path);
873
- } catch {
878
+ const existedOnPrimary = await this.safeExists(this.cachedFS, tombstone.path);
879
+ console.log(`[ConfigRepo] tombstone check: ${tombstone.path} on primary \u2192 ${existedOnPrimary ? "EXISTS" : "not found"}`);
880
+ if (existedOnPrimary) {
881
+ try {
882
+ await this.cachedFS.unlink(tombstone.path);
883
+ processed++;
884
+ } catch {
885
+ }
874
886
  }
875
- if (tVersionPath) {
887
+ if (tVersionPath && await this.safeExists(this.cachedFS, tVersionPath)) {
876
888
  try {
877
889
  await this.cachedFS.unlink(tVersionPath);
878
890
  } catch {
879
891
  }
880
892
  }
881
893
  for (const [replicaId, replica] of this.replicaBackends) {
882
- try {
883
- await replica.instance.unlink(tombstone.path);
884
- } catch {
894
+ const existed = await this.safeExists(replica.instance, tombstone.path);
895
+ console.log(`[ConfigRepo] tombstone check: ${tombstone.path} on ${replicaId} \u2192 ${existed ? "EXISTS" : "not found"}`);
896
+ if (existed) {
897
+ try {
898
+ await replica.instance.unlink(tombstone.path);
899
+ console.log(`[ConfigRepo] tombstone ${tombstone.path}: deleted on ${replicaId}`);
900
+ processed++;
901
+ } catch {
902
+ alreadyDeleted++;
903
+ }
904
+ } else {
905
+ alreadyDeleted++;
885
906
  }
886
- if (tVersionPath) {
907
+ if (tVersionPath && await this.safeExists(replica.instance, tVersionPath)) {
887
908
  try {
888
909
  await replica.instance.unlink(tVersionPath);
889
910
  } catch {
890
911
  }
891
912
  }
892
- console.log(`[ConfigRepo] tombstone ${tombstone.path}: deleted on ${replicaId}`);
893
913
  }
894
914
  }
915
+ if (processed > 0 || alreadyDeleted > 0) {
916
+ console.log(`[ConfigRepo] processTombstones: ${tombstones.length} tombstone(s), ${processed} deleted, ${alreadyDeleted} already gone`);
917
+ }
918
+ }
919
+ /**
920
+ * Safe existence check — returns false on any error instead of throwing.
921
+ * Used by processTombstones to avoid unnecessary unlink() calls.
922
+ */
923
+ async safeExists(fs, path) {
924
+ try {
925
+ if (typeof fs.exists === "function") {
926
+ const result = await fs.exists(path);
927
+ return result;
928
+ }
929
+ await fs.stat(path);
930
+ return true;
931
+ } catch (err) {
932
+ console.log(`[ConfigRepo] safeExists(${path}): threw ${err?.code ?? err?.status ?? ""} ${err?.message ?? err}`);
933
+ return false;
934
+ }
895
935
  }
896
936
  /** Public wrapper for processTombstones — used by createConfigRepo. */
897
937
  async processTombstonesPublic() {
package/dist/index.mjs CHANGED
@@ -771,37 +771,77 @@ var ConfigRepo = class {
771
771
  /**
772
772
  * Before sync: for each tombstone, delete the actual file on all replicas.
773
773
  * This prevents bi-directional sync from copying the file back.
774
+ *
775
+ * Before calling unlink() on each backend, we check exists() first.
776
+ * This avoids sending wasteful DELETE requests (or GET-then-404) to
777
+ * remote backends when the file was already removed on a previous cycle.
778
+ * Local backends (IndexedDB) are cheap to check, so the guard is
779
+ * effectively free for them.
774
780
  */
775
781
  async processTombstones() {
776
782
  const tombstones = await this.readTombstones();
777
783
  if (tombstones.length === 0) return;
778
- console.log(`[ConfigRepo] processTombstones: ${tombstones.length} tombstone(s)`);
784
+ let processed = 0;
785
+ let alreadyDeleted = 0;
779
786
  for (const tombstone of tombstones) {
780
787
  const tVersionPath = versionPathFor(tombstone.path);
781
- try {
782
- await this.cachedFS.unlink(tombstone.path);
783
- } catch {
788
+ const existedOnPrimary = await this.safeExists(this.cachedFS, tombstone.path);
789
+ console.log(`[ConfigRepo] tombstone check: ${tombstone.path} on primary \u2192 ${existedOnPrimary ? "EXISTS" : "not found"}`);
790
+ if (existedOnPrimary) {
791
+ try {
792
+ await this.cachedFS.unlink(tombstone.path);
793
+ processed++;
794
+ } catch {
795
+ }
784
796
  }
785
- if (tVersionPath) {
797
+ if (tVersionPath && await this.safeExists(this.cachedFS, tVersionPath)) {
786
798
  try {
787
799
  await this.cachedFS.unlink(tVersionPath);
788
800
  } catch {
789
801
  }
790
802
  }
791
803
  for (const [replicaId, replica] of this.replicaBackends) {
792
- try {
793
- await replica.instance.unlink(tombstone.path);
794
- } catch {
804
+ const existed = await this.safeExists(replica.instance, tombstone.path);
805
+ console.log(`[ConfigRepo] tombstone check: ${tombstone.path} on ${replicaId} \u2192 ${existed ? "EXISTS" : "not found"}`);
806
+ if (existed) {
807
+ try {
808
+ await replica.instance.unlink(tombstone.path);
809
+ console.log(`[ConfigRepo] tombstone ${tombstone.path}: deleted on ${replicaId}`);
810
+ processed++;
811
+ } catch {
812
+ alreadyDeleted++;
813
+ }
814
+ } else {
815
+ alreadyDeleted++;
795
816
  }
796
- if (tVersionPath) {
817
+ if (tVersionPath && await this.safeExists(replica.instance, tVersionPath)) {
797
818
  try {
798
819
  await replica.instance.unlink(tVersionPath);
799
820
  } catch {
800
821
  }
801
822
  }
802
- console.log(`[ConfigRepo] tombstone ${tombstone.path}: deleted on ${replicaId}`);
803
823
  }
804
824
  }
825
+ if (processed > 0 || alreadyDeleted > 0) {
826
+ console.log(`[ConfigRepo] processTombstones: ${tombstones.length} tombstone(s), ${processed} deleted, ${alreadyDeleted} already gone`);
827
+ }
828
+ }
829
+ /**
830
+ * Safe existence check — returns false on any error instead of throwing.
831
+ * Used by processTombstones to avoid unnecessary unlink() calls.
832
+ */
833
+ async safeExists(fs, path) {
834
+ try {
835
+ if (typeof fs.exists === "function") {
836
+ const result = await fs.exists(path);
837
+ return result;
838
+ }
839
+ await fs.stat(path);
840
+ return true;
841
+ } catch (err) {
842
+ console.log(`[ConfigRepo] safeExists(${path}): threw ${err?.code ?? err?.status ?? ""} ${err?.message ?? err}`);
843
+ return false;
844
+ }
805
845
  }
806
846
  /** Public wrapper for processTombstones — used by createConfigRepo. */
807
847
  async processTombstonesPublic() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zen-fs-config",
3
- "version": "0.5.9",
3
+ "version": "0.5.10",
4
4
  "description": "Distributed config management library built on ZenFS, zen-fs-cache, and zen-fs-sync",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",