zen-fs-config 0.5.9 → 0.5.11
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 +16 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +64 -11
- package/dist/index.mjs +64 -11
- package/package.json +1 -1
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
|
/**
|
|
@@ -531,6 +542,11 @@ declare class ConfigRepo implements IConfigRepo {
|
|
|
531
542
|
private updateTombstoneConfirmations;
|
|
532
543
|
/**
|
|
533
544
|
* GC: remove tombstones where all backends in backends.json have confirmed.
|
|
545
|
+
*
|
|
546
|
+
* Tombstones are deleted from ALL backends (local + replicas), not just
|
|
547
|
+
* the local IndexedDB. If we only deleted locally, the sync engine would
|
|
548
|
+
* see them as "created on target" and copy them back every cycle — causing
|
|
549
|
+
* an infinite loop of copy → GC → copy → GC.
|
|
534
550
|
*/
|
|
535
551
|
private gcTombstones;
|
|
536
552
|
/**
|
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
|
/**
|
|
@@ -531,6 +542,11 @@ declare class ConfigRepo implements IConfigRepo {
|
|
|
531
542
|
private updateTombstoneConfirmations;
|
|
532
543
|
/**
|
|
533
544
|
* GC: remove tombstones where all backends in backends.json have confirmed.
|
|
545
|
+
*
|
|
546
|
+
* Tombstones are deleted from ALL backends (local + replicas), not just
|
|
547
|
+
* the local IndexedDB. If we only deleted locally, the sync engine would
|
|
548
|
+
* see them as "created on target" and copy them back every cycle — causing
|
|
549
|
+
* an infinite loop of copy → GC → copy → GC.
|
|
534
550
|
*/
|
|
535
551
|
private gcTombstones;
|
|
536
552
|
/**
|
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
|
-
|
|
874
|
+
let processed = 0;
|
|
875
|
+
let alreadyDeleted = 0;
|
|
869
876
|
for (const tombstone of tombstones) {
|
|
870
877
|
const tVersionPath = versionPathFor(tombstone.path);
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
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
|
-
|
|
883
|
-
|
|
884
|
-
|
|
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() {
|
|
@@ -937,6 +977,11 @@ var ConfigRepo = class {
|
|
|
937
977
|
}
|
|
938
978
|
/**
|
|
939
979
|
* GC: remove tombstones where all backends in backends.json have confirmed.
|
|
980
|
+
*
|
|
981
|
+
* Tombstones are deleted from ALL backends (local + replicas), not just
|
|
982
|
+
* the local IndexedDB. If we only deleted locally, the sync engine would
|
|
983
|
+
* see them as "created on target" and copy them back every cycle — causing
|
|
984
|
+
* an infinite loop of copy → GC → copy → GC.
|
|
940
985
|
*/
|
|
941
986
|
async gcTombstones() {
|
|
942
987
|
const tombstones = await this.readTombstones();
|
|
@@ -949,9 +994,17 @@ var ConfigRepo = class {
|
|
|
949
994
|
const tombstonePath = `${DELETIONS_DIR}/${tombstoneFileName(tombstone.path)}`;
|
|
950
995
|
try {
|
|
951
996
|
await this.cachedFS.unlink(tombstonePath);
|
|
952
|
-
console.log(`[ConfigRepo] gcTombstones: removed ${tombstonePath} (all ${allBackendIds.length} backends confirmed)`);
|
|
953
997
|
} catch {
|
|
954
998
|
}
|
|
999
|
+
for (const [replicaId, replica] of this.replicaBackends) {
|
|
1000
|
+
if (await this.safeExists(replica.instance, tombstonePath)) {
|
|
1001
|
+
try {
|
|
1002
|
+
await replica.instance.unlink(tombstonePath);
|
|
1003
|
+
} catch {
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
console.log(`[ConfigRepo] gcTombstones: removed ${tombstonePath} (all ${allBackendIds.length} backends confirmed)`);
|
|
955
1008
|
}
|
|
956
1009
|
}
|
|
957
1010
|
}
|
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
|
-
|
|
784
|
+
let processed = 0;
|
|
785
|
+
let alreadyDeleted = 0;
|
|
779
786
|
for (const tombstone of tombstones) {
|
|
780
787
|
const tVersionPath = versionPathFor(tombstone.path);
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
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
|
-
|
|
793
|
-
|
|
794
|
-
|
|
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() {
|
|
@@ -847,6 +887,11 @@ var ConfigRepo = class {
|
|
|
847
887
|
}
|
|
848
888
|
/**
|
|
849
889
|
* GC: remove tombstones where all backends in backends.json have confirmed.
|
|
890
|
+
*
|
|
891
|
+
* Tombstones are deleted from ALL backends (local + replicas), not just
|
|
892
|
+
* the local IndexedDB. If we only deleted locally, the sync engine would
|
|
893
|
+
* see them as "created on target" and copy them back every cycle — causing
|
|
894
|
+
* an infinite loop of copy → GC → copy → GC.
|
|
850
895
|
*/
|
|
851
896
|
async gcTombstones() {
|
|
852
897
|
const tombstones = await this.readTombstones();
|
|
@@ -859,9 +904,17 @@ var ConfigRepo = class {
|
|
|
859
904
|
const tombstonePath = `${DELETIONS_DIR}/${tombstoneFileName(tombstone.path)}`;
|
|
860
905
|
try {
|
|
861
906
|
await this.cachedFS.unlink(tombstonePath);
|
|
862
|
-
console.log(`[ConfigRepo] gcTombstones: removed ${tombstonePath} (all ${allBackendIds.length} backends confirmed)`);
|
|
863
907
|
} catch {
|
|
864
908
|
}
|
|
909
|
+
for (const [replicaId, replica] of this.replicaBackends) {
|
|
910
|
+
if (await this.safeExists(replica.instance, tombstonePath)) {
|
|
911
|
+
try {
|
|
912
|
+
await replica.instance.unlink(tombstonePath);
|
|
913
|
+
} catch {
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
console.log(`[ConfigRepo] gcTombstones: removed ${tombstonePath} (all ${allBackendIds.length} backends confirmed)`);
|
|
865
918
|
}
|
|
866
919
|
}
|
|
867
920
|
}
|